Javascript中prototype屬性的詳解

来源:http://www.cnblogs.com/Uncle-Keith/archive/2016/09/02/5834289.html
-Advertisement-
Play Games

在典型的面向對象的語言中,如java,都存在類(class)的概念,類就是對象的模板,對象就是類的實例。但是在Javascript語言體系中,是不存在類(Class)的概念的,javascript中不是基於‘類的’,而是通過構造函數(constructor)和原型鏈(prototype chains ...


 

 

  在典型的面向對象的語言中,如java,都存在類(class)的概念,類就是對象的模板,對象就是類的實例。但是在Javascript語言體系中,是不存在類(Class)的概念的,javascript中不是基於‘類的’,而是通過構造函數(constructor)和原型鏈(prototype chains)實現的。但是在ES6中提供了更接近傳統語言的寫法,引入了Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓原型對象的寫法更加清晰、更像面向對象編程的語法而已。

 

 

  按照我的習慣,在寫文章前我會給出文章目錄。

  以下內容會分為如下小節:

  1.構造函數的簡單介紹

  2.構造函數的缺點

  3.prototype屬性的作用

  4.原型鏈(prototype chain)

  5.constructor屬性

    5.1:constructor屬性的作用

  6.instanceof運算符

 

1.構造函數的簡單介紹

  在我的一篇Javascript 中構造函數與new命令的密切關係文章中,詳細了介紹了構造函數的概念和特點,new命令的原理和用法等,如果對於構造函數不熟悉的同學,可以前往細細品味。以下做一個簡單的回顧。

  所謂構造函數,就是提供了一個生成對象的模板並描述對象的基本結構的函數。一個構造函數,可以生成多個對象,每個對象都有相同的結構。總的來說,構造函數就是對象的模板,對象就是構造函數的實例。

  構造函數的特點有:

    a:構造函數的函數名首字母必須大寫。

    b:內部使用this對象,來指向將要生成的對象實例。

    c:使用new操作符來調用構造函數,並返回對象實例。

  看一個最簡單的一個例子。

1     function Person(){
2         this.name = 'keith';
3     }
4 
5     var boy = new Person();
6     console.log(boy.name);    //'keith'

 

 

2.構造函數的缺點

  所有的實例對象都可以繼承構造函數中的屬性和方法。但是,同一個對象實例之間,無法共用屬性。

 1     function Person(name,height){
 2         this.name=name;
 3         this.height=height;
 4         this.hobby=function(){
 5             return 'watching movies';
 6         }
 7     }
 8 
 9     var boy=new Person('keith',180);
10     var girl=new Person('rascal',153);
11 
12     console.log(boy.name);    //'keith'
13     console.log(girl.name);    //'rascal'
14     console.log(boy.hobby===girl.hobby);  //false

  上面代碼中,一個構造函數Person生成了兩個對象實例boy和girl,並且有兩個屬性和一個方法。但是,它們的hobby方法是不一樣的。也就是說,每當你使用new來調用構造函數放回一個對象實例的時候,都會創建一個hobby方法。這既沒有必要,又浪費資源,因為所有hobby方法都是童顏的行為,完全可以被兩個對象實例共用。

  所以,構造函數的缺點就是:同一個構造函數的對象實例之間無法共用屬性或方法。

 

3.prototype屬性的作用

  為瞭解決構造函數的對象實例之間無法共用屬性的缺點,js提供了prototype屬性。

  js中每個數據類型都是對象(除了null和undefined),而每個對象都繼承自另外一個對象,後者稱為“原型”(prototype)對象,只有null除外,它沒有自己的原型對象。

  原型對象上的所有屬性和方法,都會被對象實例所共用

  通過構造函數生成對象實例時,會將對象實例的原型指向構造函數的prototype屬性。每一個構造函數都有一個prototype屬性,這個屬性就是對象實例的原型對象。

 1     function Person(name,height){
 2         this.name=name;
 3         this.height=height;
 4     }
 5 
 6     Person.prototype.hobby=function(){
 7         return 'watching movies';
 8     }
 9 
10     var boy=new Person('keith',180);
11     var girl=new Person('rascal',153);
12 
13     console.log(boy.name);    //'keith'
14     console.log(girl.name);    //'rascal'
15     console.log(boy.hobby===girl.hobby);  //true

  上面代碼中,如果將hobby方法放在原型對象上,那麼兩個實例對象都共用著同一個方法。我希望大家都能理解的是,對於構造函數來說,prototype是作為構造函數的屬性;對於對象實例來說,prototype是對象實例的原型對象。所以prototype即是屬性,又是對象。

  原型對象的屬性不是對象實例的屬性。對象實例的屬性是繼承自構造函數定義的屬性,因為構造函數內部有一個this關鍵字來指向將要生成的對象實例。對象實例的屬性,其實就是構造函數內部定義的屬性。只要修改原型對象上的屬性和方法,變動就會立刻體現在所有對象實例上。

1     Person.prototype.hobby=function(){
2         return 'swimming';
3     }
4     console.log(boy.hobby===girl.hobby);  //true
5     console.log(boy.hobby());    //'swimming'
6     console.log(girl.hobby());    //'swimming'

  上面代碼中,當修改了原型對象的hobby方法之後,兩個對象實例都發生了變化。這是因為對象實例其實是沒有hobby方法,都是讀取原型對象的hobby方法。也就是說,當某個對象實例沒有該屬性和方法時,就會到原型對象上去查找。如果實例對象自身有某個屬性或方法,就不會去原型對象上查找。

1     boy.hobby=function(){
2         return 'play basketball';
3     }
4     console.log(boy.hobby());    //'play basketball'
5     console.log(girl.hobby());    //'swimming'

  上面代碼中,boy對象實例的hobby方法修改時,就不會在繼承原型對象上的hobby方法了。不過girl仍然會繼承原型對象的方法。

  總結一下:

  a:原型對象的作用,就是定義所有對象實例所共用的屬性和方法。

  b:prototype,對於構造函數來說,它是一個屬性;對於對象實例來說,它是一個原型對象。

  

4.原型鏈(prototype chains)

  對象的屬性和方法,有可能是定義在自身,也有可能是定義在它的原型對象。由於原型對象本身對於對象實例來說也是對象,它也有自己的原型,所以形成了一條原型鏈(prototype chain)。比如,a對象是b對象的原型,b對象是c對象的原型,以此類推。所有一切的對象的原型頂端,都是Object.prototype,即Object構造函數的prototype屬性指向的那個對象。

  當然,Object.prototype對象也有自己的原型對象,那就是沒有任何屬性和方法的null對象,而null對象沒有自己的原型。

1     console.log(Object.getPrototypeOf(Object.prototype));    //null
2     console.log(Person.prototype.isPrototypeOf(boy))    //true

  原型鏈(prototype chain)的特點有:

    a:讀取對象的某個屬性時,JavaScript引擎先尋找對象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype還是找不到,則返回undefined

    b:如果對象自身和它的原型,都定義了一個同名屬性,那麼優先讀取對象自身的屬性,這叫做“覆蓋”(overiding)。

    c:一級級向上在原型鏈尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。如果尋找某個不存在的屬性,將會遍歷整個原型鏈。

  看概念可能比較晦澀,我們來看一個例子。但是理解了概念真的很重要。

1     var arr=[1,2,3];
2     console.log(arr.length);    //3
3     console.log(arr.valueOf())    //[1,2,3]
4     console.log(arr.join('|'))    //1|2|3    

  上面代碼中,定了一個數組arr,數組裡面有三個元素。我們並沒有給數組添加任何屬性和方法,可是卻在調用length,join(),valueOf()時,卻不會報錯。

  length屬性是繼承自Array.prototype的,屬於原型對象上的一個屬性。join方法也是繼承自Array.prototype的,屬於原型對象上的一個方法。這兩個方法是所有數組所共用的。當實例對象上沒有這個length屬性時,就會去原型對象查找。

  valueOf方法是繼承自Object.prototype的。首先,arr數組是沒有valueOf方法的,所以就到原型對象Array.prototype查找。然後,發現Array.prototype對象上沒有valueOf方法。最後,再到它的原型對象Object.prototype查找。

  來看看Array.prototype對象和Object.prototype對象分別有什麼屬性和方法。

1     console.log(Object.getOwnPropertyNames(Array.prototype))
2 //["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push", "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf", "forEach", "map", "filter", "reduce", "reduceRight", "some", "every", "find", "findIndex", "copyWithin", "fill", "entries", "keys", "values", "includes", "constructor", "$set", "$remove"]
3     console.log(Object.getOwnPropertyNames(Object.prototype))
4 // ["toSource", "toString", "toLocaleString", "valueOf", "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor"]

   我相信,大家看到這裡,對prototype還是似懂非懂的。這很正常,畢竟是js中比較重要又比較抽象的概念,不可能那麼快就掌握,再啃多幾篇,說不定掌握其精髓。在某乎上,有一個活生生的實例,可能也是大家會遇到的問題。可以看看 js構造函數和原型對象

 

5.constructor屬性

  prototype對象有一個constructor屬性,預設指向prototype對象所在的構造函數。

1     function A(){};
2     console.log(A.prototype.constructor===A)    //true

  要註意的是,prototype是構造函數的屬性,而constructor則是構造函數的prototype屬性所指向的那個對象,也就是原型對象的屬性。註意不要混淆。

1     console.log(A.hasOwnProperty('prototype'));    //true
2     console.log(A.prototype.hasOwnProperty('constructor')); //true

  由於constructor屬性是定義在原型(prototype)對象上面,意味著可以被所有實例對象繼承。

1     function A(){};
2     var a=new A();
3 
4     console.log(a.constructor);    //A()
5     console.log(a.constructor===A.prototype.constructor);//true

  上面代碼中,a是構造函數A的實例對象,但是a自身沒有contructor屬性,該屬性其實是讀取原型鏈上面的A.prototype.constructor屬性。

  5.1:constructor屬性的作用

    a:分辨原型對象到底屬於哪個構造函數

1     function A(){};
2     var a=new A();
3 
4     console.log(a.constructor===A)    //true
5     console.log(a.constructor===Array)    //false

    上面代碼表示,使用constructor屬性,確定實例對象a的構造函數是A,而不是Array

    b:從實例新建另一個實例

1     function A() {};
2     var a = new A();
3     var b = new a.constructor();
4     console.log(b instanceof A);    //true

    上面代碼中,a是構造函數A的實例對象,可以從a.constructor間接調用構造函數。

    c:調用自身的構造函數成為可能

1     A.prototype.hello = function() {
2         return new this.constructor();
3     }

    d:提供了一種從構造函數繼承另外一種構造函數的模式

1     function Father() {}
2 
3     function Son() {
4         Son.height.constructor.call(this);
5     }
6 
7     Son.height = new Father();

    上面代碼中,FatherSon都是構造函數,在Son內部的this上調用Father,就會形成Son繼承Father的效果。

    e:由於constructor屬性是一種原型對象和構造函數的關係,所以在修改原型對象的時候,一定要註意constructor的指向問題。

    解決方法有兩種,要麼將constructor屬性指向原來的構造函數,要麼只在原型對象上添加屬性和方法,避免instanceof失真。

 

6.instanceof運算符

  instanceof運算符返回一個布爾值,表示指定對象是否為某個構造函數的實例。

1     function A() {};
2     var a = new A();
3     console.log(a instanceof A);    //true

  因為instanceof對整個原型鏈上的對象都有效,所以同一個實例對象,可能會對多個構造函數都返回true。

1     function A() {};
2     var a = new A();
3     console.log(a instanceof A);    //true
4     console.log(a instanceof Object);    //true

  註意,instanceof對象只能用於複雜數據類型(數組,對象等),不能用於簡單數據類型(布爾值,數字,字元串等)。

1     var x = [1];
2     var o = {};
3     var b = true;
4     var c = 'string';
5     console.log(x instanceof Array);    //true
6     console.log(o instanceof Object);    //true
7     console.log(b instanceof Boolean);    //false
8     console.log(c instanceof String);    //false

  此外,null和undefined都不是對象,所以instanceof 總是返回false。

1     console.log(null instanceof Object);    //false
2     console.log(undefined instanceof Object);    //false

  利用instanceof運算符,還可以巧妙地解決,調用構造函數時,忘了加new命令的問題。

1     function Keith(name,height) {
2         if (! this instanceof Keith) {
3             return new Keith(name,height);
4         }
5         this.name = name;
6         this.height = height;
7     }

  上面代碼中,使用了instanceof運算符來判斷函數體內的this關鍵字是否指向構造函數Keith的實例,如果不是,就表明忘記加new命令,此時構造函數會返回一個對象實例,避免出現意想不到的結果。

 

 

    因為限於篇幅的原因,暫時介紹到這裡。

  我會在下次的分享中談談原型(prototype)對象的一些原生方法,如Object.getPrototypeOf(),Object.setPrototypeOf()等,並且介紹獲取原生對象方法的比較。

 

 

 

完。

 

 

感謝大家的閱讀。


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

-Advertisement-
Play Games
更多相關文章
  • × 目錄 [1]寫法 [2]增強 [3]實現 前面的話 本文先詳細介紹回到頂部的5種寫法,然後對其實現功能增加,最後得到最終實現 寫法 【1】錨點 使用錨點鏈接是一種簡單的返回頂部的功能實現。該實現主要在頁面頂部放置一個指定名稱的錨點鏈接,然後在頁面下方放置一個返回到該錨點的鏈接,用戶點擊該鏈接即可 ...
  • domManip 這個函數的歷史由來已久,從 jQuery 1.0 版本開始便存在了,一直到最新的 jQuery 版本。可謂是元老級工具函數。 domManip 的主要功能是為了實現 DOM 的插入和替換。具體共為以下 5 個函數服務 內部後插入(append) 內部前插入(prepend) 外部前 ...
  • 先知BOM,再知DOM BOM <a href="#">BOM</a> DOM* <a href="#">DOM</a> javaScript中的DOM操作 jQuery中的DOM操作 java中的DOM操作 基於JavaScript的DOM操作 DOM:文檔對象模型,文檔可以是.xml .html ...
  • 作為當代前端,前後端的界限越來越小,要想走的更穩,學習Linux操作系統的相關知識是必不可少的。 為什麼伺服器端採用linux操作系統? 1.linux相對於windows的性能更加穩定 2.前期投入成本低,相比於windows的伺服器產品,linux是完全開源免費的,linux支持最小化安裝,在相 ...
  • 一、圖片格式為png 1.shift+按住滑鼠左鍵,拉出參考線。 2.使用切片工具切出圖片 3.導出圖片:文件-儲存為web所用格式-預設(png-24)-選中需要導出圖片-點擊儲存-切片(選中用戶的所有切片)-保存(保存在桌面上) 二、圖片格式為psd 1.新建一個文檔 2.圖層中找到需要的圖層- ...
  • gitHub地址:https://github.com/lily1010/vue_learn/tree/master/lesson05 一 過濾器寫法 {{ message | Filter}} 二 Vue自帶的過濾器:capitalize 功能:首字母大寫 上面代碼輸出:Abc 三 Vue自帶的過 ...
  • HTML <b> 標簽 所有瀏覽器都支持 <b> 標簽。 定義和用法 <b> 標簽規定粗體文本。 所有瀏覽器都支持 <b> 標簽。 定義和用法 <b> 標簽規定粗體文本。 定義和用法 <b> 標簽規定粗體文本。 註釋:根據 HTML5 規範,在沒有其他合適標簽更合適時,才應該把 <b> 標簽作為最後 ...
  • 我的第一篇博客 ——JS的那些基礎概念 接觸前端已經整整一學年了,這是我第一次寫博客,感覺心裡裝了無數只兔子,很緊張,很激動,也很興奮。 第一次寫,也不知道有沒有什麼套路,需不需要註意文采之類的。不管了,太激動了,我就直接寫只要內容吧!下麵是我總結的一些關於JS的基礎概念: 【變數】從字面上面,變數 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...