call、apply 及 bind 函数内部实现是怎么样的?
一、call
- call改变了this指向
- 函数执行
- 支持传参
⚠️注意:this可以传入undefined或者null,此时this指向window,this参数可以传入基本类型,call会自动用Object转换,函数可以有返回值
1 | Function.prototype.call2 = function(context) { |
思考上面的写法会有什么问题?
这里假设context本身没有fn属性,这样肯定不行,我们必须保证fn属性的唯一性
- 实现方式一:es3模拟实现
首先判断 context中是否存在属性 fn,如果存在那就随机生成一个属性fnxx,然后循环查询 context对象中是否存在属性 fnxx。如果不存在则返回最终值。
1 | function fnFactory (context) { |
tips:有两种方式可以判断对象中是否存在某个属性
1.in操作符,会检查属性名是否存在对象及其原型链中,注意数组的话是检查索引而不是具体值
例如对于数组来说,4 in [2, 4, 6] 结果返回 false,因为 [2, 4, 6] 这个数组中包含的属性名是0,1,2 ,没有4。
2.Object.hasOwnProperty(…)方法,只会检查属性是否存在对象中,不会向上检查其原型链。
- 实现方式二:es6模拟实现
利用Symbol,表示独一无二的值,不能使用 new 命令,因为这是基本类型的值,不然会报错。
1 | Function.prototype.call2 = function(context) { |
测试一下:
1 | let value = 2; |
二、apply
apply与call的思路基本相同,区别在于传参为数组,实现如下
1 | Function.prototype.apply2 = function(context, arr) { |
测试一下:
1 | let value = 2; |
三、bind
bind() 函数在 ES5 才被加入,所以并不是所有浏览器都支持,IE8及以下的版本中不被支持,如果需要兼容可以使用 Polyfill 来实现。
bind方法与call/apply最大的区别就是bind返回一个绑定上下文的函数,而call/apply是直接执行了函数,特性如下:
- 可以指定this
- 返回一个绑定了this的函数
- 可以传参
- 柯里化
– 获取返回函数的参数,然后同第3点的参数合并成一个参数数组,并作为 self.apply() 的第二个参数。
⚠️特别:绑定函数也能使用new操作符创建对象
这种行为就行把原函数当作构造器,提供的this被忽略,同时调用时的参数被提供给模拟函数,可以通过修改返回函数的原型来实现
1 | Function.prototype.bind2 = function (context) { |
测试一下:
1 | var value = 2; |
bind还有一个在上文说过的特性需要实现:
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
例如:
1 | const value = 2; |
👆上面的例子this.value 输出为 undefined,既不是全局value 也不是foo对象中的value,这说明绑定的 this 对象失效了,new 的实现中生成一个新的对象,这个时候的 this指向的是 obj。
这一特性可以通过修改返回的函数的原型来实现
说明:
- 当作为构造函数时,this 指向实例,此时 this instanceof fBound 结果为 true,可以让实例获得来自绑定函数的值,即上例中实例会具有 habit 属性。
- 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
- 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值,即上例中 obj 可以获取到 bar 原型上的 friend。
⚠️ 注意:调用 bind 的不是函数,需要抛出异常。
完整代码如下:
1 | Function.prototype.bind2 = function (context) { |