题目 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。

规范参考

JavaScript 代码编辑器支持直接编辑或粘贴自定义代码
第 0 步 / 共 0 步
单步Space播放
按 → 单步 或 Space 自动播放
阶段 1: 编译预解析(变量提升与内存分配)
阶段 2: 代码执行(逐行赋值与调用求值)
单步执行原理解析

按键盘右方向键 “→” 进行单步调试,或按空格键 “Space” / 点击 “▶ 自动播放” 进行连续执行。