摘要:會造成內(nèi)存浪費的問題構(gòu)造函數(shù)繼承聲明父類聲明子類生成實例組合式繼承組合式繼承是汲取了兩者的優(yōu)點,既避免了內(nèi)存浪費,又使得每個實例化的子類互不影響。
寫在前面
既然是淺談,就不會從原理上深度分析,只是幫助我們更好地理解...
面向?qū)ο笈c面向過程面向?qū)ο蠛兔嫦蜻^程是兩種不同的編程思想,剛開始接觸編程的時候,我們大都是從面向過程起步的,畢竟像我一樣,大家接觸的第一門計算機(jī)語言大概率都是C語言,C語言就是一門典型的面向過程的計算機(jī)語言。
面向過程主要是以動詞為主,解決問題的方式是按照順序一步一步調(diào)用不同的函數(shù)。
面向?qū)ο笫且悦~為主,將問題抽象出具體的對象,而這個對象有自己的屬性和方法,在解決問題的時候,是將不同的對象組合在一起使用。
//面向過程裝大象 1.開(冰箱) 2.(大象)裝進(jìn)(冰箱) 3.關(guān)(冰箱)
//面向?qū)ο笱b大象 1. 冰箱.開門() 2. 冰箱.裝進(jìn)(大象) 3. 冰箱.關(guān)門()
從這個例子可以看出,面向?qū)ο笫且灾髦^為主,將主謂堪稱一個一個的對象,然后對象有自己的屬性和方法。
面向?qū)ο笫且怨δ軄韯澐謫栴}的,而不是步驟。功能上的統(tǒng)一保證了面向?qū)ο笤O(shè)計的可擴(kuò)展性,解決了代碼重用性的問題。
這也是在漫長的程序設(shè)計的發(fā)展過程中得到的驗證結(jié)果,面向?qū)ο蟮木幊趟枷胼^之于面向過程較好一點
面向?qū)ο笥蟹庋b、繼承和多態(tài)三大特性。
封裝:就是把事物封裝成類,隱藏事物的屬性和方法的實現(xiàn)細(xì)節(jié),僅對外公開接口。
在ES5中,并沒有class的概念,但是由于js的函數(shù)級作用域(函數(shù)內(nèi)部的變量函數(shù)外訪問不到)。所以我們可以模擬class。在es5中,類其實就是保存了一個函數(shù)的變量,這個函數(shù)有自己的屬性和方法。將屬性和方法組成一個類的過程就是封裝。
JavaScript提供了一個構(gòu)造函數(shù)(Constructor)模式,用來在創(chuàng)建對象時初始化對象。構(gòu)造函數(shù)其實就是普通的函數(shù),只不過有以下的特點
①首字母大寫(建議構(gòu)造函數(shù)首字母大寫,即使用大駝峰命名,非構(gòu)造函數(shù)首字母小寫) ②內(nèi)部使用this ③使用new生成實例
通過構(gòu)造函數(shù)添加屬性和方法實際上也就是通過this添加的屬性和方法。因為this總是指向當(dāng)前對象的,所以通過this添加的屬性和方法只在當(dāng)前對象上添加,是該對象自身擁有的。所以我們實例化一個新對象的時候,this指向的屬性和方法都會得到相應(yīng)的創(chuàng)建,也就是會在內(nèi)存中復(fù)制一份,這樣就造成了內(nèi)存的浪費。
function Cat(name,color){ this.name = name; this.color = color; this.eat = (() => { console.log("fish!") }) } //生成實例 var cat1 = new Cat("tom", "gray")
通過this定義的屬性和方法,我們實例化對象的時候斗湖重新復(fù)制一份
在類上通過this的方式添加屬性和方法會導(dǎo)致內(nèi)存浪費的現(xiàn)象,有什么辦法可以讓實例化的類所使用的屬性和方法 直接使用指針 指向同一個屬性和方法。
這就是原型的方法
JavaScript規(guī)定,每一個構(gòu)造函數(shù)都有一個prototype屬性,指向另一個對象。這個對象的所有屬性和方法,都會被構(gòu)造函數(shù)的實例繼承。 也就是說,對于那些不變的屬性和方法,我們可以直接將其添加在類的prototype對象上。
function Cat(name,color){ this.name = name; this.color = color; } Cat.prototype.type = "英短"; Cat.prototype.eat = ( () => { alert("fish!") } ) //生成實例 var cat1 = new Cat("Tom", "gray"); var cat2 = new Cat("Kobe", "purple"); console.log(cat1.type); //英短 cat2.eat(); //fish!
這時所有實例的type屬性和eat()方法,其實都是同一個內(nèi)存地址,指向prototype對象,因此就提高了運行效率。
但是這樣做也有弊端,因為實例化的對象的原型都是指向同一內(nèi)存地址,改動其中一個對象的屬性可能會影響到其他的對象
es6聲明一個類
①構(gòu)造器:構(gòu)造器內(nèi)創(chuàng)建自有屬性
②方法:聲明類實例具有的方法
class Cat { //等價于Cat構(gòu)造器 constructor(name) { this.name = name; } //更加簡單的聲明類的內(nèi)部函數(shù) //等價于 Cat.prototype.eat eat() { console.log("fish!"); } } //生成實例 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òu)造函數(shù)行為的函數(shù),其中內(nèi)部方法eat實際上就是Cat.prototype.eat()
所以說es6的class封裝類,本質(zhì)上是es5實現(xiàn)方式的語法糖
最主要的區(qū)別在于,class類的屬性是不可重新賦值和不可枚舉的,Cat.prototype就是一個只讀屬性
class和自定義類型的區(qū)別
(1)class的聲明不會提升,與let類似
(2)class的聲明自動運行于嚴(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í)行,實現(xiàn)單例模式 let cat1 = new class{ constructor (name) { this.name = name } eat() { console.log("fish!") } }("tom”) cat1.eat() //fish!繼承
繼承就是子類可以使用父類的所有功能,并且對這些功能進(jìn)行擴(kuò)展。繼承的過程,就是從一般到特殊的過程。
所謂的類式繼承就是使用的原型的方式,將方法添加在父類的原型上,然后子類的原型是父類的一個實例化對象。
//聲明父類 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() } //生成實例 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對象的作用就是為類的原型添加共有的方法的,但是類不能直接訪問這些方法,只有將類實例化之后,新創(chuàng)建的對象復(fù)制了父類構(gòu)造函數(shù)的屬性和方法,并將原型 proto 指向了父類的原型對象。這樣子類就可以訪問父類的屬性和方法,同時,父類中定義的屬性和方法不會被子類繼承。
but使用類繼承的方法,如果父類的構(gòu)造函數(shù)中有引用數(shù)據(jù)類型,就會在子類中被所有實例共用,因此一個子類的實例如果更改了這個引用數(shù)據(jù)類型,就會影響到其他子類的實例。
為了克服類繼承的缺點,才有了構(gòu)造函數(shù)繼承,構(gòu)造函數(shù)繼承的核心思想就是SuperClass.call(this, id),直接改變this的指向,使通過this創(chuàng)建的屬性和方法在子類中復(fù)制一份,因為是多帶帶復(fù)制的,所以各個實例化的子類互不影響。but會造成內(nè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) } //生成實例 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)點,既避免了內(nèi)存浪費,又使得每個實例化的子類互不影響。
//組合式繼承 //聲明父類 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(); //生成實例 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òu)造函數(shù)會被創(chuàng)建兩次(call()的時候一遍,new的時候又一遍)
組合式繼承的缺點的關(guān)鍵是 父類的構(gòu)造函數(shù)在類繼承和構(gòu)造函數(shù)繼承的組合形式被創(chuàng)建了兩邊,但是在類繼承中我們并不需要創(chuàng)建父類的構(gòu)造函數(shù),我們只要子類繼承父類的原型即可。
所以我們先給父類的原型創(chuàng)建一個副本,然后修改子類的 constructor 屬性,最后在設(shè)置子類的原型就可以了
//原型式繼承 //原型式繼承其實就是類式繼承的封裝,實現(xiàn)的功能返回一個實例,該實例的原型繼承了傳入的o對象 function inheritObject(o) { //聲明一個過渡函數(shù) function F() {} //過渡對象的原型鏈繼承父對象 F.prototype = o; //返回一個過渡對象的實例,該實例的原型繼承了父對象 return new F(); } //寄生式繼承 //寄生式繼承就是對原型繼承的第二次封裝,使得子類的原型等于父類的原型。并且在第二次封裝的過程中對繼承的對象進(jìn)行了擴(kuò)展 function inheritPrototype(subClass, superClass){ //復(fù)制一份父類的原型保存在變量中,使得p的原型等于父類的原型 var p = inheritObject(superClass.prototype); //修正因為重寫子類原型導(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");
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); } } //生成實例 var sub = new SubClass("韓二虎"); console.log(sub.name); //韓二虎 sub.showLangs(); //["java", "php", "go", "韓二虎"]多態(tài)
多態(tài)實際上是不同對象作用與同一操作產(chǎn)生不同的效果。多態(tài)的思想實際上是把 “想做什么” 和 “誰去做” 分開。
多態(tài)的好處在于,你不必再向?qū)ο笤儐枴澳闶鞘裁搭愋汀焙蟾鶕?jù)得到的答案再去調(diào)用對象的某個行為。你盡管去調(diào)用這個行為就是了,其他的一切可以由多態(tài)來負(fù)責(zé)。規(guī)范來說,多態(tài)最根本的作用就是通過吧過程化的條件語句轉(zhuǎn)化為對象的多態(tài)性,從而消除這些條件分支語句。
由于JavaScript中提到的關(guān)于多態(tài)的詳細(xì)介紹并不多,這里簡單的通過一個例子來介紹就好
//非多態(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ù)往里面堆砌條件分支語句。我們把程序中相同的部分抽象出來,那就是吃某個東西。然后再重新編程。
//多態(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 的消息時,會分別調(diào)用他們的 eat 方法,就會產(chǎn)生不同的執(zhí)行結(jié)果。對象的多態(tài)性提示我們,“做什么” 和 “怎么去做”是可以分開的,這樣代碼的彈性就增強了很多。即使以后增加了其他的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!
快樂面向?qū)ο?
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/99940.html
摘要:面向?qū)ο竺嫦驅(qū)ο缶幊痰娜Q是,簡稱,面向?qū)ο缶幊淌怯贸橄蠓绞絼?chuàng)建基于現(xiàn)實世界模型的一種編程模式。面向?qū)ο缶幊痰娜齻€主要特征是封裝繼承多態(tài)。 面向?qū)ο?面向?qū)ο缶幊痰娜Q是Object Oriented Programming,簡稱OOP,面向?qū)ο缶幊淌怯贸橄蠓绞絼?chuàng)建基于現(xiàn)實世界模型的一種編程模式。面向?qū)ο缶幊炭梢钥醋鍪鞘褂靡幌盗袑ο笙嗷f(xié)作的軟件設(shè)計,面向?qū)ο蟪绦蛟O(shè)計的目的是在編程中促...
摘要:單一職責(zé)原則可以看做是低耦合高內(nèi)聚在面向?qū)ο笤瓌t上的引申,將職責(zé)定義為引起變化的原因,以提高內(nèi)聚性來減少引起變化的原因。抽象的穩(wěn)定性決定了系統(tǒng)的穩(wěn)定性,因為抽象是不變的,依賴于抽象是面向?qū)ο笤O(shè)計的精髓,也是依賴倒置原則的核心。 Java-面向?qū)ο?什么是面過程 把題分解成一個一個步驟,每個步驟用函數(shù)實現(xiàn),依次調(diào)用即可。就是說,在進(jìn)行面向過程 編程的時候,不需要考慮那么多,上來先定義一個...
摘要:面向?qū)ο蟮娜筇攸c封裝,繼承,多態(tài)缺一不可。構(gòu)造函數(shù),是一種特殊的方法。特別的一個類可以有多個構(gòu)造函數(shù),可根據(jù)其參數(shù)個數(shù)的不同或參數(shù)類型的不同來區(qū)分它們即構(gòu)造函數(shù)的重載。 一、基本概念和背景 面向?qū)ο蟪绦蛟O(shè)計(OOP:Object-oriented programming)是一種程序設(shè)計范型,同時也是一種程序開發(fā)的方法。對象指的是類的實例。面向?qū)ο?Object Oriented,OO...
摘要:在中,并沒有對抽象類和接口的支持。例如,當(dāng)對象需要對象的能力時,可以有選擇地把對象的構(gòu)造器的原型指向?qū)ο?,從而達(dá)到繼承的效果。本節(jié)內(nèi)容為設(shè)計模式與開發(fā)實踐第一章筆記。 動態(tài)類型語言 編程語言按數(shù)據(jù)類型大體可以分為兩類:靜態(tài)類型語言與動態(tài)類型語言。 靜態(tài)類型語言在編譯時已確定變量類型,動態(tài)類型語言的變量類型要到程序運行時,待變量被賦值后,才具有某種類型。 而JavaScript是一門典型...
摘要:眾多面向?qū)ο蟮木幊趟枷腚m不盡一致,但是無論哪種面向?qū)ο缶幊陶Z言都具有以下的共通功能。原型編程以類為中心的傳統(tǒng)面向?qū)ο缶幊蹋且灶悶榛A(chǔ)生成新對象。而原型模式的面向?qū)ο缶幊陶Z言沒有類這樣一個概念。 什么是面向?qū)ο??這個問題往往會問到剛畢業(yè)的新手or實習(xí)生上,也是往往作為一個技術(shù)面試的開頭題。在這里我們不去談如何答(fu)好(yan)問(guo)題(qu),僅談?wù)勎宜斫獾拿嫦驅(qū)ο蟆?從歷...
閱讀 3557·2021-11-23 10:10
閱讀 3326·2019-08-30 14:03
閱讀 2080·2019-08-30 13:09
閱讀 3411·2019-08-29 15:29
閱讀 1553·2019-08-29 11:23
閱讀 2022·2019-08-28 18:28
閱讀 2857·2019-08-26 13:34
閱讀 2179·2019-08-26 11:32