2009-09-10 49 views
6

我正在重寫一個JavaScript項目,我希望能夠使用面向對象的方法來組織當前代碼的混亂。主要擔心的是這個JavaScript應該作爲第三方網站內部的一個小部件運行,並且我不能將它與其他網站可能使用的其他JavaScript庫衝突。什麼將是一個很好的簡約的Javascript繼承方法?

所以我在尋找一種方式來寫「類像」繼承在JavaScript中有以下要求:

  1. 無需外部庫或事物,將與外部庫衝突(即排除複製&從外部庫中粘貼)。
  2. Minimalistic - 我不希望支持代碼大於幾行代碼,我不希望開發人員在每次定義新的類或方法時都需要大量的鍋爐板。
  3. 應允許動態擴展父對象,以便子對象查看更改(原型)。
  4. 應該允許構造函數鏈接。
  5. 應允許super類型的調用。
  6. 應該仍然覺得JavaScript-ish。

起初,我試圖用簡單的原型鏈的工作:

function Shape(x,y) { 
    this.x = x; 
    this.y = y; 

    this.draw = function() { 
    throw new Error("Arbitrary shapes cannot be drawn"); 
    } 
} 

function Square(x,y,side) { 
    this.x = x; 
    this.y = y; 
    this.side = side; 

    this.draw = function() { 
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); ... 
    } 
} 
Square.prototype = new Shape(); 

這解決了要求1,2和6,但ID不允許超級電話(新的功能覆蓋父函數),構造函數鏈和動態擴展父項不會將新方法提供給子類。

任何建議將受到歡迎。

+0

你可以調用超級方法。如果你要在Triangle中定義繪製,你可以通過Triangle.prototype.draw獲得超級繪製 – 2009-09-10 11:21:25

+0

問題在於「超級」方法不能訪問當前對象的字段 - 「this」是原型而不是「this」畫。我可以這樣做'Square.prototype.draw.apply(this,arguments)',但是這很笨拙,我通常不喜歡按名稱調用它們的容器類的方法(他們應該在每個地方都使用'this')。 – Guss 2009-09-10 11:54:40

+0

和this.constructor.prototype.draw.apply(this,arguments) – 2009-09-10 12:29:11

回答

5

我建議下面的模式,這使得使用clone function的從protoypes而不是實例繼承:

function Shape(x, y) { 
    this.x = x; 
    this.y = y; 
} 

Shape.prototype.draw = function() { 
    throw new Error('Arbitrary shapes cannot be drawn'); 
}; 

function Square(x,y,side) { 
    Shape.call(this, x, y); // call super constructor 
    this.side = side; 
} 

// inherit from `Shape.prototype` and *not* an actual instance: 
Square.prototype = clone(Shape.prototype); 

// override `draw()` method 
Square.prototype.draw = function() { 
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ... 
}; 

該方法駐留在原型(這是理所應當的,無論如何,這一點很重要性能方面的原因),所以你可以通過

SuperClass.prototype.aMethod.call(this, arg1, arg2); 

調用父類的方法有了一些syntactic sugar,你可以讓JS看起來像一個傳統的基於類的語言:

var Shape = Class.extend({ 
    constructor : function(x, y) { 
     this.x = x; 
     this.y = y; 
    }, 
    draw : function() { 
     throw new Error('Arbitrary shapes cannot be drawn'); 
    } 
}); 

var Square = Shape.extend({ 
    constructor : function(x, y, side) { 
     Shape.call(this, x, y); 
     this.side = side 
    }, 
    draw : function() { 
     gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ... 
    } 
}); 
+0

PS:如果您的實施支持ES5 – Christoph 2009-09-10 11:47:56

+0

I,您可以使用Object.create()而不是定製的clone()喜歡它,雖然我可能不會實現語法糖的東西。有一個問題 - 爲什麼使用clone()而不是從實例繼承?它看起來像我一樣工作,但使用clone()的可讀性較差。 – Guss 2009-09-10 12:14:42

+0

