使用$ .extend和模块模式的简单javascriptinheritance

我想知道几年后人们怎么想用inheritance模块模式 – esque构造函数模式和没有正常的原型inheritance。 为什么程序员不为非单例js类使用模块模式? 对我来说,优点是:

  • 非常明确的公共和私人范围(易于理解代码和API)
  • 无需在回调中通过$ .proxy(fn,this)跟踪’this’指针
  • 不再使用事件处理程序等等= =等等。每当我看到’this’时,我知道它是传递给回调的上下文,它不是我跟踪以了解我的对象实例的东西。

缺点:

  • 小性能退化
  • 道格·克罗克福德可能有“手指摇摆”的风险吗?

考虑一下(只需在任何js控制台中运行)

var Animal = function () { var publicApi = { Name: 'Generic', IsAnimal: true, AnimalHello: animalHello, GetHelloCount:getHelloCount }; var helloCount = 0; function animalHello() { helloCount++; console.log(publicApi.Name + ' says hello (animalHello)'); } function getHelloCount(callback) { callback.call(helloCount); } return publicApi; }; var Sheep = function (name) { var publicApi = { Name: name || 'Woolie', IsSheep: true, SheepHello: sheepHello }; function sheepHello() { publicApi.AnimalHello(); publicApi.GetHelloCount(function() { console.log('i (' + publicApi.Name + ') have said hello ' + this + ' times (sheepHello anon callback)'); }); } publicApi = $.extend(new Animal(), publicApi); return publicApi; }; var sheepie = new Sheep('Sheepie'); var lambie = new Sheep('Lambie'); sheepie.AnimalHello(); sheepie.SheepHello(); lambie.SheepHello(); 

我的问题是我没有看到这种方法的缺点是什么? 这是一个好方法吗?

谢谢!

[更新]

感谢您的好评。 希望我能给予每个人赏金。 这就是我在寻找的东西。 基本上我的想法。 我绝不会使用模块模式来构造多个实例。 通常只有一对。 我认为它有其优点的原因是你看到的任何小的性能退化都会在编码体验的简单性中重新获得。 这些天我们写了很多代码。 我们还必须重用其他人的代码,我个人感谢有人花时间创造一个漂亮优雅的模式,而不是在有意义的时候教条地坚持原型inheritance。

我认为这归结为性能问题 。 您提到性能下降很小,但这实际上取决于应用程序的规模(2只绵羊对1000只绵羊)。 原型inheritance不应该被忽略 ,我们可以使用function和原型inheritance混合创建一个有效的模块模式。

正如文章JS中所提到的- 为什么要使用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和sheep。 一些成员如Animal.name是用每个实例创建的,但是我们知道Animal.name是静态的,所以最好将它实例化一次。 由于您的代码暗示所有动物的Animal.name应该相同,因此只需更新Animal.prototype.name(如果我们将其移动到原型),就可以轻松更新所有实例的Animal.name。

考虑一下

 var animals = []; for(var i = 0; i < 1000; i++) { animals.push(new Animal()); } 

functioninheritance/模块模式

 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'指针
  • 不再使用事件处理程序等等= =等等。每当我看到'this'时,我知道它是传递给回调的上下文,它不是我跟踪以了解我的对象实例的东西。

,但是你每次都在创建一个非常大的对象,与使用一些原型inheritance相比,它会消耗更多的内存

作为类比的事件代表团

通过使用原型获得性能的类比是通过在操作DOM时使用事件委托来提高性能。 Javascript中的事件委派

可以说你有一个大杂货清单。百胜。

 
  • Broccoli
  • Milk
  • Cheese
  • Oreos
  • Carrots
  • Beef
  • Chicken
  • Ice Cream
  • Pizza
  • Apple Pie

假设您要记录您单击的项目。 一个实现是将事件处理程序附加到每个项目(坏) ,但如果我们的列表很长,则会有很多事件需要管理。

 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); } } 

另一个实现是将一个事件处理程序附加到父(好)并让一个父处理所有单击。 正如您所看到的,这与使用原型实现通用function类似,并显着提高了性能

 //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); } } 

使用function/原型inheritance的组合重写

我认为function/原型inheritance的组合可以用一种易于理解的方式编写。 我已使用上述技术重写了您的代码。

 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(); 

