2012-02-28 34 views
7

我從Python和Smalltalk的背景來到JavaScript,我很欣賞Self和Lisp在語言中的線性。使用ECMAScript5,我想在沒有新操作員的情況下嘗試原型OO。原型OO與Object.create和命名構造函數

約束:

  • 可選new運算符創建類
  • 原型鏈必須調試支持
  • 的alloc()的init()創建樣序列是正確的的instanceof
  • 命名的構造函數WebInspector。 Objective-C和Python

這是我在實現的嘗試,以滿足t他的標準:

function subclass(Class, Base) { 
    "use strict"; 
    function create(self, args) { 
     if (!(self instanceof this)) 
      self = Object.create(this.prototype); 
     var init = self.__init__; 
     return init ? init.apply(self, args) : self; 
    } 
    if (Base instanceof Function) Base = Base.prototype; 
    else if (Base===undefined) Base = Object.prototype; 

    Class.prototype = Object.create(Base); 
    Class.prototype.constructor = Class; 
    Class.create = create; 

    Class.define = function define(name, fn) { return Class.prototype[name] = fn; }; 
    Class.define('__name__', Class.name); 
    return Class; 
} 

而且似乎在一個簡單的樣機工作:

function Family(){return Family.create(this, arguments)} 
subclass(Family, Object); 
Family.define('__init__', function __init__(n){this.name=n; return this;}); 

function Tribe(){return Tribe.create(this, arguments)} 
subclass(Tribe, Family); 
function Genus(){return Genus.create(this, arguments)} 
subclass(Genus, Tribe); 
function Species(){return Species.create(this, arguments)} 
subclass(Species, Genus); 

使用類作爲一個工廠函數:

var dog = Species('dog'); 
console.assert(dog instanceof Object); 
console.assert(dog instanceof Family); 
console.assert(dog instanceof Tribe); 
console.assert(dog instanceof Genus); 
console.assert(dog instanceof Species); 

或者使用new運算符:

var cat = new Species('cat'); 
console.assert(cat instanceof Object); 
console.assert(cat instanceof Family); 
console.assert(cat instanceof Tribe); 
console.assert(cat instanceof Genus); 
console.assert(cat instanceof Species); 

console.assert(Object.getPrototypeOf(dog) === Object.getPrototypeOf(cat)) 

我忽視了需要在我的實施中原型OO的功能?是否有Javascript慣例或互動我應該做出改變?總之,這裏的「陷阱」是什麼,並且有什麼明顯的改進呢?

我想用構造函數定義DRYer,但是我發現函數的name屬性是不可寫的,那就是支持WebKit Inspector的對象名稱的東西。我能夠構建一個評估來完成我想要的,但是......惡作劇。

+0

哇,我從來沒有使用JavaScript這樣嗎... – 2012-02-29 00:53:18

+0

EVAL是危險的,如果當你處理「不可信」 DATAS,但我不明白爲什麼一個當你這樣做[不應該使用eval,其實astring]你正在使用某種類型的eval,因爲js中沒有關聯數組。 – mpm 2012-03-02 00:11:51

+1

@camus是的,我甚至使用了'/ \ w + /'正則表達式來確保它只是一個有效的方法名稱。但它似乎使用大錘來安裝大頭針 - 如果允許設置函數的名稱屬性,它會更優雅。 Javascript是一種原型語言,畢竟... – 2012-03-02 00:38:01

回答

8

編輯:哦,我現在看到的問題。答:不,你說得對。設置函數名稱的唯一方法是使用函數聲明,這意味着在評估時。因此,您需要在源代碼中使用它(其中eval是後門進入)。我之前回答了一個更簡單的問題,但具有相同的要點:Minor drawback with Crockford Prototypical Inheritance。關於此主題的另一個資源是http://kangax.github.com/nfe/

有移動使用displayName屬性爲了從調試器外觀中分離函數的不可更改的名稱。這是在Firefox和其他一些東西中實現的,並且是一個包含在es6中的草莓人,但它尚未成爲暫定規範的一部分:http://wiki.ecmascript.org/doku.php?id=strawman:name_property_of_functions

以下是一些關於命名函數http://querypoint-debugging.googlecode.com/files/NamingJSFunctions.pdf

這裏是鉻問題討論爲什麼它沒有實現:http://code.google.com/p/chromium/issues/detail?id=17356

走上原來的答案:

自己爲自己設定來完成的,你做的很好。類似類型的東西一對夫婦的例子我做:

首先是一個簡單的「遺傳」功能,它允許你做的東西,如:

var MyObjCtor = heritable({ 
    constructor: function MyObj(){ /* ctor stuff */}, 
    super: SomeCtor, 
    prop1: val, 
    prop2: val, 
    /**etc..*/ 
}); 


function heritable(definition){ 
    var ctor = definition.constructor; 
    Object.defineProperty(ctor, 'super', { 
    value: definition.super, 
    configurable: true, 
    writable: true 
    }); 
    ctor.prototype = Object.create(ctor.super.prototype); 
    delete definition.super; 

    Object.keys(definition).forEach(function(prop){ 
    var desc = Object.getOwnPropertyDescriptor(definition, prop); 
    desc.enumerable = false; 
    Object.defineProperty(ctor.prototype, prop, desc); 
    }); 

    function construct(){ 
    var obj = new (ctor.bind.apply(ctor, [].concat.apply([null], arguments))); 
    ctor.super.call(obj); 
    return obj; 
    } 

    construct.prototype = ctor.prototype; 

    return construct; 
} 

