JS 面向?qū)ο笤斀?/h1>

寫在前面

既然是淺談,就不會從原理上深度分析芦疏,只是幫助我們更好地理解...

面向?qū)ο笈c面向過程

面向?qū)ο蠛兔嫦蜻^程是兩種不同的編程思想禀酱,剛開始接觸編程的時(shí)候,我們大都是從面向過程起步的眠菇,畢竟像我一樣,大家接觸的第一門計(jì)算機(jī)語言大概率都是C語言袱衷,C語言就是一門典型的面向過程的計(jì)算機(jī)語言捎废。面向過程主要是以動詞為主,解決問題的方式是按照順序一步一步調(diào)用不同的函數(shù)致燥。面向?qū)ο笫且悦~為主登疗,將問題抽象出具體的對象,而這個(gè)對象有自己的屬性和方法嫌蚤,在解決問題的時(shí)候辐益,是將不同的對象組合在一起使用。

//面向過程裝大象1.開(冰箱)2.(大象)裝進(jìn)(冰箱)3.關(guān)(冰箱)

//面向?qū)ο笱b大象1. 冰箱.開門()2. 冰箱.裝進(jìn)(大象)3. 冰箱.關(guān)門()

從這個(gè)例子可以看出脱吱,面向?qū)ο笫且灾髦^為主智政,將主謂堪稱一個(gè)一個(gè)的對象,然后對象有自己的屬性和方法箱蝠。面向?qū)ο笫且怨δ軄韯澐謫栴}的续捂,而不是步驟。功能上的統(tǒng)一保證了面向?qū)ο笤O(shè)計(jì)的可擴(kuò)展性宦搬,解決了代碼重用性的問題牙瓢。這也是在漫長的程序設(shè)計(jì)的發(fā)展過程中得到的驗(yàn)證結(jié)果,面向?qū)ο蟮木幊趟枷胼^之于面向過程較好一點(diǎn)床三。

封裝

面向?qū)ο笥蟹庋b一罩、繼承和多態(tài)三大特性。
封裝:就是把事物封裝成撇簿,隱藏事物的屬性和方法的實(shí)現(xiàn)細(xì)節(jié)聂渊,僅對外公開接口。

在ES5中四瘫,并沒有class的概念汉嗽,但是由于js的函數(shù)級作用域(函數(shù)內(nèi)部的變量函數(shù)外訪問不到)。所以我們可以模擬class找蜜。在es5中饼暑,類其實(shí)就是保存了一個(gè)函數(shù)的變量,這個(gè)函數(shù)有自己的屬性和方法洗做。將屬性和方法組成一個(gè)類的過程就是封裝弓叛。

1.通過構(gòu)造函數(shù)添加

JavaScript提供了一個(gè)構(gòu)造函數(shù)(Constructor)模式,用來在創(chuàng)建對象時(shí)初始化對象诚纸。構(gòu)造函數(shù)其實(shí)就是普通的函數(shù)撰筷,只不過有以下的特點(diǎn)

①首字母大寫(建議構(gòu)造函數(shù)首字母大寫,即使用大駝峰命名畦徘,非構(gòu)造函數(shù)首字母小寫)

②內(nèi)部使用this③使用new生成實(shí)例

通過構(gòu)造函數(shù)添加屬性和方法實(shí)際上也就是通過this添加的屬性和方法毕籽。因?yàn)閠his總是指向當(dāng)前對象的,所以通過this添加的屬性和方法只在當(dāng)前對象上添加井辆,是該對象自身擁有的关筒。所以我們實(shí)例化一個(gè)新對象的時(shí)候,this指向的屬性和方法都會得到相應(yīng)的創(chuàng)建杯缺,也就是會在內(nèi)存中復(fù)制一份蒸播,這樣就造成了內(nèi)存的浪費(fèi)。

function Cat(name, color) {

? ? this.name = name;

? ? this.color = color;

? ? this.eat = (() = >{

? ? ? ? console.log("fish!")

? ? })

} //生成實(shí)例

var cat1 = new Cat("tom", "gray")

通過this定義的屬性和方法萍肆,我們實(shí)例化對象的時(shí)候斗湖重新復(fù)制一份

