目錄
- 對象是什麼
- 構造函數
- 原型對象
- 實現繼承以及不同繼承方式
對象
為什麼要面向對象編程
代碼邏輯遷移更加靈活、代碼複用性高、高度模塊化
構造函數
function Person(name) {
this.name = name
this.getName = function(name) {
return name
}
}
const person = new Person()
- 函數體內部的
this指向生成的實例對象 - 生成對象用
new關鍵詞進行實例化 - 可以做初始化傳參
new實例的過程
- 創建一個空對象,作為返回的對象實例
- 將生成空對象的原型對象指向了構造函數的prototype屬性
- 將當前實例對象賦值給內部的
this - 執行構造函數初始化代碼
function myNew(Fn, params) {
// 創建一個空對象,作為返回的對象實例,將實例對象的__proto__屬性指向構造函數的原型(Fn.prototype)
const obj = Object.create(Fn.prototype)
// 將當前實例對象賦值給內部的`this`,執行構造函數初始化代碼
const result = Fn.apply(obj, params)
return (result && (typeof result === 'object' || typeof result === 'function')) ? result : obj
}
如何創建沒有news實例化的構造函數(不被外部感知)
function Person() {
// 判斷是否為new實例對象
if(!this instanceof Person) {
return new Person()
}
this.name = "Tom";
this.getName = function() {
return this.name
}
}
const person = Person();
使用構造函數的缺點
- 構造函數中的方法,會存在每一個生成的實例對象中,重複掛載其實是會導致資源浪費。
所以要使用原型對象來解決上面的問題。
原型對象
每個函數都有一個屬性——prototype。這個prototype的屬性值是一個對象(屬性的集合),默認只有一個叫做constructor的屬性,指向這個函數本身。 如下圖所示:
上圖中,SuperType是一個函數,右側的方框就是它的原型。
原型既然作為對象(屬性的集合),除了constructor外,還可以自定義許多屬性,比如下面這樣的:
function Person(name) {
this.name = name;
}
// 賦值原型對象的屬性做為實例對象上的繼承屬性,避免重複掛載方法
Person.prototype.getName = function() {
return this.name
}
“隱式原型”proto
每個對象都有一個__proto__屬性,指向創建該對象的構造函數的prototype。
注意: Object.prototype確實一個特例——它的__proto__指向的是null,切記切記!!!
從上圖可以看出:自定義函數Foo.__proto__指向Function.prototype,Object.__proto__指向Function.prototype。
但是,為什麼有Function.__proto__指向Function.prototype呢?
其實原因很簡單:Function也是一個函數,函數是一種對象,也有__proto__屬性。既然是函數,那麼它一定是被Function創建。所以Function是被自身創建的。所以它的__proto__指向了自身的Prototype
最後一個問題:Function.prototype指向的對象,它的__proto__是不是也指向Object.prototype?
答案是肯定的。因為Function.prototype指向的對象也是一個普通的被Object創建的對象,所以也遵循基本的規則。
繼承
- 原型鏈繼承
function Person() {
}
Person.prototype.getName = function() {
return this.name
}
function Man() {
}
Man.prototype = new Person();
Man.prototype.constructor = Man
本質: 重新原型對象的方式,將父對象的屬性和方法,作為子對象原型對象的屬性和方法
缺點:
- 父類屬性一旦賦值給子類的原型屬性,此時屬性屬於子類的共享屬性了
- 實例化子類時,無法向父類傳參
- 構造函數繼承
function Person() {
}
Person.prototype.getName = function() {
return this.name
}
function Man(arg) {
Person.call(this,arg)
}
// 解決了共享屬性的問題 + 子向父傳參問題
- 組合繼承
function Person() {
}
Person.prototype.getName = function() {
return this.name
}
function Man(arg) {
Person.call(this,arg)
}
Man.prototype = new Person();
Man.prototype.constructor = Man
缺點:無論何種場景,都會調用2次父構造函數
- 初始化子類原型
- 子類調用函數內部call父類的時候
- 寄生組合繼承
function Person() {
}
Person.prototype.getName = function() {
return this.name
}
function Man(arg) {
Person.call(this,arg)
}
Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man
如何實現多重繼承
function Person(name) {
this.name = name
}
Person.prototype.getName = function() {
return this.name
}
function Worker(salary) {
this.salary = salary
}
Worker.prototype.getSalary = function() {
return this.salary
}
function Man(arg) {
Person.call(this,arg)
Worker.call(this,arg)
}
Man.prototype = Object.create(Person.prototype);
Object.assign(Man.prototype, Worker.prototype);
Man.prototype.constructor = Man
參考文章
- new 實例化對象過程
- 【JS】深入理解JS原型和繼承