JavaScript:對象的創建方式

来源:http://www.cnblogs.com/xiaoxiaoyihan/archive/2016/06/13/5569184.html
-Advertisement-
Play Games

通過Object構造函數或對象字面量創建對象時,使用同一個介面創建很多對象時,會產生大量的重覆代碼。為了簡化,引入了工廠模式。 工廠模式 function createPerson(name, age, job) { var obj = new Object(); obj.name = name; ...


通過Object構造函數或對象字面量創建對象時,使用同一個介面創建很多對象時,會產生大量的重覆代碼。為了簡化,引入了工廠模式。

工廠模式

function createPerson(name, age, job) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.job = job;
    obj.sayHello(){
        alert(this.name);
    };
    return obj;
}
var p1 = createPerson("xxyh", 19, "programmer");
var p2 = createPerson("zhangsan", 18, "student");

這種創建對象的方式大大簡化了代碼,然而也存在不足,那就是無法確定對象的類型。為瞭解決這個問題,出現下麵這種模式。

構造函數模式

創建自定義的構造函數,從而定義自定義對象類型的屬性和方法。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        alert(this.name);
    };
}
var p1 = new Person("xxyh", 19, "programmer");
var p2 = new Person("Jack", 18, "student");

上例中,Person()取代了createPerson(),除此之外,還有幾點不同:

  • 沒有顯示地創建對象;
  • 直接將屬性和方法賦值給了this對象
  • 沒有return語句

創建Person對象,必須使用new操作符。分為4個步驟:

  • 創建一個新對象
  • 將構造函數的作用域賦給新對象
  • 執行構造函數中的代碼
  • 返回新對象

p1和p2分別保存著Person的一個實例。

alert(p1.constructor == Person);    // true
alert(p2.constructor == Person);    // true

檢測類型時最好使用instanceof:

alert(p1 instanceof Object);    // true
alert(p1 instanceof Person);    // true
alert(p2 instanceof Object);    // true
alert(p2 instanceof Person);    // true

p1和p2都是Object的實例,因為所有對象均繼承自Object。

2.1將構造函數當作函數

// 當作構造函數使用
var person = new Person("xxyh", 19, "programmer");
person.sayName();    // "xxyh"

// 當作普通函數
Person("zhangsan", 18, "student");    // 添加到window
window.sayName();    // "zhangsan"

// 在另一個對象的作用域中調用
var obj = new Object();
Person.call(obj, "Jack", 29, "manager");
obj.sayName();    // "Jack",obj擁有了所有屬性和方法

2.2構造函數的問題

使用構造函數的問題,就是每個方法都要在每個實例上重新創建一遍。p1和p2都有一個sayName()方法,但是他們不是一個Function的實例。在JavaScript中,函數時對象,因此每定義一個函數,就實例化了一個對象。

構造函數也可以這樣定義:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("alert(this.name)");
}

因此,不同實例上的同名函數時不相等的:

alert(p1.sayName == p2.sayName);    // false

然而,創建兩個同樣功能的Function是多餘的,根本不需要在執行代碼前就把函數綁定到特定對象上面。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName() {
    alert(this.name);
}
var p1 = new Person("xxyh", 19, "programmer");
var p2 = new Person("Jack", 18, "student");

上面將sayName()的定義移到構造函數外部,然後在構造函數內部將屬性sayName設置為全局的sayName函數。這樣,sayName包含了指向函數的指針,p1和p2共用了全局作用域中定義的同一個sayName()函數。

但是,這樣做又出現了新問題:在全局作用域中定義的函數只能被某個對象調用。而且如果對象定義了很多方法,那麼引用類型就失去了封裝性。

原型鏈模式

每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象。這個對象的用途是:包含可以由特定類型的所有實例共用的屬性和方法。prototype是通過調用構造函數而創建的那個對象實例的原型對象。使用原型對象的好處是可以讓所有對象實例共用它所包含的屬性和方法。這就是說不必在構造函數中定義對象實例的信息,而是將這些信息添加到原型對象中。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = 19;
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person1 = new Person();
person1.sayName();  // "xxyh"
var person2 = new Person();
person2.sayName();  // "xxyh"