2.通過原型prototype封裝

在類上通過this的方式添加屬性和方法會導(dǎo)致內(nèi)存浪費(fèi)的現(xiàn)象廉赔,有什么辦法可以讓實(shí)例化的類所使用的屬性和方法 直接使用指針 指向同一個(gè)屬性和方法。

這就是原型的方法

JavaScript規(guī)定匾鸥,每一個(gè)構(gòu)造函數(shù)都有一個(gè)prototype屬性蜡塌,指向另一個(gè)對象。這個(gè)對象的所有屬性和方法勿负,都會被構(gòu)造函數(shù)的實(shí)例繼承馏艾。也就是說,對于那些不變的屬性和方法奴愉,我們可以直接將其添加在類的prototype對象上琅摩。

function Cat(name, color) {

? ? this.name = name;

? ? this.color = color;

}

Cat.prototype.type = "英短";

Cat.prototype.eat = (() = >{

? ? alert("fish!")

})

//生成實(shí)例? ? ? ??

var cat1 = new Cat('Tom', 'gray');? ? ? ??

var cat2 = new Cat('Kobe', 'purple');? ? ? ??

console.log(cat1.type); //英短? ? ? ??

cat2.eat(); //fish!

這時(shí)所有實(shí)例的type屬性和eat()方法,其實(shí)都是同一個(gè)內(nèi)存地址锭硼,指向prototype對象房资,因此就提高了運(yùn)行效率。但是這樣做也有弊端檀头,因?yàn)閷?shí)例化的對象的原型都是指向同一內(nèi)存地址轰异,改動其中一個(gè)對象的屬性可能會影響到其他的對象

es6中的類和封裝

es6聲明一個(gè)類
①構(gòu)造器:構(gòu)造器內(nèi)創(chuàng)建自有屬性
②方法:聲明類實(shí)例具有的方法

class Cat { //等價(jià)于Cat構(gòu)造器

? ? constructor(name) {

? ? ? ? this.name = name;

? ? } //更加簡單的聲明類的內(nèi)部函數(shù)

? ? //等價(jià)于?

? ? Cat.prototype.eat

? ? eat() {

? ? ? ? console.log("fish!");

? ? }

} //生成實(shí)例var cat1 = new Cat("tom");

cat1.eat(); //fish!

console.log(cat1 instanceof Cat); //true

console.log(cat1 instanceof Object); //true

console.log(typeof Cat); //function

console.log(typeof Cat.prototype.eat); //function

從上面class聲明的Cat為例:Cat類是一個(gè)具有構(gòu)造函數(shù)行為的函數(shù)岖沛,其中內(nèi)部方法eat實(shí)際上就是Cat.prototype.eat()
所以說es6的class封裝類,本質(zhì)上是es5實(shí)現(xiàn)方式的語法糖
最主要的區(qū)別在于搭独,class類的屬性是不可重新賦值和不可枚舉的婴削,Cat.prototype就是一個(gè)只讀屬性

class和自定義類型的區(qū)別
(1)class的聲明不會提升,與let類似
(2)class的聲明自動運(yùn)行于嚴(yán)格模式之下
(3)class聲明的方法不可枚舉
(4)class的內(nèi)部方法沒有 constructor 屬性牙肝,無法new
(5)調(diào)用class的構(gòu)造函數(shù)必須new
(6)class內(nèi)部方法不能同名

class類的使用
class作為js中的一級公民唉俗,可以被當(dāng)作值來直接使用

//1.類名作為參數(shù)傳入函數(shù)

function createObj (ClassName) {? ??

return new ClassName()

} //2.立即執(zhí)行,實(shí)現(xiàn)單例模式

let cat1 = new class{

constructor(name) {

? ? this.name = name

}

eat() {

? ? console.log("fish!")

}

} ("tom”)

cat1.eat() //fish!"

繼承

繼承就是子類可以使用父類的所有功能配椭,并且對這些功能進(jìn)行擴(kuò)展虫溜。繼承的過程,就是從一般到特殊的過程股缸。

1.類式繼承

所謂的類式繼承就是使用的原型的方式衡楞,將方法添加在父類的原型上,然后子類的原型是父類的一個(gè)實(shí)例化對象乓序。

//聲明父類var SuperClass = function(){? ?

let id = 1;

this.name = ['java'];

this.superValue = function() {

? ? console.log('this is superValue!')

}

} //為父類添加共有方法

