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

规范参考

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

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