JS-原理分析-call()

wujiawen 发布于

call() 实现原理

call() 方法是Function的原型对象的方法(Function.prototype.call)

call() 允许为不同的对象分配和调用属于一个对象的函数/方法

call() 可以改变this的指向(但是这种说法其实并不准确,这是根据作用阐述的通俗易懂的说法,因为this是在执行上下文创建时确定的一个在执行过程中不可更改的变量)

那么 call() 方法做了什么呢?

以此调用为例:

1
Object.prototype.toString.call(obj)

主要做了以下处理:

  • obj.toString = Object.prototype.toString 将调用的函数设为被指向对象的属性

  • obj.toString() 执行该函数

  • delete obj.toString 删除该函数

如此一来,就实现了目标对象没有此函数,却可以通过call方法进行调用

模拟实现call

1
2
3
4
5
6
7
Function.prototype.call = function (context, ...args) {
context = context || window
context.fn = this // 设为目标对象的属性 this 指向调用call的函数
const result = context.fn(...args) // 调用目标对象下的该函数
delete context.fn // 删除目标对象下的该函数
return result // 有返回值就返回对应返回值,没有就返回 undefined
}

在知道了实现原理的情况下,可以联想到

  • call 会出现闭包的情况

因为 call 会直接将调用call方法的函数的引用赋给目标对象的一个属性
而函数在定义时,其定义域就已经确定了,若该函数是内部函数,则有可能会出现闭包的情况
(当然,这种情况出现的机率非常小,但是从原理上来讲,是会发生的,取决于如何使用)

apply() 实现原理

apply() 的实现原理与 call() 一样,区别只是参数的传递方式不一样,apply()要求传递数组,而call()则是一个个传递

模拟实现apply

1
2
3
4
5
6
7
Function.prototype.call = function (context, arr) {
context = context || window
context.fn = this
const result = arr.length ? context.fn(...args) : context.fn()
delete context.fn
return result
}

bind() 实现原理

bind() 会返回一个新的函数,此函数会绑定创建它时传入 bind()方法的目标对象(第一个参数)

若使用 new 关键字调用返回的新函数,则绑定的对象会失效,转为绑定new出来的实例

模拟实现bind

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.bind = function(context){
const args = Array.prototype.slice.call(arguments, 1)
const F = function(){}
const self = this
bound = function(){
const innerArgs = Array.prototype.slice.call(arguments)
const finalArgs = args.concat(innerArgs)
return self.apply((this instanceof F ? this : context), finalArgs)
}
F.prototype = self.prototype
bound.prototype = new F()
return bound
}

bind传入目标对象后,会return一个函数

返回的函数,无法再用call() 或 apply()重新绑定新的目标对象

  • 连续bind()也是无效的

如:foo.bind(obj1).bind(obj2).bind(obj3) 等同于 foo.bind(obj1)
后面的两个bind都是不生效的

原因嘛,不好表达,大致描述一下下:

第一层bind,foo函数绑定了obj1,而bind的内部调用了call/apply,call/apply都会立即执行调用它的函数
第一层bind返回的函数 f1 中,内部是调用apply绑定 foo 与 obj1(foo.apply(obj1)),若执行 f1,会立即执行 foo

第二层bind,f1函数绑定了obj2,返回的函数 f2 ,f2中调用apply绑定 f1 与 obj2(f1.apply(obj2)),若执行 f2,会立即执行 f1

从这里,我们已经可以看出结果了(第三层就不列举了,同样的原理):

我们的目标是将 foo函数 与目标对象绑定,然后执行foo函数

而第二层虽然绑定了 obj2,却是 f1 与 obj2 绑定,无法影响到 foo

所以有如下的执行链:

f2 => f1(绑定obj2) => foo(绑定obj1)

最终还是会执行 foo,并且,foo函数绑定的还是 obj1

无论多少层都是一样的结果

参考文章:
call()、apply() 参考:
https://blog.csdn.net/liubangbo/article/details/84098212
https://blog.csdn.net/Liu_yunzhao/article/details/90216066

bind() 参考:
https://www.cnblogs.com/moqiutao/p/7371988.html