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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33692912/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 15:21:44  来源:igfitidea点击:

Using Object.assign and Object.create for inheritance

javascriptecmascript-6

提问by user5325596

I typically implement inheritance along the following lines.

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

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

I've read this post by Eric Elliottand if I understand correctly I can use Object.createand Object.assigninstead? Is it really that simple?

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

As an aside, by convention constructor functions are capitalized, should object literals that act as constructors also be capitalized?

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

I realise there are many questions here discussing newversus Object.create, but they typically seem to relate to Duck.prototype = new Animal();versus Duck.prototype = Object.create(Animal.prototype);

我意识到这里有很多关于newvs 的问题Object.create,但它们通常似乎与Duck.prototype = new Animal();vsDuck.prototype = Object.create(Animal.prototype);

采纳答案by nils

Yes, it is that simple. In your example with Object.create/Object.assign, you are using a factory function to create new instances of duck(similar to the way jQuery creates new instances if you select an element with var body = $('body')). An advantage of this code style is, that it doesn't force you to call a constructor of animalwhen you want to create a new duckinstance (as opposed to ES2015 Classes).

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

Differences in initialization

初始化的差异

Maybe one interesting tidbit that works slightly differently than if you were to use a constructor (or any other initialization function):

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

When you create a duckinstace, all the properties of animalare in the [[Prototype]]slot of the duckinstance.

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

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

So daffydoes not have any own xand yproperties yet. However, when you call the following, they will be added:

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

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

Why? In the function-body of animal.move, we have the following statement:

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

this.x = this.x + x; 

So when you call this with daffy.move, thisrefers to daffy. So it will try to assign this.x + xto this.x. Since this.xis not yet defined, the [[Prototype]]chain of daffyis traversed down to animal, where animal.xis defined.

所以当你用 调用它时daffy.movethis指的是daffy. 所以它会尝试分配this.x + xthis.x. 由于this.x尚未定义, 的[[Prototype]]daffy向下遍历到animal,其中animal.x定义。

Thus in the first call, the this.xon the right side of the assignment refers to animal.x, because daffy.xis not defined. The second time daffy.move(1,2)is called, this.xon the right side will be daffy.x.

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

Alternative Syntax

替代语法

Alternatively, you could also use Object.setPrototypeOfinstead of Object.create/Object.assign(OLOO Style):

或者,您也可以使用Object.setPrototypeOf代替Object.create/Object.assignOLOO Style):

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

Naming Conventions

命名约定

I'm not aware of any established conventions. Kyle Simpson uses uppercase letters in OLOO, Eric Elliot seems to use lowercase. Personally I would stick with lower-case, because the object literals that act as constructors are already fully fledged objects themselves (not just blueprint, like classes would be).

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

Singleton

单身人士

If you only wanted a single instance (e.g. for a singleton), you could just call it directly:

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

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

duck.move(6, 7).locate().speak();

回答by Bergi

I've read this post by Eric Elliottand if I understand correctly I can use Object.createand Object.assigninstead? Is it really that simple?

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

Yes, createand assignis much more simple because they're primitives, and less magic is going on - everything you do is explicit.

是的,create而且assign要简单得多,因为它们是原始类型,并且正在发生的魔法较少 - 您所做的一切都是明确的。

However, Eric's mouseexample is a bit confusing, as he leaves out one step, and mixes the inheritance of mouses from animals with instantiating mouses.

然而,Eric 的mouse例子有点令人困惑,因为他省略了一个步骤,将鼠标从动物的继承与实例化鼠标混合在一起。

Rather let's try transcribing your duckling example again - let's start with doing it literally:

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

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

You should see that what happens here is really no different from using constructors (or classsyntax for that matter), we've just stored our prototypes directly in the variables and we're doing instantiation with an explicit call to createand constructor.

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

Now you can figure that our duck.constructordoes nothing but calling its super method, so we can actually omit it completely and let inheritance do its work:

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

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

The other thing that is often changed is the initialisation of instance properties. There is actually no reason to initialise them if we don't really need them, it's sufficient to put some default valueson the prototype:

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

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

The problem with this is that it only works for primitive values, and it gets repetitive. This is where factory functions get in:

问题在于它仅适用于原始值,并且会重复。这是工厂函数的用武之地:

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

To allow for easy inheritance, the initialisation is often not done in the factory but in a dedicated method so it can be called on "subclass" instances as well. You may call this method init, or you may call it constructorlike I did above, it's basically the same.

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

As an aside, by convention constructor functions are capitalized, should object literals that act as constructors also be capitalized?

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

If you're not using any constructors, you may assign a new meaning to capitalized variable names, yes. It might however be confusing for everyone who's not accustomed to this. And btw, they're not "object literals that act as constructors", they're just prototype objects.

如果您没有使用任何构造函数,您可以为大写的变量名称分配一个新的含义,是的。然而,对于不习惯这一点的每个人来说,这可能会令人困惑。顺便说一句,它们不是“充当构造函数的对象文字”,它们只是原型对象

回答by Shinobi881

Rather than "inheritance" you should think about what type of "instantiation pattern" you intend to use. They have different purposes for implementation.

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

The top example is prototypal and the bottom, functional-shared. Check out this link: JS Instantiation patterns

顶部的示例是原型,底部的示例是功能共享的。查看此链接:JS 实例化模式

Also, this is non-es6 related.

此外,这与 es6 无关。