另一種是在創造與libffi使用結構(節點FFI)。基本上在這裏你有構造函數繼承和原型繼承。您可以創建從構造函數繼承的構造函數,這些構造函數創建結構實例。 https://github.com/Benvie/node-ffi-tools/blob/master/lib/Struct.js

使用eval需要,以創建一個名爲功能,所以這就是,如果你需要一個名爲構造。我無需在需要的地方使用它。

+0

鏈接合併,代碼示例和代碼審查非常有用 - 謝謝。 – 2012-03-08 16:59:43

4

啥呢,我可以告訴你,從你的背景來的Javascript行爲以不同的方式比你正在嘗試做的。在你馬上討厭這個問題的答案閱讀...

我愛OOP和來自一個非常友好的OOP背景。它花了我一段時間來圍繞Prototype對象(將原型方法想象成靜態函數......只對初始化對象起作用)。在JavaScript中完成的許多事情感覺來自使用良好安全性和封裝的語言的「錯誤」。

唯一的真正原因需要使用「新」運營商如果要創建多個實例,或試圖阻止某些類型的邏輯運行,直到事件被觸發。

我知道這是不是一個答案......這只是太多鍵入評論。要真正獲得更好的視角,你應該創建對象和變量並查看你的DOM ......你會意識到你可以從任何地方從任何地方得到任何地方。我發現these articles非常足以填補細節。

祝你好運。

UPDATE

所以,如果你確實需要一個工廠方法來創建同一個對象的不同實例這裏有一些提示,我想我會聽到:

  • 請,請,請忘記你從OO封裝學到的所有規則,你知道。我這樣說是因爲,當將原始對象,字面對象和實例對象一起使用時,將會有很多方式以一種對你而言不自然的方式訪問事物。
  • 如果你打算使用任何類型的抽象或繼承,我會強烈建議玩DOM。創建一些對象並在DOM中查找它們,看看發生了什麼。你可以使用現有的原型來模擬你的繼承(它殺了我沒有接口和抽象類來擴展!)。
  • 知道在JavaScript中的一切都是一個對象...這可以讓你做一些非常酷的東西(傳遞函數,返回函數,創建簡單的回調方法)。看看javascript調用'.call()','.apply()'和'.bind()'。

知道這些兩件事情幫你出,你解決您的解決方案。爲了保持DOM清潔並遵循良好的JavaScript開發實踐,請保持全局名稱空間清潔(DOM中的窗口引用)。這也會讓你感覺所有你覺得「錯誤」的事情都會變得更好。並保持一致。

這些都是我通過試驗和錯誤學到的東西。如果您可以圍繞與傳統面嚮對象語言的不同之處,那麼Javascript就是一個偉大而獨特的語言。祝你好運!

+0

感謝您的洞察力。我確實有一個特定的用例需要創建類的新工廠方法風格,但仍然與其他地方的javascript期望兼容。 – 2012-03-07 15:37:47

+1

@ShaneHolloway好的,很酷。那麼你應該使用'new'關鍵字來創建你的對象。我會更新我的答案,看看它是否對您有所幫助。 – jjNford 2012-03-07 16:35:38

+0

感謝您的豐富經驗和提示。我同意原型編程是一個小小的心靈翹曲開始。 – 2012-03-08 16:53:20

1

您不能在沒有eval的情況下創建命名構造函數。如果庫實現名稱空間,所有你會發現它是一個隱藏屬性的構造函數。

回答問題的其餘部分,有很多JavaScript類庫。你可以自己寫,但如果你想以正確的方式去理解和實現一個正確的繼承系統有點麻煩。

我最近寫了自己的類庫,試圖改進所有的庫:Classful JS。我認爲值得一看,因爲它的簡單設計,用法和其他一些改進,比如從子類中調用任何超級方法(我沒有看到任何可以做到這一點的庫,它們都只能調用超級方法壓倒一切)。

3

這可能不會回答你的問題,這可能並不是你想做的,但這是另一種關於OO JavaScript的方法。我喜歡它主要是因爲語法;對我來說很容易,但我沒有你的背景。

// ------- Base Animal class ----------------------- 
var Animal = function(name) { 
    this.name = name; 
}; 

Animal.prototype.GetName = function() { 
    return this.name; 
}; 
// ------- End Base Animal class ----------------------- 

// ------- Inherited Dog class ----------------------- 
var Dog = function(name, breed) { 
    Animal.call(this, name); 
    this.breed = breed; 
}; 

Dog.prototype = new Animal(); 
Dog.prototype.constructor = Dog; 

Dog.prototype.GetBreed = function() { 
    return this.breed; 
}; 

Dog.prototype.Bark = function() { 
    alert(this.GetName() + ' the ' + this.GetBreed() + ' says "Arf!"'); 
}; 
// ------- End Inherited Dog class ----------------------- 

// Usage 
var harper = new Dog('Harper', 'miniature pinscher'); 
harper.Bark(); 
+1

我喜歡'.call'這個用法來初始化一個實例,這很乾淨,但爲什麼你要用大寫字母開頭的方法名 - 根據JavaScript中的命名約定,所有的名字都必須以小寫字母開頭,除了構造函數名和一些主機對象名稱 – Luc125 2012-03-07 01:55:22

+1

我不喜歡我見過的命名約定,幾乎所有的東西都是camelCase。我PascalCase我的「類」和「公共方法」和camelCase其他一切有助於可讀性。 – JoshNaro 2012-03-07 14:19:46

+0

我已經看到這個Javascript OOP成語幾個地方 - 它實質上是克羅克福德的僞古典例子,沒有他的幫手方法。不幸的是,我的用例需要「類」作爲工廠方法以及標準的Javascript構造函數正常工作。 – 2012-03-07 15:46:01