切换主题
this
指向
this
是在函数运行时绑定的,只取决于调用方式(或者说位置),为什么呢?
还记得我们在原型链中明确的一个概念吗?
在JavaScript中一切皆对象(
null
undefined
除外)!
运行环境也不例外,也是一个对象,所以函数都是在某个对象之中运行,this
就是函数运行时所在的对象(环境)。 由于JavaScript支持运行环境动态的切换,故this
的指向也是动态变化的,其指向完全取决于调用方式。
要掌握this
指向就需要先了解this
的绑定的四个规则:
- 默认绑定:只要通过不带任何修饰的独立函数调用(非执行)时,
this
一定指向全局对象;- 这里的不带任何修饰即指仅通过函数名本身调用;
- 严格模式下调用和严格模式下执行是不同的,不能混为一谈。
严格模式下的调用与执行
- 严格模式下的调用
JavaScriptconst name = 'yui' function kon() { console.log(this.name) } (function () { 'use strict' kon() // 'yui' })()
- 严格模式下的执行
JavaScriptconst name = 'yui' function kon() { 'use strict' console.log(this.name) } kon() // Uncaught TypeError
new
绑定:构造函数通过new
操作符生成的实例对象时,this
一定指向该实例对象;- 显式绑定:指通过
call()
、apply()
、bind()
、API调用的上下文
等方法直接修改时,this
一定指向用户指定的对象(即this
参数); - 隐式绑定:当函数引用有上下文对象,且通过对象属性直接调用函数时,
this
一定指向该对象。当非直接调用时:- 当对象属性嵌套调用时(对象属性引用链),只有最后一层(最内层,即只关心真正调用函数的对象)在调用有效;
- 还有些复杂情况,会在
MemberExpression
详细讨论并总结。
这四个规则前三个都非常容易理解,但是最后一条隐式绑定就不太好理解,为了能更好的理解这条规则,看下面这个例子:
JavaScript
function kon() {
console.log(this.name)
}
const name = '轻音少女'
const yui = {
name: 'yui',
say: kon
}
const mio = {
name: 'mio',
yui // es6 简写
}
kon() // '轻音少女'
yui.say() // 'yui'
mio.yui.say() // 'yui'
/* 上上强度(均单独执行) */
(yui.say)() // ?
(yui.name = yui.say)() // ?
(yui.say = yui.name)() // ?
(yui.name, yui.say)() // ?
(1, yui.say)() // ?
(yui.say, 1)() // ?
(1 && yui.say)() // ?
(0 || yui.say)() // ?
特别提醒
由于行23、行24的赋值操作会修改初始对象yui
,故需要单独执行!!!
也可以只单独执行(强制刷新)行23、行24代码
MemberExpression
在给出上述疑问解答之前,我们需要从ECMAScript规范
的角度,来了解一个概念,什么概念呢?那就是什么是MemberExpression
?
规范中明确解释了
MemberExpression
就是LeftHandSideExpression
。
五类MemberExpression
PrimaryExpression
原始表达式FunctionExpression
函数定义表达式MemberExpression[Expression]
属性访问表达式MemberExpression.IdentifierName
属性访问表达式new MemberExpression Arguments
对象创建表达式
为什么要了解它?
因为
MemberExpression
决定了函数调用时this
的指向!!!
比如上例代码中行13及以后的MemberExpression
分别为:kon
、yui.say
、mio.yui.say
、(yui.say)
、(yui.name = yui.say)
。 这些均是MemberExpression
,所以简单理解MemberExpression
其实就是()
左边的部分。
明确MemberExpression
后,像上上强度中的表达式并非五类MemberExpression
中任何一种,都涉及JavaScript简单的运算,像赋值操作、逻辑运算,逗号操作符等。
逐一击破
因此我们需要逐个计算其对应的真正MemberExpression
:
对于
(yui.say)()
,yui.say
被()
包裹,仅仅是改变了运算优先级,对MemberExpression
无任何影响:故
MemberExpression = yui.say
,即执行yui.say()
,此时的this
指向当前调用对象yui
。对于
(yui.name = yui.say)()
,先赋值yui.name = yui.say
,yui.say
将其值(kon
函数引用ref kon
)赋值给yui.name
,即yui.name = ref kon
, 此时MemberExpression
被改变:故
MemberExpression = ref kon
,即直接执行kon()
,此时的this
指向全局对象window
。对于
(yui.say = yui.name)()
,先计算(赋值)yui.say = yui.name
,yui.name
将其值(字符串'yui'
)赋值给yui.say
,即yui.name = 'yui'
,此时MemberExpression
被改变:故
MemberExpression = 'yui'
,即执行'yui'()
,此时的this
指向全局对象window
,但将报错Uncaught TypeError: yui.say is not a function
。对于
(yui.name, yui.say)()
与(1, yui.say)()
,逗号操作符即取最后一个值进行赋值,此时的MemberExpression
被改变,将yui.say
的值赋值给MemberExpression
(原理同2):- 故
MemberExpression = ref kon
,即直接执行kon()
,此时的this
指向全局对象window
。
- 故
对于
(yui.say, 1)()
,同上,取最后一个值,此时的MemberExpression
被改变:- 故
MemberExpression = 1
,即执行:1()
,此时的this
指向全局对象window
,将报错:Uncaught TypeError: (yui.say , 1) is not a function
。
- 故
对于
(1 && yui.say)()
与(0 || yui.say)()
,逻辑与运算(&&
返回第一个假值,全为真就返回最后一个值)或者逻辑或运算(||
返回第一个真值,全为假就返回最后一个值)后,都会对MemberExpression
进行赋值, 此时的MemberExpression
被改变,将yui.say
的值赋值给MemberExpression
(原理同2):- 故
MemberExpression = ref kon
,即直接执行kon()
,此时的this
指向全局对象window
。
- 故
由以上们可以给出例子中上上强度的答案了:
JavaScript
/* 上上强度(均单独执行) */
(yui.say)() // 'yui'
(yui.name = yui.say)() // '轻音少女'
(yui.say = yui.name)() // Uncaught TypeError: yui.say is not a function
(yui.name, yui.say)() // '轻音少女'
(1, yui.say)() // '轻音少女'
(yui.say, 1)() // Uncaught TypeError: (yui.say , 1) is not a function
(1 && yui.say)() // '轻音少女'
(0 || yui.say)() // '轻音少女'
不难总结出:像赋值操作、逻辑运算,逗号操作符等操作均会改变MemberExpression
,使得this
指向全局对象window
。