结论

重点是利用原型和functioninheritance来发挥它们的优势,以解决性能和可视性问题 。 最后,如果您正在处理小型JavaScript应用程序并且这些性能问题不是问题 ,那么您的方法将是可行的方法。

模块模式esque构造函数模式

这称为寄生inheritancefunctioninheritance

对我来说,优点是:

  • 非常明确的公共和私人范围(易于理解此代码和API)

对于经典构造函数模式也是如此。 顺便说一句,在你当前的代码中,不清楚animalHellogetHelloCount是否应该是私有的。 如果你关心它,那么在导出的对象文字中正确定义它们可能会更好。

  • 无需在回调中通过$ .proxy(fn,this)跟踪’this’指针
  • 不再使用事件处理程序等等= =等等。每当我看到’this’时,我知道它是传递给回调的上下文,它不是我跟踪以了解我的对象实例的东西。

那基本上是一样的。 您可以使用that解除引用绑定来解决此问题。 我并不认为这是一个巨大的劣势,因为你直接使用对象“方法”作为回调的情况非常罕见 – 除了上下文之外,你经常想要提供额外的参数。 顺便说一下,你在代码中也使用了that引用,它在那里被称为publicApi

为什么程序员不为非单例js类使用模块模式?

现在,你已经自己提出了一些不利之处。 此外,您正在失去原型inheritance – 具有其所有优点(简单性,动态性, instanceof ,……)。 当然,有些情况下它们不适用,而且您的工厂function非常好。 它确实用于这些情况。

 publicApi = $.extend(new Animal(), publicApi); … … new Sheep('Sheepie'); 

代码的这些部分也有点令人困惑。 您在此处使用不同的对象覆盖变量,它发生在代码的中间到结尾。 最好将此视为“声明”(您在此处inheritance父属性!)并将其置于函数的顶部 – 正如var publicApi = $.extend(Animal(), {…});

此外,您不应在此处使用new关键字。 您不要将这些函数用作构造函数,并且您不希望创建从Animal.prototypeinheritance的实例(这会降低您的执行速度)。 此外,它会使那些可能期望使用new构造函数调用进行原型inheritance的人感到困惑。 为清楚起见,您甚至可以将函数重命名为makeAnimalmakeSheep

在nodejs中有什么可比的方法?

这种设计模式完全与环境无关。 它将在Node.js中工作,就像在客户端和每个其他EcmaScript实现中一样。 它的某些方面甚至与语言无关。

使用您的方法,您将无法覆盖函数并如此方便地调用超级函数。

 function foo () { } foo.prototype.GetValue = function () { return 1; } function Bar () { } Bar.prototype = new foo(); Bar.prototype.GetValue = function () { return 2 + foo.prototype.GetValue.apply(this, arguments); } 

此外,在原型方法中,您可以在对象的所有实例之间共享数据。

 function foo () { } //shared data object is shared among all instance of foo. foo.prototype.sharedData = { } var a = new foo(); var b = new foo(); console.log(a.sharedData === b.sharedData); //returns true a.sharedData.value = 1; console.log(b.sharedData.value); //returns 1 

原型方法的另一个优点是节省内存。

 function foo () { } foo.prototype.GetValue = function () { return 1; } var a = new foo(); var b = new foo(); console.log(a.GetValue === b.GetValue); //returns true 

而在你的方法中,

 var a = new Animal(); var b = new Animal(); console.log(a.AnimalHello === b.AnimalHello) //returns false 

这意味着每个新对象都会创建一个新的函数实例,因为它在原型方法的所有对象之间共享。 这不会在很少的情况下产生太大的差异,但是当创建大量实例时,它将显示出相当大的差异。

此外,原型的另一个强大function是,一旦创建了所有对象,您仍然可以一次更改所有对象之间的属性(仅当它们在创建对象后不被更改)。

 function foo () { } foo.prototype.name = "world"; var a = new foo (); var b = new foo (); var c = new foo(); c.name = "bar"; foo.prototype.name = "hello"; console.log(a.name); //returns 'hello' console.log(b.name); //returns 'hello' console.log(c.name); //returns 'bar' since has been altered after object creation 

结论:如果原型方法的上述优点对您的应用程序没那么有用,那么您的方法会更好。