JavaScript Super.prop 在对象字面量中

JavaScript Super.prop in object literals

let proto = {
    whoami() { console.log('I am proto'); } 
};
let obj = { 
    whoami() { 
        Object.getPrototypeOf( obj ).whoami.call( this ); // super.whoami();
        console.log('I am obj'); 
    } 
};
Object.setPrototypeOf( obj, proto );

此代码让 obj 继承 proto。 我对这段代码 Object.getPrototypeOf( obj ).whoami.call( this ); 感到困惑,它等同于 super.whoami()。谁能解释一下这段代码是如何工作的?

在跳到解释之前,您可能需要先阅读我下面的回答中的以下主题:

  • 原型
    • (Prototype-chain)
    • 属性 阴影
  • 函数
    • (纯函数和非纯函数)
    • 闭包
    • this 上下文

原型

Prototype-chain

在JavaScript中,对象可以有prototypes。对象继承其原型的所有属性。由于原型本身是对象,它们也可以有原型。这叫做 prototype-chain.

访问对象的 属性 将导致 JS 首先尝试在对象本身上找到它,然后依次在它的每个原型上找到它。

例子

让我们定义一个人及其原型human:

const human = { isSelfAware: true };
const person = { name: "john" };
Object.setPrototypeOf(person, human);

如果我们尝试访问 person.name,那么 JS 将 return person 的 属性 因为它定义了这个 属性。

如果我们尝试访问 person.isSelfAware,JS 将 return human 的 属性 因为 person 没有 [=206] =] 已定义,但其原型已定义。

属性 阴影

如果一个对象有一个类似名字的属性作为它的原型之一,这个原型的属性被遮蔽了,因为JS会return最早定义的属性。

阴影很有用,因为对象可以定义不同的 属性 值而不影响原型。

例子

const lightBulbProto = { serialNumber: 12345, isFunctional: true };
const oldLightBulb = { isFunctional: false };
const newLightBulb = {};
Object.setPrototypeOf(oldLightBulb , lightBulbProto);
Object.setPrototypeOf(newLightBulb, lightBulbProto);

默认情况下,所有灯泡都可用。但是现在我们将一个旧灯泡定义为 non-functional (isFunctional: false).

这不会影响 newLightBulb,因为我们没有在原型上定义功能,而是在特定对象上定义功能 oldLightBulb

函数

首先:我所说的“函数”仅指函数和函数表达式function() {}。方法定义是函数表达式的语法糖,因此它们被认为是相同的。排除箭头函数表达式 () => {}

函数类型

如果一个函数是确定性的(相同的输入产生相同的输出)并且self-enclosed(没有side-effects),它就是一个纯函数;否则它是一个不纯函数。

纯函数通常易于理解和调试,因为所有相关代码都包含在其定义中。

const array = [];
function addToArray(o) { // Impure function
  array.push(o);
}

function add(a, b) { // Pure function
  return a + b;
}

作用域和闭包

JS 尝试将您的函数与尽可能小的范围捆绑在一起。如果您的函数使用其自身范围之外的变量,则在运行时创建函数时会创建一个 closure。这意味着,只要依赖函数可访问,周围的作用域就会保持活动状态。

闭包本身并不是坏事,但可能会造成混淆或导致 memory-leak。

例子

createGet 在其范围内初始化 object,然后 return 为对象 getter:

function createGet() {
  const object = {};
  return function() {
    return object;
  }
}

let get1 = createGet();
let get2 = createGet();

// They are independent!
get1().name = "get1";
get2().number = 2;

console.log(get1()); // { "name": "get1" }
console.log(get2()); // { "number": 2 }

// Release the closures!
get1 = undefined;
get2 = undefined;

代码看起来好像每个 getter 函数 return 都是同一个对象。这是不正确的,因为 createGet 的每次调用每次都会初始化不同的对象。因此,每个 getter 函数引用不同的对象,使每个 getter 独立。

由于闭包可能在每次创建时捆绑不同的作用域,因此它们可能是 memory-leak。仅当不存在对闭包的进一步引用时,它才可能是 garbage-collected。在我们的示例中,我们使用以下行执行此操作:

get1 = undefined;
get2 = undefined;

Strict-mode

除了默认的“草率模式”,JS 还具有 strict mode.

this 上下文

当前上下文实际上是当前范围内 this 的值。

函数的上下文由调用函数的方式决定。函数可以独立调用(例如 aFunc())或作为方法调用(例如 obj.aMethod())。

如果一个函数被单独调用,它会继承周围的上下文。如果没有周围的函数上下文,周围的上下文要么在“草率模式”下是globalThis,要么在严格模式下是undefined

如果函数作为方法调用 obj.aMethod()this 将引用 obj

此外,函数的第一个参数 Function.prototype.bind, Function.prototype.call and Function.prototype.apply 可以指定底层函数的上下文。

console.log("Global context:");
(function() {
  console.log("- Sloppy: this == globalThis? " + (this === globalThis));
  // In browsers: globalThis == window
})();
(function() {
  "use strict";
  console.log("- Strict: this == undefined? " + (this === undefined));
})();

const compareThis = function(o) {
  return this === o;
};
const obj = { compareThis };

console.log("As function:");
console.log("- Default: this == obj? " + compareThis(obj));
console.log("- With call(obj): this == obj? " + compareThis.call(obj, obj));

console.log("As method:");
console.log("- Default: this == obj? " + obj.compareThis(obj));
console.log("- With call(undefined): this == obj? " + obj.compareThis.call(undefined, obj));
.as-console-wrapper{max-height:unset !important}

最后:解释

首先,初始化两个对象(objproto)。 proto 将是 obj 的原型。

因为两个对象都定义了 属性 whoami,原型的 属性 将被隐藏。由于我们仍然想访问原型的 属性,因此我们必须直接访问它。为此,我们首先获取objObject.getPrototypeOf(obj),或“super”)的原型,然后访问其属性(.whoami,或“ super.whoami").

由于 whoami 最初是在 obj 上调用的,因此使用 obj 作为上下文是有意义的。出于这个原因,我们调用 whoami.call(this)(用this 等于 obj;在原型的 whoami 方法上有效地使用“super.whoami()”),因为如果我们不这样做,该方法将使用原型作为其上下文。

因为 obj 是周围词法作用域的一部分而不是 obj.whoami 的作用域,whoami 创建闭包是因为它使用 obj 中的标识符 Object.getPrototypeOf(obj)。理想情况下,我们会使用 this 来获得原型,而不是有一个不必要的闭包:Object.getPrototypeOf(this).