本文对this指向的讨论不包含箭头函数
call
先看一道面试题
function fn(a,b){
console.log(this)
console.log(a)
console.log(a+b)
}
fn.call(1)
fn.call.call(fn)
fn.call.call.call(fn,1,2)
fn.call.call.call.call(fn,1,2,3)
上边四个语句分别输出什么?
可以仔细思考一段时间
下面给出答案
fn.call(1) //Number{1}, undefined, NaN
fn.call.call(fn) //window, undefined, NaN
fn.call.call.call(fn,1,2) //Number{1}, 2, NaN
fn.call.call.call.call(fn,1,2,3) //Number{1}, 2, 5
call 函数的作用
call函数的主要作用在于改变 this指向,例如fn.call(1)也就是将call函数接受到的第一个参数1作为调用call函数的函数fn的执行上下文,然后执行fn()。this就指向了1。call函数的具体用法可参看MDN。
关键的第二句
fn.call.call(fn)。这句话,我个人被第一个出现的fn迷惑了很久,而实际上,第二句最终执行的函数是fn.call(),关键在于最后的那个.上,使用call时,最后那个.call之前的才是最后被执行的函数。例如:
var name = '小红'
var o = {
name: '小明',
sayName:function(){
console.log(this.name)
}
}
o.sayName.call(null) //小红
o.sayName.call(null)最终执行的是sayName函数体内的语句,而不是o.sayName(),我个人理解最终应该执行的可以类似于(function sayName(){console.log(this.name)})(),而该语句中的this指向window是call函数默认第一个参数是null还有undefined时的默认指向。故输出的是window.name
所以fn.call.call(fn)最终执行的语句应该是fn.call(),而call其实是每个函数都有的属性,最终定位在Function.prototype.call,每一个函数的call都是从Function的原型处获取的,而call本身是一个函数,所以call.call指向的是本身。
回到题目本身,我们知道fn.call.call(fn)最终执行的应该是call(),而此时call函数的this指向fn,那到底为什么,最后会输出那样的结果呢?我们先此问题放在一边,看看this指向的相关理解。
this永远指向最后调用它的那个对象
我非常赞同这篇文章: this、apply、call、bind 中,对this指向的说明。 this 永远指向最后调用它的那个对象,由于提到的文章已经有了大量的举例和论证,我就不过多阐述了,大意是指假如有obj1.obj2.obj3.func() 语句执行,func函数中的this只会指向obj3,也就是.func()中的那个.之前的那个对象,不再往这个对象往上寻找。定义在全局的函数中的this会指向window,如function fn(){}定义一个函数fn,然后执行fn(),等同于window.fn()。
由此,我由此推论:明确this指向的函数调用相当于this.func()
call函数的面纱
折回到第二句fn.call.call.(fn),我们已经知道最终执行的应该是call(),而此时call函数的this指向fn,所以由之上的推论(明确this指向的函数调用相当于this.func())可以推出:最终fn.call.call.(fn)执行的,可看作为fn.call(),此时fn.call()中call函数的第一个参数没传故为undefined,默认指向window,所以输出为window, undefined, NaN。其转换过程可以类比为:
/*为了好区分,这里call传入fn1 , typeof fn1 === 'function'*/
fn.call.call(fn1) =>
fn1.(fn.call()) =>
fn1.(Function.prototype.call()) =>
fn1.call()
那可能会有疑问了,第一句fn.call(1)岂不是等同于1.fn()了??实际上应该是等同于new Number(1).fn(),我推测call函数的原理大致应该是这样子的:
Function.prototype.selfCall = function(context){
context === undefined || context === null ? context = window
/*处理context类型的代码,如果是基本类型,就转为基本类型对象*/
...
/*call函数是属于函数类型的属性,调用call函数时,this指向调用call函数的函数,即typeof this === 'function'*/
context.fn = this /*将要执行的函数this*/
var args = /*处理call更多参数的代码*/
...
context.fn(...args)
delete context.fn
}
也可以参考文章: call, apply, bind实现
本篇文章还是有关 call函数 与 this指向 的老生常谈吧,由于之前参看了许多文章,一直不太得理解,看了call函数的实现,还有this的绑定,由于用得太少,看到一些用法时,还是比较朦胧。工作之余回想起这道面试题,觉得 this绑定与 call函数的实现原理相互印证,感觉对 call 与 this指向清晰了许多。起码,这道面试题,能够相对清晰的理解了。前端小白一枚,文章有许多不严谨,不清晰之处望多多海涵。
参考文献:
MDN Function.prototype.call
this、apply、call、bind
JavaScript中的call、apply、bind深入理解
call, apply, bind实现