使用Object.assign和Object.create进行继承

use*_*596 28 javascript ecmascript-6

我通常沿着以下行实现继承.

function Animal () { this.x = 0; this.y = 0;}

Animal.prototype.locate = function() { 
  console.log(this.x, this.y);
  return this;
};
Animal.prototype.move = function(x, y) {
  this.x = this.x + x;
  this.y = this.y + y; 
  return this;
}


function Duck () {
    Animal.call(this);
}

Duck.prototype = new Animal();
Duck.prototype.constructor = Duck;
Duck.prototype.speak = function () {
    console.log("quack");
    return this;
}

var daffy = new Duck();

daffy.move(6, 7).locate().speak();
Run Code Online (Sandbox Code Playgroud)

我已经阅读了Eric Elliott的这篇文章,如果我理解正确,我可以使用Object.createObject.assign不是?它真的那么简单吗?

var animal = {
   x : 0,
   y : 0,
   locate : function () { 
     console.log(this.x, this.y);
     return this;
   },
   move : function (x, y) { 
     this.x = this.x + x; 
     this.y = this.y + y;
     return this;
   }
}

var duck = function () {
   return Object.assign(Object.create(animal), {
     speak : function () { 
       console.log("quack");
       return this;
     }
   });
}

var daffy = duck();

daffy.move(6, 7).locate().speak();
Run Code Online (Sandbox Code Playgroud)

顺便说一下,按照惯例,构造函数是大写的,那么作为构造函数的对象文字是否也应该大写?

我知道有很多问题,现在讨论newObject.create,但它们通常似乎都与Duck.prototype = new Animal();Duck.prototype = Object.create(Animal.prototype);

nil*_*ils 28

是的,就这么简单.在您的示例中Object.create/Object.assign,您使用工厂函数来创建新实例duck(类似于jQuery创建新实例的方式,如果您选择元素var body = $('body')).这种代码风格的一个优点是,它不会强制您animal在想要创建新duck实例时调用构造函数(与ES2015类相反).

初始化的差异

也许一个有趣的小问题与你使用构造函数(或任何其他初始化函数)的工作方式略有不同:

创建duck实例时,所有属性animal都在实例的[[Prototype]]插槽中duck.

var daffy = duck();
console.log(daffy); // Object { speak: function() }
Run Code Online (Sandbox Code Playgroud)

因此,daffy没有任何自己xy特性呢.但是,当您调用以下内容时,它们将被添加:

daffy.move(6, 7);
console.log(daffy); // Object { speak: function(), x: 6, y: 7 }
Run Code Online (Sandbox Code Playgroud)

为什么?在函数体中animal.move,我们有以下声明:

this.x = this.x + x; 
Run Code Online (Sandbox Code Playgroud)

所以当你打电话给这个时daffy.move,this指的是daffy.所以它会尝试分配this.x + xthis.x.因为this.x还没有定义,则[[Prototype]]的链daffy遍历下降到animal,其中animal.x被定义.

因此,在第一次调用中,this.x赋值的右侧是指animal.x,因为daffy.x未定义.第二次daffy.move(1,2)被称为,this.x右侧将是daffy.x.

替代语法

或者,您也可以使用Object.setPrototypeOf而不是Object.create/Object.assign(OLOO Style):

var duck = function () {
   var duckObject = {
       speak : function () { 
           console.log("quack");
           return this;
       }
   };
   return Object.setPrototypeOf(duckObject, animal);
}
Run Code Online (Sandbox Code Playgroud)

命名约定

我不知道任何既定的惯例.Kyle Simpson在OLOO中使用大写字母,Eric Elliot似乎使用小写字母.我个人会坚持使用小写,因为充当构造函数的对象文字已经是完全成熟的对象本身(不仅仅是蓝图,就像类一样).

独生子

如果您只想要一个实例(例如单个实例),您可以直接调用它:

var duck = Object.assign(Object.create(animal), {
    speak : function () { 
        console.log("quack");
        return this;
    }
});

duck.move(6, 7).locate().speak();
Run Code Online (Sandbox Code Playgroud)


Ber*_*rgi 12

我已经阅读了Eric Elliott的这篇文章,如果我理解正确,我可以使用Object.createObject.assign不是?它真的那么简单吗?

是的,create并且assign更加简单,因为它们是原始的,而且不那么神奇 - 你所做的一切都是明确的.

然而,埃里克的mouse例子有点令人困惑,因为他省略了一步,并将动物鼠标的遗传与实例化的鼠标混合在一起.

相反,让我们再试一次转录您的小鸭示例 - 让我们从字面意义上开始:

const animal = {
  constructor() {
    this.x = 0;
    this.y = 0;
    return this;
  },
  locate() { 
    console.log(this.x, this.y);
    return this;
  },
  move(x, y) {
    this.x += x;
    this.y += y; 
    return this;
  }
};
const duck = Object.assign(Object.create(animal), {
  constructor() {
    return animal.constructor.call(this);
  },
  speak() {
    console.log("quack");
    return this;
  }
});
/* alternatively: 
const duck = Object.setPrototypeOf({
  constructor() {
    return super.constructor(); // super doesn't work with `Object.assign`
  },
  speak() { … }
}, animal); */

let daffy = Object.create(duck).constructor();
daffy.move(6, 7).locate().speak();
Run Code Online (Sandbox Code Playgroud)

您应该看到,这里所发生与使用构造函数(或真的没有不同class的语法为此事),我们刚刚存储在我们的原型直接在变量,我们正在做的实例有显式调用createconstructor.

现在你可以认为我们duck.constructor除了调用它的超级方法之外什么都不做,所以我们实际上可以完全省略它并让继承完成它的工作:

const duck = Object.assign(Object.create(animal), {
  speak() {
    console.log("quack");
    return this;
  }
});
Run Code Online (Sandbox Code Playgroud)

经常改变的另一件事是实例属性的初始化.如果我们真的不需要它们,实际上没有理由对它们进行初始化,只需在原型上放置一些默认值即可:

const animal = {
  x: 0,
  y: 0,
  locate() { 
    console.log(this.x, this.y);
  }
};
const duck = … Object.create(animal) …;

let daffy = Object.create(duck); // no constructor call any more!
daffy.x = 5; // instance initialisation by explicit assignment
daffy.locate();
Run Code Online (Sandbox Code Playgroud)

这个问题是它只适用于原始值,并且它会重复.这是工厂功能进入的地方:

function makeDuck(x, y) {
    return Object.assign(Object.create(duck), {x, y});
}
let daffy = makeDuck(5, 0);
Run Code Online (Sandbox Code Playgroud)

为了便于继承,初始化通常不是在工厂中完成,而是在专用方法中完成,因此也可以在"子类"实例上调用它.你可以称之为这种方法init,或者你可以constructor像我上面那样称它,它基本上是一样的.

顺便说一下,按照惯例,构造函数是大写的,那么作为构造函数的对象文字是否也应该大写?

如果您没有使用任何构造函数,则可以为大写变量名称赋予新的含义,是的.然而,对于那些不习惯这种情况的人来说,这可能会让人感到困惑.顺便说一句,它们不是"作为构造函数的对象文字",它们只是原型对象.

  • @ Hal50000而不是将任何人称为“大嘴鸟”,您可能想争辩说为什么“类”,“扩展”和“新”是“更好的设计”。我什至认为它们与上面的示例完全相同,只是带有更多魔术语法糖。那么“覆盖”是什么部分? (3认同)