摘要:這種構(gòu)造函數(shù)與原型混合模式,是目前在中使用最廣泛認(rèn)同度最高的一種創(chuàng)建自定義類型的方法。
JavaScript 創(chuàng)建對象
原文鏈接
亂七八糟的概念總是阻礙我們對知識更進一步的理解,所以我們先來搞清楚幾個概念之間的關(guān)系。
創(chuàng)建一個對象在 JavaScript 中,引用類型的值被稱為對象(或?qū)嵗?/strong>
強調(diào):對象、實例、實例對象、對象實例 等意。
實例 百度百科
What is the difference between an Instance and an Object?
沒對象怎么辦?找一個唄,額,是創(chuàng)建一個。
初學(xué)者最常見到的就是使用這兩種方法來創(chuàng)建單個對象:1. 使用 Object 構(gòu)造函數(shù)創(chuàng)建,2. 使用對象字面量直接創(chuàng)建
其實還可以用以下的方法創(chuàng)建一個對象:
通過構(gòu)造函數(shù)來創(chuàng)建特定類型的對象(見后文構(gòu)造函數(shù)模式)
通過原型創(chuàng)建對象(見后文原型模式)
通過 Object.create() 方法創(chuàng)建【MDN】
// 方法 1 var obj1 = new Object(); // 創(chuàng)建空對象 obj1.name = "percy"; // 為對象添加屬性 obj1.getName = function(){ // 為對象添加方法 return this.name; }; // 方法 2 var obj2 = { name: "percy", getName: function(){ return this.name; } };
使用這兩種方式創(chuàng)建對象有個明顯的缺點:即只創(chuàng)建了一個特定的對象,不便于創(chuàng)建多個擁有相同屬性和方法的不同對象。為了解決這個問題,人們便開始使用工廠模式。
工廠模式(The Factory Pattern)
擴展鏈接:
What is the difference between new Object() and object literal notation?
[Why use {} instead of new Object() and use [] instead of new Array() and true/false instead of new Boolean()?](http://stackoverflow.com/ques...
優(yōu)點:解決了創(chuàng)建多個相似對象的問題
缺點:無法判斷工廠模式創(chuàng)建的對象的具體類型,因為它創(chuàng)建的對象都是 Object 整出來的
工廠模式抽象了創(chuàng)建具體對象的過程
由于 ES6 之前,ECMAScript 沒有類(class)這個概念,所以開發(fā)人員用函數(shù)封裝了以特定接口創(chuàng)建對象的細節(jié)。
ES6 中引入了類(class)這個概念,作為對象的模板。【傳送門】
舉例如下:
function Person(name,age,job){ var obj = new Object(); obj.name = name; obj.age = age; obj.job = job; obj.getName = function(){ return this.name; }; return obj; } var person1 = Person("percy",21,"killer"); var person2 = Person("zyj",20,"queen"); console.log(person1); // Object {name: "percy", age: 21, job: "killer"} console.log(person2); // Object {name: "zyj", age: 20, job: "queen"} console.log(person1.constructor); // function Object() { [native code] } console.log(person1.constructor); // function Object() { [native code] } console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // false構(gòu)造函數(shù)模式(The Constructor Pattern)
優(yōu)點:它可以將它創(chuàng)建的對象標(biāo)識為一種特定的類型
缺點:不同實例無法共享相同的屬性或方法
constructor 屬性始終指向創(chuàng)建當(dāng)前對象的構(gòu)造(初始化)函數(shù)
使用構(gòu)造函數(shù)模式將前面的例子進行重寫如下:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.getName = function(){ return this.name; } } var person1 = new Person("percy",21,"killer"); var person2 = new Person("zyj",20,"queen"); console.log(person1); // Object {name: "percy", age: 21, job: "killer"} console.log(person2); // Object {name: "zyj", age: 20, job: "queen"} console.log(person1.constructor); // function Person() { ... } console.log(person1.constructor); // function Person() { ... } console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // true
要創(chuàng)建 Person 的新實例,必須使用 new 操作符。以這種方式調(diào)用構(gòu)造函數(shù)實際上會經(jīng)歷以下 4 個步驟:
1.創(chuàng)建一個新對象(新實例) 2.將構(gòu)造函數(shù)的作用域賦給新對象(因此 this 就指向了這個對象) 3.執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性和方法) 4.返回新對象
任何函數(shù),只要通過 new 操作符來調(diào)用,那么它就可以作為構(gòu)造函數(shù),否則就和普通函數(shù)沒什么兩樣
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.getName = function(){ return this.name; } } Person("percy",22,"無"); window.getName(); // percy
從這一小節(jié)最開始的代碼中,你可能注意到了,person1 和 person2 這兩個對象擁有相同的方法,但是它們相等嗎?
person1.getName === person2.getName // false
調(diào)用同一個方法,卻聲明了不同的對象,實在是浪費資源,所以就引進了接下來的主角:原型模式。
原型模式(The Prototype Pattern)優(yōu)點:它實現(xiàn)了不同實例可以共享屬性或方法
缺點:它省略了構(gòu)造函數(shù)初始化參數(shù)這一環(huán)節(jié),結(jié)果所有實例在默認(rèn)情況下都取得了相同的屬性值。并且如果如果原型對象中有屬性的值為引用類型的,要是實例重寫了這個屬性,那么所有實例都會使用這個重寫的屬性。
我們創(chuàng)建的每個函數(shù)都有一個 prototype(原型) 屬性,這個屬性是一個指針,指向一個原型對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。
上面的特定類型可以是通過 new Person() 形成的 Person 類型。
好,把上面的例子改寫成原型模式:
function Person(){ } Person.prototype.name = "percy"; Person.prototype.age = 21; Person.prototype.job = "killer"; Person.prototype.getName = function(){ return this.name; }; var person1 = new Person(); var person2 = new Person(); console.log(person1.name); // percy console.log(person2.name); // percy console.log(person1.getName === person2.getName); // true
構(gòu)造函數(shù)的 prototype 屬性指向它的原型對象
所有原型對象都具備一個 constructor 屬性,這個屬性指向包含 prototype 屬性的函數(shù)
[[Prototype]] 是實例指向構(gòu)造函數(shù)的原型對象的指針,目前不是標(biāo)準(zhǔn)的屬性,但 Firefox、Safari 和 Chrome 在每個對象上都支持一個 __proto__ 屬性,用來實現(xiàn) [[Prototype]]。
ECMAScript 5 增加的新方法:Object.getPrototypeOf(),它可以返回 [[Prototype]] 的值,即返回實例對象的原型。
Person.prototype.constructor === Person; // true person1.constructor === Person; // true Object.getPrototypeOf(person1) === Person.prototype; // true
當(dāng)我們訪問一個對象中的屬性時,首先會詢問實例對象中有沒有該屬性,如果沒有則繼續(xù)查找其原型對象有沒有該屬性。所以要是實例對象中定義了與原型對象中相同名字的屬性,則優(yōu)先調(diào)用實例對象中的屬性。
var p1 = new Person(); var p2 = new Person(); p1.name = "zyj"; console.log(p1.name); // zyj console.log(p2.name); // percy
Object.prototype.hasOwnProperty(prop):檢測一個屬性是存在于對象實例中,還是存在于原型中,若存在于實例中,則返回 true,否則返回 false。
var p1 = new Person(); var p2 = new Person(); p1.name = "zyj"; console.log(p1.hasOwnProperty("name")); // true console.log(p2.hasOwnProperty("name")); // false
in 操作符(prop in objectName ):判斷對象實例是否能夠訪問某個屬性(無論這個屬性是自己的還是在原型對象上的),若能訪問則返回 true,否則返回 false。
var p1 = new Person(); var p2 = new Person(); p1.name = "zyj"; console.log("name" in p1); // true console.log("name" in p2); // true
Object.keys(obj):返回對象上所有可枚舉的實例屬性
Object.getOwnPropertyNames(obj):返回對象上的所有實例屬性(不管能不能枚舉)
var p1 = new Person(); var p2 = new Person(); p1.name = "zyj"; p1.age = 22; Object.defineProperty(p1,"age",{ enumerable: false }); // 將 age 設(shè)置為不可枚舉 console.log(Object.keys(p1)); // ["name"] console.log(Object.keys(p2)); // [] console.log(Object.getOwnPropertyNames(p1)); // ["name","age"] console.log(Object.getOwnPropertyNames(p2)); // [] console.log(Object.keys(Person.prototype)); // ["name", "age", "job", "getName"] console.log(Object.getOwnPropertyNames(Person.prototype)); // ["constructor", "name", "age", "job", "getName"]更簡潔的原型語法
也許你已經(jīng)注意到了,這一節(jié)最前面的原型寫法是不是有點啰嗦,為什么每次都要寫一遍 Person.prototype 呢?好,那我們現(xiàn)在用更簡潔的原型語法如下:
function Person(){ } Person.prototype = { name: "percy", age: 21, job: "killer", getName: function(){ return this.name; } };
是不是簡潔了許多?但是這里也出現(xiàn)了一個問題,constructor 屬性不再指向 Person了,而是指向了 Object 構(gòu)造函數(shù)。記得我們在上面提到了 Person.prototype 指向的是一個對象(原型對象),而現(xiàn)在我們完全重寫了這個原型對象,所以這個原型對象的 constructor 指向了最廣泛的 Object。
var p3 = new Person(); console.log(p3 instanceof Person); // true console.log(p3 instanceof Object); // true console.log(Person.prototype.constructor === Person); // false console.log(Person.prototype.constructor === Object); // true
所以改寫上面的代碼,使 constructor 指向 Person:
function Person(){ } Person.prototype = { constructor: Person, name: "percy", age: 21, job: "killer", getName: function(){ return this.name; } };
注意,以這種方式重設(shè)constructor 屬性會導(dǎo)致它的 [[Enumerable]] 特性被設(shè)置為 false,從而 constructor 屬性變得可以枚舉了,但是原生的 constructor 屬性是不可枚舉的,所以我們利用 Object.defineProperty() 再改寫一下代碼:
function Person(){ } Person.prototype = { name: "percy", age: 21, job: "killer", getName: function(){ return this.name; } }; Object.defineProperty(Person.prototype,"constructor",{ enumerable: false, value: Person });
var p3 = new Person(); console.log(p3 instanceof Person); // true console.log(p3 instanceof Object); // true console.log(Person.prototype.constructor === Person); // true console.log(Person.prototype.constructor === Object); // false
重寫原型對象應(yīng)該在創(chuàng)建實例之前完成,否則會出現(xiàn)不可預(yù)知的錯誤
function Person(){ } var p3 = new Person(); Person.prototype = { name: "percy", age: 21, job: "killer", getName: function(){ return this.name; } }; Object.defineProperty(Person.prototype,"constructor",{ enumerable: false, value: Person }); p3.getName(); // 報錯,TypeError: p3.getName is not a function(…)
當(dāng)原型對象中有屬性的值為引用類型時...
function Person(){ } Person.prototype = { name: "percy", age: 21, job: "killer", friends: ["zyj","Shelly","Dj Aligator"], // 添加 getName: function(){ return this.name; } }; Object.defineProperty(Person.prototype,"constructor",{ enumerable: false, value: Person }); var p1 = new Person(); var p2 = new Person(); p1.job = "programmer"; p1.friends.push("Mary","Iris"); console.log(p1.job); // programmer console.log(p2.job); // killer console.log(p1.friends); // ["zyj", "Shelly", "Dj Aligator", "Mary", "Iris"] console.log(p2.friends); // ["zyj", "Shelly", "Dj Aligator", "Mary", "Iris"] console.log(p1.friends === p2.friends); // true console.log(Person.prototype.friends); // ["zyj", "Shelly", "Dj Aligator", "Mary", "Iris"]
看出問題來了嗎?當(dāng)原型對象中有屬性的值為引用類型時,要是一個實例重寫了這個屬性,那么所有的實例都會使用這個重寫后的屬性。要是還不了解的話,可以看看我以前的文章,談的是基本類型和引用類型在內(nèi)存中的存儲方式,以及改變它們的值時,內(nèi)存中是如何變化的。
組合使用構(gòu)造函數(shù)模式和原型模式(Combination Constructor/Prototype Pattern)原理:構(gòu)造函數(shù)模式用于實例自己的屬性,而原型模式用于定義方法和需要共享的屬性。結(jié)果,每個實例都會有自己的一份實例屬性的副本,但同時又共享著對方法的引用,最大限度地節(jié)省了內(nèi)存。
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ["zyj"]; } Person.prototype = { getName: function(){ return this.name; } }; Object.defineProperty(Person.prototype,"constructor",{ enumerable: false, value: Person }); var person1 = new Person("percy","21","killer"); var person2 = new Person("Bob","26","developer"); person1.friends.push("Iris","Alice"); console.log(person1.name); // percy console.log(person2.name); // Bob console.log(person1.friends); // ["zyj", "Iris", "Alice"] console.log(person2.friends); // ["zyj"] console.log(person1.friends === person2.friends); // false console.log(person1.getName === person2.getName); // true
這種構(gòu)造函數(shù)與原型混合模式,是目前在 ECMAScript 中使用最廣泛、認(rèn)同度最高的一種創(chuàng)建自定義類型的方法。
為上面的代碼補一張圖吧 :)!
動態(tài)原型模式(Dynamic Prototype Pattern)原理:將所有信息封裝到構(gòu)造函數(shù)中。
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ["zyj"]; if(typeof this.getName != "function" ){ Person.prototype.getName = function(){ return this.name; }; Person.prototype.getJob = function(){ return this.job; }; } } var person = new Person("percy",21,"programmer"); console.log(person.getName()); // percy console.log(person.getJob()); // programmer
將所有信息封裝到構(gòu)造函數(shù)里,很完美,有木有?
這里使用 if 語句檢查原型方法是否已經(jīng)初始化,從而防止多次初始化原型方法。
這種模式下,不能使用對象自面量重寫原型對象。因為在已經(jīng)創(chuàng)建了實例的情況下再重寫原型對象的話,會切斷現(xiàn)有實例與新原型對象之間的聯(lián)系。
看這里,有更詳細的對上面代碼的解釋,鏈接
寄生構(gòu)造函數(shù)模式(Parasitic Constructor Pattern)似曾相識哈!
一句話闡明:除了使用 new 操作符并把包裝函數(shù)叫做構(gòu)造函數(shù)之外,這個模式跟工廠模式其實是一模一樣的。
function Person(name,age,job){ var obj = new Object(); obj.name = name; obj.age = age; obj.job = job; obj.getName = function(){ return this.name; }; return obj; } var person1 = new Person("percy",21,"killer"); var person2 = new Person("zyj",20,"queen"); person1.getName(); // percy person2.getName(); // zyj
建議在可以使用其他模式的情況下,不要使用這種模式。
穩(wěn)妥構(gòu)造函數(shù)模式(Durable Constructor Pattern)
穩(wěn)妥構(gòu)造函數(shù)遵循與寄生構(gòu)造函數(shù)類似的模式,但是有 2 點不同:
一是新創(chuàng)建對象的實例方法不引用 this
二是不使用 new 操作符調(diào)用構(gòu)造函數(shù)
function Person(name,age,job){ var obj = new Object(); // 可以在這里定義私有變量和函數(shù) obj.getName = function(){ return name; }; return obj; } var person1 = new Person("percy",21,"killer"); var person2 = new Person("zyj",20,"queen"); person1.getName(); // percy person2.getName(); // zyj
注意,在這種模式下創(chuàng)建的對象中,除過調(diào)用 getName() 方法外,沒有其他方法訪問 name 的值。
我想問個問題,最后的這個模式可以用在哪些地方呢?希望有經(jīng)驗的朋友解答一下。
參考資料【書】《JavaScript 高級程序設(shè)計(第三版)》
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/80295.html
摘要:對象在中,除了數(shù)字字符串布爾值這幾個簡單類型外,其他的都是對象。那么在函數(shù)對象中,這兩個屬性的有什么區(qū)別呢表示該函數(shù)對象的原型表示使用來執(zhí)行該函數(shù)時這種函數(shù)一般成為構(gòu)造函數(shù),后面會講解,新創(chuàng)建的對象的原型。這時的函數(shù)通常稱為構(gòu)造函數(shù)。。 本文原發(fā)于我的個人博客,經(jīng)多次修改后發(fā)到sf上。本文仍在不斷修改中,最新版請訪問個人博客。 最近工作一直在用nodejs做開發(fā),有了nodejs,...
摘要:注意句柄棧并不是調(diào)用棧中的一部分,但句柄域卻在棧中。一個依賴于構(gòu)造函數(shù)和析構(gòu)函數(shù)來管理下層對象的生命周期。對象模板用來配置將這個函數(shù)作為構(gòu)造函數(shù)而創(chuàng)建的對象。 如果你已經(jīng)閱讀過了上手指南,那么你已經(jīng)知道了如何作為一個單獨的虛擬機使用 V8 ,并且熟悉了一些 V8 中的關(guān)鍵概念,如句柄,域 和上下文。在本文檔中,還將繼續(xù)深入討論這些概念并且介紹其他一些在你的 C++ 應(yīng)用中使用 V8 的...
摘要:對象的分類內(nèi)置對象原生對象就是語言預(yù)定義的對象,在標(biāo)準(zhǔn)定義,有解釋器引擎提供具體實現(xiàn)宿主對象指的是運行環(huán)境提供的對象。不過類型是中所有類型的父級所有類型的對象都可以使用的屬性和方法,可以通過的構(gòu)造函數(shù)來創(chuàng)建自定義對象。 對象 javaScript中的對象,和其它編程語言中的對象一樣,可以比照現(xiàn)實生活中的對象來理解。在JavaScript中,一個對象可以是一個單獨擁有屬性和類型的實體。和...
摘要:在最開始的時候,原型對象的設(shè)計主要是為了獲取對象的構(gòu)造函數(shù)。同理數(shù)組通過調(diào)用函數(shù)通過調(diào)用原型鏈中描述了原型鏈的概念,并將原型鏈作為實現(xiàn)繼承的主要方法。 對象的創(chuàng)建 在JavaScript中創(chuàng)建一個對象有三種方式。可以通過對象直接量、關(guān)鍵字new和Object.create()函數(shù)來創(chuàng)建對象。 1. 對象直接量 創(chuàng)建對象最直接的方式就是在JavaScript代碼中使用對象直接量。在ES5...
摘要:當(dāng)談到語言與其他編程語言相比時,你可能會聽到一些令人困惑東西,其中之一是工廠函數(shù)和構(gòu)造函數(shù)。好的,讓我們用構(gòu)造函數(shù)做同樣的實驗。當(dāng)我們使用工廠函數(shù)創(chuàng)建對象時,它的指向,而當(dāng)從構(gòu)造函數(shù)創(chuàng)建對象時,它指向它的構(gòu)造函數(shù)原型對象。 showImg(https://segmentfault.com/img/bVbr58T?w=1600&h=900); 當(dāng)談到JavaScript語言與其他編程語言...
摘要:在中函數(shù)是一等對象,它們不被聲明為任何東西的一部分,而所引用的對象稱為函數(shù)上下文并不是由聲明函數(shù)的方式?jīng)Q定的,而是由調(diào)用函數(shù)的方式?jīng)Q定的。更為準(zhǔn)確的表述應(yīng)該為當(dāng)對象充當(dāng)函數(shù)的調(diào)用函數(shù)上下文時,函數(shù)就充當(dāng)了對象的方法。 引言:當(dāng)理解了對象和函數(shù)的基本概念,你可能會發(fā)現(xiàn),在JavaScript中有很多原以為理所當(dāng)然(或盲目接受)的事情開始變得更有意義了。 1.JavaScript...
閱讀 3012·2021-10-27 14:15
閱讀 2999·2021-09-07 10:18
閱讀 1320·2019-08-30 15:53
閱讀 1570·2019-08-26 18:18
閱讀 3373·2019-08-26 12:15
閱讀 3460·2019-08-26 10:43
閱讀 654·2019-08-23 16:43
閱讀 2207·2019-08-23 15:27