动态

详情 返回 返回

JavaScript 之原型、原型鏈 - 动态 详情

前言

其他編程語言如 Java 等使用 new 命令時,都會調用“”的構造函數。但是,JavaScript沒有“”,本身並不提供一個 class 實現(雖然在ES6中提供了class 關鍵字,但其只是語法糖,JavaScript仍然是基於原型的)。於是,JavaScript作了一個簡化的思想,new 命令後面跟的不是類,而是構造函數,用構造函數生成實例對象,但其缺點是無法共享屬性和方法。於是,就為構造函數設置了一個 prototype 屬性,這個屬性包含一個對象(prototype對象)。所有實例對象需要共享的屬性和方法都放在這個對象裏,那些不需要共享的屬性和方法就放在構造函數裏。

💡温馨提示:本文全文 1986 個字,推薦閲讀時間為 10m,加油老鐵!

一、顯式原型(prototype)

1.1 介紹

每個函數在創建之後都會有一個名為prototype 屬性:

function Parent() {

}
Parent.prototype.name = 'kite';
let child = new Parent();
console.log(child.name);

這個屬性指向函數的原型對象(通過Function.prototype.bind方法構造出來的函數是個例外,它沒有prototype屬性),即調用構造函數創建的實例的原型,也就是例子中child的原型。

何為原型:每一個JavaScript對象(null 除外)都會和另一個對象相關聯,這個對象就是原型,上面也提到每個對象都會從原型中“繼承屬性”。

原型表明了構造函數和實例原型的關係

1.2 作用

顯式原型用來實現基於原型的繼承與屬性的共享。

二、隱式原型(__proto__)

2.1 介紹

JS中任意對象都具有一個內置屬性[[prototype]],在ES5之前沒有標準方法訪問,大多數瀏覽器通過__proto__來訪問。ES5中有了對這個內置屬性標準的get方法:Object.getPrototypeOfObject.prototype 是個例外,它的__proto__null)。

function Parent() {

}
let child = new Parent();
console.log(child.__proto__ === Parent.prototype); // true

2.2 作用

隱式原型構成原型鏈,同樣用於實現基於原型的繼承。舉例來説:
當我們訪問對象中的某個屬性時,如果在對象中找不到,就會一直沿着__proto__(原型的原型)依次查找,直到找到最頂層為止。

function Parent() {

}
Parent.prototype.name = 'dave';
let child = new Parent();
child.name = 'kite';
console.log(child.name); // 'kite'
delete child.name;
console.log(child.name); // 'dave'

上面的例子中,給對象child添加name屬性,當訪問name屬性時,找到了對象本身的屬性值kite。刪除name屬性之後,再次訪問name屬性,在對象中找不到該屬性,再在原型中尋找,找到dave。假設屬性在原型中也沒有找到該屬性,則會再去原型的原型中查找。

我們知道原型是個對象,既然是對象就可以用最原始的方式創建:

let obj = new Object();

原型就是通過Object 構造對象生成的。那 Object.prototype 的原型又是什麼?

Object.prototype.__proto__ = null; // true

null表示沒有對象,表明在此處可以停止查找了。

2.3 指向

__proto__ 指向創建這個對象的函數的顯式原型,其關鍵在於找到創建這個對象的構造函數。創建對象有三種形式:對象字面量;newclass);ES5的Object.create()。本質只有一種new

2.3.1 對象字面量

let obj = {
    name: 'ctt'
}

對象字面量聲明的對象繼承自 Object ,和 new Object一樣,其原型為
Object.prototype。而 Object.prototype 不繼承任何屬性和方法。

2.3.2 new

使用構造函數創建的對象,它的屬性繼承自構造函數。
具體分以下幾種情況:

  1. 內建對象
    Array() ,它繼承於Array.prototype Array.prototype 為一個對象,這個對象由 Object() 這個構建函數創建。因此,Array.prototype.__proto__ === Object.prototype,原型鏈為:Array.prototype -> Object.prototype -> null
  2. 自定義對象
  3. 默認情況下:

    function Foo() {};
    let foo = new Foo();
    Foo.prototype.__proto__ === Object.prototype;
  • 其他情況:

    // 想讓Foo繼承Bar
    function Bar() {
    }
    Foo.prototype = new Bar();
    Foo.prototype.__prototype__ = Bar.prototype;
    // 重新定義Foo.prototype
    Foo.prototype = {
      a: 1,
      b: 2
    };
    Foo.prototype.__proto__ = Object.prototype;

