您好,登錄后才能下訂單哦!
前言
俗話說“在js語言中,一切都對象”,而且創建對象的方式也有很多種,所以今天我們做一下梳理
最簡單的方式
JavaScript創建對象最簡單的方式是:對象字面量形式或使用Object構造函數
對象字面量形式
var person = new Object(); person.name = "jack"; person.sayName = function () { alert(this.name) }
使用Object構造函數
var person = { name: "jack"; sayName: function () { alert(this.name) } }
明顯缺點:創建多個對象時,會出現代碼重復,于是乎,‘工廠模式'應運而生
工廠模式
通俗一點來理解工廠模式,工廠:“我創建一個對象,創建的過程全由我來負責,但任務完成后,就沒我什么事兒了啊O(∩_∩)O哈哈~”
function createPerson (name) { var o = new Object(); o.name = name; o.sayName = function () { alert(this.name) } return o } var p1 = new createPerson("jack");
明顯缺點:所有的對象實例都是`Object`類型,幾乎類型區分可言啊!你說無法區分類型,就無法區分啊,我偏不信!那咱們就來看代碼吧:
var p1 = new createPerson("jack"); var p2 = new createPerson("lucy"); console.log(p1 instanceof Object); //true console.log(p2 instanceof Object); //true
你看,是不是這個理兒;所以為了解決這個問題,我們采用‘構造函數模式'
構造函數模式
構造函數模式,就是這個函數我只管創建某個類型的對象實例,其他的我一概不管(注意到沒有,這里已經有點類型的概念了,感覺就像是在搞小團體嘛)
function Person (name) { this.name = name; this.sayName = function () { alert(this.name) } } function Animal (name) { this.name = name; this.sayName = function () { alert(this.name) } } var p1 = new Person("jack") p1.sayName() //"jack" var a1 = new Animal("doudou") a1.sayName() //"doudou" console.log(p1 instanceof Person) //true console.log(a1 instanceof Animal) //true console.log(p1 instanceof Animal) //false(p1顯然不是Animal類型,所以是false) console.log(a1 instanceof Person) //false(a1也顯然不是Person類型,所以同樣是false)
上面這段代碼證明:構造函數模式的確可以做到對象類型的區分。那么該模式是不是已經完美了呢,然而并不是,我們來一起看看下面的代碼:
//接著上面的代碼 console.log(p1.sayName === a1.sayName) //false
發現問題了嗎?`p1`的`sayName`竟然和`a1`的`sayName`不是同一個,這說明什么?說明‘構造函數模式'根本就沒有‘公用'的概念,創建的每個對象實例都有自己的一套屬性和方法,‘屬性是私有的',這個我們可以理解,但方法你都要自己搞一套,這就有點沒必要了
明顯缺點:上面已經描述了,為了解決這個問題,又出現了一種新模式‘原型模式',該模式簡直就是一個階段性的跳躍,下面我們來看分一下‘原型模式'
原型模式
這里要記住一句話:構造函數中的屬性和方法在每個對象實例之間都不是共享的,都是各自搞一套;而要想實現共享,就要將屬性和方法存到構造函數的原型中。這句話什么意思呢?下面我們來詳細解釋
當建立一個構造函數時(普通函數亦然),會自動生成一個`prototype`(原型),構造函數與`prototype`是一對一的關系,并且此時`prototype`中只有一個`constructor`屬性(哪有,明明還有一個`__proto__`呢,這個我們先不在此討論,后面會有解釋)
這個`constructor`是什么?它是一個類似于指針的引用,指向該`prototype`的構造函數,并且該指針在默認的情況下是一定存在的
console.log(Person.prototype.constructor === Person) //true
剛才說過`prototype`是`自動生成`的,其實還有另外一種手動方式來生成`prototype`:
function Person (name) { this.name = name } Person.prototype = { //constructor: Person, age: 30 } console.log(Person.prototype) //Object {age: 30} console.log(Person.prototype.constructor === Person) //false
Tips:為了證明的確可以為構造函數手動創建`prototype`,這里給`prototype`加了`name`屬性。
可能你已經注意到了一個問題,這行代碼:
console.log(Person.prototype.constructor === Person) //false
結果為什么是`false`啊?大哥,剛才的`prototype`是默認生成的,然后我們又用了另外一種方式:手動設置。具體分析一下手動設置的原理:
1.構造函數的`prototype`其實也是一個對象
2.當我們這樣設置`prototype`時,其實已經將原先`Person.prototype`給切斷了,然后又重新引用了另外一個對象
3.此時構造函數可以找到`prototype`,但`prototype`找不到構造函數了
Person.prototype = { //constructor: Person, // 因為constructor屬性,我沒聲明啊,prototype就是利用它來找到構造函數的,你竟然忘了聲明 age: 30 }
4.所以,要想顯示手動設置構造函數的原型,又不失去它們之間的聯系,我們就要這樣:
function Person (name) { this.name = name } Person.prototype = { constructor: Person, //constructor一定不要忘了!! age: 30 }
畫外音:“說到這里,你還沒有講原型模式是如何實現屬性與方法的共享啊”,不要急,馬上開始:
對象實例-構造函數-原型,三者是什么樣的關系呢?
看明白這張圖的意思嗎?
1.當對象實例訪問一個屬性時(方法依然),如果它自身沒有該屬性,那么它就會通過`__proto__`這條鏈去構造函數的`prototype`上尋找
2.構造函數與原型是一對一的關系,與對象實例是一對多的關系,而并不是每創建一個對象實例,就相應的生成一個`prototype`
這就是原型模式的核心所在,結論:在原型上聲明屬性或方法,可以讓對象實例之間共用它們
然后原型模式就是完美的嗎?并不是,它有以下兩個主要問題:
問題1:如果對象實例有與原型上重名的屬性或方法,那么,當訪問該屬性或方法時,實例上的會屏蔽原型上的
function Person (name) { this.name = name } Person.prototype = { constructor: Person, name: 'lucy' } var p1 = new Person('jack'); console.log(p1.name); //jack
問題2:由于實例間是共享原型上的屬性和方法的,所以當其中一個對象實例修改原型上的屬性(基本值,非引用類型值或方法時,其他實例也會受到影響
原因就是,當實例自身的基本值屬性與原型上的重名時,實例就會創建該屬性,留著今后自己使用,而原型上的屬性不會被修改;但如果屬性是引用類型值,如:`Array`、`Object`,當發生重名時,實例是不會拷貝一份新的留給自己使用的,還是堅持實例間共享,所以就會出現上圖中的情況
以上兩個問題就是原型模式的明顯缺點,為了改掉這些缺點,我們一般會采用一種組合模式“組合使用構造函數模式和原型模式”,其實在原型模式這一節,該模式已經有所應用了
組合使用構造函數模式和原型模式
這種模式可謂是集構造函數模式和原型模式之所長,用構造函數模式來定義對象實例的屬性或方法,而共享的屬性或方法就交給原型模式
function Person (name) { this.name = name //實例的屬性,在構造函數中聲明 } Person.prototype = { constructor: Person, sayName: function () { //共享的方法存在原型中 alert(this.name) } }
注:此模式目前是ECMAScript中使用最廣泛、認同度最高的一種創建自定義類型的方法
-----------------
下面要介紹的幾個模式是針對不同場景的,而不是說`組合使用構造函數模式和原型模式`有什么缺點,又用這幾個模式來彌補,不是這樣的
動態原型模式
特點:共享的方法是在構造函數中檢測并聲明的,原型并沒有被顯示創建
function Person (name) { this.name = name; if (typeof this.sayName !== 'function') { //檢查方法是否存在 console.log('sayName方法不存在') Person.prototype.sayName = function () { alert(this.name) } } else { console.log('sayName方法已存在') } } var p1 = new Person('jack'); //'sayName方法不存在' p1.sayName(); //因為sayName不存在,我們來創建它,所以這里輸出'jack' var p2 = new Person('lucy'); //'sayName方法已存在' p2.sayName(); //這時sayName已存在,所以輸出'lucy'
當`Person`構造函數第一次被調用時,`Person.prototype`上就會被添加`sayName`方法;《Javascript高級程序設計》一書說到:使用動態原型模式時,不能使用對象字面量重寫原型。我們來理解一下:
分析:
1.`p1`實例創建,此時原型沒有`sayName`方法,那我們就為原型添加一個
2.隨后,我們以字面量的形式重寫了原型,這時舊的原型并沒有被銷毀,而且它和`p1`還保持著聯系
3.之后的實例,也就是這里的`p2`,都是與新原型保持聯系;所以`p1`、`p2`有各自的構造器原型,即使它們的構造器是同一個
所以切記:當我們采用動態原型模式時,千萬不要以字面量的形式重寫原型
寄生構造函數模式
了解此模式之前,我們先來想一個問題:構造函數為什么要用`new`關鍵字調用?代碼說話:
我們發現什么?如果不是`new`方法調用構造函數,那么就要顯式的`return`,否則構造函數就不會有返回值;但如果使用`new`,那就沒有這個問題了
下面我們再來看寄生構造函數模式:
function Person (name) { var o = new Object(); o.name = name; o.sayName = function () { alert(this.name) }; return o } var p1 = new Person('jack'); //與工廠模式唯一不同之處:使用new調用 p1.sayName(); //jack
其實new不new都無所謂,因為我們已經顯式的return o
那么寄生構造函數模式到底有什么應用場景呢?據《javascript高級程序設計》一書記載,舉例:如果我們想創建一個具有額外方法的特殊數組,那么我們可以這樣做:
function SpecialArray () { var values = new Array(); Array.prototype.push.apply(values,arguments); values.toPipedString = function () { return this.join('|') } return values } var colors = new SpecialArray('red','blue','green'); alert(colors.toPipedString()) //'red|blue|green'
最后重要的一點:該模式和構造函數和原型無緣,也就是不能區分實例類型,因為該模式生成的實例,它的構造函數都是Object,原型都是Object.prototype
穩妥構造函數模式
該模式與寄生構造函數相比,主要有兩點不同:
1.創建對象實例的方法不引用this
2.不使用new操作符調用構造函數
按照穩妥構造函數的要求,可以將前面的Person構造函數重寫如下:
function Person (name) { var o = new Object(); o.sayName = function () { alert(name) //這里其實涉及到了閉包的知識,因此產生了私有屬性的概念 } return o }
此模式最適合在一些安全的環境中(這些環境中會禁止使用this和new),同理,此模式與構造函數和原型也無緣
結語
以上就是對js中創建對象的方式的總結,希望對大家有所幫助
這篇基于JS對象創建常用方式及原理分析就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。