06-原型

原型

原型鏈.png
    function Person(name) {
        this.name = name;
    }
    var p1 = new Person('Leo');
    var obj = new Object();
    var fn = new Function();
// 對象的原型最終指向Object.prototype,Object.prototype的原型指向null
    console.log( obj.__proto__ === Object.prototype ); // true  普通對象的原型
    console.log( Person.prototype.__proto__ === Object.prototype ); // true 自定義構造函數原型的原型
    console.log( Function.prototype.__proto__ === Object.prototype ); // true Function構造函數原型的原型
    console.log( Object.prototype.__proto__ === null ); // true Object構造函數原型的原型
// 實例的原型指向創建該實例的構造函數的原型
    console.log( p1.__proto__ === Person.prototype ); // true 實例對象的原型
// 函數的原型指向Function.prototype
    console.log( fn.__proto__ === Function.prototype ); // true  普通函數的原型
    console.log( Person.__proto__ === Function.prototype ); // true 自定義構造函數的原型
    console.log( Function.__proto__ === Function.prototype ); // true Function構造函數的原型
    console.log( Object.__proto__ === Function.prototype ); // true Object構造函數的原型
// 實例的constructor屬性,繼承自其原型
    console.log( p1.hasOwnProperty('constructor') ); // false
    console.log( obj.hasOwnProperty('constructor') ); // false
    console.log( fn.hasOwnProperty('constructor') ); // false
// Function.prototype是一個函數
    console.log( typeof Function.prototype ); // function
  • prototype (原型)屬性
    每個函數都有一個prototype屬性,這個屬性指向一個對象,這個對象就是原型對象。
    原型對象的作用就是定義所有實例對象共享的屬性和方法。
    實例對象可以看作從原型對象衍生出來的子對象,原型對象上的所有屬性和方法,都能被實例對象共享。
    實例對象本身沒有某個屬性或方法時,會到原型對象去尋找該屬性或方法。
    原型對象上的變動會立刻體現在所有實例對象上。
    如果重寫整個原型,會切斷現有原型與任何之前已存在的對象實例之間的聯系,它們引用的依舊是之前的原型。
    function Person(name) {
        this.name = name;
    };
    Person.prototype.sayHi = function () {
        console.log('Hi,'+ this.name);
    }
    var p1 = new Person('Leo');
// 先創建的實例對象,后修改原型,Person.prototype指向被新對象覆蓋
    Person.prototype = { // 字面量相當于Object的實例
        constructor: Person, //校正constructor屬性的指向,否則指向Object
        getName: function () {
            console.log(this.name);
        }
    }
    var p2 = new Person('Tom');
// 重寫原型前創建的實例的原型依舊指向以前的原型
    p1.sayHi(); // Hi,Leo
    p1.getName(); // 報錯
// 重寫原型后創建的實例的原型依舊指向新的原型
    p2.sayHi(); // 報錯
    p2.getName(); // Tom
  • constructor (構造函數)屬性
    每個原型對象都會自動獲得一個constructor屬性,這個屬性默認指向prototype屬性所在函數(即構造函數)。
    創建了自定義構造函數后,其原型對象默認只會取得constructor屬性,其他的方法則從Object繼承而來。
    constructor屬性定義在原型對象上面,可以被所有實例對象繼承。
    constructor屬性不是實例對象自身屬性,而是繼承自原型對象。
    constructor屬性的作用,是分辨原型對象到底屬于哪個構造函數.
    function Preson() {}
    var p1 = new Preson();
    console.log(p1.constructor); // function P() {}
    console.log(p1.hasOwnProperty('constructor'));// false
    console.log(p1.constructor === Preson); // true
    console.log(p1.constructor === Function); // false