SuperClass.prototype.getSuperValue = function() {

? ? return this.superValue();

}; //聲明子類var SubClass = function() {? ??

this.subValue = (() = >{

? ? console.log('this is subValue!')

})

}

//繼承父類

SubClass.prototype = new SuperClass();

//為子類添加共有方法

SubClass.prototype.getSubValue = function() {

? ? return this.subValue()

}

//生成實(shí)例

var sub1 = new SubClass();

var sub2 = new SubClass();sub1.getSuperValue(); //this is superValue!

sub1.getSubValue(); //this is subValue!

console.log(sub1.id); //undefined

console.log(sub1.name); //["java"]sub1.name.push("php");?

console.log(sub1.name); //["java", "php"]

console.log(sub2.name); //["java", "php"]

其中最核心的是SubClass.prototype = new SuperClass();
類的原型對象prototype對象的作用就是為類的原型添加共有的方法的寺酪,但是類不能直接訪問這些方法,只有將類實(shí)例化之后替劈,新創(chuàng)建的對象復(fù)制了父類構(gòu)造函數(shù)的屬性和方法寄雀,并將原型?proto?指向了父類的原型對象。這樣子類就可以訪問父類的屬性和方法陨献,同時(shí)盒犹,父類中定義的屬性和方法不會被子類繼承。

but使用類繼承的方法眨业,如果父類的構(gòu)造函數(shù)中有引用數(shù)據(jù)類型急膀,就會在子類中被所有實(shí)例共用,因此一個(gè)子類的實(shí)例如果更改了這個(gè)引用數(shù)據(jù)類型龄捡,就會影響到其他子類的實(shí)例卓嫂。

構(gòu)造函數(shù)繼承

為了克服類繼承的缺點(diǎn),才有了構(gòu)造函數(shù)繼承聘殖,構(gòu)造函數(shù)繼承的核心思想就是SuperClass.call(this, id),直接改變this的指向晨雳,使通過this創(chuàng)建的屬性和方法在子類中復(fù)制一份,因?yàn)槭菃为?dú)復(fù)制的奸腺,所以各個(gè)實(shí)例化的子類互不影響餐禁。but會造成內(nèi)存浪費(fèi)的問題

//構(gòu)造函數(shù)繼承//聲明父類

var SuperClass = function(id) {

? ? var name = 'java'this.languages = ['java', 'php', 'ruby'];

? ? this.id = id

} //聲明子類

var SubClass = function(id) {

? ? SuperClass.call(this, id)

} //生成實(shí)例

var sub1 = new SubClass(1);

var sub2 = new SubClass(2);

console.log(sub2.id); // 2

console.log(sub1.name); //undefined

sub1.languages.push("python");

console.log(sub1.languages); // ['java', 'php', 'ruby', 'python']

console.log(sub2.languages); // ['java', 'php', 'ruby']

組合式繼承

組合式繼承是汲取了兩者的優(yōu)點(diǎn),既避免了內(nèi)存浪費(fèi)突照,又使得每個(gè)實(shí)例化的子類互不影響帮非。

//組合式繼承//聲明父類

var SuperClass = function(name) {

? ? this.languages = ['java', 'php', 'ruby'];

? ? this.name = name;

} //聲明父類原型方法

SuperClass.prototype.showLangs = function() {

? ? console.log(this.languages);

} //聲明子類

var SubClass = function(name) {

? ? SuperClass.call(this, name)

} //子類繼承父類(鏈?zhǔn)嚼^承)

SubClass.prototype = new SuperClass(); //生成實(shí)例

var sub1 = new SubClass('python');

var sub2 = new SubClass('go');

sub2.showLangs(); //['java', 'php', 'ruby']

sub1.languages.push(sub1.name);

console.log(sub1.languages); //["java", "php", "ruby", "python"]

console.log(sub2.languages); //['java', 'php', 'ruby']

