变体 1 - Mixin
function SomeType() {
var priv = "I'm private";
this.publ = "I'm public";
this.action = function() {
return priv + this.publ;
};
}
var obj = new SomeType();
使用此方法,您每次调用时都会创建一个新对象new SomeType()
,创建其所有方法并将所有此方法添加到新对象中。每次创建对象时。
优点
- 它看起来像经典继承,因此对于 Java-C#-C++ 等人来说很容易理解。
- 每个实例可以有私有变量,因为每个创建的对象都有一个函数闭包
- 它允许多重继承,也称为 Twitter-mixins 或函数式 mixins
obj instanceof SomeType
将返回真
缺点
- 随着您创建的对象越多,它会消耗更多的内存,因为对于每个对象,您都在创建一个新的闭包并再次创建它的每个方法。
- 私有属性是
private
,不是protected
,子类型不能访问它们
- 没有简单的方法可以知道一个对象是否有某个类型作为超类。
遗产
function SubType() {
SomeType.call(this);
this.newMethod = function() {
// can't access priv
return this.publ;
};
}
var child = new SubType();
child instanceof SomeType
将返回 false 没有其他方法可以知道孩子是否有 SomeType 方法,而不是一一查看它是否有它们。
变体 2 - 带有原型的对象字面量
var obj = {
publ: "I'm public",
_convention: "I'm public too, but please don't touch me!",
someMethod: function() {
return this.publ + this._convention;
}
};
在这种情况下,您正在创建一个对象。如果您只需要这种类型的一个实例,它可能是最好的解决方案。
优点
缺点
遗产
你可以继承一个对象原型。
var child = Object.create(obj);
child.otherMethod = function() {
return this._convention + this.publ;
};
如果您使用的是旧浏览器,则需要保证Object.create
作品:
if (!Object.create) {
Object.create = function(obj) {
function tmp() { }
tmp.prototype = obj;
return new tmp;
};
}
要知道一个对象是否是另一个对象的原型,您可以使用
obj.isPrototypeOf(child); // true
变体 3 - 构造函数模式
更新:这是模式 ES6 类是. 如果您使用 ES6 类,您将在后台遵循此模式。
class SomeType {
constructor() {
// REALLY important to declare every non-function property here
this.publ = "I'm public";
this._convention = "I'm public too, but please don't touch me!";
}
someMethod() {
return this.publ + this._convention;
}
}
class SubType extends SomeType {
constructor() {
super(/* parent constructor parameters here */);
this.otherValue = 'Hi';
}
otherMethod() {
return this._convention + this.publ + this.otherValue;
}
}
function SomeType() {
// REALLY important to declare every non-function property here
this.publ = "I'm public";
this._convention = "I'm public too, but please don't touch me!";
}
SomeType.prototype.someMethod = function() {
return this.publ + this._convention;
};
var obj = new SomeType();
如果您不继承并记住重新分配构造函数属性,则可以重新分配原型而不是添加每个方法:
SomeType.prototype = {
constructor: SomeType,
someMethod = function() {
return this.publ + this._convention;
}
};
如果页面中有下划线或 jquery,则使用 _.extend 或 $.extend
_.extend(SomeType.prototype, {
someMethod = function() {
return this.publ + this._convention;
}
};
引擎盖下的new
关键字只是这样做:
function doNew(Constructor) {
var instance = Object.create(Constructor.prototype);
instance.constructor();
return instance;
}
var obj = doNew(SomeType);
你拥有的是一个没有方法的函数;它只有一个prototype
带有函数列表的属性,new
操作符的意思是创建一个新对象并使用这个函数的原型(Object.create
)和constructor
属性作为初始值设定项。
优点
- 高性能
- 原型链将让您知道一个对象是否从某种类型继承
缺点
遗产
function SubType() {
// Step 1, exactly as Variation 1
// This inherits the non-function properties
SomeType.call(this);
this.otherValue = 'Hi';
}
// Step 2, this inherits the methods
SubType.prototype = Object.create(SomeType.prototype);
SubType.prototype.otherMethod = function() {
return this._convention + this.publ + this.otherValue;
};
var child = new SubType();
您可能会认为它看起来像是 Variation 2 的超集……您是对的。类似于变体 2,但具有初始化函数(构造函数);
child instanceof SubType
并将child instanceof SomeType
返回true
好奇心:幕后instanceof
操作员所做的是
function isInstanceOf(obj, Type) {
return Type.prototype.isPrototypeOf(obj);
}
变体 4 - 覆盖__proto__
当你Object.create(obj)
在引擎盖下这样做时
function fakeCreate(obj) {
var child = {};
child.__proto__ = obj;
return child;
}
var child = fakeCreate(obj);
该__proto__
属性直接修改对象的隐藏[Prototype]
属性。由于这可能会破坏 JavaScript 行为,因此它不是标准的。并且首选标准方式(Object.create
)。
优点
缺点
- 非标
- 危险的; 你不能有一个哈希图,因为
__proto__
键可以改变对象的原型
遗产
var child = { __proto__: obj };
obj.isPrototypeOf(child); // true
评论问题
1. var1: SomeType.call(this) 中发生了什么?“呼叫”有特殊功能吗?
哦,是的,函数是对象,所以它们有方法,我会提到三个: .call( )、.apply()和.bind()
当你在函数上使用 .call() 时,你可以传递一个额外的参数,上下文,this
函数内部的值,例如:
var obj = {
test: function(arg1, arg2) {
console.log(this);
console.log(arg1);
console.log(arg2);
}
};
// These two ways to invoke the function are equivalent
obj.test('hi', 'lol');
// If we call fn('hi', 'lol') it will receive "window" as "this" so we have to use call.
var fn = obj.test;
fn.call(obj, 'hi', 'lol');
因此,当我们这样做时,SomeType.call(this)
我们将对象传递this
给函数SomeCall
,正如您所记得的,该函数将向对象添加方法this
。
2. var3:您的“真正定义属性”是指我是否在函数中使用它们?是公约吗?因为获得 this.newProperty 而不将它与其他成员函数定义在同一级别不是问题。
我的意思是您的对象将具有的任何不是函数的属性都必须在构造函数上定义,而不是在原型上定义,否则您将面临更令人困惑的 JS 问题之一。你可以在这里看到它,但它超出了这个问题的重点。
3. Var3:如果我不重新分配构造函数会怎样?
实际上,您可能看不到差异,这就是使其成为危险错误的原因。每个函数的原型对象都有一个constructor
属性,因此您可以从实例访问构造函数。
function A() { }
// When you create a function automatically, JS does this:
// A.prototype = { constructor: A };
A.prototype.someMethod = function() {
console.log(this.constructor === A); // true
this.constructor.staticMethod();
return new this.constructor();
};
A.staticMethod = function() { };
这不是最佳实践,因为不是每个人都知道它,但有时它会有所帮助。但是如果你重新分配原型......
A.prototype = {
someMethod = function() {
console.log(this.constructor === A); // false
console.log(this.constructor === Object); // true
this.constructor.staticMethod();
return new this.constructor();
}
};
A.prototype
是一个新对象,Object
比原型的一个实例,Object.prototype
并且Object.prototype.constructor
是Object
. 令人困惑,对吧?:P
因此,如果您覆盖原型并且不重置“constructor”属性,它将引用Object
而不是A
,并且如果您尝试使用“constructor”属性访问某些静态方法,您可能会发疯。