callstack 和 this 在 .defineProperty 方法期间的行为

Behaviour of callstack and this during .defineProperty method

前言:

Feel free to skip to the actual question below, if you find the 'backstory' here unnecessary. But I do believe it adds good amount of detail and further context for the question

最近我一直在尝试对象和 [[ Prototype ]] 链,

首先,我很困惑为什么我的 this 引用 return 在 NaN 中使用以下代码编辑:

var obj = {
  a: 2
}

Object.defineProperty(obj, 'b', {
  value: this.a + 2,
  enumerable: true
});

console.log(obj.b);

首先,我对为什么感到困惑,但后来 NaN 行为让我震惊。
我的 obj.b 计算为 undefined + 2,因此 return 在 NaN

现在,显然更改代码以显式引用 value: obj.a + 2 就可以解决问题,但我想进一步调查为什么 this.a returns 为 undefined.


基本上,我的问题是我的调用堆栈引用了 Object.prototype.a,因为 Object.defineProperty 是隐式委托的 Object.prototype.defineProperty

因此,将 a 明确设置为 Object.prototype 的 属性 将解决此问题:

var obj = {
  a: 2
}

Object.prototype.a = obj.a;

Object.defineProperty(obj, 'b', {
  value: this.a + 2,
  enumerable: true
});

console.log(obj.b);

这 return 是 4 的预期结果(虽然是的,创建一个 属性 a 到整个 Object.prototype 是绝对不是最好的编程实践,但出于说明目的,它会很好地服务)

问题:

然而,让我感到困惑的是,如果我 运行 在表达式上使用 console.trace,我的调用堆栈完全由 (anonymous) 组成 - 所以基本上 global (默认)this 关键字的隐式绑定。

You will have to run the code in your own console, as sadly console.trace() is unfortunately not supported by jsfiddle (least to my knowledge)

var obj = {
  a: 2
}

Object.prototype.a = obj.a;

console.trace(Object.defineProperty(obj, 'b', {
  value: this.a + 2,
  enumerable: true
}));


TL/DR:

因此,将这个问题总结为两个快速要点:

  1. 为什么调用堆栈 return 仅 (anonymous) 而不是 Object.prototype
  2. 在我们的 .defineProperty() 方法中将 this 显式绑定到引用对象 obj 的正确方法是什么?

提前致谢。

1) 为什么 console.trace returns 仅(匿名)

一旦您意识到自己错误地使用了 console.trace(),这种行为就会变得很明显。如果你去 Mozilla 的手册并查找 console.trace() 你会看到它应该像这样使用:

function foo() {
  function bar() {
    console.trace();
  }
  bar();
}

foo();

给出以下输出:

bar
foo
<anonymous>

换句话说,您必须从 内部调用 console.trace() 某个函数,而不是用它包装函数调用。这是因为 console.trace() 首先检查调用它的函数的名称 (bar),然后是父调用函数的名称 (foo ),然后是其父级的名称,依此类推,直到它到达最外层的执行上下文,这是一个 anonymous 函数 - 主执行线程,所有代码都从这里开始.

每当你在 devtools 中执行一些代码(我假设你正在使用它)时,它总是在这个全局执行上下文中以包装匿名函数的形式执行。由于您立即从此匿名函数调用 console.trace(),因此打印的内容是 - anonymous.

另外,console.trace() 接受参数。当您调用 console.trace() 时,您可以使用任意数量的参数,这些参数会在堆栈跟踪之前简单地打印到控制台。由于 Object.defineProperty() returns 您正在为其定义新 属性 的对象,这就是您传递给 console.trace() 的对象。

TL;DR - console.trace() 首先将其参数打印到控制台(即 obj,即 {一个:2,乙:4});然后它继续打印堆栈跟踪,它仅包含主要执行上下文,当您从控制台调用代码时,它是一个匿名函数。

2) 这个,定义属性 等等

Object.defineProperty的第三个参数是一个简单的对象,不是函数!它的属性在初始化期间立即解析,然后才传递到 define属性。这意味着以下代码在功能上与您的代码相同:

// First we initialize the third argument, separately from the call
// to .defineProperty()

var descriptor = {
  value: this.a + 2,
  enumerable: true
};

// at this point descriptor is already constructed. If you do
// console.log(descriptor.value) here, you will get NaN or maybe
// 4 or something else, depending on what you did before

Object.defineProperty(obj, 'b', descriptor);
// Here we simply invoke .defineProperty with descriptor as the third argument.  

你还必须知道,当你 运行 在主执行上下文中编写代码时(例如,从 devtools 控制台调用代码),this 指的是 Window对象。 Window 没有 属性 a,这就是 this.a 未定义的原因。

更有趣的是this.a(或window.a)在你Object.prototype.a = 2之后变成了2。发生这种情况是因为Window仍然是一个[=毕竟48=]Object。如果你将一些属性分配给对象对象原型(!!!),你将得到这个属性 适用于您代码中的所有对象!令人困惑?是的!现在我只想说你打算做什么(根据你的描述)可能是这样的:

obj.prototype = {a: 2};

或者这个:

var proto = {}; // First create a prototype
obj.prototype = proto; // Then assign it to the prototype property of your obj
obj.prototype.a = 2; // Now we can alter props on the prototype

看出区别了吗?这更有意义吗?当然,你这样做之后,你的代码将不再有效,因为this.a又会变成undefined。

结果如下:按照这种方式您无法实现您期望的行为。 属性 描述符(参数 #3)实际上比 valueenumerable 的组合更强大,你应该能够如果您在其上实现了 set() 和 get() 方法,则可以获得您想要的行为。另外,我相信您可以使用代理来获得类似的行为。没有提供更多细节,因为我建议您先阅读函数执行上下文,然后再阅读更多关于原型的内容,也许还有一些关于堆栈溢出的关于 this 行为的好问题。之后您可以尝试查看建议的解决方案。