JS對象創(chuàng)建與繼承的匯總梳理
引言
在 6 月更文中零零散散講了 JS 的對象創(chuàng)建和對象繼承,有工友對此還是表示疑惑,要注意:這是兩個不同但又相關的東西,千萬別搞混了!
這些文章是:
驀然回首,“工廠、構(gòu)造、原型”設計模式,正在燈火闌珊處
JS精粹,原型鏈繼承和構(gòu)造函數(shù)繼承的 “毛病”
本篇作為匯總篇,來一探究竟??!沖沖沖
對象創(chuàng)建
不難發(fā)現(xiàn),每一篇都離不開工廠、構(gòu)造、原型這 3 種設計模式中的至少其一!
讓人不禁想問:JS 為什么非要用到這種 3 種設計模式了呢??
正本溯源,先從對象創(chuàng)建講起:
我們本來習慣這樣聲明對象(不用任何設計模式)
let car= {
price:100,
color:"white",
run:()=>{console.log("run fast")}
}
當有兩個或多個這樣的對象需要聲明時,是不可能一直復制寫下去的:
let car1 = {
price:100,
color:"white",
run:()=>{console.log("run fast")}
}
let car2 = {
price:200,
color:"balck",
run:()=>{console.log("run slow")}
}
let car3 = {
price:300,
color:"red",
run:()=>{console.log("broken")}
}
這樣寫:
- 寫起來麻煩,重復的代碼量大;
- 不利于修改,比如當 car 對象要增刪改一個屬性,需要多處進行增刪改;
工廠函數(shù)
肯定是要封裝啦,第一個反應,可以 借助函數(shù) 來幫助我們批量創(chuàng)建對象~
于是乎:
function makeCar(price,color,performance){
let obj = {}
obj.price = price
obj.color= color
obj.run = ()=>{console.log(performance)}
return obj
}
let car1= makeCar("100","white","run fast")
let car2= makeCar("200","black","run slow")
let car3= makeCar("300","red","broken")
這就是工廠設計模式在 JS 創(chuàng)建對象時應用的由來~
到這里,對于【對象創(chuàng)建】來說,應該夠用了吧?是,在不考慮擴展的情況下,基本夠用了。
但這個時候來個新需求,需要創(chuàng)建 car4、car5、car6 對象,它們要在原有基礎上再新增一個 brand 屬性,會怎么寫?
第一反應,直接修改 makeCar
function makeCar(price,color,performance,brand){
let obj = {}
obj.price = price
obj.color= color
obj.run = ()=>{console.log(performance)}
obj.brand = brand
return obj
}
let car4= makeCar("400","white","run fast","benz")
let car5= makeCar("500","black","run slow","audi")
let car6= makeCar("600","red","broken","tsl")
這樣寫,不行,會影響原有的 car1、car2、car3 對象;
那再重新寫一個 makeCarChild 工廠函數(shù)行不行?
function makeCarChild (price,color,performance,brand){
let obj = {}
obj.price = price
obj.color= color
obj.run = ()=>{console.log(performance)}
obj.brand = brand
return obj
}
let car4= makeCarChild("400","white","run fast","benz")
let car5= makeCarChild("500","black","run slow","audi")
let car6= makeCarChild("600","red","broken","tsl")
行是行,就是太麻煩,全量復制之前的屬性,建立 N 個相像的工廠,顯得太蠢了。。。
構(gòu)造函數(shù)
于是乎,在工廠設計模式上,發(fā)展出了:構(gòu)造函數(shù)設計模式,來解決以上復用(也就是繼承)的問題。
function MakeCar(price,color,performance){
this.price = price
this.color= color
this.run = ()=>{console.log(performance)}
}
function MakeCarChild(brand,...args){
MakeCar.call(this,...args)
this.brand = brand
}
let car4= new MakeCarChild("benz","400","white","run fast")
let car5= new MakeCarChild("audi","500","black","run slow")
let car6= new MakeCarChild("tsl","600","red","broken")
構(gòu)造函數(shù)區(qū)別于工廠函數(shù):
- 函數(shù)名首字母通常大寫;
- 創(chuàng)建對象的時候要用到 new 關鍵字(new 的過程這里不再贅述了,之前文章有);
- 函數(shù)沒有 return,而是通過 this 綁定來實現(xiàn)尋找屬性的;
到此為止,工廠函數(shù)的復用也解決了。
構(gòu)造+原型
新的問題在于,我們不能通過查找原型鏈從 MakeCarChild 找到 MakeCar
car4.__proto__===MakeCarChild.prototype // true MakeCarChild.prototype.__proto__ === MakeCar.prototype // false MakeCarChild.__proto__ === MakeCar.prototype // false
無論在原型鏈上怎么找,都無法從 MakeCarChild 找到 MakeCar
這就意味著:子類不能繼承父類原型上的屬性
這里提個思考問題:為什么“要從原型鏈查找到”很重要?為什么“子類要繼承父類原型上的屬性”?就靠 this 綁定來找不行嗎?
于是乎,構(gòu)造函數(shù)設計模式 + 原型設計模式 的 【組合繼承】應運而生
function MakeCar(price,color,performance){
this.price = price
this.color= color
this.run = ()=>{console.log(performance)}
}
function MakeCarChild(brand,...args){
MakeCar.call(this,...args)
this.brand = brand
}
MakeCarChild.prototype = new MakeCar() // 原型繼承父類的構(gòu)造器
MakeCarChild.prototype.constructor = MakeCarChild // 重置 constructor
let car4= new MakeCarChild("benz","400","white","run fast")
現(xiàn)在再找原型,就找的到啦:
car4.__proto__ === MakeCarChild.prototype // true MakeCarChild.prototype.__proto__ === MakeCar.prototype // true
其實,能到這里,就已經(jīng)很很優(yōu)秀了,該有的都有了,寫法也不算是很復雜。
工廠+構(gòu)造+原型
但,總有人在追求極致。
上述的組合繼承,父類構(gòu)造函數(shù)被調(diào)用了兩次,一次是 call 的過程,一次是原型繼承 new 的過程,如果每次實例化,都重復調(diào)用,肯定是不可取的,怎樣避免?
工廠 + 構(gòu)造 + 原型 = 寄生組合繼承 應運而生
核心是,通過工廠函數(shù)新建一個中間商 F( ),復制了一份父類的原型對象,再賦給子類的原型;
function object(o) { // 工廠函數(shù)
function F() {}
F.prototype = o;
return new F(); // new 一個空的函數(shù),所占內(nèi)存很小
}
function inherit(child, parent) { // 原型繼承
var prototype = object(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
function MakeCar(price,color,performance){
this.price = price
this.color= color
this.run = ()=>{console.log(performance)}
}
function MakeCarChild(brand,...args){ // 構(gòu)造函數(shù)
MakeCar.call(this,...args)
this.brand = brand
}
inherit(MakeCarChild,MakeCar)
let car4= new MakeCarChild("benz","400","white","run fast")
car4.__proto__ === MakeCarChild.prototype // true MakeCarChild.prototype.__proto__ === MakeCar.prototype // true
ES6 class
再到后來,ES6 的 class 作為寄生組合繼承的語法糖:
class MakeCar {
constructor(price,color,performance){
this.price = price
this.color= color
this.performance=performance
}
run(){
console.log(console.log(this.performance))
}
}
class MakeCarChild extends MakeCar{
constructor(brand,...args){
super(brand,...args);
this.brand= brand;
}
}
let car4= new MakeCarChild("benz","400","white","run fast")
car4.__proto__ === MakeCarChild.prototype // true MakeCarChild.prototype.__proto__ === MakeCar.prototype // true
有興趣的工友,可以看下 ES6 解析成 ES5 的代碼:原型與原型鏈 - ES6 Class的底層實現(xiàn)原理 #22
對象與函數(shù)
最后本瓜想再談談關于 JS 對象和函數(shù)的關系:
即使是這樣聲明一個對象,let obj = {} ,它一樣是由構(gòu)造函數(shù) Object 構(gòu)造而來的:
let obj = {}
obj.__proto__ === Object.prototype // true
在 JS 中,萬物皆對象,對象都是有函數(shù)構(gòu)造而來,函數(shù)本身也是對象。
對應代碼中的意思:
- 所有的構(gòu)造函數(shù)的隱式原型都等于 Function 的顯示原型,函數(shù)都是由 Function 構(gòu)造而來,Object 構(gòu)造函數(shù)也不例外;
- 所有構(gòu)造函數(shù)的顯示原型的隱式原型,都等于 Object 的顯示原型,F(xiàn)unction 也不例外;
// 1. Object.__proto__ === Function.prototype // true // 2. Function.prototype.__proto__ === Object.prototype // true
這個設計真的就一個大無語,大糾結(jié),大麻煩。。。
只能先按之前提過的歪理解記著先:Function 就是上帝,上帝創(chuàng)造了萬物;Object 就是萬物。萬物由上帝創(chuàng)造(對象由函數(shù)構(gòu)造而來),上帝本身也屬于一種物質(zhì)(函數(shù)本身卻也是對象);
對于本篇來說,繼承,其實都是父子構(gòu)造函數(shù)在繼承,然后再由構(gòu)造函數(shù)實例化對象,以此來實現(xiàn)對象的繼承。
到底是誰在繼承?函數(shù)?對象?都是吧~~
小結(jié)
本篇由創(chuàng)建對象說起,講了工廠函數(shù),它可以做一層最基本的封裝;
再到,對工廠的拓展,演進為構(gòu)造函數(shù);
再基于原型特點,構(gòu)造+原型,得出組合繼承;
再追求極致,講到寄生組合;
再講到簡化書寫的 Es6 class ;
以及最后對對象與函數(shù)的思考。
更多關于JS對象創(chuàng)建繼承的資料請關注腳本之家其它相關文章!
相關文章
arrify 轉(zhuǎn)數(shù)組實現(xiàn)示例源碼解析
這篇文章主要為大家介紹了arrify 轉(zhuǎn)數(shù)組實現(xiàn)示例源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
原型和原型鏈 prototype和proto的區(qū)別詳情
原型是function對象下的屬性,它定義了構(gòu)造函數(shù)的共同祖先,也就是一個父子級的關系,子對象會繼承父對象的方法和屬性,每個實例對象下都有__proto__屬性,通過屬性__proto__指向構(gòu)造函數(shù)的原型對象,當?shù)竭_末端時,返回null,這樣一層一層向頂端查找,就形成了原型鏈2021-10-10

