日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

【面试必备】javascript的原型和继承

發布時間:2025/7/14 75 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【面试必备】javascript的原型和继承 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

摘要:  原型、閉包、作用域等知識可以說是js中面試必考的東西,通過你理解的深度也就能衡量出你基本功是否扎實。今天來復習一下javascript的原型和繼承,雖說是老生常談的話題,但對于這些知識,自己親手寫一遍能更加透徹的理解,能用自己的話說明白了,也就真正理解了。

 原型、閉包、作用域等知識可以說是js中面試必考的東西,通過你理解的深度也就能衡量出你基本功是否扎實。今天來復習一下javascript的原型和繼承,雖說是老生常談的話題,但對于這些知識,自己親手寫一遍能更加透徹的理解,能用自己的話說明白了,也就真正理解了。

原型是什么?

  在javascript中,通過關鍵字new調用構造器函數或者使用字面量聲明,我們可以得到一個對象實例。每個對象實例內部都持有一個指針,指向一個普通的對象,這個普通的對象就是原型,這是天生的。為什么說它是普通的對象呢?因為它確實沒什么特別的地方,同樣也是某個構造器函數的一個實例,這個構造器可以是Object,可以是Array,也可以是其他你自己定義的構造器函數。在js中,對象實例的原型是不可訪問的,不過在chrome和Firefox瀏覽器中,我們可以用一個名為__proto__的屬性來訪問到,來看一下所謂的原型長什么樣:

  我用string的包裝類來創建了一個對象s,可以看到s的原型是一個對象,該對象上包含了一系列方法,比如我們熟悉的charAt。這里也就很明顯了,我們平時調用s.charAt(0),其實調用的是s的原型上的方法,也就是說,原型上的屬性可以被對象訪問到,就像是在訪問自身的屬性一樣。可以認為原型就像孕婦肚子里的孩子一樣,孩子的胳膊也可以算是孕婦的胳膊,都在自己身上嘛。不過區別是這里的原型只是一個引用,并不是真正的包含這個對象。注意不要被__proto__后面的那個String迷惑到,s的原型是一個Object的實例,而不是String的實例。下面的代碼可以證明:

s.__proto__ instanceOf String; //false s.__proto__ instanceOf Object; //trues.hasOwnProperty('charAt'); //false s.__proto__.hasOwnProperty('charAt'); //true復制代碼

  要明白這個原型指針到底指向什么,就需要明白對象是如何創建出來的,所以接下來有必要了解一下構造器函數。

  javascript中沒有類,但可以把函數當類使,被用來當做類構造器的函數就叫構造器函數,一般把首字母大寫來與普通函數進行區別,其實就是豬鼻子插根蔥而已——裝象。js中一切都是對象,所以函數也是對象,所以函數也有一個原型指針。與實例對象不同的是,函數這種特殊的對象,它的原型可以通過prototype屬性顯式的訪問到,來看看String類的原型是啥樣的:

  好像跟我們上面看到的s的原型是一模一樣的。。。是這樣嗎?驗證一下:

  這是什么原因呢?我們就要細究一下var s = new String('s');在執行的時候到底發生了什么,其實就是用new關鍵字調用函數String的時候發生了什么:

  • 創建一個空對象obj,即Object的一個實例
  • 把這個空對象obj綁定到函數的上下文環境中,相當于把this指向了obj
  • 執行函數,這個過程就把函數中的屬性、方法拷貝到了obj中
  • 將obj的原型指向函數的prototype屬性
  • 返回這個obj對象,s作為它的引用。
  •   到這里就可以得出結論了:對象實例與它的構造器函數擁有同一個原型,這個原型指向的是構造器的父類的一個實例。

      我第一次提到了“父類”,在面向對象的語言中,如果B繼承自A,我們說A是B的父類。javascript是通過原型實現繼承的,所以我也可以說,我的原型指向誰,誰就是我的父類。通過上面的代碼我們可以得出:

    String.prototype === s.__proto__ //true String.prototype instanceOf Object //true復制代碼

      可以用面向對象語言的話說,Object就是String的父類。之所以這么說是因為這樣容易記住,再來重復一遍結論:對象實例與它的構造器函數擁有同一個原型,這個原型指向的是構造器的父類的一個實例。這個結論是非常有用的,由于對象實例的原型是不可訪問的(__proto__只是瀏覽器提供的能力),我們可以通過constructor屬性得到它的構造器,然后用構造器的prototype屬性來訪問到原型,像這樣:

    s.constructor.prototype復制代碼

      理解的過程像是在做一道道證明題一樣。盡管有大師推薦在js中用構造器函數這個稱呼來代替類,但為了便于理解和記憶,我還是這么叫吧~

    原型的一些特性

      明白是原型是什么東西,來看看原型都有哪些特性。其實也不能說是原型的特性,而是javascript語言的特性。

      首先要看的就是所謂的原型鏈。每個對象都有原型,而對象的原型也是一個普通對象,那么就可以形成一個鏈,例如String對象的原型是Object類的一個實例,而Object對象的原型是一個空對象,空對象的原型是null。除去null不看的話,原型鏈的頂端是一個空對象{}

      當我們訪問對象的一個屬性時,會先從對象自身找,如過自身沒有,就會順著原型鏈一直往上找,直到找到為止。如果最后也沒找到,則返回undefined。這樣對象的內容就會很“豐富”,我的是我的,原型的也是我的。通過修改原型的指向,對象可以獲得相應原型上的屬性,js就是通過這種方式實現了繼承。

      有一點需要注意的是,屬性的讀操作會順著原型鏈來查找,而寫操作卻不是。如果一個對象沒有屬性a,為該對象的a屬性賦值會直接寫在該對象上,而不是先在原型上找到該屬性然后修改值。舉個例子:

    var s = new String('string'); s.charAt(0); //返回s s.hasOwnProperty('charAt'); //返回false 說明charAt不是自身的方法,而是原型上的 s.charAt = function(){return 1;} //為s的charAt賦值 s.hasOwnProperty('charAt'); //返回true 說明自身有了charAt方法 s.charAt(0); //返回1 這時候調用charAt找到了自身的方法 s.constructor.prototype.charAt.call(s,0); //返回s 調用原型上的charAt方法結果與原來一樣復制代碼

      上面的例子說明,為對象的屬性賦值是不會影響到原型的。這也是合理的,因為創建出來的對象s,它的原型是一個指針,指向了構造器的原型。如果原型被修改,那么該類的其他實例也會跟著改變,這顯然是不愿意看到的。

      我們愿意看到的是,修改了一個構造器的原型,由它構造出的實例也跟著動態變化,這是符合邏輯的。比如我們創建一個Person類,然后修改其原型上的屬性,觀察它的實例的變化:

    function Person(name){this.name = name; } Person.prototype.age = 10; var p1 = new Person('p1'); console.log(p1.age); //10 Person.prototype.age = 11; console.log(p1.age); //11復制代碼

      這是因為age存在于原型上,p1只是擁有一個指針指向原型,原型發生改變后,用p1.age訪問該屬性必然也跟著變化。

    用原型實現繼承

      用原型實現繼承的思路非常簡單,令構造函數的原型指向其父類的一個實例,這樣父類中的屬性和方法也就相當于被引用到了,調用起來和調用自己的一樣。比如定義一個Programmer類繼承自Person:

    function Person(name){this.name = name; } Person.prototype.age = 10;function Programmer(name){this.name = name; } Programmer.prototype = new Person(); Programmer.prototype.constructor = Programmer; var p1 = new Programmer('p1'); console.log(p1.age); //10復制代碼

      可以看到Programmer的實例p1繼承了Person的屬性age。另外需要注意的就是constructor的修正。因為我們new一個Person對象出來,它的constructor指向自身的構造函數Person,所以在Programmer的原型中,這個constructor始終是Person,這與邏輯是不符的,所以必須顯式的“糾正”一下這個副作用,讓Programmer原型上的constructor指向自己。

      以上代碼實現了一個基本的繼承。但其中還是有不少可以擴展的地方,如果面試的時候只答出上面的這些,只能算是及格吧。關于如何優化繼承的代碼,有位大牛的文章分析的十分詳細,出于篇幅原因我在本篇就不再陳述。直接貼上鏈接地址:www.cnblogs.com/sanshi/arch…,共六篇系列博客,非常詳細。

    ----------------補充于2014.01.07---------------------

      在上面的繼承實現方式中,有一個消耗內存的地方,就是為子類指定原型時需要new一個父類的對象,有人做了比較好的處理,今天看到了代碼,據說是coffeescript中的,抄在這里:

    var _hasProp = {}.hasOwnProperty; var extends = function(child,parent){for(var key in parent){if(_hasProp.call(parent,key)){child[key] = parent[key];}}function ctor(){this.constructor = child;}ctor.prototype = parent.prototype;child.prototype = new ctor();child._super_ = parnet.prototype;return child; }復制代碼

      是一個完整的實現繼承的方法。在內部創建了一個最小化的對象,減少內存消耗。

    繼承的另一種實現方式

      除了用原型,還有一種方式也可以實現繼承,叫做類復制。怎么個復制法呢,看下面的代碼:

    function People(name){this.name = name;this.age = 11;this.getName = function(){return this.name;} }function Worker(name){People.call(this,name); }var w1 = new Worker('w1'); console.log(w1.getName()); //w1 console.log(w1.age); //11復制代碼

      在People構造器中所有的屬性和方法都用this關鍵字定義在了自身,而不是放在它的原型上。在子類Worker中,用call把People當作函數執行了一下,并傳入this作為上下文對象。這樣就相當于把People中的所有語句拿過來執行一次,所有屬性的定義也都被復制過來了。同樣可以實現繼承。完全與原型無關。

      那么這種方式與原型繼承有何區別呢?最大的區別就在于原型是一個引用,所有實例都引用一個共享的對象,每次創建出一個實例時,并不會復制原型的內容,只是用一個指針指過去。而類復制的方法不存在共有的東西,每創建一個對象都把構造器中的代碼執行一次,當構造器中的方法較多時,會消耗很多的內存。而原型繼承就不會了,只需一個指針指過去就完了。

      由這種工作方式產生的另一個區別就是動態修改,我們知道在原型繼承中,只要修改了構造器原型中的值,實例對象也跟著變化。但是類復制就不能了,每個對象都有自己的一份數據,已創建出來的對象不會再受構造器的影響了。

      另外還有一點,就是屬性的訪問速度。類復制的方式,對象的屬性都在自身,所以在查找的時候可以立即找到,而原型繼承在查找的時候還得順著原型鏈向上查找,其訪問速度肯定不如類復制的快。

    總結

      以上是我理解到的原型與繼承的知識點,可能理解還是沒有那么透徹,只是從比較淺的層次梳理了一下。與原型相關的知識還有很多有深度的,還有待于繼續研究。這篇博客寫完我也感覺到,寫一篇基礎知識分析的文章真是挺困難的,需要你對每一個細節都掌握清楚,生怕稍不注意就給別人誤導。可能自己的水平也有待提高吧,本篇就先分析到這個程度,不知這個程度能否達到初級前端工程師的門檻。后續收集到了面試題,我會結合分析。

    分類: javascript相關,前端面試題 本文轉自呂大豹博客園博客,原文鏈接:http://www.cnblogs.com/lvdabao/p/3502944.html。


    總結

    以上是生活随笔為你收集整理的【面试必备】javascript的原型和继承的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。