ES6 `static get length()` 未按预期继承
ES6 `static get length()` is not inherited as expected
我在为我的 ES6 class 扩展的 length
属性 提供静态 getter 函数时遇到问题。
事实证明,实际的 Function.length
getter 总是优先于我自己的实现。
class Foo {
static get value() {
return 'Foo';
}
static get length() {
return this.value.length;
}
}
class Bar extends Foo {
static get value() {
return `${super.value}Bar`;
}
}
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 0
在上面的示例中,Foo
完全符合我的预期,Bar
没那么多。 Bar.value
确实 return 'FooBar'
,但 Bar.length
是 0
让我感到惊讶。
我花了一段时间才意识到 0
的来源,因为我完全期望它是 6
(并且在某种程度上会理解 3
)。
事实证明 Bar.length
提供的 0
值实际上是 Bar
的 constructor
函数的长度,我在用 ES5 表示法编写相同示例时意识到了这一点,不过,有一种快速的方法可以证明这一点;只需将 constructor
添加到 Bar
.
class Bar extends Foo {
constructor(a, b, c, d) {
// four configured parameters
}
static get value() {
return `${super.value}Bar`;
}
}
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 4
解决这个问题的方法有:
- 将
static get length()
添加到所有扩展(不是我的想法
继承)
- 使用不同的 属性 名称(例如
static get size()
按预期工作,但不是 JS 中常用的 属性)
- 从内置的 class 扩展基础,它有一个有效的
length
(例如 class Foo extends Array {...}
)-
None 这些是我想做的,如果有更合适的方法的话。
So my question is; does anyone know a proper way to have a custom property override which is inherited as expected, or am I being too stubborn?
如前所述,我通过将 class 语法编写为(我认为)将是 ES5 等价物来弄清楚出了什么问题,因为它可能对其他开发人员有益,并且可能会阐明如何我认为 ES6 class我会把它留在这里。
(如果有人知道如何在 Whosebug 上折叠这个位,请随时 edit/suggest)
我认为 ES5 语法中发生了什么
我知道 ES6 classes 主要是围绕 JS 原型继承的语法糖,所以 Bar
似乎发生的事情是这样的;
function Foo() {}
Object.defineProperties(Foo, {
value: {
configurable: true,
get: function() {
return 'Foo';
}
},
length: {
configurable: true,
get: function() {
return this.value.length;
}
}
});
function Bar() {}
Bar.prototype = Object.create(Object.getPrototypeOf(Foo));
Object.defineProperties(Bar, {
value: {
configurable: true,
get: function() {
return 'Bar' + Foo.value;
}
}
});
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 0
我希望 Foo
的 属性 描述符会被考虑在内,例如:
function Bar() {}
Bar.prototype = Object.create(Object.getPrototypeOf(Foo));
Object.defineProperties(Bar, Object.assign(
// inherit any custom descriptors
Object.getOwnPropertyDescriptors(Foo),
{
configurable: true,
value: {
get: function() {
return 'Bar' + Foo.value;
}
}
}
));
console.log(Foo.value, Foo.length); // 'foo', 3
console.log(Bar.value, Bar.length); // 'bar', 6
关于静态成员
ES6 class 的静态成员实际上是 函数对象 的成员,而不是它的原型对象。考虑以下示例,其中我将使用常规方法而不是 getter,但其机制与 getters:
相同
class Foo {
static staticMethod() {
return 'Foo static';
}
nonStaticMethod() {
return 'Foo non-static';
}
}
staticMethod
将成为构造函数对象的成员,而 nonStaticMethod
将成为该函数对象原型的成员:
function Foo() {}
Foo.staticMethod = function() {
return 'Foo static';
}
Foo.prototype.nonStaticMethod = function() {
return 'Foo non-static';
}
如果你想从 Foo
'instance' 运行 staticMethod
你必须先导航到它的 constructor
,这是函数staticMethod
是以下成员的对象:
let foo = new Foo();
foo.staticMethod(); // Uncaught TypeError: foo.staticMethod is not a function
// Get the static member either on the class directly
// (this works IF you know that Foo is foo's constructor)
Foo.staticMethod(); // > 'Foo static'
// this is the same, provided that neither 'prototype' nor
// 'prototype.constructor' has been overridden afterwards:
Foo.prototype.constructor.staticMethod(); // > 'Foo static'
// ...or by getting the prototype of foo
// (If you have to perform a computed lookup of an object's constructor)
// You'll want to perform such statements in a try catch though...
Object.getPrototypeOf(foo).constructor.staticMethod(); // > 'Foo static'
function.length
所有 functions have a length
property 告诉您该函数接受多少个参数:
function Foo(a, b) {}
Foo.length; // > 2
因此在您的示例中,从 Bar.length
到 Foo.length
的原型查找确实不会发生,因为 length
已经直接在 Bar
上找到。简单的覆盖将不起作用:
Foo.length = 3;
Foo.length; // still 2
那是因为属性是不可写的。让我们用 getOwnPropertyDescriptor:
来验证
Object.getOwnPropertyDescriptor(Foo, 'length');
/*
{
value: 0,
writable: false,
enumerable: false,
configurable: true
}
*/
此外,除了您定义的 value
getter,您还可以只使用 function.name
来获取函数的名称/class 构造函数:
Foo.name; // > 'Foo'
让我们用它来覆盖 Foo
上的 length
属性。我们仍然能够覆盖 Foo.length
因为 属性 是可配置的:
Object.defineProperty(Foo, 'length', {
get() {
return this.name.length;
}
});
Foo.length; // 3
这是代码膨胀
非常不希望为每个扩展 class 都执行此操作,或者为每个扩展定义一个静态 getter,这与上面的代码等效。如果不对某种函数对象进行任何修饰,就不可能完全覆盖行为。但是既然我们知道 classes 只是语法糖,而我们实际上只是在处理对象和函数,那么写一个装饰器就很容易了!
function decorateClasses(...subjects) {
subjects.forEach(function(subject) {
Object.defineProperty(subject, 'value', {
get() {
const superValue = Object.getPrototypeOf(this).value || '';
return superValue + this.name;
},
enumerable: true,
configurable: true,
});
Object.defineProperty(subject, 'length', {
get() {
return this.value.length;
},
enumerable: true,
configurable: true,
});
})
}
此函数接受一个或多个对象,在这些对象上它将覆盖 length
属性 并设置 value
属性。两者都是 访问器 和 get
方法。 get value
显式执行原型查找,然后将结果与它所属的函数名称结合起来。所以,如果我们有 3 classes:
class Foo {
}
class Bar extends Foo {
}
class Baz extends Bar {
}
我们可以一次装饰所有 classes:
decorateClasses(Foo, Bar, Baz);
如果我们在所有三个 classes(函数)上访问 value
和 length
,我们将得到所需的结果:
Foo.value; // 'Foo'
Foo.length; // 3
Bar.value; // 'FooBar'
Bar.length; // 6
Baz.value; // 'FooBarBaz'
Baz.length; // 9
我在为我的 ES6 class 扩展的 length
属性 提供静态 getter 函数时遇到问题。
事实证明,实际的 Function.length
getter 总是优先于我自己的实现。
class Foo {
static get value() {
return 'Foo';
}
static get length() {
return this.value.length;
}
}
class Bar extends Foo {
static get value() {
return `${super.value}Bar`;
}
}
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 0
在上面的示例中,Foo
完全符合我的预期,Bar
没那么多。 Bar.value
确实 return 'FooBar'
,但 Bar.length
是 0
让我感到惊讶。
我花了一段时间才意识到 0
的来源,因为我完全期望它是 6
(并且在某种程度上会理解 3
)。
事实证明 Bar.length
提供的 0
值实际上是 Bar
的 constructor
函数的长度,我在用 ES5 表示法编写相同示例时意识到了这一点,不过,有一种快速的方法可以证明这一点;只需将 constructor
添加到 Bar
.
class Bar extends Foo {
constructor(a, b, c, d) {
// four configured parameters
}
static get value() {
return `${super.value}Bar`;
}
}
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 4
解决这个问题的方法有:
- 将
static get length()
添加到所有扩展(不是我的想法 继承) - 使用不同的 属性 名称(例如
static get size()
按预期工作,但不是 JS 中常用的 属性) - 从内置的 class 扩展基础,它有一个有效的
length
(例如class Foo extends Array {...}
)-
None 这些是我想做的,如果有更合适的方法的话。
So my question is; does anyone know a proper way to have a custom property override which is inherited as expected, or am I being too stubborn?
如前所述,我通过将 class 语法编写为(我认为)将是 ES5 等价物来弄清楚出了什么问题,因为它可能对其他开发人员有益,并且可能会阐明如何我认为 ES6 class我会把它留在这里。 (如果有人知道如何在 Whosebug 上折叠这个位,请随时 edit/suggest)
我认为 ES5 语法中发生了什么
我知道 ES6 classes 主要是围绕 JS 原型继承的语法糖,所以 Bar
似乎发生的事情是这样的;
function Foo() {}
Object.defineProperties(Foo, {
value: {
configurable: true,
get: function() {
return 'Foo';
}
},
length: {
configurable: true,
get: function() {
return this.value.length;
}
}
});
function Bar() {}
Bar.prototype = Object.create(Object.getPrototypeOf(Foo));
Object.defineProperties(Bar, {
value: {
configurable: true,
get: function() {
return 'Bar' + Foo.value;
}
}
});
console.log(Foo.value, Foo.length); // 'Foo', 3
console.log(Bar.value, Bar.length); // 'FooBar', 0
我希望 Foo
的 属性 描述符会被考虑在内,例如:
function Bar() {}
Bar.prototype = Object.create(Object.getPrototypeOf(Foo));
Object.defineProperties(Bar, Object.assign(
// inherit any custom descriptors
Object.getOwnPropertyDescriptors(Foo),
{
configurable: true,
value: {
get: function() {
return 'Bar' + Foo.value;
}
}
}
));
console.log(Foo.value, Foo.length); // 'foo', 3
console.log(Bar.value, Bar.length); // 'bar', 6
关于静态成员
ES6 class 的静态成员实际上是 函数对象 的成员,而不是它的原型对象。考虑以下示例,其中我将使用常规方法而不是 getter,但其机制与 getters:
相同class Foo {
static staticMethod() {
return 'Foo static';
}
nonStaticMethod() {
return 'Foo non-static';
}
}
staticMethod
将成为构造函数对象的成员,而 nonStaticMethod
将成为该函数对象原型的成员:
function Foo() {}
Foo.staticMethod = function() {
return 'Foo static';
}
Foo.prototype.nonStaticMethod = function() {
return 'Foo non-static';
}
如果你想从 Foo
'instance' 运行 staticMethod
你必须先导航到它的 constructor
,这是函数staticMethod
是以下成员的对象:
let foo = new Foo();
foo.staticMethod(); // Uncaught TypeError: foo.staticMethod is not a function
// Get the static member either on the class directly
// (this works IF you know that Foo is foo's constructor)
Foo.staticMethod(); // > 'Foo static'
// this is the same, provided that neither 'prototype' nor
// 'prototype.constructor' has been overridden afterwards:
Foo.prototype.constructor.staticMethod(); // > 'Foo static'
// ...or by getting the prototype of foo
// (If you have to perform a computed lookup of an object's constructor)
// You'll want to perform such statements in a try catch though...
Object.getPrototypeOf(foo).constructor.staticMethod(); // > 'Foo static'
function.length
所有 functions have a length
property 告诉您该函数接受多少个参数:
function Foo(a, b) {}
Foo.length; // > 2
因此在您的示例中,从 Bar.length
到 Foo.length
的原型查找确实不会发生,因为 length
已经直接在 Bar
上找到。简单的覆盖将不起作用:
Foo.length = 3;
Foo.length; // still 2
那是因为属性是不可写的。让我们用 getOwnPropertyDescriptor:
来验证Object.getOwnPropertyDescriptor(Foo, 'length');
/*
{
value: 0,
writable: false,
enumerable: false,
configurable: true
}
*/
此外,除了您定义的 value
getter,您还可以只使用 function.name
来获取函数的名称/class 构造函数:
Foo.name; // > 'Foo'
让我们用它来覆盖 Foo
上的 length
属性。我们仍然能够覆盖 Foo.length
因为 属性 是可配置的:
Object.defineProperty(Foo, 'length', {
get() {
return this.name.length;
}
});
Foo.length; // 3
这是代码膨胀
非常不希望为每个扩展 class 都执行此操作,或者为每个扩展定义一个静态 getter,这与上面的代码等效。如果不对某种函数对象进行任何修饰,就不可能完全覆盖行为。但是既然我们知道 classes 只是语法糖,而我们实际上只是在处理对象和函数,那么写一个装饰器就很容易了!
function decorateClasses(...subjects) {
subjects.forEach(function(subject) {
Object.defineProperty(subject, 'value', {
get() {
const superValue = Object.getPrototypeOf(this).value || '';
return superValue + this.name;
},
enumerable: true,
configurable: true,
});
Object.defineProperty(subject, 'length', {
get() {
return this.value.length;
},
enumerable: true,
configurable: true,
});
})
}
此函数接受一个或多个对象,在这些对象上它将覆盖 length
属性 并设置 value
属性。两者都是 访问器 和 get
方法。 get value
显式执行原型查找,然后将结果与它所属的函数名称结合起来。所以,如果我们有 3 classes:
class Foo {
}
class Bar extends Foo {
}
class Baz extends Bar {
}
我们可以一次装饰所有 classes:
decorateClasses(Foo, Bar, Baz);
如果我们在所有三个 classes(函数)上访问 value
和 length
,我们将得到所需的结果:
Foo.value; // 'Foo'
Foo.length; // 3
Bar.value; // 'FooBar'
Bar.length; // 6
Baz.value; // 'FooBarBaz'
Baz.length; // 9