【问题标题】:Using Object.assign and Object.create for inheritance使用 Object.assign 和 Object.create 进行继承
【发布时间】:2016-02-15 01:04:19
【问题描述】:

我通常按照以下方式实现继承。

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();

我已经阅读了this post by 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();

顺便说一句,按照惯例,构造函数是大写的,作为构造函数的对象字面量也应该大写吗?

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

【问题讨论】:

  • 如果你使用的是 ES2015,你应该使用class & extend
  • @Amit 这显然不是他问的问题。使用classextend 是否是一个好主意是另一个讨论的一部分。
  • @Amit - 为什么我应该使用classextend?如果我这样做,我最终不得不使用new,不是吗?使用classextendObject.assign(Object.create(... 有什么优势?
  • 我在其中添加了 ES6 标签,因为主要问题是我可以使用 Object.create 和 Object.assign
  • 按照这种模式,您宁愿拥有var duck = Object.assign(Object.createvar daffy = Object.create(duck)

标签: javascript ecmascript-6


【解决方案1】:

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

初始化的区别

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

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

var daffy = duck();
console.log(daffy); // Object { speak: function() }

所以daffy 还没有任何自己的xy 属性。但是,当您调用以下内容时,它们将被添加:

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

为什么?在animal.move的函数体中,我们有如下语句:

this.x = this.x + x; 

所以当你用daffy.move 调用它时,this 指的是daffy。所以它会尝试将this.x + x分配给this.x。由于尚未定义this.x,因此daffy[[Prototype]] 链将向下遍历到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);
}

命名约定

我不知道任何既定的约定。 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();

【讨论】:

  • 感谢您的长回答。我想Object.assign(Object.create 相对于setPrototypeOf 的一个优势是我可以更轻松地做“多重继承”(因为需要更好的术语),例如Object.assign(Object.create(animal), duckObj, someOtherObj, anotherObj)Object.assign(Object.create有什么缺点吗?
  • Object.assign(Object.create(animal), duckObj, someOtherObj, anotherObj) 更像是一个混合了原型的 mixin :) 生成的对象将包含 dadObj、someOtherObj 和 anotherObj 自己的属性,并且只会在 [[Prototype]] 插槽中包含 animale。
  • 如果你想在原型链中有多个对象,你必须在Object.create:Object.assign(Object.create({animal: animal, bird: bird}), duck);Object.setPrototypeOf(duck, {animal: animal, bird: bird});中混合它们
  • 我不知道使用 Object.assign/Object.create 的任何缺点,除了一个在大多数情况下可能无关紧要的小问题:使用 Object.create 创建的对象(而不是 new) '不从浏览器中的hiddenClass 支持中获利:medium.com/javascript-scene/…
  • 这条评论 If you want to have multiple objects in the prototype chain, 让我想知道如果我想要 animal.isPrototypeOf(daffy)bird.isPrototypeOf(daffy) 都返回 true,我将如何实例化 daffy
【解决方案2】:

我已经阅读了this post by Eric Elliott,如果我理解正确,我可以使用Object.createObject.assign 来代替吗?真的这么简单吗?

是的,createassign 更简单,因为它们是原始的,并且发生的魔法更少 - 你所做的一切都是明确的。

但是,Eric 的 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();

您应该看到这里发生的事情与使用构造函数(或class 语法)没有什么不同,我们只是将原型直接存储在变量中,并且我们正在通过显式调用来进行实例化createconstructor

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

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

另一个经常改变的是实例属性的初始化。如果我们真的不需要它们,实际上没有理由初始化它们,在原型上放置一些 默认值 就足够了:

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();

这个问题是it only works for primitive values,它变得重复了。这是工厂函数进入的地方:

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

为了便于继承,初始化通常不在工厂中完成,而是在专用方法中完成,因此它也可以在“子类”实例上调用。这个方法你可以叫init,也可以叫constructor,和我上面做的一样,基本一样。

顺便说一句,按照惯例,构造函数是大写的,作为构造函数的对象字面量也应该大写吗?

如果你不使用任何构造函数,你可以为大写的变量名赋予新的含义,是的。然而,对于不习惯这一点的每个人来说,这可能会让人感到困惑。顺便说一句,它们不是“充当构造函数的对象字面量”,它们只是 原型对象

【讨论】:

  • 这是最好的答案。谢谢!
  • “我读过 Eric Elliott 的这篇文章”是你停下来无视一切的地方。没有人比这个大嘴笨蛋更了解好的设计。一切都可以用'new'、'call'和'object.create'来完成——最后一个只用于继承。上面的例子被重写了,以至于成为克鲁格邓宁效应的唯一证明。值得庆幸的是 ES2015 合理地添加了 extend 和 class 关键字。
  • @Hal50000 与其称任何人为“大嘴笨蛋”,您可能想争论为什么classextendsnew 是“更好的设计”。我什至会争辩说,它们的设计与上面的示例完全相同,只是使用了更多神奇的语法糖。那么什么部分被“覆盖”了呢?
【解决方案3】:

您应该考虑打算使用哪种类型的“实例化模式”,而不是“继承”。它们有不同的实施目的。

顶部示例是原型,底部示例是功能共享的。看看这个链接:JS Instantiation patterns

另外,这与 es6 无关。

【讨论】:

  • 这不包括他的例子。是的,Object.assign 是 ES6。
  • 不知道你为什么说“另外,这与 es6 无关。” Object.assign是 ES6 标准的一部分。
  • ES6 作为您本质上使用 classes 的新标准。发布的代码是专门与 ES5 相关的。
  • @nils,如果您查看我最初回复中的链接,您就会明白我在说什么。
  • 不,它与 ES5 没有具体关系(参见上面的两个 cmets,Object.assign 在 ES5 中不存在)。 Object.create/Object.assign 组合在 ES6 中是一个完全有效的模式。如上所述,您的链接与Object.create/Object.assign 案例无关,它仅与x.prototype 一起讨论Object.create。这是 OP 没有特别要求的。
猜你喜欢
  • 1970-01-01
  • 2023-03-20
  • 2015-03-20
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多