alert(person1.sayName == person2.sayName);  // true

3.1理解原型對象

只要創建一個新函數,就會為該函數創建一個prototype屬性,這個屬性指向函數的原型對象。在預設情況下,所有原型對象都會自動獲得一個constructor屬性。這個屬性包含一個指向prototype屬性所在函數的指針。Person.prototype.constructor指向Person。

當調用構造函數創建一個實例,實例的內部將包含指向構造函數的原型對象的指針(內部屬性),稱為[[Prototype]]。在Firefox、Safari和Chrome通過_proto_訪問。這個連接存在於實例與構造函數的原型對象之間,而不是存在於實例與構造函數之間。

下圖展示了各個對象之間的關係:

Person.prototype指向了原型對象,而Person.prototype.constructor又指回了Person。原型中除了constructor屬性,還有其他添加的屬性。Person實例中都包含一個內部屬性,該屬性僅僅指向了Person.prototype,它們和構造函數沒有直接關係。

雖然無法訪問[[Prototype]],但是可以通過isPrototypeOf()方法來確定對象之間是否存在這種關係。

alert(Person.prototype.isPrototypeOf(person1));    // true
alert(Person.prototype.isPrototypeOf(person2));    // true

在讀取某個對象的屬性時,都會執行一次搜索,目標是具有給定名字的屬性。搜索首先從對象實例本身開始。搜索首先從對象實例本身出發開始,如果在實例中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找給定名字的屬性。如果在原型對象中找到了這個屬性,則返回屬性的值。

可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值。如果在實例中添加一個與實例原型中的一個屬性同名的屬性,該屬性將會屏蔽原型中的屬性。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "oooo";
alert(person1.name);    // "oooo"
alert(person2.name);    // "xxyh"

上例中,person1中的name屬性屏蔽了原型中的name屬性。

當對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。這也就是說,這個屬性的存在會阻止對原型中那個屬性的訪問。使用delete可以完成刪除實例屬性。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "oooo";
alert(person1.name);    // "oooo"
alert(person2.name);    // "xxyh"

delete person1.name;
alert(person1.name);    // "xxyh"

hasOwnProperty()可以檢測一個屬性是存在於實例中,還是存在於原型中。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty("name"));    // false

person1.name = "oooo";
alert(person1.hasOwnProperty("name"));    // true

下圖展示了不同情況的實現與原型的關係:

3.2原型與in操作符

使用in操作符的方式:單獨使用、在for-in迴圈中使用。在單獨使用時,in操作符會在通過對象能夠訪問給定屬性時返回true,無論該屬性存在於實例中還是原型中。

function Person() {

}
Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person1 = new Person();

alert("name" in person1);   // true

person1.name = "oooo";
alert("name" in person1);   // true

結合前面的hasOwnProperty()特點,可以確定某個屬性是原型中的屬性還是實例中的屬性。如果in操作符返回true而hasOwnProperty返回false,則屬性是原型中的屬性。

function hasPrototypeProperty(object, name) {
    return !object.hasOwnProperty(name)&& (name in object);
}

接下來,看看hasPrototypeProperty()的用法:

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person = new Person();
alert(hasPrototypeProperty(person, "name"));    // true
person.name = "oooo";

alert(hasPrototypeProperty(person, "name"));    // false

在使用for-in迴圈時返回的是所有能夠通過對象訪問的、可枚舉的屬性,包括實例中的屬性和原型中的屬性。屏蔽了原型中不可枚舉數據(即[[Enumerable]]標記為false的屬性)的實例屬性也會在for-in中返回,因為根據規定,開發人員定義的屬性都是可枚舉的。

要取得對象上所有可枚舉的實例屬性,可以使用Object.keys()方法。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var keys = Object.keys(Person.prototype);
alert(keys);            // name, age, job, sayName

var p1 = new Person();
p1.name = "oooo";
p1.age = 15;
var p1_keys = Object.keys(p1);
alert(p1_keys);         // name, age

如果需要得到所有實例屬性,可以使用Object.getOwnPropertyNames()方法

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);    // "constructor,name,age,job,sayName"

3.3更簡單的原型語法

為了精簡輸入,用一個包含所有屬性和方法的對象字面量來重寫整合原型對象。

function Person() {      
}

Person.prototype = {
    name : "xxyh",
    age : 18,
    job : "programmer",
    sayName : function () {
        alert(this.name);
    }
};

上面將Person.prototype設置為等於一個以對象字面量形式創建的新對象。結果相同,但是constructor屬性不在指向Person了。

通過instanceof能返回正確結果,但是constructor無法確定對象的類型:

var boy = new Person();
alert(boy instanceof Object);       // true
alert(boy instanceof Person);       // true
alert(boy.constructor == Person);   // false
alert(boy.constructor == Object);   // true

可以通過下麵的方式設置constructor的值:

function Person() {
}

Person.prototype = {
    constructor : Person,
    name : "xxyh",
    age : 18,
    job : "programmer",
    sayName : function () {
        alert(this.name);
    }
};

3.4原型鏈的動態性

由於在原型中查找值的過程是一次搜索,因此對原型對象所做的任何修改都會反映到實例上。但是如果重寫整個原型對象,結果就不同了。調用構造函數時會為實例添加一個指向最初原型的[[prototype]]指針,而把原型修改為另一個對象就等於切斷了構造函數與最初原型的聯繫。實例中的指針僅指向原型,而不指向構造函數。

function Person() {
}

var boy = new Person();
Person.prototype = {
    constructor : Person,
    name : "xxyh",
    age : 29,
    job : "programmer",
    sayName : function () {
        alert(this.name);
    }
};

boy.sayName();    // 錯誤

具體過程如下:

從上面可以看出,重寫原型對象切斷了現有原型與任何之前已經存在的對象實例之間的聯繫;它們引用的是最初的原型。

3.5原生對象的原型

所有原生引用類型都是在構造函數的原型上定義了方法。通過原生對象的原型,不僅可以取得預設方法,而且可以定義新方法。

String.prototype.startsWith = function (text) {
    return this.indexOf(text) == 0;
};

var msg = "good morning";
alert(msg.startsWith("good"));  // true

3.6原型對象的問題

原型模式存在兩個問題:

  • 在預設情況下都取得相同的屬性值。
  • 原型中的所有屬性是實例共用的

下麵看一個例子:

function Person() {

}

Person.prototype = {
    constructor: Person,
    name: "xxyh",
    age : 18,
    job : "programmer",
    friends:["張三", "李四"],
    sayName: function () {
        alert(this.name);
    }
};

var p1 = new Person();
var p2 = new Person();

p1.friends.push("王五");

alert(p1.friends);                  // 張三,李四,王五
alert(p2.friends);                  // 張三,李四,王五
alert(p1.friends == p2.friends);    // true

上面通過p1.friends添加了一項,由於friends數組存在於Person.prototype中,所以在p2.friends也反映出來了。可是,實例一般都是要有屬於自己的全部屬性的。

組合使用構造函數模式和原型模式

構造函數模式用於定義實例屬性,原型模式用於定義方法和共用的屬性。這樣,每個實例都會有自己的一份實例屬性的副本,但是同時又共用著對方法的引用。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["張三", "李四"];
}
Person.prototype = {
    constructor: Person,
    sayName: function () {
        alert(this.name);
    }
}

var p1 = new Person("蕭蕭弈寒", 18, "programmer");
var p2 = new Person("魁拔", 10, "捉妖");

p1.friends.push("王五");
alert(p1.friends);                  // 張三,李四,王五
alert(p2.friends);                  // 張三,李四
alert(p1.friends == p2.friends);    // false
alert(p1.sayName == p2.sayName);    // true

上例中,實例屬性都是在構造函數中定義的,共用屬性constructor和方法sayName()則是在原型中定義的。p1.friends的修改並不會影響到p2.friends的結果。

動態原型模式