以上兩種情況改寫了Foo.prototype,所以Foo.prototype.constructor跟着改變,constructor和原構造函數 Foo切斷了聯繫。

  1. 構造函數
    構造函數就是Function()的實例,因此構造函數的隱式原型指向
    Function.prototype 。引擎創建了Object.prototype ,而後又創建了
    Function.prototype,通過__proto__ 將兩者聯繫起來。

Function.prototype === Function.__proto__ ,其他所有的構造函數都可以通過原型鏈找到 Function.prototype ,並且 function Function() 本質也是一個函數,為了不產生混亂,將 function Function__proto__ 聯繫到了Function.prototype 上。

2.3.3 class

在ES5中,每個對象都有一個 __proto__ 屬性,指向對應構造函數的prototype 屬性,而 class 作為構造函數的語法糖,同時具有 prototype 屬性和 __proto__ 屬性,所以同時存在兩條繼承鏈。

  • 子類的 __proto__ 表示構造函數的繼承,總是指向父類;
  • 子類的 prototype 屬性的 __proto__ 表示方法的繼承,總是指向父類的
    prototype
class Parent {

}
class Child extends Parent {

}
Child.__proto__ === Parent;
Child.prototype.__proto__ === Parent.prototype;

作為一個對象,子類 Child 的原型(__proto__)是父類 Parent ;作為一個構造函數,子類的原型對象(prototype )是父類原型對象(prototype )的實例。

2.3.4 Object.create()

Object.create() 是ES5的方法,可以調用這個方法來創建一個新對象。新對象的原型就是傳入的第一個參數。

三、構造函數(constructor)

上面説明構造函數和實例都可以指向原型,接下來講的這個屬性就是讓原型指向構造函數的(沒有指向實例的屬性,因為構造函數可以生成多個實例),它就是constructor屬性,每個原型都有一個constructor屬性指向構造函數。

function Parent() {

}
console.log(Parent === Parent.prototype.constructor);

關於原型的各類關係總結如圖:
6C8EFCFC-52BD-44DA-BE78-7F7D277DF9FA.png

四、this 和 prototype 定義方法的區別

  1. 利用this實現的方法,可以訪問類中的私有變量和私有方法。而利用原型對象實現的方法,無法訪問類中的私有變量和方法。
  2. 實例訪問對象的屬性或者方法時,將按照搜索原型鏈prototype chain的規則進行。首先查找自身的靜態屬性、方法,繼而查找構造上下文的可訪問屬性、方法,最後查找構造的原型鏈。
  3. thisprototype定義的另一個不同點是在內存中佔用空間不同。使用“this”關鍵字,實例初始化時為每個實例開闢構造方法所包含的所有屬性、方法和所需空間,而使用prototype定義的,由於prototype實際上是指向父級的引用,因此在初始化和存儲上比“this”節約資源。

五、總結

  1. 每個函數在創建之後都會有一個名為prototype屬性,這個屬性指向函數的原型對象(顯式原型),所有實例對象需要共享的屬性和方法都放在這個對象裏;
  2. 任意對象都具有一個內置屬性 __proto__ 指向創建這個對象的函數的顯式原型;
  3. class 作為構造函數的語法糖,同時具有 prototype 屬性和 __proto__ 屬性,作為一個對象,子類 Child 的原型(__proto__)是父類 Parent ;作為一個構造函數,子類的原型對象(prototype )是父類原型對象(prototype )的實例。
  4. 每個原型都有一個constructor屬性指向構造函數,即 Parent === Parent.prototype.constructor

參考

JavaScript - 原型、原型鏈 · Issue #13 · cttin/cttin.github.io · GitHub

user avatar toopoo 头像 dingtongya 头像 linlinma 头像 u_17443142 头像 shuirong1997 头像 jiavan 头像 xiaolei_599661330c0cb 头像 zzd41 头像 assassin 头像 yangxiansheng_5a1b9b93a3a44 头像 user_ze46ouik 头像 54r9rxzy 头像
点赞 89 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.