JS構造函數內的方法與構造函數prototype屬性上方法的對比


本文的目的是讓大家理解什么情況下把函數的方法寫在JavaScript的構造函數上,什么時候把方法寫在函數的 prototype 屬性上;以及這樣做的好處.

為了閱讀方便,我們約定一下:把方法寫在構造函數內的情況我們簡稱為 函數內方法 ,把方法寫在 prototype 屬性上的情況我們簡稱為 prototype上的方法

首先我們先了解一下這篇文章的重點:

  • 函數內的方法:使用函數內的方法我們可以 訪問到函數內部的私有變量 ,如果我們通過構造函數 new 出來的對象需要我們操作構造函數內部的私有變量的話,

    我們這個時候就要考慮使用函數內的方法.

  • prototype上的方法:當我們需要 通過一個函數創建大量的對象 ,並且這些對象還都有許多的方法的時候;這時我們就要考慮在函數的 prototype 上添加這些方法.

    這種情況下我們代碼的 內存占用 就比較小.

  • 在實際的應用中,這兩種方法往往是結合使用的;所以我們要首先了解我們需要的是什么,然后再去選擇如何使用.

我們還是根據下面的代碼來說明一下這些要點吧,下面是 代碼部分 :

// 構造函數A
function A(name) {
    this.name = name || 'a';
    this.sayHello = function() {
        console.log('Hello, my name is: ' + this.name);
    }
}

// 構造函數B
function B(name) {
    this.name = name || 'b';
}
B.prototype.sayHello = function() {
    console.log('Hello, my name is: ' + this.name);
};

var a1 = new A('a1');
var a2 = new A('a2');
a1.sayHello();
a2.sayHello();

var b1 = new B('b1');
var b2 = new B('b2');
b1.sayHello();
b2.sayHello();

我們首先寫了兩個構造函數,第一個是 A ,這個構造函數里面包含了一個方法 sayHello ;第二個是構造函數 B ,我們把那個方法 sayHello 寫在了構造函數 B 的 prototype 屬性上面.

 

需要指出的是,通過這兩個構造函數 new 出來的對象具有一樣的屬性和方法,但是它們的區別我們可以通過下面的一個圖來說明:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

我們通過使用構造函數 A 創建了兩個對象,分別是 a1 , a2 ;通過構造函數 B 創建了兩個對象 b1 , b2 ;我們可以發現 b1 , b2 這兩個對象的那個 sayHello 方法都是指向了它們的構造函數的 prototype 屬性的 sayHello 方法.而 a1 , a2 都是在自己內部定義了這個方法.

定義在構造函數內部的方法,會在它的每一個實例上都克隆這個方法;定義在構造函數的 prototype 屬性上的方法會讓它的所有示例都共享這個方法,但是不會在每個實例的內部重新定義這個方法 .如果我們的應用需要創建很多新的對象,並且這些對象還有許多的方法,為了節省內存,我們建議把這些方法都定義在構造函數的 prototype 屬性上 當然,在某些情況下,我們需要將某些方法定義在構造函數中,這種情況一般是因為我們需要訪問構造函數內部的私有變量 .

下面我們舉一個兩者結合的例子,代碼如下:

function Person(name, family) {
    this.name = name;
    this.family = family;
    
    var records = [{type: "in", amount: 0}];

    this.addTransaction = function(trans) {
        if(trans.hasOwnProperty("type") && trans.hasOwnProperty("amount")) {
           records.push(trans);
        }
    }

    this.balance = function() {
       var total = 0;

       records.forEach(function(record) {
           if(record.type === "in") {
             total += record.amount;
           }
           else {
             total -= record.amount;
           }
       });
    
        return total;
    };
};

Person.prototype.getFull = function() {
    return this.name + " " + this.family;
};

Person.prototype.getProfile = function() {
     return this.getFull() + ", total balance: " + this.balance();
};

在上面的代碼中,我們定義了一個 Person 構造函數;這個函數有一個內部的私有變量 records ,這個變量我們是不希望通過函數內部以外的方法去操作這個變量,所以我們把操作這個變量的方法都寫在了函數的內部.而把一些可以公開的方法寫在了 Person 的 prototype 屬性上,比如方法 getFull 和 getProfile .

把方法寫在構造函數的內部,增加了通過構造函數初始化一個對象的成本,把方法寫在 prototype屬性上就有效的減少了這種成本.你也許會覺得,調用對象上的方法要比調用它的原型鏈上的方法快得多,其實並不是這樣的,如果你的那個對象上面不是有很多的原型的話,它們的速度其實是差不多的。

另外,需要注意的一些地方:

  • 首先如果是在函數的 prototype 屬性上定義方法的話,要牢記一點,如果你改變某個方法,那么由這個構造函數產生的所有對象的那個方法都會被改變.

  • 還有一點就是變量提升的問題,我們可以稍微的看一下下面的代碼:

    func1(); // 這里會報錯,因為在函數執行的時候,func1還沒有被賦值. error: func1 is not a function
    var func1 = function() {
        console.log('func1');
    };
    
    func2(); // 這個會被正確執行,因為函數的聲明會被提升.
    function func2() {
        console.log('func2');
    }

    關於對象序列化的問題.定義在函數的 prototype 上的屬性不會被序列化,可以看下面的代碼:

    function A(name) {
        this.name = name;
    }
    A.prototype.sayWhat = 'say what...';
    
    var a = new A('dreamapple');
    console.log(JSON.stringify(a));

    我們可以看到輸出結果是 {"name":"dreamapple"}

  • 參考的文章或者問答:

技術交流QQ群:15129679


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM