在js中是否要模擬傳統(tǒng)編程語言的類,是個一直以來都有爭議的話題,不同的項目,不同的團隊,在類的使用上會有不同的看法,不過,一旦決定要使用類,那么至少需要一套良好的實現(xiàn),CoffeeScript在語言內部實現(xiàn)了類的模擬,我們來看一看一個完整的例子
class Gadget
@CITY = "beijing"
@create: (name, price) ->
new Gadget(name, price)
_price = 0
constructor: (@name, price) ->
_price = price
sell: =>
"Buy #{@name} with #{_price} in #{Gadget.CITY}"
iphone = new Gadget("iphone", 4999)
console.log iphone.name #=> "iphone"
console.log iphone.sell() #=> "Buy iphone with 4999 in beijing"
ipad = Gadget.create("ipad", 3999)
console.log ipad.sell() #=> "Buy ipad with 3999 in beijing"
這個Gadget類具有通常語言中類的功能:
constructor是構造函數(shù),必須用這個名稱,類似ruby中的initialize
name是實例變量,可以通過iphone.name獲取
構造函數(shù)中如果給實例變量賦值,直接將@name寫在參數(shù)中即可,等價于在函數(shù)體中的@name = name
_price是私有變量,需要賦初始值
sell是實例方法
create是類方法,注意這里使用了@create,這和ruby有些像,在定義時的this指的是這個類本身
CITY是類變量
要注意的是,對于實例方法,要用=>來綁定this,這樣可以作為閉包傳遞,比如
iphone = new Gadget("iphone", 4999)
$("#sell").click(iphone.sell())
如果不用=>,閉包被調用時就會丟失實例對象的值(iphone)
對于熟悉基于類的面向對象編程的人,CoffeeScript的類是一目了然的,下面來看看對應的js代碼
var Gadget,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Gadget = (function() {
var _price;
Gadget.name = 'Gadget';
Gadget.CITY = "beijing";
Gadget.create = function(name, price) {
return new Gadget(name, price);
};
_price = 0;
function Gadget(name, price) {
this.sell = __bind(this.sell, this);
this.name = name;
_price = price;
}
Gadget.prototype.sell = function() {
return "Buy " + this.name + " with " + _price + " in " + Gadget.CITY;
};
return Gadget;
})();
以上的代碼有很多值得注意的地方
整體上來說,CoffeeScript的類模擬使用的是一個*構造函數(shù)閉包*,這是最常用的模擬類的模式,好處是可以完整地封裝內部變量,且可以使用new來生成實例對象
_price就是被封裝在閉包內部的私有變量
sell這樣的實例方法是原型方法,并且在初始化時使用自定義的bind函數(shù)綁定實例(用=>定義的情況)
create和CITY這樣的類成員使用構造函數(shù)的屬性實現(xiàn),重復一下,在CoffeeScript類定義中的this指的是整個閉包Gadget
Gadget.name是額外定義的類名屬性
類的繼承
CoffeeScript中為方便地實現(xiàn)類的繼承也定義了自己的語法,我們把上面的類簡化,來看一下如何繼承:
class Gadget
constructor: (@name) ->
sell: =>
"Buy #{@name}"
class IPhone extends Gadget
constructor: -> super("iphone")
nosell: =>
"Don't #{@sell()}"
iphone = new IPhone
iphone.nosell() #=> Don't Buy iphone
使用extends關鍵字可以繼承父類中的所有實例屬性,比如sell
super方法可以調用父類的同名方法
如果不覆蓋constructor,則她被子類默認調用
來看一下對應的js代碼,這有一些復雜,我們把和上邊類定義中重復的地方去掉,只留下繼承的實現(xiàn)部分
var Gadget, IPhone,
__extends = function(child, parent) {
for (var key in parent) {
if ({}.hasOwnProperty.call(parent, key))
child[key] = parent[key];
}
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
};
IPhone = (function(_super) {
__extends(IPhone, _super);
IPhone.name = 'IPhone';
function IPhone() {
this.nosell = __bind(this.nosell, this);
IPhone.__super__.constructor.call(this, "iphone");
}
IPhone.prototype.nosell = function() {
return "Don't " + (this.sell());
};
return IPhone;
})(Gadget);
這里重點有三個,
__extends函數(shù)使用了代理構造函數(shù)ctor來實現(xiàn)繼承,這是非常普遍的js中對象繼承的實踐模式,進一步解釋一下
使用代理構造函數(shù)的目的是為了避免子類被更改時父類受到影響
使用ctor.prototype = parent.prototype的意義是只繼承定義在prototype上的公用屬性
父類的類成員被直接引用拷貝到子類,而不是原型繼承
super的實現(xiàn)方法是parent.prototype.constructor.call(this)
更多建議: