2010-03-14 106 views
12

在道格拉斯克羅克福德的JavaScript:好部件他建議我們使用功能繼承。這裏有一個例子:Javascript功能繼承與原型

var mammal = function(spec, my) { 
    var that = {}; 
    my = my || {}; 

    // Protected 
    my.clearThroat = function() { 
     return "Ahem"; 
    }; 

    that.getName = function() { 
     return spec.name; 
    }; 

    that.says = function() { 
     return my.clearThroat() + ' ' + spec.saying || ''; 
    }; 

    return that; 
}; 

var cat = function(spec, my) { 
    var that = {}; 
    my = my || {}; 

    spec.saying = spec.saying || 'meow'; 
    that = mammal(spec, my); 

    that.purr = function() { 
     return my.clearThroat() + " purr"; 
    }; 

    that.getName = function() { 
     return that.says() + ' ' + spec.name + ' ' + that.says(); 
    }; 

    return that; 
}; 

var kitty = cat({name: "Fluffy"}); 

主要的問題,我有這個的是,每次我做一個mammalcat的JavaScript解釋器必須重新編譯其中的所有功能。也就是說,你不能在實例之間共享代碼。

我的問題是:如何讓此代碼更高效?例如,如果我製作了數千個對象,那麼修改此模式以利用對象的最佳方法是什麼?

+0

「*必須重新編譯它的所有功能。也就是說,你不能在實例之間分享代碼*「 - 否。代碼是共享的,只需要創建具有不同範圍值的不同函數對象。這不是一個巨大的開銷。 – Bergi 2013-03-26 00:42:20

回答

8

那麼,如果你打算製作大量的mammalcat,你就不能那樣做。取而代之的是舊式的方式(原型)和財產繼承。您仍然可以按照上面的方式來執行構造函數,但不使用thatmy,而使用隱式this和某些表示基類的變量(在本例中爲this.mammal)。

cat.prototype.purr = function() { return this.mammal.clearThroat() + "purr"; } 

我會用另一個名字比my爲基礎的訪問,並將其存儲在thiscat構造。在這個例子中我使用了mammal,但是如果你想讓靜態訪問全局對象,這可能不是最好的。另一個選項是命名變量base

+0

鏈接'my'對象通過'this.mammal'就是我會失去它的隱私,因爲有人可以做'cat.mammal.clearThroat()'並訪問受保護的方法。 – cdmckay 2010-03-14 02:39:07

+2

所以如果你想隱私那麼你需要按照慣例來做,例如,在被保護的方法之前加上下劃線,如果這看起來太鬆散,那麼即使這個「功能性繼承」模式本身也是一個慣例,Javascript並不是一個「整潔的怪胎」語言,它是更加「粗糙和準備好」。如你發現的那樣,使它工作的整潔怪異的方式會降低性能,但是不要過多地懲罰 - 需要數以萬計的對象創建才能顯着影響你的整體運行速度,給你的電子郵件xample。 – Plynx 2010-03-14 02:43:14

+2

「有人可以做'cat.mammal.clearThroat()'並訪問受保護的方法」 - 並非每個問題都需要用代碼來解決。儘可能尊重,你不寫Windows。如果有人濫用你的代碼,那太棒了,因爲這意味着某人真的在使用你的代碼。更好地專注於製作有用的代碼,並且易於正確使用,而不是防範假設問題。 – 2010-03-14 03:19:11

0

,如果你想隱私,你不喜歡你protyping可能會或可能,不喜歡這種方式:

(注意:它使用jQuery.extend)

var namespace = namespace || {}; 

// virtual base class 
namespace.base = function (sub, undefined) { 

    var base = { instance: this }; 

    base.hierarchy = []; 

