1

我在为length我的 ES6 类扩展的属性提供静态 getter 函数时遇到问题。事实证明,实际的Function.lengthgetter 总是优先于我自己的实现。

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函数的长度Bar,我在用 ES5 表示法编写相同的示例时意识到了这一点,但有一种快速的方法可以证明这一点;只需添加一个constructorBar.

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 中常用的属性)
  • length从具有工作(例如class Foo extends Array {...})的内置类扩展基础-

如果有更合适的方法可以做到这一点,这些都不是我想做的。

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


如前所述,我通过将类语法编写为(我相信)将是 ES5 等价物来找出问题所在,因为它可能对其他开发人员有益,并且可能会阐明我认为ES6 类是如何工作的在这里。(如果有人对如何在 Stackoverflow 上使这个位可折叠有提示,请随时编辑/建议)

我想在 ES5 语法中发生了什么

我知道 ES6 类主要是围绕 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
4

1 回答 1

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

所有函数都有一个length属性,告诉您该函数接受多少个参数:

function Foo(a, b) {}
Foo.length; // > 2

Bar.length因此,在您的示例中, to的原型查找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来获取函数/类构造函数的名称:

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属性。两者都是具有方法的访问器getget value显式执行原型查找,然后将结果与其所属函数的名称结合起来。所以,如果我们有 3 个类:

class Foo {

}

class Bar extends Foo {

}

class Baz extends Bar {

}

我们可以一次装饰所有类:

decorateClasses(Foo, Bar, Baz);

如果我们在所有三个类(函数)上访问value和,我们会得到想要的结果:length

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

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

Baz.value; // 'FooBarBaz'
Baz.length; // 9
于 2017-09-09T08:38:34.550 回答