切换主题
this指向
this是在函数运行时绑定的,只取决于调用方式(或者说位置),为什么呢?
还记得我们在原型链中明确的一个概念吗?
在JavaScript中一切皆对象(
nullundefined除外)!
运行环境也不例外,也是一个对象,所以函数都是在某个对象之中运行,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。