简单的JavaScript继承使用$ .extend和模块模式

我想了好几年现在人们想到用模块式模式构造器模式继承并且没有正常的原型继承。 为什么程序员不为非单例式js类使用模块模式? 对我来说,优点是:

  • 非常明确的公共和私人范围(易于理解代码和api)
  • 不需要在回调中通过$ .proxy(fn,this)跟踪'this'指针
  • 没有更多的var = this,等等事件处理程序等等。每当我看到一个'this'时,我知道它是传递给回调的上下文,它不是我正在跟踪的知道我的对象实例的东西。
  • 缺点:

  • 小的perf降解
  • 道格·克罗克福德可能冒着“手指摇摆”的风险吗?
  • 考虑一下(只需在任何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();
    

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

    谢谢!

    [更新]

    谢谢你的回应。 希望我能给大家赏金。 这正是我正在寻找的。 基本上我的想法。 我永远不会使用模块模式来构造超过几个东西的实例。 通常只有一对夫妇。 我认为它有其优点的原因是你看到的任何小的性能下降都会在编码体验的简单性中被重新捕获。 这几天我们有很多代码可以写。 我们还必须重用其他人的代码,并且我个人很欣赏,当有人花时间创建一个很好的优雅模式,而不是在有意义时以教条式的方式坚持原型继承。


    我认为这归结于表现问题 。 你提到性能下降很小,但这取决于应用程序的规模(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.name是用每个实例创建的,但我们知道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 :)
    

    如上所示,使用您当前的模块模式,我们会失去更新所有成员应该共用的属性的效率。

    如果您对可见性感兴趣,我会使用与当前使用的相同的模块化方法来封装私有成员,但如果需要访问这些成员,也可以使用特权成员访问这些成员。 特权成员是提供访问私有变量的接口的公共成员。 最后添加通用成员到原型。

    当然,要走这条路,你需要跟踪这一点。 在你的实现中确实存在

  • 不需要在回调中通过$ .proxy(fn,this)跟踪'this'指针
  • 没有更多的var = this,等等事件处理程序等等。每当我看到一个'this'时,我知道它是传递给回调的上下文,它不是我正在跟踪的知道我的对象实例的东西。
  • ,但是你每次创建一个非常大的对象,与使用一些原型继承相比,它会消耗更多的内存

    事件委派作为比喻

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

    可以说你有一个大杂货清单。好的。

    <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应用程序,并且这些性能问题不是问题 ,那么您的方法将是可行的方法。


    一个模块式样式的构造器模式

    这被称为寄生遗传或功能遗传。

    对我来说,优点是:

  • 非常明确的公共和私人范围(易于理解此代码和api)
  • 传统的构造函数模式也是如此。 顺便说一下,在你当前的代码中, animalHellogetHelloCount是否应该是私人的并不是很清楚。 在导出的对象文本中定义它们可能会更好,如果你关心的话。

  • 不需要在回调中通过$ .proxy(fn,this)跟踪'this'指针
  • 没有更多的var = this,等等事件处理程序等等。每当我看到一个'this'时,我知道它是传递给回调的上下文,它不是我正在跟踪的知道我的对象实例的东西。
  • 这基本上是一样的。 要么你使用that取消引用或结合来解决这个问题。 我认为这不是一个巨大的缺点,因为直接将对象“方法”用作回调的情况非常罕见 - 除了上下文之外,您通常还需要提供其他参数。 顺便说一句,你在代码中也使用了that引用,它在那里被称为publicApi

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

    现在,你已经自己命名了一些缺点。 此外,你正在失去原型继承 - 具有它的所有优点(简单性,动态性, instanceof ...)。 当然,有些情况下他们不适用,并且您的工厂功能非常好。 这确实用于这些情况。

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

    代码的这些部分也有点混乱。 你在这里用不同的对象覆盖变量,它发生在你的代码的中间到尾部。 最好把它看作是一个“声明”(你在继承父属性!)并将它放在函数的顶部 - 正如var publicApi = $.extend(Animal(), {…});

    另外,你不应该在这里使用new关键字。 您不使用函数作为构造函数,并且您不希望创建从Animal.prototype继承的实例(这会减慢执行速度)。 此外,它混淆可能期望与构造函数调用原型传承人new 。 为了清楚起见,甚至可以将函数重命名为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 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
    

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

    链接地址: http://www.djcxy.com/p/50127.html

    上一篇: Simple javascript inheritance using $.extend and module pattern

    下一篇: Is it bad practice to make a setter return "this"?