ES6`static get length()`未按预期继承

时间:2017-09-09 08:18:41

标签: javascript es6-class javascript-inheritance

我在为ES6类的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确实返回'FooBar',但Bar.length 0让我感到惊讶。

我花了一段时间才意识到0来自哪里,因为我完全预期它会6(并且会在某种程度上理解3)。 事实证明,0提供的Bar.length值实际上是constructor的{​​{1}}函数的长度,我在ES5表示法中写了相同的例子时意识到了这一点,有一种快速的方法可以证明这一点;只需将Bar添加到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()按预期工作,但不是JS中通常使用的属性)
  • 从内置类扩展基础,该类具有工作static get size()(例如length) -

如果有更合适的方法,这些都不是我想要做的。

  

所以我的问题是;有没有人知道一个正确的方法来获得一个按预期继承的自定义属性覆盖,还是我太顽固了?

如上所述,我通过将类语法编写为(我认为)与ES5等效的方法来解决问题,因为它可能对其他开发人员有益,并且可能会对我认为的有所了解/ em> ES6课程的工作我将把它留在这里。 (如果有人知道如何在Stackoverflow上使这个位可折叠,请随时编辑/建议)

我想在ES5语法

中发生了什么

我知道ES6类主要是围绕JS原型继承的语法糖,所以class Foo extends Array {...}似乎发生了类似的事情;

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

1 个答案:

答案 0 :(得分:3)

关于静态成员

ES6类的静态成员实际上是函数对象的成员,而不是其原型对象。请考虑以下示例,其中我将使用常规方法而不是getter,但机制与getter相同:

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';
}

如果您想从staticMethod'实例'运行Foo您必须先导航到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.lengthFoo.length的原型查找确实永远不会发生,因为length已经直接在Bar上找到。简单的覆盖不起作用:

Foo.length = 3;
Foo.length; // still 2

那是因为该属性是不可写的。让我们与getOwnPropertyDescriptor验证:

Object.getOwnPropertyDescriptor(Foo, 'length');
/*
    { 
        value: 0,
        writable: false,
        enumerable: false,
        configurable: true
    }
*/

此外,您可以使用function.name来获取函数/类构造函数的名称,而不是您定义的value getter:

Foo.name; // > 'Foo'

让我们使用它覆盖length上的Foo属性。我们仍然可以覆盖Foo.length,因为属性是可配置的

Object.defineProperty(Foo, 'length', {
    get() {
        return this.name.length;
    }
});

Foo.length; // 3

这是代码膨胀

非常不希望为每个扩展类执行此操作,或为每个扩展类定义静态getter,这相当于上面的代码。如果没有任何某种功能对象的装饰,就不可能完全覆盖行为。但是既然我们知道类只是语法糖,而我们实际上只是处理对象和函数,那么编写装饰器很容易!

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个班级:

class Foo {

}

class Bar extends Foo {

}

class Baz extends Bar {

}

我们可以一次装饰所有课程:

decorateClasses(Foo, Bar, Baz);

如果我们在所有三个类(函数)上访问valuelength,我们会得到所需的结果:

Foo.value; // 'Foo'
Foo.length; // 3

Bar.value; // 'FooBar'
Bar.length; // 6

Baz.value; // 'FooBarBaz'
Baz.length; // 9