constructor屬性是一種原型對象與構造函數的關聯關系,修改原型對象時,一般同時校正constructor的指向。

  • [[Prototype]] 內部屬性
    當用構造函數創建一個實例對象后,該實例對象包含一個[[Prototype]]內部屬性,指向構造函數的原型對象。
    [[Prototype]]存在于實例對象和原型對象之間,而不是實例和構造函數之間。
    [[Prototype]]內部屬性通過proto屬性來訪問。

  • 原型鏈
    原型對象本身也是對象,也有自己原型對象,所以就形成了一條原型鏈(prototype chain)。
    所有對象的對象原型終點可以可以上溯到Object.prototype。
    Object.prototype對象的原型對象是沒有任何屬性和方法的null對象,null對象沒有原型對象。
    讀取對象的某個屬性屬性時,先尋找對象本身的屬性,如果找不到就在原型鏈上找,直到Object.prototype。
    如果對象自身和它的原型,都定義了一個同名屬性,那么優先讀取對象自身的屬性.

  • instanceof 運算符
    instanceof返回一個布爾值,表示左邊的實例對象是否為右邊的構造函數的實例。
    instanceof的實質是檢查構造函數的原型對象是否在實例對象的原型鏈上。
    instanceof對整個原型鏈上的對象都有效,因此同一個對象,可能會對多個構造函數都返回true。
    JavaScript之中,只要是對象,就有對應的構造函數。因此,instanceof運算符可以判斷值的類型。
    instanceof只適用于對象,不適用基本類型的值。

    var d = new Date();
    console.log(d instanceof Date); // true
    console.log(d instanceof Object); // true
// 等同于
    console.log(Date.prototype.isPrototypeOf(d)); // true
  • in 運算符和 for…in 循環
    in運算符返回一個布爾值,表示一個對象是否具有某個屬性。它不區分該屬性是對象自身的屬性,還是繼承的屬性。
    'length' in Date // true
    'toString' in Date // true

獲得對象的所有可枚舉屬性(不管是自身的還是繼承的),可以使用for...in循環。

    for ( var name in object ) {
      if ( object.hasOwnProperty(name) ) {
        /* loop code */
      }
    }
  • Object.prototype.hasOwnProperty()
    對象實例的hasOwnProperty方法返回一個布爾值,用于判斷某個屬性定義在對象自身,還是定義在原型鏈上。
    hasOwnProperty方法是JavaScript之中唯一一個處理對象屬性時,不會遍歷原型鏈的方法。
    Date.hasOwnProperty('length'); // true
    Date.hasOwnProperty('toString'); // false
  • Object.getOwnPropertyNames()
    Object.getOwnPropertyNames方法返回一個數組,成員是對象本身的所有屬性的鍵名,不包含繼承的屬性鍵名
    Object.getOwnPropertyNames(String);
// ["length", "name", "arguments", "caller", "prototype", "fromCharCode", "fromCodePoint", "raw"]
  • Object.prototype.isPrototypeOf()
    對象實例isPrototypeOf方法,用來判斷一個對象是否是另一個對象的原型。
    只有某個對象處在原型鏈上,isPrototypeOf都返回true。
    Object.prototype處在原型鏈頂端,所以對各種實例都返回true,只有繼承null的對象除外。
    Object.prototype.isPrototypeOf({});// true
    Object.prototype.isPrototypeOf([]);// true
    Object.prototype.isPrototypeOf(/xyz/);// true
    Person.prototype.isPrototypeOf(p1);// true
    Object.prototype.isPrototypeOf(Object.create(null));// false
  • Object.prototype.proto
    proto方法可以獲取和改寫某個對象的原型對象。
    proto方法應少用,而是使用標準方法Object.getPrototypeOf()和Object.setPrototypeOf()。

  • Object.getPrototypeOf()
    Object.getPrototypeOf方法接受一個對象,返回該對象的原型,是獲取原型對象的標準方法。

// 對象的原型是Object.prototype
    console.log(Object.getPrototypeOf({}) === Object.prototype); // true
// 函數的原型是Function.prototype
    function Person() {};
    console.log(Object.getPrototypeOf(Person) === Function.prototype); // true
// 實例對象p1的原型是創建該實例的構造函數的原型
    var p1 = new Person();
    console.log(Object.getPrototypeOf(p1) === Person.prototype); // true

