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}
最后:解释
首先,初始化两个对象(obj
和proto
)。 proto
将是 obj
的原型。
因为两个对象都定义了 属性 whoami
,原型的 属性 将被隐藏。由于我们仍然想访问原型的 属性,因此我们必须直接访问它。为此,我们首先获取obj
(Object.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)
.
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}
最后:解释
首先,初始化两个对象(obj
和proto
)。 proto
将是 obj
的原型。
因为两个对象都定义了 属性 whoami
,原型的 属性 将被隐藏。由于我们仍然想访问原型的 属性,因此我们必须直接访问它。为此,我们首先获取obj
(Object.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)
.