搞明白 JavaScript 原型與原型鏈

寫在前面

  • 所有引用類型(函數,數組,對象)都擁有__proto__屬性(隱式原型)
  • 所有函數擁有prototype屬性(顯式原型)(僅限函數)
  • 原型對象:擁有prototype屬性的對象,在定義函數時就被創建

原型prototype

1. 原型 prototype

prototype就是英語“原型”的意思。原型是構造函數創建對象的原始模型

原型的特點
原型也是對象,原型是函數對象的一個屬性
原型自帶 constructor 屬性, constructor 指定構造函數
構造函數創建出的對象會繼承原型的屬性和方法

// 任何函數都有原型,只是原型對於普通函數沒什麼大用,但對於構造函數用處極大
function fun(){
    console.log("您好")
}

console.log(fun.prototype);
console.log(typeof fun.prototype);

在JavaScript中,任何一個函數,都有一個prototype屬性,指向一個對象。打印prototype屬性,你會發現這個屬性指向一個空對象。打印prototype的類型,發現是object類型。

2. 原型對象與實例

一個函數的原型,對於普通函數來說,沒任何作用。但是如果函數是一個構造函數,那麼函數的原型,用處極大!

        function Person(name,age,sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }

        Person.prototype = {
            major :'計算機科學與技術',
        }

        let why = new Person('高歡',18,'男');

        console.log(why.name); //高歡
        console.log(why.age);  //18
        console.log(why.sex);  //男
        console.log(why.major); //'計算機科學與技術'

why 就是構造函數 Person 的實例化對象.

有一個很奇怪的現象,那就是我們明明沒有給 Person 構造函數添加 major 屬性,那爲什麼我們可以通過 Person 的實例化對象 why 訪問到這個屬性呢???

這個時候我們就要了解 原型 的概念, 在前面我們已經提到每一個構造函數都有一個 prototype 屬性,指向一個一個對象 ,這個對象就是構造函數 Person 的原型.

簡單來說就是構造函數通過prototype屬性訪問到的對象就是這個構造函數的原型.

打印Person.prototype 得到如下的結果:
在這裏插入圖片描述
當一個對象被 new 出來的時候,不僅僅執行了構造函數裏面的語句,我們的感覺,構造函數的原型中,所有的屬性也加給了這個對象,因此我們通過 why 訪問到 major 屬性.

3. 實例對象的__proto__屬性

當一個對象被new出來的時候,不僅僅執行了構造函數裏面的語句,也會把這個函數的__proto__指向構造函數的 prototype

        // 構造函數
        // 構造函數裏面沒有任何語句,也就是說,這個構造函數在執行的時候,不會給創建出來的對象添加任何屬性
        function Person() {

        }
        //構造函數的原型,我們更改了構造函數的原型,爲一個新的對象:
        Person.prototype = {
            name:'why',
            age : 18,
            sex : '女',
            major :'計算機科學與技術',
        }

        //當一個對象被new出來的時候,不僅僅執行了構造函數裏面的語句,也會把這個函數的__proto__指向構造函數的prototype。
        let why = new Person();
        //當我們試圖訪問sex、age屬性的時候,身上沒有。那麼就去查找原型,原型身上有,就當做了自己的屬性返回了。
        console.log(why.name);         //高歡
        console.log(why.age);            //18
        console.log(why.sex);            //男
        console.log(why.major);        //'計算機科學與技術'

        console.log(why.__proto__);
        console.log(why.__proto__ === Person.prototype)   //true

任何一個對象,都有__proto__屬性,這個屬性是Chrome自己的屬性,別的瀏覽器不兼容,但是別的瀏覽器也有原型對象,只不過不能通過__proto__進行訪問而已。 這是屬性指向自己的原型對象。

每個實例化對象身上都有一個 _proto_ 屬性,這個屬性指向了實例化對象的原型對象.

簡而言之,通過構造函數實例化出的對象都有__proto__ s屬性,t通過這個屬性訪問到的對象就是這個實例化對象的原型對象

打印why.__proto__,結果如下:
在這裏插入圖片描述
仔細一看,咦,事情不對,這打印的結果咋和之前Person.prototype打印的結果那麼像呢.
我們執行下面的代碼來觀察:

        console.log(Person.prototype.constructor);
        console.log(why.__proto__.constructor);
        console.log(why.__proto__.constructor === Person.prototype.constructor)