獲取原型對象方法的比較

    // 獲取實例對象obj原型的三種方法:
    var obj = new Object();
    obj.__proto__; // 瀏覽器內部屬性,其他環境不可以部署
    obj.constructor.prototype; // 手動修改原型對象時會失效,需要同時修改constructor屬性
    Object.getPrototypeOf(obj)// 推薦
  • Object.setPrototypeOf()
    Object.setPrototypeOf方法可以為現有對象設置原型,返回一個新對象。
    該方法接受兩個參數,第一個參數是現有對象,第二個參數是原型對象。
    var a = { name: 'Leo'};
    var b = Object.setPrototypeOf({}, a); // b對象本身為空,原型為a
    console.log(a.isPrototypeOf(b)); // true a是b的原型
    console.log(b.hasOwnProperty(name)); // false, b.name不是b自身的屬性
    console.log(b.name); // Leo
    // 等價于
    var b = {__proto__: a};
    console.log(b.name); // Leo

new命令通過構造函數新建實例對象,實質是將實例對象的原型,指向構造函數的prototype屬性,然后在實例對象上執行構造函數。

    function Person(name, age) {
        this.name = name;
        this.age = age;
    };
    var p1 = new Person('Leo'); // 使用new新建構造函數
// 等價于
    var p1 = Object.setPrototypeOf({}, Person.prototype);// 將實例對象的原型指向構造函數的prototype屬性
    Person.call(p1, 'Leo', 26);// 在實例對象上執行構造函數,并傳入參數
    console.log(p1.name); // Leo
    console.log(p1.age); // 26
  • Object.create()
    Object.create方法用于從原型對象生成新的實例對象,可以替代new命令。
    該方法接受一個對象為參數,返回一個新的對象,傳入的對象成為新對象的原型。必須提供原有對象,否則報錯。
    var A = {
        sayHi: function () {
            console.log('hello');
        }
    };
    var B = Object.create(A); // 在A的基礎上生成B,此時A就成為B的原型
    B.sayHi(); // hello B繼承了A的所有屬性和方法
    // 等同于
    var A = function () {};
    A.prototype.sayHi = function () {
        console.log('hello');
    }
    B = new A();
    B.sayHi() // hello

Object.create方法實質是新建一個構造函數F,讓F的prototype屬性指向作為原型的對象o,最后返回一個F的實例。

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

下面三種生成新對象的方式是等價的。

var o1 = Object.create({});
var o2 = Object.create(Object.prototype);
var o3 = new Object();

Object.create方法生成的新對象,動態的繼承了原型。在原型上的修改,會立刻反映在新對象上。

var A = {x: 1};
var B = Object.create(A);
A.x = 2;
console.log(B.x); // 2

Object.create方法還可以接受第二個參數,該參數是一個屬性描述對象,它所描述的對象屬性,會添加到新對象。
Object.create方法生成的對象,繼承了他的原型對象的構造函數。

function A() {}
var a = new A();
var b = Object.create(a); // b對象的原型是a,因此繼承了a的構造函數A
console.log(b.constructor === A); // true
console.log(b instanceof A); // true

參考資料:
《JavaScript高級程序設計》
《JavaScript 標準參考教程》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容

  • 工廠模式類似于現實生活中的工廠可以產生大量相似的商品,去做同樣的事情,實現同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 7,799評論 2 17
  • var a = 1; console.log(typeof a);// 'number' var b = '1';...
    zdnexus閱讀 335評論 0 0
  • 一、函數對象(Function)和普通對象(Object) 由function定義或Function實例化的對象為...
    Franchen閱讀 820評論 0 4
  • 創建對象 對象是無序屬性的集合,其屬性可以包含基本值,對象或者函數,即由若干個“鍵值對”(key-value)構成...
    LeoCong閱讀 171評論 0 0
  • 繼承 Javascript中繼承都基于兩種方式:1.通過原型鏈繼承,通過修改子類原型的指向,使得子類實例通過原型鏈...
    LeoCong閱讀 326評論 0 0