@Guss:在我看來,使用實例進行繼承是不好的做法,因爲它會運行超類的構造函數;取決於構造函數實際做了什麼,這可能會產生不希望的副作用;在子類的構造函數中顯式調用超類的構造函數更乾淨,因爲只有當你真的想創建一個新實例時,它纔會運行初始化代碼 – Christoph 2009-09-10 12:26:57

4

Douglas Crockford在Javascript中使用classicalprototypal繼承,這應該是一個很好的起點。

+3

我對Crockford的文章很熟悉,我不喜歡它們。他要麼需要大量的鍋爐板代碼(看看在你的原型繼承鏈接中構造一個對象的第一個例子),還是需要幾十行「糖」的代碼行。或兩者。結果並沒有看到所有類型的'uber'和'beget'的JavaScript-ish - 目的是受過訓練的JavaScript開發人員可以查看代碼並立即理解正在發生的事情。 – Guss 2009-09-10 10:58:54

+0

儘管如此,嘗試實現所需的功能並不容易,但最終還是會出現一些鍋爐板。並且......公平地說:每位受過培訓的JavaScript開發人員都應該熟悉Crockford的工作:) – roosteronacid 2009-09-10 13:38:41

-1

此外,克羅克福德的啓發,但我有他所謂的「功能繼承」使用「構造函數」良好的經驗。因人而異。

UPDATE:對不起,我忘了:你仍然需要使用superior方法來擴充Object,才能更好地訪問超級方法。可能不適合你。

var makeShape = function (x, y) { 
    that = {}; 
    that.x = x; 
    that.y = y; 
    that.draw = function() { 
     throw new Error("Arbitrary shapes cannot be drawn"); 
    } 
    return that; 
}; 

var makeSquare = function (x, y, side) { 
    that = makeShape(x, y); 
    that.side = side; 
    that.draw = function() { 
     gotoXY(that.x,that.y); lineTo(that.x+that.side, that.y); ... 
    } 
    return that; 
}; 
+0

樣板太多了,但是謝謝你嘗試:-) – Guss 2009-09-10 12:03:59

1

好的,在JavaScript中重現類/實例風格的系統的技巧是,您只能在實例上使用原型繼承。因此,您需要能夠創建僅用於繼承的「非實例」實例,並且具有與構造函數本身分開的初始化方法。

這是最小的系統予使用,傳遞一個特殊一次性值到構造(添加褶邊之前)將它構造對象而不初始化它:

Function.prototype.subclass= function() { 
    var c= new Function(
     'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+ 
     'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); ' 
    ); 
    if (this!==Object) 
     c.prototype= new this(Function.prototype.subclass._FLAG); 
    return c; 
}; 
Function.prototype.subclass._FLAG= {}; 

使用的new Function()是一個避免在子類()上形成不必要的閉包。如果您願意,可以用更漂亮的function() {...}表達式替換它。

用法是比較乾淨的,一般像Python風格的對象只略微笨拙的語法:

var Shape= Object.subclass(); 
Shape.prototype._init= function(x, y) { 
    this.x= x; 
    this.y= y; 
}; 
Shape.prototype.draw= function() { 
    throw new Error("Arbitrary shapes cannot be drawn"); 
}; 

var Square= Shape.subclass(); 
Square.prototype._init= function(x, y, side) { 
    Shape.prototype._init.call(this, x, y); 
    this.side= side; 
}; 
Square.prototype.draw= function() { 
    gotoXY(this.x, this.y); 
    lineTo(this.x+this.side, this.y); // ... 
}; 

猴修補一個內置(功能)是有點懷疑,但使得它愉快的閱讀,沒有人可能想通過一個函數for...in

+0

爲什麼'Function'構造函數? – kangax 2009-09-11 02:31:39

+0

'function(){...}'在它正在被使用的函數的作用域上作了一個閉包,在該函數中留下了對參數和局部變量的引用。 '新的函數()'避免了這一點。雖然這兩種方式都無關緊要,但因爲關閉中沒有重量級的東西。 – bobince 2009-09-11 11:10:24

+1