    base.fn = { 

     // check to see if base is of a certain class (must be delegated) 
     is: function (constr) { 

      return (this.hierarchy[this.hierarchy.length - 1] === constr); 
     }, 

     // check to see if base extends a certain class (must be delegated) 
     inherits: function (constr) { 

      for (var i = 0; i < this.hierarchy.length; i++) { 

       if (this.hierarchy[i] == constr) return true; 
      } 
      return false; 
     }, 

     // extend a base (must be delegated) 
     extend: function (sub) { 

      this.hierarchy.push(sub.instance.constructor); 

      return $.extend(true, this, sub); 
     }, 

     // delegate a function to a certain context 
     delegate: function (context, fn) { 

      return function() { return fn.apply(context, arguments); } 
     }, 

     // delegate a collection of functions to a certain context 
     delegates: function (context, obj) { 

      var delegates = {}; 

      for (var fn in obj) { 

       delegates[fn] = base.fn.delegate(context, obj[fn]); 
      } 

      return delegates; 
     } 
    }; 

    base.public = { 
     is: base.fn.is, 
     inherits: base.fn.inherits 
    }; 

    // extend a sub-base 
    base.extend = base.fn.delegate(base, base.fn.extend); 

    return base.extend(sub); 
}; 

namespace.MyClass = function (params) { 

    var base = { instance: this }; 

    base.vars = { 
     myVar: "sometext" 
    } 

    base.fn = { 
     init: function() { 

      base.vars.myVar = params.myVar; 
     }, 

     alertMyVar: function() { 

      alert(base.vars.myVar); 
     } 

    }; 

    base.public = { 
     alertMyVar: base.fn.alertMyVar 
    }; 

    base = namespace.base(base); 

    base.fn.init(); 

    return base.fn.delegates(base,base.public); 
}; 

newMyClass = new namespace.MyClass({myVar: 'some text to alert'}); 
newMyClass.alertMyVar(); 

唯一的缺點是,由於的隱私範圍,你只能擴展虛擬類而不是實例類。

這裏是我如何擴展namespace.base以綁定/取消綁定/觸發自定義事件的示例。

// virtual base class for controls 
namespace.controls.base = function (sub) { 

    var base = { instance: this }; 

    base.keys = { 
     unknown: 0, 
     backspace: 8, 
     tab: 9, 
     enter: 13, 
     esc: 27, 
     arrowUp: 38, 
     arrowDown: 40, 
     f5: 116 
    } 

    base.fn = { 

     // bind/unbind custom events. (has to be called via delegate) 
     listeners: { 

      // bind custom event 
      bind: function (type, fn) { 

       if (fn != undefined) { 

        if (this.listeners[type] == undefined) { 
         throw (this.type + ': event type \'' + type + '\' is not supported'); 
        } 

        this.listeners[type].push(fn); 
       } 

       return this; 
      }, 

      // unbind custom event 
      unbind: function (type) { 

       if (this.listeners[type] == undefined) { 
        throw (this.type + ': event type \'' + type + '\' is not supported'); 
       } 

       this.listeners[type] = []; 

       return this; 
      }, 

      // fire a custom event 
      fire: function (type, e) { 

       if (this.listeners[type] == undefined) { 
        throw (this.type + ': event type \'' + type + '\' does not exist'); 
       } 

       for (var i = 0; i < this.listeners[type].length; i++) { 

        this.listeners[type][i](e); 
       } 

       if(e != undefined) e.stopPropagation(); 
      } 
     } 
    }; 

    base.public = { 
     bind: base.fn.listeners.bind, 
     unbind: base.fn.listeners.unbind 
    }; 

    base = new namespace.base(base); 

    base.fire = base.fn.delegate(base, base.fn.listeners.fire); 

    return base.extend(sub); 
}; 
1

讓我來介紹一下永不使用prototype的Classical Inheritance。這是一個不好的編碼工作,但會教你真正的經典的繼承總是比原型繼承

做一個custructor:

function Person(name, age){ 
    this.name = name; 
    this.age = age; 
    this.sayHello = function(){return "Hello! this is " + this.name;} 
} 

再拍cunstructor,從它繼承:

function Student(name, age, grade){ 
    Person.apply(this, [name, age]); 
    this.grade = grade 
} 

很簡單! Student調用(適用)Person自身與nameage參數自負照顧grade參數。

現在讓我們做一個Student的實例。

var pete = new Student('Pete', 7, 1); 

pete對象現在將包含nameagegradesayHello性能。它擁有所有這些屬性。他們沒有通過原型上傳到Person。如果我們改變Person這樣:

