JavaScript筑基(五):this与左值

173 阅读3分钟

this与左值

this 绑定场景

点进这篇文章应该更多的是被左值这个有点陌生的词吸引,而 this 已经被大家讲烂了,无非以下四种场景:

  • 作为普通的函数调用,那么 this严格模式下为 undefined ,而在非严格模式下会指向全局对象,如window
  • 作为某个对象的方法,那么 this 就会指向这个对象
  • 通过 new Fun() 创建新实例,那么 this 会指向这个实例
  • 使用 call / apply / bind 会将函数中的 this 指向输入的对象

同时由于箭头函数本身没有this,所以它使用的 this 是定义时离它最近的 this ,且 new () => {} 是会报错的。

关于优先级,有:new > 显式绑定call / apply / bind) > 隐式绑定(被对象调用) > 默认绑定(普通函数)

几个表达式

既然已经熟知了这些规则。那么下面的几个表达式,答案会是什么呢?

 
 "use strict"
 let obj = {};
 let foo = function () {
   console.log(this);
 };
 obj.foo = foo;
 ​
 // 下面这几个会怎么输出?
 (foo = obj.foo)();
 (obj.foo = foo)();
 (obj.foo)();
 (1 && obj.foo)();
 (obj.foo = obj.foo)();

左值

要理解上面的表达式会怎么运行,就要谈谈左值的概念了。

在《环境变量》一节中提到。环境记录可以理解为标识符 -> 变量的映射表。举个例子 var a = 2 ,这里的 a 就算是标识符,而 2 就是变量(具体的储存区)。

基于这个概念,再了解两个前提:

  • 在词法环境中可以被解析成变量的有标识符属性名。换句话说,属性名也是一种标识符,而标识符不一定是属性名
  • 在JavaScript的底层操作中,标识符和变量是真正意义上的引用关系

而所谓的左值,可以直接理解为变量,它代表了内存中的可操作空间。因为任意标识符都可以直接引用到变量,所以任一标识符都可以是左值

标识符 -> 变量 -> 可操作空间 -> 左值

举个例子,在赋值的过程中:

 A = B

关键的前两个步骤:

  1. 先找到标识符 A 所引用的变量,设为左值 LRef(在JavaScript规范中左值记作LRef

  2. 再找到标识符 B 所引用的变量,取出其中的数据,设为 RVal

    ...

这里可以把变量理解为一个空间,里面的数据可以一直更新,而数据就是具体的值。

换言之,左值指的是可赋值的事物,而仅仅代表了数据数据本身是无任何属性的

而左值与上文列出来的几个 this 绑定规则又有什么关系呢?

这些规则的前提是,函数是一个左值

表达式分析

显然的是,foo() / obj.foo() 这两个的()左边都是变量(标识符),满足函数是左值的前提。

再来看看 (foo = obj.foo)();

 (foo = obj.foo);
 // 返回值如下
 ƒ () {
   console.log(this);
 }

这里的表达式虽然返回了一个函数体,但是这个函数体是一个具体的数据,而不是左值。所以调用之后是返回 undefined

同理,后面如果是通过表达式返回的一个数据,则相当于是在全局调用了一个具有 foo | obj.foo 相同函数体的一个新函数

所以结果如下

 (foo = obj.foo)();  // undefined
 (obj.foo = foo)();  // undefined
 (obj.foo)();        // obj 这里的圆括号中没有运算符,相当于没有操作
 (1 && obj.foo)();   // undefined
 (obj.foo = obj.foo)(); // undefined

参考资料

《JavaScript权威指南》(第七版)

精读Javascript系列(四)过渡篇:左值与This绑定

JavaScript深入之从ECMAScript规范解读this