1、js中的原型和原型鏈
和其他的面向對象編程語言不同,最開始js並沒有引入class的概念,但是js中有在大量使用對象,爲了保證對象之間的聯繫,JavaScript引入了原型與原型鏈的概念。
1.1、什麼是原型
在js中,每一個構造函數都擁有一個prototype屬性,這個屬性指向一個對象,也就是原型對象。原型對象默認擁有一個constructor屬性,指向指向它的那個構造函數,每個對象都擁有一個隱藏的屬性[[prototype]],指向它的原型對象。原型對象就是用來存放實例中共有的那部分屬性。
1.2. 什麼是原型鏈
js中所有的對象都是由它的原型對象繼承而來。而原型對象自身也是一個對象,它也有自己的原型對象,這樣層層上溯,就形成了一個類似鏈表的結構,這就是原型鏈, 所有原型鏈的終點都是Object函數的prototype屬性.
1.3. 常見的題目
Array.__proto__ === Function.prototype // true
Array.__proto__.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true
function F(){}
Function.prototype.a = function(){
console.log('a1')
}
Function.prototype.b = function(){
console.log('b1')
}
Object.prototype.a = function(){
console.log('a2')
}
Object.prototype.b = function(){
console.log('b2')
}
let f = new F()
f.a()
f.b()
F.a()
F.b()
2、js中的繼承
js主要存在6種繼承方式。
2.1 原型鏈繼承
function Parent(){
this.parent = [1,2,3]
}
Parent.prototype.getName = function(){
console.log(this.parent)
}
function Child(child){
this.child = child
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
優點:
- 這種方式的優點很明顯,多個實例可以共享原型鏈上定義的屬性和方法。
缺點:
- 每個實例對引用類型的屬性的修改也會被其他實例共享,這不是我們想看到的
- 創建child的時候無法像構造函數傳參,child實例無法初始化父類屬性
2.1 構造函數繼承
function Parent(parent){
this.parent = parent
}
Parent.prototype.getName = function(){
console.log(this.parent)
}
function Child(name,child){
Parent.call(this,name)
this.child = child
}
優點:
- 克服了原型鏈繼承帶來的2個缺點
缺點:
- 子類無法繼承父類原型鏈上的方法;
- 每次生成子類實例都會執行一次父函數
2.2 組合繼承(常用的一種方式)
function Parent(parent){
this.parent = parent
}
Parent.prototype.getName = function(){
console.log(this.parent)
}
function Child(name,child){
Parent.call(this,name)
this.child = child
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
優點:解決了上面兩種方法的缺點
- 子類的構造函數會代替原型上的那個父類構造函數
- 每個新實例引入的構造函數屬性是私有的
缺點:
- 父類的構造函數執行兩次Parent.call(this,name)/new Parent()
- 子類的構造函數會代替原型上的那個父類構造函數
2.4 寄生組合繼承(常用)
function Parent(parent){
this.parent = parent
}
Parent.prototype.getName = function(){
console.log(this.parent)
}
function Child(name,child){
Parent.call(this,name)
this.child = child
}
function inheritPrototype(Parent, Child){
//創建父類原型的一個副本,把副本賦值給子類原型,而不是new Parent(),減少一次父構造函數的調用
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
}
解決了組合繼承中父類構造函數執行兩次的問題
繼承擴展的知識點
1. 是Object.create的原理
Object.create => function(obj){
var f = function(){};
f.prototype = obj;
return new f();
}
先在內部創建一個空構造函數
把構造函數的原型指向傳進來的obj對象
通過new創建對象並返回
2. Object.create,new Object(), {}三個的區別(百度)
Object.create是使用指定的原型proto對象及其屬性properties去創建一個新的對象,也就是我們新建的對象的原型對象是傳入create的對象。
值得注意的是當create的參數爲null的時候創建的新對象完全是一個空對象,沒有原型,也就是沒有繼承Object.prototype上的方法。
new Object()和{}本質上沒有區別,它們創建的新對象的原型指向的是 Object.prototype。
new Object()和{}在初始化的過程上有區別,前者是用構造函數實例化對象,後者是直接創建JSON對象,後者的初始化比較方便,可以在初始化的時候同時賦值。而兩種方法創建的對象在使用上都是一樣的,所以使用的時候都建議用後者,沒有new的過程比較高效。
3. new的原理
function myNew () {
//創建一個實例對象
let obj = new Object()
//傳入構造函數
let fn = Array.prototype.shift.call(arguments)
let args = Array.prototype.slice.call(arguments,1)
//實現繼承
obj.__proto__ = fn.prototype
//調用構造器,改變this指向到實例
let result = fn.apply(obj,args)
return typeof result === 'object' ? result : obj
}
- 創建一個新對象
- 將構造函數的作用域賦給新對象
- 調用構造器,改變this指向到實例,執行構造函數中的代碼(爲這個新對象添加屬性)
- 返回新對象。