如何使用50行javaScript代码实现简单版的call,apply,bind


Posted in Javascript onAugust 14, 2019

在实现自己的call,apply,bind前,需要复习一下this.

所谓的this其实可以理解成一根指针:

其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象,这就是精髓。最关键所在

this的四种指向:

当this所在的函数被普通调用时,指向window,如果当前是严格模式,则指向undefined

function test() {
 console.log(this);
};

test();
指向window 输出下面的代码:
// Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}
严格模式
'use strict';
function test() {
 console.log(this);
};
test();
// undefined

当this所在当函数被以obj.fn()形式调用时,指向obj

var obj = {
 name: 'segmentFault',
 foo: function() {
  console.log(this.name);
 }
}
obj.foo();
// 'segmentFault'

还可以这么做

function test() {
 console.log(this.name);
}
var obj = {
 name: 'qiutc',
 foo: test
}
obj.foo();
// 'qiutc'

当call,apply加入后,this的指向被改变了

function a(a,b,c) {
    console.log(this.name);
    console.log(a,b,c)
  }
  const b = {
    name: "segmentFault"
  }
  a.call(b,1,2,3)    
  //输出 segmentFault和 1,2,3
  function a(a,b,c) {
    console.log(this.name);
    console.log(a,b,c)
  }
  a.apply(b,[1,2,3])
  //输出segmentFault和1,2,3

遇到bind后 :

function a() {
    console.log(this.name);
  }
  const b = {
    name: "segmentFault"
  }
  a.bind(b, 1, 2, 3)

此时控制台并没有代码输出,因为bind会重新生成并且返回一个函数,这个函数的this指向第一个参数

function a() {
    console.log(this.name);
  }
  const b = {
    name: "segmentFault"
  }
  const c = a.bind(b, 1, 2, 3)
  c()
  //此时输出segmentFault

正式开始自己实现call :

在函数原型上定义自己的myCall方法:

Function.prototype.myCall = function (context, ...arg) {
    const fn = Symbol('临时属性')
    context[fn] = this
    context[fn](...arg)
    delete context[fn]
  }

四行代码实现了简单的call,思路如下:

  • 通过对象属性的方式调用函数,这个函数里面的this指向这个对象
  • 每次调用新增一个symbol属性,调用完毕删除
  • 这个symbol属性就是调用mycall方法的函数
  • 函数形参中使用...arg是将多个形参都塞到一个数组里,在函数内部使用arg这个变量时,就是包含所有形参的数组
  • 在调用 context[fn](...arg)时候,...arg是为了展开数组,依次传入参数调用函数

为了简化,今天都不做类型判断和错误边际处理,只把原理讲清楚。

自己实现apply

在函数原型上定义自己的myApply方法:

//实现自己的myApply
  Function.prototype.myApply = function (context, arg) {
    const fn = Symbol('临时属性')
    context[fn] = this
    context[fn](...arg)
    delete context[fn]
  }
  const obj2 = {
    a: 1
  }
  test.myApply(obj2, [2, 3, 4])

同理,只是apply传递的第二个参数是数组,这里我们只需要在调用时,将参数用...把数组展开即可

自己实现bind:

bind跟apply,call的本质区别,bind不会改变原函数的this指向,只会返回一个新的函数(我们想要的那个this指向),并且不会调用。但是apply和bind会改变原函数的this指向并且直接调用

bind在编写框架源码,例如koa等中用得特别多:

//实现自己的myBind
  Function.prototype.myBind = function (context, ...firstarg) {
    const that = this
    const bindFn = function (...secoundarg) {
      return that.myCall(context, ...firstarg, ...secoundarg)
    }
    bindFn.prototype = Object.create(that.prototype)
    return bindFn
  }

  var fnbind = test.myBind(obj, 2)
  fnbind(3)

同理 自己定义好原型上的myBind方法

this劫持 保留最初的调用mybind方法的那个对象

返回一个新的函数 这个新的函数内部this指向已经确定,使用的是我们的mycall方法

学习需要循序渐进,建议根据本文顺序去封装一遍,是比较轻松的,当然bind还需要判断是否是new调用.

完整版本bind

