1. 背景
前端面試經(jīng)常會問到這個問題,這是一個比較讓人回答起來比較頭疼的問題,為什么頭疼呢,因為javaScript在被創(chuàng)造出來的時候就具備了和現(xiàn)有的強語言有了很大的區(qū)別,因為出發(fā)點就是一個腳本語言,而并非想java等語言,所以js的原型就相對比較復雜。想要搞明白原型和原型鏈首先我們需要搞明白兩個問題:
- 搞清楚
prototype
與__proto__
是什么- 搞清楚
prototype
與__proto__
的關系和指向問題
2. 原型的定義和作用
這個面試要考的
定義:原型就是指函數(shù)的
prototype
屬性所引用的對象,這個對象就是原型
作用:原型的作用就是為了實現(xiàn)對象之間的數(shù)據(jù)共享
es5獲取對象原型的標準方法是Object.getPrototypeOf()
舉例來說明一下原型的作用
用構造函數(shù)生成實例對象,有一個缺點,那就是無法共享屬性和方法。
function DOG(name) {
this.name = name;
this.species = '犬科';
}
var dogA = new DOG('大毛');
var dogB = new DOG('二毛');
dogA.species = '貓科';
console.log(dogA.species) //=>貓科
console.log(dogB.species) //=>貓科
console.log(dogA)
console.log(dogB)
運行結果:結論:每一個實例對象,都有自己的屬性和方法的副本。這不僅無法做到數(shù)據(jù)共享,也是極大的資源浪費。
優(yōu)化代碼使用prototype
function DOG(name) {
this.name = name;
}
DOG.prototype = { species : '犬科' };
var dogA = new DOG('大毛');
var dogB = new DOG('二毛');
dogA.species = '貓科';
console.log(dogA.species) //=>貓科
console.log(dogB.species) //=>貓科
console.log(dogA)
console.log(dogB)
結果:1.實例對象需要共享的屬性和方法,都放在這個對象里面;那些不需要共享的屬性和方法,就放在構造函數(shù)里面
2.實例對象一旦創(chuàng)建,將自動引用prototype對象的屬性和方法。也就是說,實例對象的屬性和方法,分成兩種,一種是本地的,另一種是引用的。
3. prototype
和__proto__
的區(qū)別
先搞清楚一個概念
所有的構造函數(shù)都是需要用new來實例化
那么Object,F(xiàn)unction,Array,String,String,Data,Boolean等等都是構造函數(shù)
測試一下
function fun1() {}
var obg = {}
console.dir(fun1)
console.log("=======================")
console.dir(obg)
console.log("=======================")
console.dir(Array)
什么意思呢?就是為了說明一個問題 (很重要)
函數(shù)擁有
prototype
和__proto__
屬性
對象只擁有__proto__
屬性
為什么函數(shù)具有prototype
屬性,上面已經(jīng)簡單說明了說明,詳情請看Javascript繼承機制的設計思想
說明:__proto__
不是一個規(guī)范屬性, 但是這個屬性已在ES6語言規(guī)范中標準化,不推薦使用,但是我們可以利用它來學習原型的原理,建議使用Object.getPrototypeOf/Reflect.getPrototypeOf
和Object.setPrototypeOf/Reflect.setPrototypeOf
,但是現(xiàn)代瀏覽器基本都支持包括 IE11
該屬性沒有寫入 ES6 的正文,而是寫入了附錄,原因是proto前后的雙下劃線,說明它本質上是一個內部屬性,而不是一個正式的對外的 API ,只是由于瀏覽器廣泛支持,才被加入了 ES6
5. 原型鏈
定義:當原型對象找不到需要的屬性時通過內部屬性
prototype
或__proto__
向上查找,一次類推就構成了一個鏈,這個構成對象的鏈就叫做原型鏈
Object.prototype是對象的最終原型,絕大多數(shù)對象最終都會繼承自Object.prototype,而Object.prototype的原型是null。
JavaScript的原型鏈邏輯遵從以下通用規(guī)則
通用規(guī)則
1.對象有
__proto__
屬性,函數(shù)有prototype
屬性
2.對象是由函數(shù)生成的
3.生成對象時, 對象的__proto__
屬性指向的就是函數(shù)的prototype
屬性
-
一般情況
// 創(chuàng)建空對象時,實際上是我們用Object函數(shù)來生成的對象
var obj = {}
console.log(obj.__proto__ === Object.prototype) //true
// 我們也可以顯式的使用Object函數(shù)來創(chuàng)建對象:
var obj1 = Object()
console.log(obj1.__proto__ === Object.prototype) //true
var obj2 = new Object()
console.log(obj2.__proto__ === Object.prototype) //true
// 當我們使用函數(shù)來創(chuàng)建自定義的對象時,上面的規(guī)則同樣適用:
function Fun() { }
var f1 = new Fun()
console.log(f1.__proto__ === Fun.prototype) //true
-
函數(shù)對象
//函數(shù)作為一種特殊的對象, 如果函數(shù)做為對象使用,上面的規(guī)則依然有效
//函數(shù)對象是由Function函數(shù)生成的
function Func(){}
console.log(Func.__proto__===Function.prototype) //true
//函數(shù)本身也是由Function本身
console.log(Function.__proto__===Function.prototype) //true
Object函數(shù)既然是函數(shù),那生成它的函數(shù)自然是Function函數(shù),上面的規(guī)則依然有效
console.log(Object.__proto__===Function.prototype) //true
-
特殊情況一
先來打印下Object.prototype看看
console.log(Object.prototype)
打印出一堆方法,其實這些方法都是javaScript的默認方法,但是奇怪的是Object.prototype 屬性中沒有__proto__
屬性
console.log(Object.prototype.__proto__) // null
這就是Object函數(shù)特殊情況了:Object.prototype.proto === null,我們知道,這就是JavaScript原型鏈的終點了。如果Object.prototype.proto === Object.prototype,想想原型鏈沒有了終點,太可怕了,所以在原型鏈的最頂端,JavaScript規(guī)定了Object.prototype.proto === null。
-
特殊情況二
先來打印下Function.prototype看看
console.log(Function.prototype)
// ? () { [native code] }
函數(shù)內部是[native code],也就是系統(tǒng)編譯好的二進制代碼函數(shù),這就暫時沒法深究了。現(xiàn)在讓我們來看看我們最關心的proto屬性:
console.log(Function.prototype.__proto__)
打印的結果竟然和Object.prototype結果一樣,測試下
console.log(Function.prototype.__proto__ === Object.prototype ) //true
竟然真的一樣。又回到了第一種特使情況 我的那個天啊
這就是原型鏈懂了嗎
5. 原型繼承
我們平時一般說的繼承,是原型繼承,不是改變構造函數(shù)的原型
寫個簡單的例子看看
function User(){}
User.prototype.name = function(){
console.log('我是User');
}
function Admin(){}
Admin.prototype = User.prototype
var admin = new Admin()
admin.name()
//我是User
看起來好像沒有問題,我們拿到了User的name方法,但是我在如果重新一下Admin的name方法呢
function User(){}
User.prototype.name = function(){
console.log('我是User');
}
function Admin(){}
Admin.prototype = User.prototype
Admin.prototype.name = function(){
console.log('我是Admin');
}
var admin = new Admin()
var user = new User()
admin.name() //我是Admin
user.name() //我是Admin
問題來的竟然user.name()也打印出來了我是Admin,這是一件非常糟糕的事情,
那要怎么做呢
function User(){}
User.prototype.name = function(){
console.log('我是User');
}
function Admin(){}
// 記住一定原型繼承,不是改變構造函數(shù)的原型
Admin.prototype.__proto__ = User.prototype
Admin.prototype.name = function(){
console.log('我是Admin');
}
var admin = new Admin()
var user = new User()
admin.name() //我是Admin
user.name() //我是User
6. 如何實現(xiàn)原型繼承
學習繼承那就先要有個父類,在js中實際上是沒有類的概念,在es6中class雖然很像類,但實際上只是es5上語法糖而已
//這是一個基類或者說是父類
function Admin(name) {
this.name = name || 'admin'
this.add = function () {
console.log('我是:' + this.name + ",我有新增功能");
}
}
Admin.prototype.Edit = function (val) {
console.log('我是:' + this.name + ",我的功能是編輯" + val);
}
1.原型鏈繼承
將父類的實例作為子類的原型
function User(){
this.name = '李四'
}
User.prototype = new Admin()
User.prototype.constructor = User
var user = new User()
user.add() // 我是:李四,我有新增功能
console.log(user instanceof Admin) //true
優(yōu)點:簡單易于實現(xiàn),父類的新增的實例與屬性子類都能訪問
缺點:
1.可以在子類中增加實例屬性,如果要新增加原型屬性和方法需要在new 父類構造函數(shù)的后面;
2.無法實現(xiàn)多繼承
3.創(chuàng)建子類實例時,不能向父類構造函數(shù)中傳參數(shù)
無法實現(xiàn)多繼承