javascript中的this关键字
上一张初略版this分析导图先:
this在英文中是一个指示代词,指示将要表达中的名词。
js王国中的this有会是指向谁了,先看看this常有的误解认识
-
误解1: this指向函数本身
function fun(){ this.count++ console.log('invoke fun') } fun.count=0 fun() console.log(fun.count) 输出: invoke fun 0
上面的代码并没有输出1, 可见this并非指向函数本身
-
误解2: this指向当前作用域
function f1(){ var a = 'inner.f1' this.f2() } function f2(){ console.log('a='+this.a) } var a = 'global' f1() 输出: a=global
同样上面的代码然而并没有输出'a=inner.f1',这样this也并非指向当前作用域。
上面已经描述this不是什么了,那么this到底该指向什么了。 其实在js中一个函数被调用时,引擎会创建一个执行上下文记录,该记录包含函数调用位置,函数调用方式,传入的参数信息。this就是执行上下文记录中的一个属性。 this是动态绑定的,并非编写代码时决定的。通过分析函数的调用位置,分析出this的绑定对象。这其中有四大绑定规则。
一、绑定规则:
-
默认绑定
函数作为独立调用方式时,其采用默认绑定,或者无法应用其他绑定规则时的使用的默认绑定规则。默认绑定时,this指向全局对象。
例如:function fun(){ console.log('name:'+this.name) } var name = 'global' fun() 输出: name:global
fun函数在上面代码中使用独立调用方式,this指向了全局对象,name作为全局定义的对象。
*** 当上述代码fun内部运行在strict mode下时,全局定义的对象不能用于默认绑定,同时会报错TypeError: this is undefined。 -
隐式绑定
一个对象中含有指向函数的引用属性,并通过这个属性间接引用函数,从而把this隐式绑定到这个对象。这就是隐式绑定。简单点说:
当函数有被对象包含着时,这是函数内的this便指向被包含对的对象,上代码示例:function fun(){ console.log('fun name:'+this.name) } var obj = { name: 'obj', fun: fun, funX: function(){ console.log('funX name:'+this.name) } } obj.fun() obj.funX() 输出: fun name:obj funX name:obj
可以看到上面代码中调用方式:obj.fun/obj.funX, 这是函数内部的this指向了obj对象,这样this.name就会使用了obj.name
-
显示绑定
如果按照隐式绑定中的方法调用,但不想要this绑定到默认对象,可以通过js中另外两种调用方式apply/call,这两种方法中第一个参数都是为显示为函数内部this指向的对象,上代码示例:var objA={ name: 'objA' } var objB={ name: 'objB', fun: function(){ console.log('name:' + this.name) } } objB.fun() objB.fun.call(objA) objB.fun.apply(objA) 输出: name:objB name:objA name:objA
上面代码中,objB.fun()使用了隐式绑定,输出了objB中的name属性值,另外两种调用方式则改变了函数中的this对象使其绑定到了对象objA.
***同时还有一种显示绑定方式bind,将其称为硬绑定。
var objA={ name: 'objA' } var objB={ name: 'objB', fun: function(){ console.log('name:' + this.name) } } var fun = objB.fun.bind(objA) fun() 输出: name:objA
上面代码中通过bind方式定义了一个函数引用fun,后续调用时,其中的this指向了定义时的对象,故而输出了objA,name值
-
new绑定
在函数作为构造函数方式调用时,函数内的this指向了新创建的对象。
例如:function Fun(name){ this.name = name } var fun = new Fun('fun') console.log(fun.name) 输出: fun
上述函数Fun作为构造函数方式调用(其实Fun也是js中一名普通的函数),其实此时发生了如下几件事:
- 创建一个对象
- 将新创建的对象绑定到函数中的this上
- 新创建的对象的[[prototype]]指向了Fun.prototype
- 如果函数内部没有显示返回值,那么new表达式执行完后默认返回该新创建的对象
所以如上显示的那样,输出了'fun'
有了上面描述的四大绑定规则,那么在实际的函数方法中,该使用哪种绑定规则判断this了,此时还需要了解这其中的优先级关系。
二、优先级判断
唔有疑问,默认绑定无疑是四种优先级最低的,先搁置一边,探索下其余三种的优先级。
隐式绑定 VS 显示绑定
var objA={
name: 'objA'
}
var objB={
name: 'objB',
fun: function(){
console.log('name:' + this.name)
}
}
objB.fun()
objB.fun.call(objA)
name:objB
name:objA
上述代码可以看出显示绑定优先级高于隐式绑定规则: 显示绑定 > 隐式绑定
接下来在看下隐式绑定与new绑定的优先级关系:
隐式绑定 VS new绑定
var obj={
name: null,
setName: function(name){
this.name = name
}
}
obj.setName('obj')
console.log('obj.name:' + obj.name)
var obj2 = new obj.setName('obj2')
console.log('obj.name:' + obj.name)
console.log('obj2.name:' + obj2.name)
输出:
obj.name:obj
obj.name:obj
obj2.name:obj2
上述代码中new obj.setName('obj2'), 并非更改了obj对象中的name属性,可见new绑定的优先级高于隐式绑定: new绑定 > 隐式绑定
最后看下显示绑定与new绑定方式优先级别:
显示绑定 VS new绑定方式
apply/call方式不能与new连用,此处使用硬绑定方式bind与new连用
var obj={
name:null
}
function setName(name){
this.name = name
}
var newSetName = setName.bind(obj)
var newObj = new newSetName('new')
console.log('obj.name:'+obj.name)
console.log('newObj.name:'+newObj.name)
输出:
obj.name:null
newObj.name:new
上面代码看出及时setName显示硬绑定this为obj,但是new方式调用时还是更改了setName中的this引用指向,由此可见new绑定优先级高于显示绑定:new绑定 > 显示绑定
那么总结上面的优先级规则:
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
平常情况按照如上规则判断均会有效,但凡事均会有例外,再看下例外的规则
例外1. 显示绑定apply/call/bind时传入了null/undefined时,此时this并非指向了null/undefined,而是使用了默认绑定规则
function fun(){
console.log(this.name)
}
var name = 'outer'
fun.call(null)
输出
outer
上述this.name输出了全局变量name值。
当然你会问为什么需要传入null值,有一种case如需要使用bind预设值一些参数值,如:
function multiple(mul, number){
console.log('result:' + (mul * number))
}
var fun = multiple.bind(null, 5)
fun(2)
输出:
result:10
例外2. 函数赋值方式形成的间接调用,此时this也是使用了默认绑定规则
function print(){
console.log(this.name)
}
var name = 'outer'
var obj={
name: 'inner',
print: print
}
var fun
(fun = obj.print)()
输出:
outer
写在最后
ES6中的箭头函数,this的规则比较特殊,它是继承了外层的this对象,由外层的作用域来决定。
上代码:
function fun(){
return () => {
console.log(this.name)
}
}
var obj1={
name:'obj1'
}
var obj2={
name:'obj2'
}
var reFun = fun.call(obj1)
reFun.call(obj2)
输出:
obj1
上述代码可以看出并没有输出obj2,而是输出了fun函数调用时this指向的对象obj1对应的name属性,可以看出,箭头函数中的this指向了外层函数中的this对象。可以看出箭头函数有些反this机制,而是采用了“静态”绑定。
参考资料:[美]KYLE SIMPSON著 赵望野 梁杰 译 《你不知道的javascript》上卷
网友评论