Function.prototype.myBind = function (objThis, ...params) {
  const thisFn = this; // 存储源函数以及上方的params(函数参数)
  // 对返回的函数 secondParams 二次传参
  let fToBind = function (...secondParams) {
    console.log('secondParams',secondParams,...secondParams)
    const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
    const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
    return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果
  };
  fToBind.prototype = Object.create(thisFn.prototype); // 复制源函数的prototype给fToBind
  return fToBind; // 返回拷贝的函数
};

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
html页面显示年月日时分秒和星期几的两种方式
Aug 20 Javascript
jquery实现键盘左右翻页特效
Apr 30 Javascript
JS面向对象(3)之Object类,静态属性,闭包,私有属性, call和apply的使用,继承的三种实现方法
Feb 25 Javascript
在AngularJS中使用jQuery的zTree插件的方法
Apr 21 Javascript
jQuery实现页面评论栏中访客信息自动填写功能的方法
May 23 Javascript
React创建组件的三种方式及其区别
Jan 12 Javascript
基于JavaScript实现验证码功能
Apr 01 Javascript
Ionic3 UI组件之Gallery Modal详解
Jun 07 Javascript
详解JS数组Reduce()方法详解及高级技巧
Aug 18 Javascript
使用JS动态显示文本
Sep 09 Javascript
Angular6 用户自定义标签开发的实现方法
Jan 08 Javascript
如何提升vue.js中大型数据的性能
Jun 21 Javascript
微信小程序之数据绑定原理解析
Aug 14 #Javascript
微信公众号平台接口开发 菜单管理的实现
Aug 14 #Javascript
vue.js中ref和$refs的使用及示例讲解
Aug 14 #Javascript
微信公众号平台接口开发 获取微信服务器IP地址方法解析
Aug 14 #Javascript
vue filter 完美时间日期格式的代码
Aug 14 #Javascript
如何对react hooks进行单元测试的方法
Aug 14 #Javascript
vue 中 命名视图的用法实例详解
Aug 14 #Javascript
You might like
PHP操作mysql函数详解,mysql和php交互函数
2011/05/19 PHP
PHP中改变图片的尺寸大小的代码
2011/07/17 PHP
ThinkPHP框架里隐藏index.php
2016/04/12 PHP
javascript 三种方法实现获得和设置以及移除元素属性
2013/03/20 Javascript
javascript教程:关于if简写语句优化的方法
2014/05/17 Javascript
Nodejs实战心得之eventproxy模块控制并发
2015/10/27 NodeJs
JavaScript中的数组遍历forEach()与map()方法以及兼容写法介绍
2016/05/19 Javascript
javascript基础知识
2016/06/07 Javascript
最佳的JavaScript错误处理实践
2016/07/16 Javascript
AngularJS 面试题集锦
2016/09/06 Javascript
jQuery实现可移动选项的左右下拉列表示例
2016/12/26 Javascript
js上下视差滚动简单实现代码
2017/03/07 Javascript
Bootstrap模态框插入视频的实现代码
2017/06/25 Javascript
js实现扫雷小程序的示例代码
2017/09/27 Javascript
js实现登录注册框手机号和验证码校验(前端部分)
2017/09/28 Javascript
利用babel将es6语法转es5的简单示例
2017/12/01 Javascript
Postman的下载及安装教程详解
2018/10/16 Javascript
vue中 v-for循环的用法详解
2020/02/19 Javascript
详解关于Vue单元测试的几个坑
2020/04/26 Javascript
jQuery 常用特效实例小结【显示与隐藏、淡入淡出、滑动、动画等】
2020/05/19 jQuery
[03:11]不朽宝藏三外观展示
2020/09/18 DOTA
Python实现输出某区间范围内全部素数的方法
2018/05/02 Python
Django ORM多对多查询方法(自定义第三张表&ManyToManyField)
2019/08/09 Python
Python 实现取多维数组第n维的前几位
2019/11/26 Python
Python日期格式和字符串格式相互转换的方法
2020/02/18 Python
python多进程下的生产者和消费者模型
2020/05/07 Python
浅谈python量化 双均线策略(金叉死叉)
2020/06/03 Python
Python制作数据预测集成工具(值得收藏)
2020/08/21 Python
matplotlib设置颜色、标记、线条,让你的图像更加丰富(推荐)
2020/09/25 Python
python hmac模块验证客户端的合法性
2020/11/07 Python
金讯Java笔试题目
2013/06/18 面试题
如何做好总经理助理
2013/11/12 职场文书
学生自我鉴定格式及范文
2014/09/16 职场文书
个人纪律作风整改措施思想汇报
2014/10/12 职场文书
党员个人承诺书
2015/04/27 职场文书
小学五年级(说明文3篇)
2019/08/13 职场文书