動態原型模式把所有信息都封裝在了構造函數中,而通過在構造函數中初始化原型,又保持了同時使用構造函數和原型的優點。這就是說,可以通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。

 function Person(name, age, job) {
        
    // 屬性
    this.name = name;
    this.age = age;
    this.job = job;
    
    // 方法
    if (typeof this.sayName != "function") {
        Person.prototype.sayName = function () {
            alert(this.name);
        }
    }
}

這裡只在sayName()方法不存在時,才會將它添加到原型中,只會在初次調用構造函數時執行。

寄生構造函數模式

這種模式的思想是創建一個函數,該函數的作用是封裝創建對象的代碼,然後再返回新創建的對象。

function Person(name, age) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function () {
        alert(this.name);
    }
    return obj;
}

var boy = new Person("xxyh", 19, "programmer");
boy.sayName();

需要說明:首先,返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係;構造函數返回的對象與在構造函數外部創建的對象沒有不同。不能依賴instanceof操作符來確定對象類型。

穩妥構造函數模式

穩妥對象指的是沒有公共屬性,而且其方法也不引用this的對象。穩妥構造函數遵循與寄生構造函數類似的模式,但是有兩點不同:

  • 新創建對象的實例方法不引用this;
  • 不使用new操作符調用構造函數

重寫Person構造函數如下:

function Person(name, age, job) {
    var obj = new Object();
    obj.sayName = function () {
        alert(name);
    };

    return obj;
}

與寄生構造函數模式類似,使用穩妥構造函數模式創建的對象與構造函數之間也沒有什麼關係,因此instanceof操作符對這種對象也沒有意義。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 眾所周知在前段的工作中註冊信息或獲取當前手機號信息等需求,我們基本上都要涉及到手機號驗證碼,在網路上搜索的案例雜亂無章,用的時候全是坑,真心不想用了,然後自己寫了一個獲取手機驗證碼的倒計時效果。有需求的伙伴們可以看看怎麼實現的。 如何獲取手機驗證碼? 小月不知道大家是利用什麼平臺去獲取驗證碼的,但是 ...
  • 前段時間做了一個項目,涉及到上傳本地圖片以及預覽的功能,正好之前瞭解過 html5(點擊查看更多關於web前端的有關資源) 可以上傳本地圖片,然後再網上看了一些demo結合自己的需求,終於搞定了。(PS : 不得不承認我這個人有多懶,沒有需求的時候我向來不主動去學習)。移動端完全支持哦!已測試。 下 ...
  • 原型包括三個獨立但相關的訪問器。這三個單詞都是對單詞prototype做了一些變化。 C.prototype用於建立由new C()創建的對象的原型 Object.getPrototypeOf(obj)是ES5中用來獲取obj對象的原型對象的標準方法 obj.__proto__是獲取obj對象... ...
  • .bottomTable{ background-color: rgb(249,249,249); z-index:99999999; position:fixed; bottom:0; left:0; width:100%; _position:absolute; /* for IE6 */ /* ...
  • javascript的列印輸出document.write() 1 document.write(hello world !); ...
  • 效果展示 http://hovertree.com/texiao/jquery/77/看慣了左右切換的幻燈片,何問起向您推薦一個新穎的,旋轉切換,通過點擊按鈕的相應區域可以使幻燈片以旋轉的方式來切換圖片,非常有創意。點擊哪個顏色就切換到那個顏色,還帶音效。背景圖可以自定義。進入源碼下載頁面 http ...
  • React作為目前最流行的前端框架之一,其受歡迎程度不容小覷,從這門框架上我們可以學到許多其他前端框架所缺失的東西,也是其創新性所在的地方,比如虛擬DOM、JSX等。那麼接下來我們就來學習一下這門框架是如何構建起一個單頁應用的。 前言 首先在學習這門框架前,你需要對以下知識有所瞭解: 原生JS基礎 ...
  • 在mvc/mvvm類框架出現之前,開發者通常需要手動更新html並維護html與數據之間的關係。隨著mvc思想在前端社區的普及和發展,view層和model層的解耦和分離機制已經是各框架的標配了。令人欣喜的是,angular2在現有各框架的理論基礎上對數據綁定重新進行了抽象,在架構上進行了革新,很有 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...