but警告:組合式繼承方法固然好,但是會導(dǎo)致一個(gè)問題,父類的構(gòu)造函數(shù)會被創(chuàng)建兩次(call()的時(shí)候一遍末盔,new的時(shí)候又一遍)

寄生組合繼承

組合式繼承的缺點(diǎn)的關(guān)鍵是?父類的構(gòu)造函數(shù)在類繼承和構(gòu)造函數(shù)繼承的組合形式被創(chuàng)建了兩邊筑舅,但是在類繼承中我們并不需要創(chuàng)建父類的構(gòu)造函數(shù),我們只要子類繼承父類的原型即可庄岖。所以我們先給父類的原型創(chuàng)建一個(gè)副本豁翎,然后修改子類的 constructor 屬性角骤,最后在設(shè)置子類的原型就可以了

//原型式繼承

//原型式繼承其實(shí)就是類式繼承的封裝隅忿,實(shí)現(xiàn)的功能返回一個(gè)實(shí)例,該實(shí)例的原型繼承了傳入的o對象

function inheritObject(o) {

? ? //聲明一個(gè)過渡函數(shù)

? ? function F() {}

? ? //過渡對象的原型鏈繼承父對象

? ? F.prototype = o;

? ? //返回一個(gè)過渡對象的實(shí)例邦尊,該實(shí)例的原型繼承了父對象

? ? return new F();

}

//寄生式繼承//寄生式繼承就是對原型繼承的第二次封裝背桐,使得子類的原型等于父類的原型。并且在第二次封裝的過程中對繼承的對象進(jìn)行了擴(kuò)展

function inheritPrototype(subClass, superClass) {

? ? //復(fù)制一份父類的原型保存在變量中蝉揍,使得p的原型等于父類的原型

? ? var p = inheritObject(superClass.prototype);

? ? //修正因?yàn)橹貙懽宇愒蛯?dǎo)致子類constructor屬性被修改

? ? p.constructor = subClass; //設(shè)置子類的原型

? ? subClass.prototype = p;

} //定義父類

var SuperClass = function(name) {

? ? this.name = name;

? ? this.languages = ["java", "php", "python"]

} //定義父類原型方法

SuperClass.prototype.showLangs = function() {

? ? console.log(this.languages);

} //定義子類

var SubClass = function(name) {

? ? SuperClass.call(this, name)

}

inheritPrototype(SubClass, SuperClass);

var sub1 = new SubClass('go');

es6中的繼承

class SuperClass {

? ? constructor(name) {

? ? ? ? this.name = name this.languages = ['java', 'php', 'go'];

? ? }

? ? showLangs() {

? ? ? ? console.log(this.languages);

? ? }

}

class SubClass extends SuperClass {

? ? constructor(name) {

? ? ? ? super(name)

? ? } //重寫父類中的方法

? ? showLangs() {

? ? ? ? this.languages.push(this.name)?

? ? ? ?console.log(this.languages);

? ? }

} //生成實(shí)例

var sub = new SubClass('韓二虎');

console.log(sub.name); //韓二虎

sub.showLangs(); //["java", "php", "go", "韓二虎"]

多態(tài)

多態(tài)實(shí)際上是不同對象作用與同一操作產(chǎn)生不同的效果链峭。多態(tài)的思想實(shí)際上是把 “想做什么” 和 “誰去做” 分開。
多態(tài)的好處在于又沾,你不必再向?qū)ο笤儐枴澳闶鞘裁搭愋汀焙蟾鶕?jù)得到的答案再去調(diào)用對象的某個(gè)行為弊仪。你盡管去調(diào)用這個(gè)行為就是了,其他的一切可以由多態(tài)來負(fù)責(zé)杖刷。規(guī)范來說励饵,多態(tài)最根本的作用就是通過吧過程化的條件語句轉(zhuǎn)化為對象的多態(tài)性,從而消除這些條件分支語句滑燃。
由于JavaScript中提到的關(guān)于多態(tài)的詳細(xì)介紹并不多役听,這里簡單的通過一個(gè)例子來介紹就好

//非多態(tài)? ??

