使用Object.assign和Object.create进行继承
Using Object.assign and Object.create for inheritance
我通常按照以下几行实现继承。
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();
我读过Eric Elliott的这篇文章,如果我理解正确,我可以用Object.create
和Object.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();
顺便说一句,按照惯例,构造函数函数是大写的,充当构造函数的对象文字是否也应该大写?
我意识到在这里讨论new
与Object.create
有很多问题,但它们似乎通常与Duck.prototype = new Animal();
与Duck.prototype = Object.create(Animal.prototype);
有关
是的,就这么简单。在您的Object.create/Object.assign
示例中,您使用一个工厂函数来创建duck
的新实例(类似于jQuery在选择具有var body = $('body')
的元素时创建新实例的方式)。这种代码样式的一个优点是,当您想要创建一个新的duck
实例(与ES2015类相反)时,它不会强制您调用animal
的构造函数。
初始化差异
也许有一个有趣的花絮,它的工作原理与使用构造函数(或任何其他初始化函数)略有不同:
创建duck
实例时,animal
的所有属性都在duck
实例的[[Prototype]]
插槽中。
var daffy = duck();
console.log(daffy); // Object { speak: function() }
因此CCD_ 16还没有任何自己的CCD_ 17和CCD_。然而,当您调用以下内容时,它们将被添加:
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样式):
var duck = function () {
var duckObject = {
speak : function () {
console.log("quack");
return this;
}
};
return Object.setPrototypeOf(duckObject, animal);
}
命名惯例
我不知道有什么既定的惯例。Kyle Simpson在OLOO中使用大写字母,Eric Elliot似乎使用小写字母。就我个人而言,我会坚持小写,因为充当构造函数的对象文字本身已经是成熟的对象(而不仅仅是像类那样的蓝图)。
辛格尔顿
如果您只想要一个实例(例如,对于singleton),您可以直接调用它:
var duck = Object.assign(Object.create(animal), {
speak : function () {
console.log("quack");
return this;
}
});
duck.move(6, 7).locate().speak();
我读过Eric Elliott的这篇文章,如果我理解正确,我可以用
Object.create
和Object.assign
代替吗?真的那么简单吗?
是的,create
和assign
要简单得多,因为它们是基元,而且没有那么神奇——你所做的一切都是显式的。
然而,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
语法)没有什么不同,我们只是将原型直接存储在变量中,并通过显式调用create
和constructor
进行实例化。
现在你可以想象,我们的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();
这样做的问题在于,它只适用于基元值,并且会变得重复。这就是工厂功能进入的地方:
function makeDuck(x, y) {
return Object.assign(Object.create(duck), {x, y});
}
let daffy = makeDuck(5, 0);
为了便于继承,初始化通常不在工厂中进行,而是在专用方法中进行,因此也可以在"子类"实例上调用。你可以称这个方法为init
,也可以像我上面所做的那样称它为constructor
,基本上是一样的。
顺便说一句,按照惯例,构造函数函数是大写的,充当构造函数的对象文字是否也应该大写?
如果你不使用任何构造函数,你可以给大写的变量名赋予一个新的含义,是的。然而,这可能会让每个不习惯的人感到困惑。顺便说一句,它们不是"充当构造函数的对象文字",它们只是原型对象。
您应该考虑使用哪种类型的"实例化模式",而不是"继承"。它们有不同的实施目的。
上面的例子是原型,下面的例子是功能共享的。查看此链接:JS实例化模式
此外,这与6无关。
- 使用Object.create()的角度服务继承
- 使用Object.create作为原型的原型继承将[Object]作为实例的原型
- 使用 Object.create 进行继承
- 为什么 Object.create 在 JavaScript 中的原型继承方面没有做我所期望的
- Javascript Object.create 不是从父级继承的
- 为什么 Object 对象中的某些属性不会在子对象中继承
- ECMAScript 5定义了用于构造和继承的通用工厂模式[Object.create()]
- 原型继承和Object.create
- 使用Object.keys获取用于创建构造函数实例的属性,然后打印继承的属性
- Object.create与新继承混淆
- 使用Object.assign和Object.create进行继承
- 将' this '的继承扩展到' object '的方法/属性
- 在javascript“object”中继承属性
- for-in vs Object.没有继承属性的keys forEach
- 是否可以确定使用object创建的对象.create继承了JavaScript中的Array
- Object.create(BaseObject)和util的区别是什么?继承(MyObject BaseObject)
- 没有new或Object.create的原型继承
- 在JavaScript中继承Object的属性
- ie9中的Javascript对象列表无法继承Object.prototype
- 有人能解释JavaScript中的对象和函数层次结构吗?与JavaScript一样,所有内容都是从Object继承的