Skip to content
本页目录

this指向

this是在函数运行时绑定的,只取决于调用方式(或者说位置),为什么呢?

还记得我们在原型链中明确的一个概念吗?

在JavaScript中一切皆对象(null undefined除外)!

运行环境也不例外,也是一个对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。 由于JavaScript支持运行环境动态的切换,故this的指向也是动态变化的,其指向完全取决于调用方式

要掌握this指向就需要先了解this的绑定的四个规则:

  1. 默认绑定:只要通过不带任何修饰的独立函数调用(非执行)时,this一定指向全局对象;
    • 这里的不带任何修饰即指仅通过函数名本身调用;
    • 严格模式下调用和严格模式下执行是不同的,不能混为一谈。
    严格模式下的调用与执行
    • 严格模式下的调用
    JavaScript
    const name = 'yui'
    function kon() {
      console.log(this.name)
    }
    
    (function () {
      'use strict'
      kon() // 'yui'
    })()
    • 严格模式下的执行
    JavaScript
    const name = 'yui'
    function kon() {
      'use strict'
      console.log(this.name)
    }
    
    kon() // Uncaught TypeError
  2. new绑定:构造函数通过new操作符生成的实例对象时,this一定指向该实例对象;
  3. 显式绑定:指通过call()apply()bind()API调用的上下文等方法直接修改时,this一定指向用户指定的对象(即this参数);
  4. 隐式绑定:当函数引用有上下文对象,且通过对象属性直接调用函数时,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
  1. PrimaryExpression 原始表达式
  2. FunctionExpression 函数定义表达式
  3. MemberExpression[Expression] 属性访问表达式
  4. MemberExpression.IdentifierName 属性访问表达式
  5. new MemberExpression Arguments 对象创建表达式

为什么要了解它?

因为MemberExpression决定了函数调用时this的指向!!!

比如上例代码中行13及以后的MemberExpression分别为:konyui.saymio.yui.say(yui.say)(yui.name = yui.say)。 这些均是MemberExpression,所以简单理解MemberExpression其实就是()左边的部分。

明确MemberExpression后,像上上强度中的表达式并非五类MemberExpression中任何一种,都涉及JavaScript简单的运算,像赋值操作、逻辑运算,逗号操作符等。

逐一击破

因此我们需要逐个计算其对应的真正MemberExpression

  1. 对于(yui.say)()yui.say()包裹,仅仅是改变了运算优先级,对MemberExpression无任何影响:

    MemberExpression = yui.say,即执行yui.say(),此时的this指向当前调用对象yui

  2. 对于(yui.name = yui.say)(),先赋值yui.name = yui.sayyui.say将其值(kon函数引用 ref kon)赋值给yui.name,即yui.name = ref kon, 此时MemberExpression被改变:

    MemberExpression = ref kon,即直接执行kon(),此时的this指向全局对象window

  3. 对于(yui.say = yui.name)(),先计算(赋值)yui.say = yui.nameyui.name将其值(字符串'yui')赋值给yui.say,即yui.name = 'yui',此时MemberExpression被改变:

    MemberExpression = 'yui',即执行'yui'(),此时的this指向全局对象window,但将报错Uncaught TypeError: yui.say is not a function

  4. 对于(yui.name, yui.say)()(1, yui.say)(),逗号操作符即取最后一个值进行赋值,此时的MemberExpression被改变,将yui.say赋值给MemberExpression(原理同2):

    • MemberExpression = ref kon,即直接执行kon(),此时的this指向全局对象window
  5. 对于(yui.say, 1)(),同上,取最后一个值,此时的MemberExpression被改变:

    • MemberExpression = 1,即执行:1(),此时的this指向全局对象window,将报错:Uncaught TypeError: (yui.say , 1) is not a function
  6. 对于(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