我认为归结为性能问题。您提到性能下降很小,但这实际上取决于应用程序的规模(2 只羊对 1000 只羊)。 不应忽略原型继承,我们可以使用功能继承和原型继承的混合来创建有效的模块模式。
正如帖子JS - Why use Prototype?中提到的,
prototype 的优点之一是您只需初始化原型成员一次,
而构造函数中的成员是为每个实例创建的。
实际上,您可以直接访问原型,而无需创建新对象。
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.name,但我们知道
那 Animal.name 是静态的,所以最好实例化一次。由于您的代码暗示 Animal.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 :)
如上所示,使用您当前的模块模式,我们失去了效率
更新所有成员应该共有的属性。
如果您关心可见性,我会使用与您当前用于封装私有成员相同的模块化方法,但也使用
priviledged members 用于在需要联系这些成员时访问它们。 特权成员是提供访问私有变量的接口的公共成员。最后在原型中添加通用成员。
当然要走这条路,您需要对此进行跟踪。
确实,在您的实现中有
-
无需在回调中通过 $.proxy(fn, this) 跟踪“this”指针
-
没有更多的 var that = this 等带有事件处理程序等。每当我看到“this”时,我知道它是被传递给回调的上下文,这不是我跟踪了解我的对象实例的东西。
,但您每次都在创建一个非常大的对象,与使用某些原型继承相比,这将消耗更多内存。
类比事件委托
使用原型获得性能的类比是在操作 DOM 时通过使用事件委托来提高性能。Event Delegation in 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>
假设您要记录您单击的项目。
一种实现是将事件处理程序附加到每个项目(坏),但如果我们的列表很长,将会有很多事件需要管理。
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 应用程序并且这些性能问题不是问题,
那么您的方法将是可行的方法。