JavaScript中的原型prototype完全解析
要理解JS中的prototype, 首先必須弄清楚以下幾個概念
1. JS中所有的東西都是對象
2. JS中所有的東西都由Object衍生而來, 即所有東西原型鏈的終點指向Object.prototype
// ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", // "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__", // "__lookupSetter__"] console.log(Object.getOwnPropertyNames(Object.prototype));
3. JS中構(gòu)造函數(shù)和實例(對象)之間的微妙關(guān)系
構(gòu)造函數(shù)通過定義prototype來約定其實例的規(guī)格, 再通過 new 來構(gòu)造出實例, 他們的作用就是生產(chǎn)對象.
而構(gòu)造函數(shù)(方法)本身又是方法(Function)的實例, 因此也可以查到它的__proto__(原型鏈)
Object / function F() {} 這樣子的就是構(gòu)造函數(shù)啦, 一個是JS原生API提供的, 一個是自定義的
new Object() / new F() 這樣子的就是實例啦
實例就"只能"查看__proto__來得知自己是基于什么prototype被制造出來的,
而"不能"再重新定義實例的prototype妄想創(chuàng)造出實例的實例了.
實踐出真知, 只有自己動手觀察/思考才能真正領(lǐng)悟:
// 先來看看構(gòu)造函數(shù)到底是什么 // function Empty() {} function Empty() {} console.log(Function.prototype, Function.__proto__); // Object {} function Empty() {} console.log(Object.prototype, Object.__proto__); function F() {} // F {} function Empty() {} console.log(F.prototype, F.__proto__);
你可能已經(jīng)暈了, 我們來分解一下。
prototype
prototype輸出的格式為: 構(gòu)造函數(shù)名 原型
首先看下Object.prototype輸出了什么?
Object {} -> 前面的Object為構(gòu)造函數(shù)的名稱, 后面的那個表示原型, 這里是一個{}, 即一個Object對象的實例(空對象)
那么 F {} 我們就明白是什么意思了, F 就是構(gòu)造函數(shù)的名稱, 原型也是一個空對象
// 再來看看由構(gòu)造函數(shù)構(gòu)造出來的實例 var o = new Object(); // var o = {}; // undefined Object {} console.log(o.prototype, o.__proto__); function F() {} var i = new F(); // undefined F {} console.log(i.prototype, i.__proto__);
我們再深入一點, 定義下 F 的原型看看到底會發(fā)生些什么?
function F() {} F.prototype.a = function() {}; var i = new F(); // undefined F {a: function} console.log(i.prototype, i.__proto__);
這樣我們就清楚的看到 i 是由 F 構(gòu)造出來的, 原型是 {a: function}, 就是原本的空對象原型新增了一個 a 方法
我們再換一種情況, 完全覆蓋 F 的原型會怎么樣?
function F() {} F.prototype = { a: function() {} }; var i = new F(); // undefined Object {a: function} console.log(i.prototype, i.__proto__);
咦~ 為什么這里表明 i 是由 Object 構(gòu)造出來的? 不對吧!
因為我們完全將 F 的prototype覆蓋, 其實也就是將原型指定為對象{a: function}, 但這會造成原本的constructor信息丟失, 變成了對象{a: function}指定的constructor.
那么對象{a: function}的constructor是什么呢?
因為對象{a: function}其實就相對于
var o = {a: function() {}} // new了一個Object
那么o的constructor當(dāng)然是 Object 啦
我們來糾正下這個錯誤
function F() {} F.prototype = { a: function() {} } // 重新指定正確的構(gòu)造函數(shù) F.prototype.constructor = F; var i = new F(); // undefined F {a: function, constructor: function} console.log(i.prototype, i.__proto__);
現(xiàn)在又能得到正確的原型信息了~
原型鏈
然后來看看什么原型鏈又是個什么東西?
簡單的來講和OOP中的繼承關(guān)系(鏈)是一樣的, 一層一層往上找, 直至最終的 Object.prototype
最最關(guān)鍵的是要弄清楚JS中哪些東西是(實例)對象, 這個簡單了, JS中所有東西都是對象!
再要弄清楚就是任何一個對象都是有一個原型的!
那么我們來證明一下:
Object // 這是一個函數(shù), 函數(shù)是 Function 的實例對象, 那么就是由 Function 構(gòu)造出來的 Object.__proto__ == Function.prototype // 那么Object的原型, true // 這個是一個普通對象了, 因此屬于 Object 的實例 Function.prototype.__proto__ == Object.prototype // true // 這已經(jīng)是原型鏈的最頂層了, 因此最終的指向 null Object.prototype.__proto__ == null // true Function // 這也是一個函數(shù), 沒錯吧! Function.__proto__ == Function.prototype // true function A() {} // 這是一個自定義的函數(shù), 終歸還是一個函數(shù), 沒錯吧! A.__proto__ == Function.prototype // 任何函數(shù)都是 Function 的實例, 因此A的原型是? var a = new A() a.__proto__ == A.prototype // 實例a是由A構(gòu)造函數(shù)構(gòu)造出來的, 因此a的原型是由A的prototype屬性定義的 A.prototype.__proto__ == Object.prototype // 普通對象都是 Object 的示例
Prototype和__proto__
每一個對象都包含一個__proto__,指向這個的對象的“原型”。
類似的事情是,每一個函數(shù)都包含一個prototype,這個prototype對象干什么的了?
咱們看看如下代碼,用構(gòu)造函數(shù)來創(chuàng)建一個對象(上面是用字面量的形式創(chuàng)建對象)。
function Foo(){}; var foo = new Foo(); console.log(foo.__proto__);
試想想,這個foo對象的__proto__會指向什么?
一個包含constructor屬性的對象?看不太懂沒關(guān)系,把函數(shù)Foo的prototype屬性打印出來,對比一下就知道了。
function Foo(){}; var foo = new Foo(); console.log(foo.__proto__); console.log(Foo.prototype); console.log(foo.__proto__ === Foo.prototype);
原來,new出來的對象foo的__proto__就只指向函數(shù)Foo的prototype。
foo.__proto__ --> Foo.prototype
JS這么設(shè)計有何意義了?回憶下上面說的,在JS的世界中,對象不是根據(jù)類(模具)創(chuàng)建出來的,而是從原型(另一個對象)衍生出來的。
當(dāng)我們執(zhí)行new操作創(chuàng)建一個新的對象時,先不深入new操作的具體實現(xiàn),但有一點我們是肯定的——就是為新對象的__proto__指向一個原型對象。
就剛才這段代碼
function Foo(){}; var foo = new Foo();
foo.__proto__到底要指向誰了?你怎么不能指向Foo這個函數(shù)本身吧,雖然函數(shù)也是對象,這個有機(jī)會會詳細(xì)講。但如何foo.__proto__指向Foo固然不合適,因為Foo是一個函數(shù),有很多邏輯代碼,foo作為一個對象,繼承邏輯處理沒有任何意義,它要繼承的是“原型對象”的屬性。
所以,每個函數(shù)會自動生成一個prototype對象,由這個函數(shù)new出來的對象的__proto__就指向這個函數(shù)的prototype。
foo.__proto__ --> Foo.prototype
總結(jié)
說了這么多,感覺還是沒完全說清楚,不如上一張圖。我曾經(jīng)參考過其他網(wǎng)友的圖,但總覺得哪里沒說清楚,所以我自己畫了一張圖,如果覺得我的不錯,請點個贊!(老子可是費(fèi)了牛勁才畫出來)。
咱們就著這張圖,記住如下幾個事實:
1. 每個對象中都有一個_proto_屬性。
JS世界中沒有類(模具)的概念,對象是從另一個對象(原型)衍生出來的,所以每個對象中會有一個_proto_屬性指向它的原型對象。(參考左上角的那個用字面量形式定義的對象obj,它在內(nèi)存中開辟了一個空間存放對象自身的屬性,同時生成一個_proto_指向它的原型——頂層原型對象。)
2. 每個函數(shù)都有一個prototype屬性。
“構(gòu)造函數(shù)”為何叫構(gòu)造函數(shù),因為它要構(gòu)造對象。那么根據(jù)上面第一條事實,構(gòu)造出來的新對象的_proto_屬性指向誰了?總不能指向構(gòu)造函數(shù)自身,雖然它也是個對象,但你不希望新對象繼承函數(shù)的屬性與方法吧。所以,在每個構(gòu)造函數(shù)都會有一個prototype屬性,指向一個對象作為這個構(gòu)造函數(shù)構(gòu)造出來的新對象的原型。
3. 函數(shù)也是對象。
每個函數(shù)都有一些通用的屬性和方法,比如apply()/call()等。但這些通用的方法是如何繼承的呢?函數(shù)又是怎么創(chuàng)建出來的呢?試想想,一切皆對象,包括函數(shù)也是對象,而且是通過構(gòu)造函數(shù)構(gòu)造出來的對象。那么根據(jù)上面第二條事實,每個函數(shù)也會有_proto_指向它的構(gòu)造函數(shù)的prototype。而這個構(gòu)造函數(shù)的函數(shù)就是Function,JS中的所有函數(shù)都是由Function構(gòu)造出來的。函數(shù)的通用屬性與方法就存放在Function.prototype這個原型對象上。
- js核心基礎(chǔ)之構(gòu)造函數(shù)constructor用法實例分析
- JavaScript精煉之構(gòu)造函數(shù) Constructor及Constructor屬性詳解
- 不用構(gòu)造函數(shù)(Constructor)new關(guān)鍵字也能實現(xiàn)JavaScript的面向?qū)ο?/a>
- Javascript的構(gòu)造函數(shù)和constructor屬性
- javascript prototype的深度探索不是原型繼承那么簡單
- JavaScript為對象原型prototype添加屬性的兩種方式
- js中使用使用原型(prototype)定義方法的好處詳解
- javascript prototype原型詳解(比較基礎(chǔ))
- JS構(gòu)造函數(shù)與原型prototype的區(qū)別介紹
- js使用原型對象(prototype)需要注意的地方
- js構(gòu)造函數(shù)constructor和原型prototype原理與用法實例分析
相關(guān)文章
Ajax responseText解析json數(shù)據(jù)案例詳解
這篇文章主要介紹了Ajax responseText解析json數(shù)據(jù)案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08javascript實現(xiàn)網(wǎng)頁屏蔽Backspace事件,輸入框不屏蔽
這篇文章主要介紹了如何實現(xiàn)網(wǎng)頁屏蔽Backspace事件而輸入框不屏蔽,需要的朋友可以參考下2015-07-07淺談JavaScript中的Math.atan()方法的使用
這篇文章主要介紹了JavaScript中的Math.atan()方法的使用,是JS入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-06-06原生javascript 學(xué)習(xí)之js變量全面了解
下面小編就為大家?guī)硪黄鷍avascript 學(xué)習(xí)之js變量全面了解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-07-07