當然可以。我只是不確定爲什麼你使用「重」的函數,其中普通的'function(){}'足夠了。 – kangax 2009-09-12 19:06:43

0

您可以使用Crockford在他的書「JavaScript the good parts」中提出的功能模式。想法是使用closore來創建私有字段,並使用previleged函數來訪問這些字段。這裏是一個滿足您的要求,6解決方案之一:

var people = function (info) { 
    var that = {}; 
    // private 
    var name = info.name; 
    var age = info.age; 
    // getter and setter 
    that.getName = function() { 
     return name; 
    }; 
    that.setName = function (aName) { 
     name = aName; 
    }; 
    that.getAge = function() { 
     return age; 
    }; 
    that.setAge = function (anAge) { 
     age = anAge; 
    }; 
    return that; 
}; 

var student = function (info) { 
    // super 
    var that = people(info); 
    // private 
    var major = info.major; 
    that.getMajor = function() { 
     return major; 
    }; 
    that.setMajor = function (aMajor) { 
     major = aMajor; 
    }; 
    return that; 
}; 

var itStudent = function (info) { 
    // super 
    var that = student(info); 
    var language = info.language; 
    that.getLanguage = function() { 
     return language; 
    }; 
    that.setLanguage = function (aLanguage) { 
     language = aLanguage; 
    }; 
    return that; 
}; 

var p = person({name : "Alex", age : 24}); 
console.debug(p.age); // undefined 
console.debug(p.getAge()); // 24 

var s = student({name : "Alex", age : 24, major : "IT"}); 
console.debug(s.getName()); // Alex 
console.debug(s.getMajor()); // IT 

var i = itStudent({name : "Alex", age : 24, major : "IT", language : "js"}); 
console.debug(i.language); // Undefined 
console.debug(i.getName()); // Alex 
console.debug(i.getMajor()); // IT 
console.debug(i.getLanguage()); // js 
+0

我不喜歡這個函數語法,因爲一方面它不像面向對象的代碼(因此當你試圖讓程序員在經典的OO中訓練與你一起工作時是有害的),另一方面,您可以使用混合模式來獲得原型繼承的優勢。 – Guss 2012-04-23 10:35:56

1

最常見的模式研究這個問題是在Mozilla Developer Network描述時,我發現。我已經更新了他們的榜樣,包括一個超類方法的調用,並顯示在日誌中的警報消息:

// Shape - superclass 
 
function Shape() { 
 
    this.x = 0; 
 
    this.y = 0; 
 
} 
 

 
// superclass method 
 
Shape.prototype.move = function(x, y) { 
 
    this.x += x; 
 
    this.y += y; 
 
    log += 'Shape moved.\n'; 
 
}; 
 

 
// Rectangle - subclass 
 
function Rectangle() { 
 
    Shape.call(this); // call super constructor. 
 
} 
 

 
// subclass extends superclass 
 
Rectangle.prototype = Object.create(Shape.prototype); 
 
Rectangle.prototype.constructor = Rectangle; 
 

 
// Override method 
 
Rectangle.prototype.move = function(x, y) { 
 
    Shape.prototype.move.call(this, x, y); // call superclass method 
 
    log += 'Rectangle moved.\n'; 
 
} 
 

 
var log = ""; 
 
var rect = new Rectangle(); 
 

 
log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true 
 
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true 
 
rect.move(1, 1); // Outputs, 'Shape moved.' 
 
alert(log);

  1. 在五年既然你問這個問題,似乎瀏覽器對繼承的支持得到了改善,所以我不認爲你需要一個外部庫。
  2. 這是我見過的最簡單的技術,我不知道你是否考慮過太多的樣板。
  3. 它按要求使用原型,因此向父級添加新方法也應將它們提供給子對象。
  4. 您可以在示例中看到構造函數鏈接。
  5. 超級類型調用也在示例中。
  6. 我不確定它是否感覺JavaScript-ish,你必須自己決定。
+0

看起來不錯,並且確實感覺JavaScript-ish,但是還有很多樣板: - /仍然是一個很好的解決方案。 – Guss 2015-06-09 13:24:12

相關問題