可以看到 Person.prototype 的打印結果與why.__proto__的結果是相同的.比較的結果爲 true
在這裏插入圖片描述
所以說他們二者的結果本質上是相同的

但是我們把構造函數的 prototype 屬性打印得到的對象稱爲構造函數的原型
把實例化對象的 _proto_ 屬性打印得到的對象稱爲實例化對象的原型對象

這樣做有利於我們更好的區分二者

用下面的一張圖來解釋三者之間的關係:
在這裏插入圖片描述

稱 {constructor:Person} 爲構造函數的原型
稱 {constructor:Person} 爲實例化對象的原型對象
Person 的實例化對象 why 的原型
why 是構造函數 Person 的實例化對象

說着麼半天.其實就是了 2 個對象和一個函數的故事。任何一個函數都有原型,原型是一個對象,用prototype來訪問。

當這個函數是構造函數的時候,new出來的對象,它們的原型對象就是這個構造函數的原型。(任何普通函數只要通過new操作符調用就可以稱之爲構造函數)

prototype我們稱爲“原型”,只有函數有原型

__proto__我們稱爲“原型對象”,任何對象都有原型對象。

2. 原型鏈機制

1. 原型鏈的本質

只要是對象,一定有原型對象,就是說只要這個東西是個對象,那麼一定有__proto__ 屬性。

我們想實例的原型對象也是一個對象,那麼我們迫切的想看看這個原型對象的原型對象.如果這理論沒有問題的話,這個原型對象應該也有 __proto__ 指向他的原型對象吧.
這句話很繞,但是如果你已經理解了什麼是原型對象的話理解這句話也不會有很大的難度.

function People(){
    
}
var xiaoming = new People();
// 原型對象的原型對象
console.log(xiaoming.__proto__.__proto__);
// 原型對象的原型對象的構造函數是誰
console.log(xiaoming.__proto__.__proto__.constructor); //Object

// 那我們看看還能不不能再向上查原型對象
console.log(xiaoming.__proto__.__proto__.__proto__);
// 結果爲null

也就是上面的一句話錯了

世界上只有一個對象沒有原型對象,這個對象就是Object.prototype。

2. 原型鏈查找

當我們試圖訪問一個對象身上的屬性的時候,如果這個對象身上有這個屬性,則返回它的值。如果它身上沒有這個屬性,那麼將訪問它的原型對象,檢測它的原型對象身上是否有這個值,如果有返回它原型對象身上的這個值。

根據上面的一張圖就能想明白一些事情,一個對象天生帶一些屬性和方法,其實並不一定是你創建這個對象時就有的,很大的可能,是這個方法或屬性是在這個對象的原型鏈上的某個對象所擁有的.

這就跟作用域有些相似之處了,當我們在某處使用某個變量時,會先從當前作用域查找,如果有則使用,如果沒有,則往上一層作用域查找,以此類推,如果直到全局作用域還沒找到,就會報錯.
在這裏插入圖片描述
比如:

function People(){
    
}
var xiaoming = new People();
// 小明馬上可以調用toString()方法
console.log(xiaoming.toString());
// 打印 "[object Object]"

// 這個方法是小明原型對象的原型對象身上的方法,我們可以看下
console.log(xiaoming.__proto__.__proto__);

Object是一個函數,是系統內置的構造函數,用於創造對象的。Object.prototype是所有對象的原型鏈終點。
所以,當我們在一個對象上打點調用某個方法的時候,系統會沿着原型鏈去尋找它的定義,一直找到Object.prototype

Object.prototype是所有對象原型鏈的終點,現在我強行給它添加一個屬性

        Object.prototype.sayName= function() {
                alert(`我叫${this.name},今年${this.age}歲了`);
        }
        why.sayName();
        // 豈止xiaoming能sayHello 數組也能sayHello
		var arr = [1,2,3];
		arr.sayHello();

		// 然後世界上一切都能sayHello了
		"麼麼噠".sayHello();

Object.prototype是所有對象的原型鏈的終點,所以我們直接給Object.prototype增加一個方法,那麼世界所有的對象都能調用這個方法.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章