js 原型鏈的那些事兒

前言

通常談到js中的繼承的時候,必定會遇到原型,原型鏈的問題,原型裏面又有prototype,__proto__,constructor屬性,講到這兒,不少同窗是否是都一頭霧水,傻傻分不清楚,由於工做中用到的地方是少之又少,再加上es6又出了extends語法糖,更加不用理會之,可是對於理解繼承,原型和原型鏈是很重要的,理解函數和對象,理解prototype和__proto__,construct之間的關係尤其重要,不過本文對繼承不予以深究,另起一篇文章寫之,今天咱們只討論js中的原型和原型鏈。javascript

首先,容在下提出一個問題。
到底prototype和__proto__是否是指同一個東西呢?
答案天然非也。java

還有一個問題,就是ie8,9下是沒有__proto__的概念的,如何解決這個問題?
這個問題在這篇文章結束以前會說明。es6

如今咱們先來分析js中的對象。
js中對象是很重要的,所謂萬物皆對象,可是js中對象分爲兩種,普通對象和函數對象函數

普通對象和函數對象

先來看幾個例子this

function f1(){};
 var f2 = function(){};
 var f3 = new Function('str','console.log(str)');

 var o3 = new f1();
 var o1 = {};
 var o2 = new Object();

 console.log(typeof Object); //function
 console.log(typeof Function); //function
 console.log(typeof o1); //object
 console.log(typeof o2); //object
 console.log(typeof o3); //object
 console.log(typeof f1); //function
 console.log(typeof f2); //function
 console.log(typeof f3); //function

在上面的例子中,o1 o2 o3 爲普通對象,f1 f2 f3 爲函數對象。
那麼怎麼區分普通對象和函數對象呢?
其實很簡單,凡是經過new Function()建立的對象都是函數對象,其餘的都是普通對象。f1,f2,歸根結底都是經過 new Function()的方式進行建立的。Function Object 也都是經過 New Function()建立的。.net

原型對象

接下來先說一下原型對象
在js中,每當定義一個對象的時候,對象中都會包含一些預約義的屬性。其中函數對象的一個屬性就是原型對象prototype
普通對象沒有prototype,只有__proto__屬性,看下面的例子prototype

function f1(){};
 console.log(f1.prototype) //{constructor:ƒ f1(),__proto__:Object}
 console.log(typeof f1.prototype) //Object
 console.log(typeof Function.prototype) // Function,這個特殊,由於Function是經過new Function建立的
 console.log(typeof Object.prototype) // Object
 console.log(typeof Function.prototype.prototype) //undefined

從console.log(f1.prototype) //{constructor:ƒ f1(),__proto__:Object}能夠看出f1.prototype就是f1的一個實例對象,就是在建立f1的時候,建立了一個它的實例對象,並把它賦給了prototype原型對象。代碼以下設計

const temp = new f1();
f1.prototype = temp;

那麼看到這兒,你們確定會說,爲何要有原型對象?這個原型對象有什麼用?
剛開始我就提到了,繼承裏會用到。看下下面的代碼:code

function Cat(name){
    this.name = name;
}
Cat.prototype.getName = function(){
    alert(this.name);
}
const qqq = new Cat('qqq');
qqq.getName();//qqq

從上面的代碼中能夠看出,經過給Cat的原型對象添加屬性方法,那麼Cat的實例都會擁有這個方法並能夠調用之。有同窗可能會有疑問,爲何在原型對象上添加了屬性方法,它的實例就也能夠擁有這個方法呢?這就牽扯到接下來講到的原型鏈了。對象

原型鏈

首先,js的對象(普通對象和函數對象)都會有__proto__屬性,指向建立它的構造函數的原型對象,好比上面的例子

qqq.__proto__ === Cat.prototype;//true
Cat.prototype.__proto__ === Object.prototype;//true
Object.prototype.__proto__//null

這就造成了原型鏈,會一直查找原型對象的__proto__屬性,直到爲null。
有幾個比較特殊的例子,來看一下
1.Object.__proto__ === Function.prototype // true
Object是函數對象,是經過new Function()建立,因此Object.__proto__指向Function.prototype。
2.Function.__proto__ === Function.prototype // true
Function 也是對象函數,也是經過new Function()建立,因此Function.__proto__指向Function.prototype。
3.Function.prototype.__proto__ === Object.prototype //true
Function.prototype是個函數對象,理論上他的__proto__應該指向 Function.prototype,就是他本身,本身指向本身,沒有意義。
JS一直強調萬物皆對象,函數對象也是對象,給他認個祖宗,指向Object.prototype。Object.prototype.__proto__ === null,保證原型鏈可以正常結束。

constructor

constructor是這麼定義的。
在 Javascript 語言中,constructor 屬性是專門爲 function 而設計的,它存在於每個 function 的prototype 屬性中。這個 constructor 保存了指向 function 的一個引用。

Cat.prototype.constructor === Cat //true
 Function.prototype.constructor === Function //true
 Object.prototype.constructor === Object //true

這裏也有要注意的
1.注意Object.constructor===Function;//true 自己Object就是Function函數構造出來的
2.如何查找一個對象的constructor,就是在該對象的原型鏈上尋找碰到的第一個constructor屬性所指向的對象

總結

1.原型和原型鏈是實現繼承的一種方式
2.原型鏈真正的繼承是靠__proto__,而不是prototype,且看如下這個例子

var animal = function(name){
   this.name = name;
}
var cat = function(){};
animal.say = 'lalala';
cat.prototype = animal;
var ca = new cat();
console.log(cat.say);//undefined
console.log(ca.say);//lalala

從輸出結果能夠看出,雖然cat的prototype指向了animal,可是讀取say屬性的時候並不會根據prototype找,ca自己雖然也沒有say屬性,可是看下面這段代碼

ca.__proto__ = cat.prototype
cat.prototype = animal

因此ca.say輸出lalala
3.以前遺留的問題,關於兼容ie的__proto__
ie9有Object.getPrototypeof()方法

function a(){console.log("aaa")};
   const b = new a();
   Object.getPrototypeof(b) === b.__proto__//true

ie8不支持Object.getPrototypeof方法,能夠結合constructor和prototype

function a(){console.log("aaa")};
   const b = new a();
   b.constructor.prototype === b.__proto__//true

最後再思考下new()過程都作了些什麼?(好比new A())

  1. 建立新對象var obj = {};
  2. 將新對象的construct屬性指向構造函數,__proto__屬性指向構造函數的prototype
  3. 執行構造函數,A.call(obj),將this指向obj
  4. 返回新對象(注意:若是構造函數返回this,基本類型或者不返回,就是返回新對象,若是返回引用類型,就是返回引用類型)

好了,今天就先寫這麼多,明天結合今天的原型和原型鏈總結下繼承以及每一個繼承的優缺點~~~

參考資料

相關文章
相關標籤/搜索