没有类声明的情况下如何扩展Array?

时间:2019-06-23 06:40:08

标签: javascript ecmascript-6 es6-class

我真的很努力地制作一个可以继承数组所有属性的自定义对象,但其行为与普通实例相同,也就是说,instanceofconstructor会表现得像您想要的那样。我已经读过,类声明只是语法糖,所以我从来没有求助于它们(我对它们知之甚少)。

在我取得重大突破之前,我制造了可憎的东西:

function arrayLike() {
    let al = [];

    //make obj.constructor work
    Object.defineProperty(al, 'constructor', {value: arrayLike}); 

    //add methods

    //make (obj instanceof arrayLike) == true
    return new Proxy(al, {
        getPrototypeOf() {
            return arrayLike.prototype;
        },
    })
}

//make (obj instanceof Array) == true
Reflect.setPrototypeOf(arrayLike.prototype, Array.prototype);

碰巧的是,我看到一个非常接近我想做的课堂例子,然后发现它非常适合这项工作:

class arrayLike extends Array {
    //add methods
}

在Chrome DevToos中查看它,我可以看到我创建的东西根本没有相同的结构。

如果类声明确实是语法糖,那么如果没有它,如何创建该对象呢?

2 个答案:

答案 0 :(得分:2)

Javascript是一种具有继承形式的语言,称为原型继承

其背后的想法是,给定一个对象,它具有一个称为 prototype 的隐藏属性,该属性是对另一个对象(称为原型对象)的引用。

当您要求javascript引擎为您提供对象属性的值时,这种关系很重要,我们将其称为 foo 只是为了解决这个问题。 javascript引擎将首先检查您的对象,以查看其是否具有名为 foo 的属性:如果在您的对象上定义了该属性,则返回其值并完成搜索。否则,如果您的对象没有名为 foo 的属性,则将搜索其原型对象,并再次重复相同的过程。

递归地重复此过程,直到研究了所有所谓的原型链。原型链的根是一个内置javascript对象,您可以使用表达式 Object.prototype 进行引用,并且是所有其他javascript对象都源自的对象。请注意,如果组成整个原型链的对象中的所有对象中缺少 foo 属性,则返回值 undefined

这是内置在javascript中的真正继承形式,而这实际上是ES6 class 键盘背后的业务,这是一种便利,可以隐藏这种混乱并给您以javascript具有形式的印象类继承(类继承已广为人知,大多数程序员发现它比原型继承更容易思考)。

为了获取一个对象并决定其行为类似于数组,您可以做的最基本的操作如下:

const myArray = [];
const myObject = { foo: "bar" }

Object.setPrototypeOf(myObject, myArray);

myObject.push("hello");
myObject.push("world");
console.log(myObject.length); // prints 2

This book是我所知道的关于javascript语言的最佳参考。 This is good too,但是如今有点过时了,而且跟以前的学习一样不容易。

通过使用函数作为构造函数,可以实现比上一个示例复杂得多的示例。实际上,这是实现类继承的ES5老式方法,这是您在ES5时为模仿类所做的事情:

function SpecialArray(name) {
  this.name = name;
}

SpecialArray.prototype = []; 

// fix the constructor property mess (see the book linked above)
Object.defineProperty(SpecialArray.prototype, "constructor", {
  value: SpecialArray,
  enumerable: false,
  writable: true
});

SpecialArray.prototype.getSalutation = function() {
  return "Hello my name is " + this.name;
};

const mySpecialArray = new SpecialArray("enrico");

// you can call the methods and properties defined on Array.prototype
mySpecialArray.push("hello");
mySpecialArray.push("world");
console.log(mySpecialArray.length); // prints 2

// you can use the methods and properties defined on SpecialArray.prototype
console.log(mySpecialArray.name); // prints enrico
console.log(mySpecialArray.getSalutation()); // prints Hello my name is enrico

// important sanity checks to be sure that everything works as expected
console.log(mySpecialArray instanceof Array); // prints true
console.log(mySpecialArray instanceof SpecialArray); // prints true
console.log(mySpecialArray.constructor === SpecialArray); // prints true

// you can iterate over the special array content
for (item of mySpecialArray){
  console.log(item);
}

// you can read special array entries
console.log(mySpecialArray[1]); // prints world

答案 1 :(得分:0)

编辑:我研究了babel的转码,发现需要额外的触摸才能正确扩展Array之类的内置类,我们需要先将Array构造函数包装在普通的Wrapper函数中,否则原型链会在施工时断裂。

function _wrapNativeSuper(Class) {
  _wrapNativeSuper = function _wrapNativeSuper(Class) {
    function Wrapper() {
      var instance = Class.apply(this, arguments)
      instance.__proto__ = this.__proto__.constructor.prototype;
      return instance;
    }
    Wrapper.prototype = Object.create(Class.prototype, {
      constructor: {
        value: Wrapper,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    Wrapper.__proto__ = Class;
    return Wrapper;
  };
  return _wrapNativeSuper(Class);
}

类声明语法可以做三件事。

  1. 正确设置构造函数
  2. 正确设置原型链
  3. 继承静态属性

因此,要重播class Foo extends Array {}在老式js中的操作,您需要相应地做这三件事。

// 0. wrap the native Array constructor
// this step is only required when extending built-in objects like Array
var _Array = _wrapNativeSuper(Array)

// 1. setup the constructor
function Foo() { return _Array.apply(this, arguments) }

// 2. setup prototype chain
function __dummy__() { this.constructor = Foo }
__dummy__.prototype = _Array.prototype
Foo.prototype = new __dummy__()

// 3. inherit static properties
Foo.__proto__ = _Array

以下可运行示例:

function _wrapNativeSuper(Class) {
  _wrapNativeSuper = function _wrapNativeSuper(Class) {
    function Wrapper() {
      var instance = Class.apply(this, arguments)
      instance.__proto__ = this.__proto__.constructor.prototype;
      return instance;
    }
    Wrapper.prototype = Object.create(Class.prototype, {
      constructor: {
        value: Wrapper,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    Wrapper.__proto__ = Class;
    return Wrapper;
  };
  return _wrapNativeSuper(Class);
}

// 0. wrap the native Array constructor
// this step is only required when extending built-in objects like Array
var _Array = _wrapNativeSuper(Array)

// 1. setup the constructor
function Foo() { return _Array.apply(this, arguments) }

// 2. setup prototype chain
function __dummy__() { this.constructor = Foo }
__dummy__.prototype = _Array.prototype
Foo.prototype = new __dummy__()

// 3. inherit static properties
Foo.__proto__ = _Array


// test
var foo = new Foo;
console.log('instanceof?', foo instanceof Foo);

Foo.prototype.hi = function() { return 'hello' }
console.log('method?', foo.hi());