如何在JavaScript中「正確」建立自定義對象?

我不知道最好的方法是建立具備屬性和方法的JavaScript對象。 程序員

我看過一些示例,該人員先使用var self = this ,而後再使用self. 在全部功能中確保範圍始終正確。 編程

而後,我看到了使用.prototype添加屬性的示例,而其餘示例則是內聯的。 閉包

有人能夠給我一個帶有某些屬性和方法的JavaScript對象的正確示例嗎? app


#1樓

當在構造函數調用期間使用關閉「 this」的技巧時,是爲了編寫一個函數,該函數能夠被其餘不想調用對象方法的對象用做回調。 它與「使範圍正確」無關。 編程語言

這是一個普通的JavaScript對象: 函數

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

閱讀Douglas Crockford對JavaScript的評價,可能會使您受益不淺約翰·雷西格John Resig )也很聰明。 祝好運! ui


#2樓

您也可使用結構以這種方式進行操做: this

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

而後 : spa

var counter1 = createCounter();
counter1.increaseBy(4);

#3樓

道格拉斯·克羅克福德(Douglas Crockford)《好零件》中普遍討論了該主題。 他建議避免操做員建立新對象。 相反,他建議建立定製的構造函數。 例如: prototype

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

在Javascript中,函數是對象,可用於與new運算符一塊兒構造對象。 按照約定,打算用做構造函數的函數以大寫字母開頭。 您常常會看到相似的東西:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

若是您在實例化一個新對象時忘記使用new運算符,則獲得的是一個普通的函數調用, 調用綁定到全局對象而不是新對象。


#4樓

我至關頻繁地使用這種模式-我發現它在須要時給了我很大的靈活性。 在使用中,它很是相似於Java樣式的類。

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

這將使用建立時調用的匿名函數,並返回一個新的構造函數。 因爲匿名函數僅被調用一次,所以您能夠在其中建立私有靜態變量(它們在閉包內部,對於該類的其餘成員可見)。 構造函數基本上是一個標準的Javascript對象-您在其中定義私有屬性,並將公共屬性附加this變量。

基本上,這種方法將Crockfordian方法與標準Javascript對象結合在一塊兒,以建立功能更強大的類。

您能夠像使用其餘任何Javascript對象同樣使用它:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

#5樓

有兩種用於在JavaScript中實現類和實例的模型:原型方式和閉包方式。 二者都有優勢和缺點,而且有不少擴展的變體。 許多程序員和庫使用不一樣的方法和類處理實用程序功能來覆蓋該語言的某些較醜陋的部分。

結果是,在混合公司中,您將擁有各類各樣的元類,它們的行爲略有不一樣。 更糟糕的是,大多數JavaScript教程材料都很糟糕,而且在某種程度上折衷以覆蓋全部基礎,使您很是困惑。 (可能做者也感到困惑。JavaScript的對象模型與大多數編程語言有很大不一樣,而且在許多地方都設計得很差。)

讓咱們從原型方式開始。 這是您能夠得到的最多JavaScript原生語言:最少的開銷代碼,instanceof將與此類對象的實例一塊兒使用。

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

咱們能夠向new Shape建立的實例中添加方法,方法是將其寫入此構造函數的prototype查找中:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

如今能夠對其進行子類化,您最多能夠調用JavaScript進行子類化的內容。 爲此,咱們徹底替換了怪異的魔術prototype屬性:

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

在向其中添加方法以前:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

這個示例將起做用,而且在許多教程中您將看到相似的代碼。 可是,夥計, new Shape()很醜陋:即便沒有建立實際的Shape,咱們也正在實例化基類。 因爲JavaScript很是草率,所以它能夠工做在這種簡單狀況下:它容許傳入零個參數,在這種狀況下, xy變爲undefined ,並分配給原型的this.xthis.y 若是構造函數執行任何更復雜的操做,則它的外觀會平坦。

所以,咱們須要作的是找到一種建立原型對象的方法,該對象在類級別包含咱們想要的方法和其餘成員,而無需調用基類的構造函數。 爲此,咱們將不得不開始編寫輔助代碼。 這是我所知道的最簡單的方法:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

這會將基類原型中的成員轉移到一個新的構造函數,該函數不執行任何操做,而後使用該構造函數。 如今咱們能夠簡單地寫:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