var hobby = function(animal) {

? ? if (animal == 'cat') {

? ? ? ? cat.eat()

? ? } else if (animal == 'dog') {

? ? ? ? dog.eat()

? ? }

}

var cat = {

? ? eat: function() {

? ? ? ? alert("fish!")

? ? }

}

var dog = {

? ? eat: function() {

? ? ? ? alert("meat!")

? ? }

}

console.log(123);

hobby('cat'); //fish!

hobby('dog'); //meat!

從上面的例子能看到,雖然 hobby 函數(shù)目前保持了一定的彈性表窘,但這種彈性很脆弱的典予,一旦需要替換或者增加成其他的animal,必須改動hobby函數(shù)乐严,繼續(xù)往里面堆砌條件分支語句瘤袖。我們把程序中相同的部分抽象出來,那就是吃某個(gè)東西昂验。然后再重新編程捂敌。

//多態(tài)? ? ?

var hobby = function(animal){? ??

if (animal.eat instanceof Function) {

? ? animal.eat();

}

}

var cat = {

? ? eat: function() {

? ? ? ? alert("fish!")

? ? }

}

var dog = {

? ? eat: function() {

? ? ? ? alert("meat!")

? ? }

}

現(xiàn)在來看這段代碼中的多態(tài)性。當(dāng)我們向兩種 animal 發(fā)出 eat 的消息時(shí)凛篙,會分別調(diào)用他們的 eat 方法黍匾,就會產(chǎn)生不同的執(zhí)行結(jié)果。對象的多態(tài)性提示我們呛梆,“做什么”?和?“怎么去做”是可以分開的锐涯,這樣代碼的彈性就增強(qiáng)了很多。即使以后增加了其他的animal填物,hobby函數(shù)仍舊不會做任何改變纹腌。

//多態(tài)? ? ?

var hobby = function(animal) {

? ? if (animal.eat instanceof Function) {

? ? ? ? animal.eat();

? ? }

}

var cat = {

? ? eat: function() {

? ? ? ? alert("fish!")

? ? }

}

var dog = {

? ? eat: function() {

? ? ? ? alert("meat!")

? ? }

}

var aoteman = {

? ? eat: function() {

? ? ? ? alert("lil-monster!")

? ? }

}

hobby(cat); //fish!hobby(dog); //meat!

hobby(aoteman); //lil-monster!

感興趣的小伙伴霎终,可以關(guān)注公眾號【grain先森】,回復(fù)關(guān)鍵詞 “vue”升薯,獲取更多資料莱褒,更多關(guān)鍵詞玩法期待你的探索~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市涎劈,隨后出現(xiàn)的幾起案子广凸,更是在濱河造成了極大的恐慌,老刑警劉巖蛛枚,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谅海,死亡現(xiàn)場離奇詭異,居然都是意外死亡蹦浦,警方通過查閱死者的電腦和手機(jī)扭吁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盲镶,“玉大人侥袜,你說我怎么就攤上這事「然撸” “怎么了枫吧?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長顽照。 經(jīng)常有香客問我由蘑,道長,這世上最難降的妖魔是什么代兵? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任尼酿,我火速辦了婚禮,結(jié)果婚禮上植影,老公的妹妹穿的比我還像新娘裳擎。我一直安慰自己,他們只是感情好思币,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布鹿响。 她就那樣靜靜地躺著,像睡著了一般谷饿。 火紅的嫁衣襯著肌膚如雪惶我。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天博投,我揣著相機(jī)與錄音绸贡,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛听怕,可吹牛的內(nèi)容都是我干的捧挺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尿瞭,長吁一口氣:“原來是場噩夢啊……” “哼闽烙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起声搁,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤黑竞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酥艳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摊溶,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爬骤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年充石,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霞玄。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骤铃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坷剧,到底是詐尸還是另有隱情惰爬,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布惫企,位于F島的核電站撕瞧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狞尔。R本人自食惡果不足惜丛版,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望偏序。 院中可真熱鬧页畦,春花似錦、人聲如沸研儒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽端朵。三九已至好芭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冲呢,已是汗流浹背舍败。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓤湘。 一個(gè)月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓瓢颅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弛说。 傳聞我的和親對象是個(gè)殘疾皇子挽懦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容