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

Ron*_*ist 20 javascript jquery inheritance module

我想知道几年后人们会想到使用模块模式 - esque构造函数模式继承并且没有正常的原型继承.为什么程序员不使用模块模式用于非单例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();
Run Code Online (Sandbox Code Playgroud)

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

谢谢!

[更新]

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

Ant*_*nyS 35

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

正如文章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
Run Code Online (Sandbox Code Playgroud)

在你的函数中,每次调用构造函数时都会产生额外的开销来创建一个全新的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());
}
Run Code Online (Sandbox Code Playgroud)

功能继承/模块模式

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 !
}
Run Code Online (Sandbox Code Playgroud)

与原型

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 :)
Run Code Online (Sandbox Code Playgroud)

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

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

当然要走这条路,你需要跟踪这个.确实,在你的实现中有

  • 无需在回调中通过$ .proxy(fn,this)跟踪'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>
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

//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);
   }
}
Run Code Online (Sandbox Code Playgroud)

使用功能/原型继承的组合重写

我认为功能/原型继承的组合可以用一种易于理解的方式编写.我使用上述技术重写了您的代码.

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();
Run Code Online (Sandbox Code Playgroud)

结论

重点是利用原型和功能继承来解决性能和可视性问题.最后,如果您正在处理小型JavaScript应用程序并且这些性能问题不是问题,那么您的方法将是可行的方法.