先看一下下面的代碼,如果你很簡單就能理解,那么可以跳過這一篇文章了。
var x = new y();
// 1、所有的實例的原型都是由其構(gòu)造函數(shù)的原型對象:
x.__proto__ === y.prototype
// 2、所有的構(gòu)造函數(shù)都是 Function 的實例,有1可知:
y.__proto__ === Function.prototype
// 3、因為構(gòu)造函數(shù)的原型對象也是對象,所以由1可知:
y.prototype.__proto__ === Object.prototype
// 4、Object 是頂級對象,所以它不符合第3條規(guī)則:
Object.prototype.__proto__ // is null
沒看明白的我們繼續(xù)。。。
想要理解原型鏈,先知原型為何物
先了解一下原型模式吧:原型模式
在原型模式中我們可以利用過一個原型對象來指明我們所要創(chuàng)建對象的類型,然后通過復(fù)制這個對象的方法來獲得與該對象一模一樣的對象實例,所以:
- 實例是由原型拷貝而來的。
再其次,我們都知道,JS中的聲明,實際是 new 了一個構(gòu)造函數(shù),比如:
var a = {};
// 實際是
var a = new Object();
可知,a 是一個對象實例,所以 new Object 構(gòu)造函數(shù)做了什么?
- 構(gòu)造函數(shù)拷貝了相應(yīng)的對象并返回了這個拷貝,即拷貝原型返回了一個實例。
- 構(gòu)造函數(shù)存在一個原型對象,這個原型對象就是構(gòu)造函數(shù)拷貝的對象。
- 構(gòu)造函數(shù)的 prototype 字段所指向它要拷貝的原型對象,即“構(gòu)造函數(shù).prototype”
構(gòu)造函數(shù).prototype ---> 以 String 為例就是 String.prototype
由上述可以,實例的原型就是構(gòu)造函數(shù)上的原型對象,而JS中,為了實現(xiàn)繼承,實例的 __proto__ 字段所指向它的原型,,即“實例.__proto__”。
實例.__proto__ ---> 以 var a = {} 為例,就是 a.__proto__
構(gòu)造函數(shù).prototype 指向它要拷貝的原型對象,而 實例.__proto__ 指向它的原型,都指向同一個對象,故:
var x = new y();
x.__proto__ === y.prototype
現(xiàn)在應(yīng)該知道原型是什么了吧。
函數(shù)實例
其實構(gòu)造函數(shù)是一個函數(shù)實例,所以可以知道:
Object.__proto__ === Function.prototype // true
Array.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true
// 特殊的 Function
Function.__proto__ === Function.prototype // true
萬物皆對象
再研究一下,因為函數(shù)的 prototype 一個對象實例,所以構(gòu)造函數(shù)的原型對象的原型屬于誰呢?
答案是:Object ,萬物皆對象。
Array.prototype.__proto__ === Object.prototype // true
String.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true
// 物皆對象,所以 Object 的原型對象已經(jīng)是頂級了
Object.prototype.__proto__ // null
所以,這里已經(jīng)解釋了最開始的三行代碼。
順帶提一下:constructor
以上就是原型和原型對象的概念,順便提一個原型對象上的屬性:constructor ,它指向創(chuàng)造這個原型對象的函數(shù),即:
// 函數(shù)的原型對象的構(gòu)造函數(shù)就是它自己
Array.prototype.constructor === Array // true
String.prototype.constructor === String // true
Function.prototype.constructor === Function // true
Object.prototype.constructor === Object // true
// 實例的原型的構(gòu)造函數(shù)就是聲明的這個實例
var a = [], b = {}, c = '';
a.__proto__.constructor === Array // true
b.__proto__.constructor === Object // true
c.__proto__.constructor === String // true
現(xiàn)在來理解一下原型鏈。
何為原型鏈
其實上面有代碼已經(jīng)初現(xiàn)端倪了:
Array.prototype.__proto__ === Object.prototype // true
構(gòu)造函數(shù)的原型對象的原型,是不是有些拗口,這其實就是原型鏈,再來一個看幾個代碼:
var a = [];
a.__proto__ // = Array.prototype
a.__proto__.constructor // = Array
a.__proto__.constructor.prototype // = Array.prototype
有一萬中寫法來顯示一些東西,其實原型鏈的大致概念是:
- 實例使用屬性和方法會依次檢索本身和其原型,而原型也屬實例,故也會檢索本身和其原型,所以會形成一級一級地向上檢測查找的鏈式結(jié)構(gòu),這種結(jié)構(gòu)就稱原型鏈。
所以上面的例子可以這樣簡寫:
var a = [], b = {}, c = '';
a.constructor === Array // true
b.constructor === Object // true
c.constructor === String // true
因為 a 沒有 constructor ,所以會去找原型上的 constructor ,so...
再看一個例子,因為原型鏈的存在,所以可以在構(gòu)造函數(shù)的原型對象上添加方法,這樣所有由此函數(shù)創(chuàng)建的實例都可以調(diào)用此方法:
function a(){}
a.prototype.say = function(x){ console.log(x) }
var b = new a();
var c = new a();
b.say(‘hello’); // hello
c.say(‘你好’); // 你好
簡單提一下 ES6 的類的定義
上面的寫法怎么看會有點奇怪,ES6 新增了 class 的聲明,可以這樣寫一個類:
// 定義類
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`你好,我是${this.name},我今年${this.age}歲`);
}
}
// 實例
var my = new User('jack', 22);
my.sayHello(); // 你好,我是jack,我今年22歲
對比一下ES5代碼:
// 定義類
function User(name, age){
this.name = name;
this.age = age;
}
User.prototype.sayHello = function(){
console.log(`你好,我是${this.name},我今年${this.age}歲`);
}
// 實例
var my = new User('jack', 22);
my.sayHello(); // 你好,我是jack,我今年22歲
Class 定義其本身是個語法糖,可以用ES5來實現(xiàn),具體參考此處
OK,說完了,原型鏈就這一點點。