function Person(name, age){ 
    this.name = name; 
    this.age = age; 
    this.sayHello = function(){ 
    return "Hello! this is " + this.name + ". I am " this.age + " years old"; 
    } 
} 

pete沒有收到更新。如果我們撥打pete.sayHello,ti將返回Hello! this is pete。它不會得到新的更新。

0

要正確使用Javascript的基於原型的繼承,你可以使用fastClasshttps://github.com/dotnetwise/Javascript-FastClass

你有更簡單的inheritWith味:

var Mammal = function (spec) { 
    this.spec = spec; 
}.define({ 
    clearThroat: function() { return "Ahem" }, 
    getName: function() { 
     return this.spec.name; 
    }, 
    says: function() { 
     return this.clearThroat() + ' ' + spec.saying || ''; 
    } 
}); 

var Cat = Mammal.inheritWith(function (base, baseCtor) { 
    return { 
     constructor: function(spec) { 
      spec = spec || {}; 
      baseCtor.call(this, spec); 
     }, 
     purr: function() { 
      return this.clearThroat() + " purr"; 
     }, 
     getName: function() { 
      return this.says() + ' ' + this.spec.name + this.says(); 
     } 
    } 
}); 

var kitty = new Cat({ name: "Fluffy" }); 
kitty.purr(); // Ahem purr 
kitty.getName(); // Ahem Fluffy Ahem 

如果你很關心性能,那麼你有fastClass味:

var Mammal = function (spec) { 
    this.spec = spec; 
}.define({ 
    clearThroat: function() { return "Ahem" }, 
    getName: function() { 
     return this.spec.name; 
    }, 
    says: function() { 
     return this.clearThroat() + ' ' + spec.saying || ''; 
    } 
}); 

var Cat = Mammal.fastClass(function (base, baseCtor) { 
    return function() { 
     this.constructor = function(spec) { 
      spec = spec || {}; 
      baseCtor.call(this, spec); 
     }; 
     this.purr = function() { 
      return this.clearThroat() + " purr"; 
     }, 
     this.getName = function() { 
      return this.says() + ' ' + this.spec.name + this.says(); 
     } 
    } 
}); 

var kitty = new Cat({ name: "Fluffy" }); 
kitty.purr(); // Ahem purr 
kitty.getName(); // Ahem Fluffy Ahem 

順便說一句,你的初始代碼不會做任何但我已經從字面上尊重它。

fastClass實用程序:

Function.prototype.fastClass = function (creator) { 
    var baseClass = this, ctor = (creator || function() { this.constructor = function() { baseClass.apply(this, arguments); } })(this.prototype, this) 

    var derrivedProrotype = new ctor(); 

    if (!derrivedProrotype.hasOwnProperty("constructor")) 
     derrivedProrotype.constructor = function() { baseClass.apply(this, arguments); } 

    derrivedProrotype.constructor.prototype = derrivedProrotype; 
    return derrivedProrotype.constructor; 
}; 

inheritWith實用程序:

Function.prototype.inheritWith = function (creator, makeConstructorNotEnumerable) { 
    var baseCtor = this; 
    var creatorResult = creator.call(this, this.prototype, this) || {}; 
    var Derrived = creatorResult.constructor || 
    function defaultCtor() { 
     baseCtor.apply(this, arguments); 
    }; 
    var derrivedPrototype; 
    function __() { }; 
    __.prototype = this.prototype; 
    Derrived.prototype = derrivedPrototype = new __; 

    for (var p in creatorResult) 
     derrivedPrototype[p] = creatorResult[p]; 

    if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead 
     Object.defineProperty(derrivedPrototype, 'constructor', { 
      enumerable: false, 
      value: Derrived 
     }); 

    return Derrived; 
}; 

define實用程序:

Function.prototype.define = function (prototype) { 
    var extendeePrototype = this.prototype; 
    if (prototype) 
     for (var p in prototype) 
      extendeePrototype[p] = prototype[p]; 
    return this; 
} 

[*免責聲明,我是開放源碼包和名的作者的方法本身可能會被重新命名爲未來`*]