共计 2311 个字符,预计需要花费 6 分钟才能阅读完成。
前言
稍微翻了一下 call,apply,bind 的各种论坛上的文章,发现讲的都太浅了,大部分都只讲了个用法,对于实现的原理却都没有提,因此,在这里,我写下这篇文章,希望能让大家认识到原理所在。
众所周知,这三个函数都是改变执行上下文的,那么我们来捋一捋,这些函数内部到底做了什么。
call
Function 是函数对象的构造方法,call,apply,bind 都是函数原型上的方法 作为实例 他自身也有这三个方法
圈中的为原型方法,方块的为实例方法,另外 length 属性就是 argument 的长度,我们通常调用一个函数
function a(){ console.log(this,’a’)};
function b(b){console.log(b)}
a.call(b,’ 我是 B 的参数 ’)
执行 a, 并把 context 指向 b,这里大家都没有疑问,那么问题来了
function a(){ console.log(this,’a’)};
function b(){console.log(this,’b’)}
a.call.call.call(b,’b’) // 这个结果是什么呢?
答案是
傻眼了吧?怎么执行了 B 并且 this 指向了这个 b 字符串
我们来分析一下 call 是原型上的方法 那么 a.call 他本身也是一个函数 所以 a.call.call.call 不就是 a.call.call 的原型上的 call 方法么?所以不就是执行 call.call 并改变 call.call 的上下文
我们来撸一遍 call 的源码,
第一个参数是上下文,当我们 call(null),this 指向了 window 当我们传入字符串 会把字符串包装成对象
a.call 执行 this 是指向 a 的(谁调用 this 指向谁)然后又执行了 a 方法,所以内部是
Function.prototype.call = function(context){
context = context ? Object(context):window
this() // 因为调用的都是函数 所以 this 是一个函数 也就是 a
}
那这样并未改变 this 指向啊,咋办?上句话不是说((谁调用 this 指向谁)),所以我们要在内部改变掉 this,做如下修改
Function.prototype.call = function(context){
context = context ? Object(context):window
context.fn = this
context.fn() // 通过调用 context.fn 来改变调用者 实现 fn 的 this 指向 context 即改变 a 内部的 this
}
那么参数怎么传呢,我们首先要拿到 call 的里的参数
Function.prototype.call = function(context,…args){
context = context ? Object(context):window
context.fn = this
let r = context.fn(…args) // 通过调用 context.fn 来改变调用者 实现 fn 的 this 指向 context 即改变 a 内部的 this
delete context.fn // 删除属性
return r // 返回执行的结果
}
现在我们回头分析一下 a.call.call.call(b,’b’)a.call.call 是个函数,它调用 call 方法 执行它 我们进入函数里面看看
首先把 context 也就是 b 转为字符串对象 它的属性上赋予 fn 也就是 a.call.call,然后执行 context.fn(…args),也就是 a.call.call(‘b’) 接着删除 fn 返回执行结果 宏观来看 是 a.call.call 这个函数去执行并传入 (‘b’) a.call.call 也就是 Function.prototype.call 所以就是 call(‘b’) 所以啊,结果才是 this 是指向 string 的 b 并且参数是 undefined
Function.prototype.call = function(context,…args){
context = context ? Object(context):window
context.fn = this //a.call.call(‘b’)
var r = context.fn(…args) // 通过调用 context.fn 来改变调用者 实现 fn 的 this 指向 context 即改变 a 内部的 this
delete context.fn
return r
}
function a(){ console.log(this,’a’)};
function b(args){console.log(‘ 我是 this:’ + this,’ 我是 b 的参数 args:’ + args)}
a.call.call.call(b,’ 我到底是参数呢还是 this’)
结论!一个函数 call2 次或者 2 次以上 执行的永远是 b,并且 call 的第二个参数成为当前 context
apply
apply 就是参数不同 直接上代码
Function.prototype.apply = function(context,…args){
context = context ? Object(context):window
context.fn = this;
var r;
if(args.length){
r = context.fn(…args)
delete context.fn
}else{
r = context.fn()
}
return r
}
function a(args){console.log(this,args)};
function b(){console.log(‘ 我是 this:’ + this)}
a.apply(b,[1,2,3,4])
bind 明天写 累了