我认为这归结为性能问题。您提到性能下降很小,但这实际上取决于应用程序的规模(2 只羊对 1000 只羊)。原型继承不应该被忽视,我们可以使用功能继承和原型继承的混合创建一个有效的模块模式。
正如文章JS - 为什么使用原型?,原型的优点之一是您只需要初始化原型成员一次,而构造函数中的成员是为每个实例创建的。实际上,您可以直接访问原型,而无需创建新对象。
Array.prototype.reverse.call([1,2,3,4]);
//=> [4,3,2,1]
function add() {
//convert arguments into array
var arr = Array.prototype.slice.call(arguments),
sum = 0;
for(var i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
add(1,2,3,4,5);
//=> 15
在您的函数中,每次调用构造函数时都会产生额外的开销来创建全新的 Animal 和sheep。每个实例都会创建一些成员,例如 Animal.name,但我们知道 Animal.name 是静态的,因此最好实例化一次。由于您的代码暗示 Animal.name 应该在所有动物中都相同,因此如果我们将 Animal.prototype.name 移动到原型中,只需更新 Animal.prototype.name 即可轻松更新所有实例的 Animal.name。
考虑这个
var animals = [];
for(var i = 0; i < 1000; i++) {
animals.push(new Animal());
}
功能继承/模块模式
function Animal() {
return {
name : 'Generic',
updateName : function(name) {
this.name = name;
}
}
}
//update all animal names which should be the same
for(var i = 0;i < animals.length; i++) {
animals[i].updateName('NewName'); //1000 invocations !
}
与原型
Animal.prototype = {
name: 'Generic',
updateName : function(name) {
this.name = name
};
//update all animal names which should be the same
Animal.prototype.updateName('NewName'); //executed only once :)
如上所示,对于您当前的模块模式,我们失去了更新所有成员应该共有的属性的效率。
如果您关心可见性,我会使用您当前用于封装私有成员的相同模块化方法,但
如果需要访问这些成员,也可以使用特权成员访问这些成员。特权成员是提供访问私有变量的接口的公共成员。最后在原型中添加通用成员。
当然,走这条路,您将需要跟踪这一点。确实,在您的实施中有
-
无需在回调中通过 $.proxy(fn, this) 跟踪“this”指针
-
没有更多的 var that = this 等带有事件处理程序等。每当我看到“this”时,我知道它是被传递给回调的上下文,这不是我跟踪了解我的对象实例的东西。
,但是您每次都在创建一个非常大的对象,与使用某些原型继承相比,这将消耗更多的内存。
事件委托作为类比
与使用原型获得性能的类比是在操作 DOM 时使用事件委托来提高性能。Javascript 中的事件委托
假设你有一个大杂货清单。百胜。
<ul ="grocery-list">
<li>Broccoli</li>
<li>Milk</li>
<li>Cheese</li>
<li>Oreos</li>
<li>Carrots</li>
<li>Beef</li>
<li>Chicken</li>
<li>Ice Cream</li>
<li>Pizza</li>
<li>Apple Pie</li>
</ul>
假设您要记录您单击的项目。一种实现是将事件处理程序附加到每个 item(bad),但如果我们的列表很长,将会有很多事件需要管理。
var list = document.getElementById('grocery-list'),
groceries = list.getElementsByTagName('LI');
//bad esp. when there are too many list elements
for(var i = 0; i < groceries.length; i++) {
groceries[i].onclick = function() {
console.log(this.innerHTML);
}
}
另一种实现是将一个事件处理程序附加到父级(好)并让那个父级处理所有点击。如您所见,这类似于使用原型来实现通用功能并显着提高性能
//one event handler to manage child elements
list.onclick = function(e) {
var target = e.target || e.srcElement;
if(target.tagName = 'LI') {
console.log(target.innerHTML);
}
}
使用功能/原型继承的组合重写
我认为功能/原型继承的组合可以以易于理解的方式编写。我已经使用上述技术重写了您的代码。
var Animal = function () {
var helloCount = 0;
var self = this;
//priviledge methods
this.AnimalHello = function() {
helloCount++;
console.log(self.Name + ' says hello (animalHello)');
};
this.GetHelloCount = function (callback) {
callback.call(null, helloCount);
}
};
Animal.prototype = {
Name: 'Generic',
IsAnimal: true
};
var Sheep = function (name) {
var sheep = new Animal();
//use parasitic inheritance to extend sheep
//http://www.crockford.com/javascript/inheritance.html
sheep.Name = name || 'Woolie'
sheep.SheepHello = function() {
this.AnimalHello();
var self = this;
this.GetHelloCount(function(count) {
console.log('i (' + self.Name + ') have said hello ' + count + ' times (sheepHello anon callback)');
});
}
return sheep;
};
Sheep.prototype = new Animal();
Sheep.prototype.isSheep = true;
var sheepie = new Sheep('Sheepie');
var lambie = new Sheep('Lambie');
sheepie.AnimalHello();
sheepie.SheepHello();
lambie.SheepHello();
结论
要点是利用原型继承和功能继承两者的优势来解决性能和可见性问题。最后,如果您正在开发小型 JavaScript 应用程序并且这些性能问题不是问题,那么您的方法将是可行的方法。