我通常沿着以下几行实现继承。
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.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);
答案 0 :(得分:26)
是的,就这么简单。在使用Object.create/Object.assign
的示例中,您使用工厂函数来创建duck
的新实例(类似于jQuery创建新实例的方式,如果您选择具有var body = $('body')
的元素)。此代码样式的一个优点是,当您要创建新的animal
实例(而不是ES2015类)时,它不会强制您调用duck
的构造函数。
初始化的差异
也许一个有趣的小问题与你使用构造函数(或任何其他初始化函数)的方式略有不同:
当您创建duck
实例时,animal
的所有属性都位于[[Prototype]]
实例的duck
位置。
var daffy = duck();
console.log(daffy); // Object { speak: function() }
因此daffy
尚未拥有任何自己的x
和y
属性。但是,当您拨打以下电话时,会添加以下内容:
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
,因此[[Prototype]]
的{{1}}链遍历到daffy
,其中animal
已定义。
因此,在第一次调用中,作业右侧的animal.x
引用this.x
,因为animal.x
未定义。第二次调用daffy.x
时,右侧的daffy.move(1,2)
将为this.x
。
替代语法
或者,您也可以使用daffy.x
代替Object.setPrototypeOf
(OLOO Style):
Object.create/Object.assign
命名约定
我不知道任何既定的惯例。 Kyle Simpson在OLOO中使用大写字母,Eric Elliot似乎使用小写字母。我个人会坚持使用小写,因为充当构造函数的对象文字已经是完全成熟的对象本身(不仅仅是蓝图,就像类一样)。
<强>的Singleton 强>
如果您只想要一个实例(例如单个实例),您可以直接调用它:
var duck = function () {
var duckObject = {
speak : function () {
console.log("quack");
return this;
}
};
return Object.setPrototypeOf(duckObject, animal);
}
答案 1 :(得分:12)
我已阅读this post by 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();
这个问题是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
,它基本相同。
顺便说一句,按照惯例,构造函数是大写的,那么作为构造函数的对象文字是否也应该大写?
如果您没有使用任何构造函数,则可以为大写变量名称赋予新的含义,是的。然而,对于那些不习惯这种情况的人来说,这可能会让人感到困惑。顺便说一下,它们不是“作为构造函数的对象文字”,它们只是原型对象。
答案 2 :(得分:0)
您应该考虑打算使用什么类型的“实例化模式”,而不是“继承”。它们具有不同的实施目的。
最好的例子是原型,底部是功能共享的。查看此链接:JS Instantiation patterns
此外,这与非es6相关。