题目 30 / 31
把对象嵌套多一层,会改变 this 的解析方式吗?
知识点: this 绑定难度: 高级
通过 person1.obj.foo1() 这样的路径调用到的方法,this 依然是从调用点解析出来的——多一层属性访问并不会自带一个接收者。
使用执行可视化器解题在下方逐步观察每个绑定、函数调用和控制台输出。预测输出结果
Person 把 this.obj 构建成一个嵌套对象,里面有两个方法,各自返回一个内部函数。请预测全部六行输出。
var name = "Window";
function Person(name) {
this.name = name;
this.obj = {
name: "Obj",
foo1: function () {
return function () {
console.log(this.name);
}
},
foo2: function () {
return () => console.log(this.name)
},
}
}
var person1 = new Person("Person1");
var person2 = new Person("Person2");
person1.obj.foo1()();
person1.obj.foo1.call(person2)();
person1.obj.foo1().call(person2);
person1.obj.foo2()();
person1.obj.foo2.call(person2)();
person1.obj.foo2().call(person2);查看答案与完整解析
正确输出
Window
Window
Person2
Obj
Person2
Obj为什么会这样
obj.foo1 返回一个普通(非箭头)函数。person1.obj.foo1()() 和 person1.obj.foo1.call(person2)() 都是裸调用这个返回的函数,因此不管 foo1 本身怎么被调用,都走默认绑定——都输出 "Window"。
person1.obj.foo1().call(person2) 随后显式用 person2 作为 this 调用这个返回的函数,输出 "Person2"。
obj.foo2 返回一个箭头函数,每次 foo2 运行时都会重新创建,并捕获那次 foo2 调用时的 this。person1.obj.foo2()() 是把 foo2 当作 obj 的方法调用——隐式绑定使这次调用的 this = obj,于是这个箭头函数捕获的是 obj,输出 "Obj"。
person1.obj.foo2.call(person2)() 显式强制这次 foo2 调用的 this = person2,于是这次返回的箭头函数捕获的是 person2,输出 "Person2"。person1.obj.foo2().call(person2) 再次把 foo2 当作方法调用(this = obj,和第一次 foo2 调用一样),因此返回的箭头函数依然捕获 obj——对这个已经创建好的箭头函数调用 .call(person2) 毫无作用,仍然输出 "Obj"。
这道题考察什么
- 隐式绑定永远看的是调用点那个点号左边紧挨着的对象——person1.obj.foo2() 绑定的 this 是 obj,而不是 person1
- 把属性嵌套得更深一层并不会改变四条绑定规则中的任何一条——它只是改变了"点号左边的对象"是从哪里读到的
- obj.foo2 内部的箭头函数在每次 foo2() 调用时都会被重新创建,因此每次调用都可能捕获不同的 this——这一点和构造函数里只创建一次的方法不同
运行环境前提
假设是经典的非严格模式脚本;new Person(...) 按照本系列其它题目使用的 [[Construct]] 语义,为构造函数调用提供 this。