而不是new Shape()錯誤。 如今,對於構建類,咱們有了一組可接受的原語。

在此模型下,咱們能夠考慮一些改進和擴展。 例如,這裏是語法糖版本:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

兩種版本都具備沒法繼承構造函數的缺點,就像許多語言同樣。 所以,即便您的子類在構造過程當中未添加任何內容,它也必須記住使用基本所需的任何參數來調用基本構造函數。 這可使用apply稍微自動化,可是仍然須要寫出:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

所以,一個常見的擴展是將初始化內容分解爲本身的函數,而不是構造函數自己。 而後,該函數能夠從基礎繼承而來:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

如今,每一個類都有相同的構造函數樣板。 也許咱們能夠將其移到其本身的幫助器函數中,這樣就沒必要繼續鍵入它,例如,代替Function.prototype.subclass ,將其舍入並讓基類的Function吐出子類:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

...雖然看起來語法稍微有些笨拙,但開始看起來更像其餘語言。 若是願意,您能夠添加一些其餘功能。 也許您想讓makeSubclass記住並記住一個類名,並使用它提供默認的toString 。 也許您想讓構造函數檢測在沒有new運算符的狀況下意外調用了它(不然一般會致使很是煩人的調試):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

也許您想傳遞全部新成員並將makeSubclass添加到原型中,以避免您不得不編寫Class.prototype... 許多類系統都這樣作,例如:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

在對象系統中,您可能會認爲有許多潛在的功能是理想的,沒有人真正贊成一個特定的公式。


而後是封閉方式 。 經過徹底不使用繼承,避免了JavaScript基於原型的繼承問題。 代替:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

如今,每一個Shape實例都將擁有其本身的toString方法(以及咱們添加的任何其餘方法或其餘類成員)的副本。

每一個實例都有本身的每一個類成員的副本的很差的地方是它的效率較低。 若是您要處理大量的子類實例,那麼原型繼承可能會爲您提供更好的服務。 如您所見,調用基類的方法也有點煩人:咱們必須記住該方法在子類構造函數覆蓋它以前就被忘了。

[並且由於這裏沒有繼承,因此instanceof運算符將沒法工做; 若是須要,您將必須提供本身的類嗅探機制。 儘管您能夠用與原型繼承相似的方式擺弄原型對象,但這有點棘手,並且僅僅爲了使instanceof正常工做也不值得。]

每一個實例都有本身的方法的好處是,該方法能夠綁定到擁有它的特定實例。 這是由於JavaScript的結合難以想象的方式有用的this在方法調用,其中有結果,若是你從脫離其全部者的方法:

var ts= mycircle.toString;
alert(ts());

那麼this方法內部的對象將不是預期的Circle實例(它其實是全局window對象,從而致使普遍的調試麻煩)。 實際上,一般在採用方法並將其分配給setTimeoutonclickEventListener時一般會發生這種狀況。

使用原型方法,您必須爲每一個此類分配包括一個閉包:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

或者,未來(或如今,若是您破解Function.prototype),也可使用function.bind()來作到這一點:

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

若是您的實例是經過閉包方式完成的,則綁定是經過實例變量的閉包免費完成的(一般稱爲thatself ,但我我的不建議後者,由於self在JavaScript中已經具備另外一種不一樣的含義)。 可是,您沒有在上面的代碼段中免費獲取參數1, 1所以,若是須要這樣作,您仍然須要另外一個閉包或bind()

閉包方法也有不少變體。 您可能更願意忽略this徹底,建立一個新的that和返回它,而不是使用new操做:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

哪一種方法合適? 都。 哪一個是「最佳」? 那要看你的狀況了。 FWIW當我作大量面向對象的事情時,我傾向於爲真正的JavaScript繼承建立原型,併爲簡單的一次性頁面效果而關閉。

可是,這兩種方式對於大多數程序員來講都是違反直覺的。 二者都有許多潛在的混亂變化。 若是您使用其餘人的代碼/庫,則您將同時遇到這兩種狀況(以及許多中間方案和一般不完善的方案)。 沒有一個廣泛接受的答案。 歡迎來到JavaScript對象的美好世界。

[這是「爲何JavaScript不是我最喜歡的編程語言」的第94部分。]

相關文章
相關標籤/搜索