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
关键的前两个步骤:
-
先找到标识符
A
所引用的变量,设为左值LRef
(在JavaScript规范中左值记作LRef
) -
再找到标识符
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权威指南》(第七版)