js--原型和原型鏈相關問題

lcc發表於2021-09-09

前言

  閱讀本文前先來思考一個問題,我們在 js 中建立一個變數,我們並沒有給這個變數新增一些方法,比如 toString() 方法,為什麼我們可以直接使用這個方法呢?如以下程式碼,帶著這樣的問題,我們來學習本節的原型和原型鏈的一些知識。

 

正文

  1.建構函式建立物件問題

     function Person(name, age) {
        this.name = name;
        this.age = age;
        this.sayName = function () {
          console.log(this.name);
        };
      }
      var person = new Person("xiaoming", 18);
      person.sayName(); // xiaoming

 

  當使用建構函式建立物件person時,即使用new操作符構造一個例項物件的時候,首先會建立一塊新的記憶體空間,標記為person例項,執行建構函式會建立一個物件,會將物件的原型指向建構函式的 prototype 屬性,然後執行上下文中的this指向這個物件,最後再執行整個函式,如果返回值不是物件,則返回新建的物件,建立的物件有一個建構函式 constructor 屬性。其實質就是建立一個 object 引用型別的例項,然後把例項儲存在變數 person 中。

  總結 :new 操作符呼叫建構函式經歷了以下四步:

  (1)建立一個新物件;

  (2)將建構函式的作用域賦值給新物件(因此 this 指向這個新物件);

  (3)執行建構函式中的程式碼(為這個構造的新物件新增屬性);

  (4)返回這個新物件。

  2.原型相關問題

  我們建立的每個函式都有一個 prototype 屬性,這個屬性是一個指標,指向一個物件,而這個物件包含了通過該建構函式例項的物件所共享的屬性和方法。那麼,prototype 就是通過呼叫建構函式而建立的那個物件例項的原型物件。

 

      function Person() {}
      Person.prototype.name = "xioming";
      Person.prototype.sayHello = function () {
        console.log(this.name);
      };
      var person1 = new Person();
      person1.sayHello(); //xiaoming
      var person2 = new Person();
      person2.sayHello(); //xiaoming

 

  上面的程式碼,我們將 sayHello() 方法和所有屬性直接新增到了 Person 的 prototype 屬性中,建構函式變成了空函式,通過此建構函式建立的新物件例項,具有相同的屬性和方法,與純建構函式建立的物件不同的是所有例項共享了這些屬性和方法。

  (1)理解原型物件

  無論什麼時候,只要建立一個新函式,就會根據一種特定的規則為該函式建立一個 prototype 屬性,這個屬性指向函式的原型物件,預設情況下,所有原型物件都會自動獲得一個 constructor (建構函式)屬性,這個屬性包含一個指向 prototype 屬性所在函式的指標。例如上面的例子中 Person.prototype.constructor 指向 Person,同樣,我們可以繼續為原型物件新增別的屬性和方法。建立了自定義建構函式之後,其原型只會取得constructor屬性,其他方法都是通過從object繼承而來,當呼叫建構函式建立一個新例項後,該例項內部會包含一個指標指向建構函式的原型物件,這個指標叫 __proto__ ,因此可以通過下面的圖來表示上面例子的程式碼。

  因此通過上面的圖不難得出,js 中獲取原型的方法有如下三種:

  (1)person1.__proto__

  (2)Object.getPrototype(person1)

  (3)person1.constructor.prototype

  同樣可以通過 isPrototypeOf() 方法來確定物件之間是否存在原型關係,Person.prototype.isPrototypeOf( person1 )返回值為 true 。當然也可以如下使用Object.getPrototype(person1).name返回值為“ xiaoming ”。再來看下下面的這段程式碼:

     function Person() {}
      Person.prototype.name = "xioming";
      Person.prototype.sayHello = function () {
        console.log(this.name);
      };
      var person1 = new Person();
      person1.name="xiaohong"
      console.log(person1.name);//“xiaohong”

  通過上面的程式碼不難得出物件屬性的訪問順序,每當程式碼中讀取某個物件的屬性是,都會執行一次搜尋,目標是給定名字的屬性,搜尋首先從物件例項本身開始,如果在例項中找到了具有給定名字的屬性,則返回該屬性的值,如果沒有找到,則會繼續搜尋__proto__指標指向的原型物件,在原型物件中找到具有相同名字的屬性。雖然可以通過物件例項訪問儲存在原型物件中的值,但是不能通過獨享例項重新原型中的值,如果我們在例項中新增一個屬性,而該屬性與原型中的屬性同名,那麼就在例項中建立該屬性,該屬性就會遮蔽原型物件中的那個屬性。即新增了同名屬性後,這個屬性就會阻止我們訪問原型中的屬性。即使我們把這個屬性值設定為null,也只會在例項中訪問這個屬性,不會恢復對原型的同名屬性的訪問,要想恢復,只能使用 delete 操作符完全刪除該例項屬性。因此,官方也提供了一個 hasOwnProperty() 方法來判斷該屬性是否屬於例項物件,如果是則返回 true,否則返回 false 。

  最後總結得出原型,建構函式,例項物件三者之間的關係如下:

 

  (2)原型與 in 操作符

  使用 in 操作符有兩種情況,一種是直接使用,另外一種是 for- in迴圈中使用,在單獨使用的時候,in 操作符會在通過物件能夠訪問屬性的時候返回 true ,無論該屬性存在於物件例項還是原型物件中。"name" in person1 返回 true ,因此結合 hasOwnProperty() 方法可以判斷屬性是存在於自身例項中,還是存在於原型物件中。使用 for-in 迴圈時,返回的是所有能夠在物件中可以訪問,可以列舉的屬性,其中既包含例項中的有包含原型中的,tostring(),valueOf()....但是由於瀏覽器版本限制,這種方式不推薦使用。

  要取得物件上所有可列舉的例項屬性,可以通過 Object.keys() 方法,該方法接收一個物件作為引數,返回一個包含所有可列舉屬性的字串陣列。

  (3)原型的原型關係如下圖:

  2.原型鏈

  理解原型鏈之前,先來看如下程式碼:

 

     function Person() {}
      Person.prototype.name = "xioming";
      Person.prototype.sayHello = function () {
        console.log(this.name);
      };
      var person1 = new Person();
      console.log(person1.toString()); //[object Object]

 

  這就是剛開始講到的,為什麼每一個物件都包含 tostring() 這個方法呢,有了原型的瞭解,這裡用到原型鏈,當我們訪問一個物件的屬性或者方法時,如果這個物件例項內部不存在這個屬性或者方法,那麼他會在原型物件裡找這個同名屬性或者方法,這個原型物件又有自己的原型,於是這麼一層一層找下去,也就產生了原型鏈這個概念,原型鏈的盡頭一般來說都是 Object.prototype ,所有者就產生了新建的物件都會存在 toString() 等方法。

總結

  以上就是本文的全部內容,希望給讀者帶來些許的幫助和進步,方便的話點個關注,小白的成長之路會持續更新一些工作中常見的問題和技術點。

 

相關文章