JS對象創建常用方式及原理分析

来源:http://www.cnblogs.com/sampapa/archive/2017/06/27/7086457.html
-Advertisement-
Play Games

此文章是稍早前寫的,本次屬於文章遷移@2017.06.27 前言 俗話說“在js語言中,一切都對象”,而且創建對象的方式也有很多種,所以今天我們做一下梳理 最簡單的方式 JavaScript創建對象最簡單的方式是:對象字面量形式或使用Object構造函數 對象字面量形式 使用Object構造函數 明 ...


====此文章是稍早前寫的,本次屬於文章遷移@2017.06.27====

前言

俗話說“在js語言中,一切都對象”,而且創建對象的方式也有很多種,所以今天我們做一下梳理

最簡單的方式

JavaScript創建對象最簡單的方式是:對象字面量形式或使用Object構造函數

對象字面量形式

1 var person = new Object();
2   person.name = "jack";
3   person.sayName = function () {
4   alert(this.name)
5 }

使用Object構造函數

1 var person = {
2   name: "jack";
3   sayName: function () {
4     alert(this.name)
5   }
6 }

明顯缺點:創建多個對象時,會出現代碼重覆,於是乎,‘工廠模式’應運而生

工廠模式

通俗一點來理解工廠模式,工廠:“我創建一個對象,創建的過程全由我來負責,但任務完成後,就沒我什麼事兒了啊O(∩_∩)O哈哈~”

 1 function createPerson (name) {
 2   var o = new Object();
 3   o.name = name;
 4   o.sayName = function () {
 5     alert(this.name)
 6   }
 7   return o
 8 }
 9 
10 var p1 = new createPerson("jack");

明顯缺點:所有的對象實例都是`Object`類型,幾乎類型區分可言啊!你說無法區分類型,就無法區分啊,我偏不信!那咱們就來看代碼吧:

1 var p1 = new createPerson("jack");
2 var p2 = new createPerson("lucy");
3 
4 console.log(p1 instanceof Object); //true
5 console.log(p2 instanceof Object); //true

你看,是不是這個理兒;所以為瞭解決這個問題,我們採用‘構造函數模式’

構造函數模式

構造函數模式,就是這個函數我只管創建某個類型的對象實例,其他的我一概不管(註意到沒有,這裡已經有點類型的概念了,感覺就像是在搞小團體嘛)

 1 function Person (name) {
 2   this.name = name;
 3   this.sayName = function () {
 4     alert(this.name)
 5   }
 6 }
 7 
 8 function Animal (name) {
 9   this.name = name;
10   this.sayName = function () {
11     alert(this.name)
12   }
13 }
14 
15 var p1 = new Person("jack")
16 p1.sayName() //"jack"
17 
18 var a1 = new Animal("doudou")
19 a1.sayName() //"doudou"
20 
21 console.log(p1 instanceof Person) //true
22 console.log(a1 instanceof Animal) //true
23 console.log(p1 instanceof Animal) //false(p1顯然不是Animal類型,所以是false)
24 console.log(a1 instanceof Person) //false(a1也顯然不是Person類型,所以同樣是false)

上面這段代碼證明:構造函數模式的確可以做到對象類型的區分。那麼該模式是不是已經完美了呢,然而並不是,我們來一起看看下麵的代碼:

1 //接著上面的代碼
2 console.log(p1.sayName === a1.sayName) //false

發現問題了嗎?`p1`的`sayName`竟然和`a1`的`sayName`不是同一個,這說明什麼?說明‘構造函數模式’根本就沒有‘公用’的概念,創建的每個對象實例都有自己的一套屬性和方法,‘屬性是私有的’,這個我們可以理解,但方法你都要自己搞一套,這就有點沒必要了
明顯缺點:上面已經描述了,為瞭解決這個問題,又出現了一種新模式‘原型模式’,該模式簡直就是一個階段性的跳躍,下麵我們來看分一下‘原型模式’

原型模式

這裡要記住一句話:構造函數中的屬性和方法在每個對象實例之間都不是共用的,都是各自搞一套;而要想實現共用,就要將屬性和方法存到構造函數的原型中。這句話什麼意思呢?下麵我們來詳細解釋
當建立一個構造函數時(普通函數亦然),會自動生成一個`prototype`(原型),構造函數與`prototype`是一對一的關係,並且此時`prototype`中只有一個`constructor`屬性(哪有,明明還有一個`__proto__`呢,這個我們先不在此討論,後面會有解釋)

這個`constructor`是什麼?它是一個類似於指針的引用,指向該`prototype`的構造函數,並且該指針在預設的情況下是一定存在的

1 console.log(Person.prototype.constructor === Person) //true

剛纔說過`prototype`是`自動生成`的,其實還有另外一種手動方式來生成`prototype`:

1 function Person (name) {
2   this.name = name
3 }
4 Person.prototype = {
5   //constructor: Person,
6   age: 30
7 }
8 console.log(Person.prototype) //Object {age: 30}
9 console.log(Person.prototype.constructor === Person) //false

Tips:為了證明的確可以為構造函數手動創建`prototype`,這裡給`prototype`加了`name`屬性。
可能你已經註意到了一個問題,這行代碼:

1 console.log(Person.prototype.constructor === Person) //false

結果為什麼是`false`啊?大哥,剛纔的`prototype`是預設生成的,然後我們又用了另外一種方式:手動設置。具體分析一下手動設置的原理:
1.構造函數的`prototype`其實也是一個對象

2.當我們這樣設置`prototype`時,其實已經將原先`Person.prototype`給切斷了,然後又重新引用了另外一個對象

3.此時構造函數可以找到`prototype`,但`prototype`找不到構造函數了

1 Person.prototype = {
2   //constructor: Person, // 因為constructor屬性,我沒聲明啊,prototype就是利用它來找到構造函數的,你竟然忘了聲明
3   age: 30
4 }

4.所以,要想顯示手動設置構造函數的原型,又不失去它們之間的聯繫,我們就要這樣:

1 function Person (name) {
2   this.name = name
3 }
4 Person.prototype = {
5   constructor: Person, //constructor一定不要忘了!!
6   age: 30
7 }

畫外音:“說到這裡,你還沒有講原型模式是如何實現屬性與方法的共用啊”,不要急,馬上開始:

對象實例-構造函數-原型,三者是什麼樣的關係呢?

看明白這張圖的意思嗎?
1.當對象實例訪問一個屬性時(方法依然),如果它自身沒有該屬性,那麼它就會通過`__proto__`這條鏈去構造函數的`prototype`上尋找
2.構造函數與原型是一對一的關係,與對象實例是一對多的關係,而並不是每創建一個對象實例,就相應的生成一個`prototype`
這就是原型模式的核心所在,結論:在原型上聲明屬性或方法,可以讓對象實例之間共用它們

然後原型模式就是完美的嗎?並不是,它有以下兩個主要問題:
問題1:如果對象實例有與原型上重名的屬性或方法,那麼,當訪問該屬性或方法時,實例上的會屏蔽原型上的

1 function Person (name) {
2   this.name = name
3 }
4 Person.prototype = {
5   constructor: Person,
6   name: 'lucy'
7 }
8 var p1 = new Person('jack');
9 console.log(p1.name); //jack

問題2:由於實例間是共用原型上的屬性和方法的,所以當其中一個對象實例修改原型上的屬性(基本值,非引用類型值或方法時,其他實例也會受到影響


原因就是,當實例自身的基本值屬性與原型上的重名時,實例就會創建該屬性,留著今後自己使用,而原型上的屬性不會被修改;但如果屬性是引用類型值,如:`Array`、`Object`,當發生重名時,實例是不會拷貝一份新的留給自己使用的,還是堅持實例間共用,所以就會出現上圖中的情況

以上兩個問題就是原型模式的明顯缺點,為了改掉這些缺點,我們一般會採用一種組合模式“組合使用構造函數模式和原型模式”,其實在原型模式這一節,該模式已經有所應用了

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

這種模式可謂是集構造函數模式和原型模式之所長,用構造函數模式來定義對象實例的屬性或方法,而共用的屬性或方法就交給原型模式

 1 function Person (name) {
 2   this.name = name //實例的屬性,在構造函數中聲明
 3 }
 4 
 5 Person.prototype = {
 6   constructor: Person,
 7   sayName: function () { //共用的方法存在原型中
 8     alert(this.name)
 9   }
10 }

註:此模式目前是ECMAScript中使用最廣泛、認同度最高的一種創建自定義類型的方法

-----------------

下麵要介紹的幾個模式是針對不同場景的,而不是說`組合使用構造函數模式和原型模式`有什麼缺點,又用這幾個模式來彌補,不是這樣的

動態原型模式

特點:共用的方法是在構造函數中檢測並聲明的,原型並沒有被顯示創建

 1 function Person (name) {
 2   this.name = name;
 3   if (typeof this.sayName !== 'function') { //檢查方法是否存在
 4     console.log('sayName方法不存在')
 5     Person.prototype.sayName = function () {
 6       alert(this.name)
 7     }
 8   } else {
 9     console.log('sayName方法已存在')
10   }
11 }
12 
13 var p1 = new Person('jack'); //'sayName方法不存在'
14 p1.sayName(); //因為sayName不存在,我們來創建它,所以這裡輸出'jack'
15 var p2 = new Person('lucy'); //'sayName方法已存在'
16 p2.sayName(); //這時sayName已存在,所以輸出'lucy'

當`Person`構造函數第一次被調用時,`Person.prototype`上就會被添加`sayName`方法;《Javascript高級程式設計》一書說到:使用動態原型模式時,不能使用對象字面量重寫原型。我們來理解一下:

 

分析:
1.`p1`實例創建,此時原型沒有`sayName`方法,那我們就為原型添加一個
2.隨後,我們以字面量的形式重寫了原型,這時舊的原型並沒有被銷毀,而且它和`p1`還保持著聯繫
3.之後的實例,也就是這裡的`p2`,都是與新原型保持聯繫;所以`p1`、`p2`有各自的構造器原型,即使它們的構造器是同一個

所以切記:當我們採用動態原型模式時,千萬不要以字面量的形式重寫原型

寄生構造函數模式

瞭解此模式之前,我們先來想一個問題:構造函數為什麼要用`new`關鍵字調用?代碼說話:

我們發現什麼?如果不是`new`方法調用構造函數,那麼就要顯式的`return`,否則構造函數就不會有返回值;但如果使用`new`,那就沒有這個問題了

下麵我們再來看寄生構造函數模式:

 1 function Person (name) {
 2   var o = new Object();
 3   o.name = name;
 4   o.sayName = function () {
 5     alert(this.name)
 6   };
 7   return o
 8 }
 9 
10 var p1 = new Person('jack'); //與工廠模式唯一不同之處:使用new調用
11 p1.sayName(); //jack

其實new不new都無所謂,因為我們已經顯式的return o

那麼寄生構造函數模式到底有什麼應用場景呢?據《javascript高級程式設計》一書記載,舉例:如果我們想創建一個具有額外方法的特殊數組,那麼我們可以這樣做:

 1 function SpecialArray () {
 2   var values = new Array();
 3   Array.prototype.push.apply(values,arguments);
 4     values.toPipedString = function () {
 5     return this.join('|')
 6   }
 7   return values
 8 }
 9 
10 var colors = new SpecialArray('red','blue','green');
11 alert(colors.toPipedString()) //'red|blue|green'

最後重要的一點:該模式和構造函數和原型無緣,也就是不能區分實例類型,因為該模式生成的實例,它的構造函數都是Object,原型都是Object.prototype

穩妥構造函數模式

該模式與寄生構造函數相比,主要有兩點不同:
1.創建對象實例的方法不引用this
2.不使用new操作符調用構造函數
按照穩妥構造函數的要求,可以將前面的Person構造函數重寫如下:

1 function Person (name) {
2   var o = new Object();
3   o.sayName = function () {
4     alert(name) //這裡其實涉及到了閉包的知識,因此產生了私有屬性的概念
5   }
6   return o
7 }

此模式最適合在一些安全的環境中(這些環境中會禁止使用this和new),同理,此模式與構造函數和原型也無緣

結語

以上就是對js中創建對象的方式的總結,希望對大家有所幫助


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

-Advertisement-
Play Games
更多相關文章
  • if 函數的實現步驟: function +名字() 指定id , 指定開關(display: none or block) if + else 構成邏輯 控制開關 決定在哪裡安置一個燈泡, 指定一個id給某個標簽 把開關用電線連著燈泡, 安裝開關 #+id名稱{ 屬性1= 賦值, 屬性 2 = 賦... ...
  • 沒有什麼不能用一張圖來解決。 ...
  • 在今天之前,我沒有很系統的看過,學習過vue。也是第一次嘗試用vue寫個小應用 現在開始研究配環境 這邊參考的是https://cn.vuejs.org/v2/guide/installation.html#%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7 ...
  • 說點小案例angular的排序 ...
  • 1. 原型鏈繼承 Child.prototype = new Parent(); 2. call(thisObj, param1, param2,...) 3. apply(thisObj, [param1,param2,...]) 4. 組合繼承(call+原型鏈 / apply+原型鏈) 5.寄 ...
  • [1]引入 [2]display [3]顯式網格 [4]間距 [5]網格線 [6]網格線命名 [7]網格區域命名 [8]隱式網格 [9]隱式命名 [10]網格項目層級 [11]對齊 ...
  • 圓形 circle 矩形 rect 橢圓 ellipse 線 line 折線 polyline 多邊形 polygon 路徑 path 可用於路徑數據的命令 M = moveto L = lineto H = horizontal lineto V = vertical lineto C = cur ...
  • 1.SVG 是用XML格式定義的矢量圖。 2.文件體積小,能夠被大量的壓縮。 3.圖片可無限放大而不失真(矢量圖的基本特征)。 4.在視網膜顯示屏上效果極佳。 5.能夠實現互動和濾鏡效果。 6.svg元素里提供了“width”和“height”兩個屬性來定義SVG圖片的高度和寬度。 7.fill屬性 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...