题目 29 / 31
在构造函数里挂载的方法,还遵循调用点规则吗?
知识点: this 绑定难度: 高级
在构造函数内部写 this.foo = function(){...},只是改变了这个函数挂在哪里,并不会改变它自己的 this 之后如何被解析——调用点规则仍然说了算。
使用执行可视化器解题在下方逐步观察每个绑定、函数调用和控制台输出。预测输出结果
Person 在构造函数内部给 this 挂载了四个方法,对应上一题的四种 this 形态。请预测调用 person1 的这些方法(其中几个还会再对 person2 调用一次)产生的全部十行输出。
var name = "Window";
function Person(name) {
this.name = name;
this.foo1 = function () {
console.log(this.name);
}
this.foo2 = () => console.log(this.name);
this.foo3 = function () {
return function () {
console.log(this.name);
}
}
this.foo4 = function () {
return () => console.log(this.name)
}
}
var person1 = new Person("Person1");
var person2 = new Person("Person2");
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);查看答案与完整解析
正确输出
Person1
Person2
Person1
Person1
Window
Window
Person2
Person1
Person2
Person1为什么会这样
foo1 是在构造函数里赋给 this.foo1 的一个普通函数。person1.foo1() 走隐式绑定(this = person1,输出 "Person1");person1.foo1.call(person2) 显式覆盖(输出 "Person2")。
foo2 是赋给 this.foo2 的箭头函数——它只在构造函数为 person1 运行的那一刻被创建一次,从此永久捕获那次调用的 this(也就是 person1)。person1.foo2() 和 person1.foo2.call(person2) 都输出 "Person1":显式绑定动不了箭头函数,而且和上一题的 foo2 不同,这里的箭头函数确实是以 person1 作为 this 被创建的,而不是 window。
foo3 每次运行都返回一个全新的普通函数。person1.foo3()() 和 person1.foo3.call(person2)() 都是裸调用这个返回值,因此不管 foo3 本身怎么被调用,都走默认绑定——都输出 "Window"。person1.foo3().call(person2) 随后显式把返回的函数绑定到 person2 上调用,输出 "Person2"。
foo4 这次返回的箭头函数确实调用了 console.log(this.name)(和上一题的 foo4 不同)。person1.foo4()() 在 this = person1(foo4() 这次调用走隐式绑定)的时候创建并立即调用这个箭头函数,输出 "Person1"。person1.foo4.call(person2)() 则强制这次 foo4 调用的 this = person2,于是返回的箭头函数捕获的是 person2,输出 "Person2"。person1.foo4().call(person2) 又一次以 this = person1 创建箭头函数,之后对这个已经创建好的箭头函数调用 .call(person2) 毫无作用,因此仍输出 "Person1"。
这道题考察什么
- 在构造函数里写 this.foo = function(){...} 只是一次普通的属性赋值——它不会像箭头函数那样把这个函数的 this "冻结"住
- 箭头函数的 this 只在它被创建的那一刻被永久捕获一次——对 this.foo2 来说,那一刻就是"Person 构造函数正在为这个具体实例运行"的时候
- 同样形状的代码(foo3/foo4)在两道题里可以表现完全不同,纯粹取决于返回的函数是"会打印的箭头函数"还是"只 return 值的箭头函数"还是"普通函数"——要读函数体,不能只看外形
运行环境前提
假设是经典的非严格模式脚本;new Person(...) 按照本系列其它题目使用的 [[Construct]] 语义,为构造函数调用提供 this。