面向?qū)ο笫且环N軟件開發(fā)方法吮成,是一種對現(xiàn)實世界理解和抽象的方法频丘,是計算機編程技術(shù)發(fā)展到一定階段后的產(chǎn)物蓝仲。隨著時代的發(fā)展俱病,計算機被用于解決越來越復(fù)雜的問題。一切事物皆對象袱结,通過面向?qū)ο蟮姆绞搅料叮瑢F(xiàn)實世界的事物抽象成對象。通過面向?qū)ο蟮姆椒ㄇ婵保谟萌死斫獾姆绞綄?fù)雜系統(tǒng)進行分析咱揍、設(shè)計與編程,今天我們就來學(xué)習(xí)一下JavaScript面向?qū)ο蟮乃枷搿?/p>
面向過程和面向?qū)ο缶幊谈攀?br>
面向過程編程就是分析出解決問題的步驟棚饵,然后使用函數(shù)把這些步驟一步步實現(xiàn)煤裙,重心放在完成的每個過程上。
面向?qū)ο髣t是以封裝的思想噪漾,將問題分析得到的數(shù)據(jù)封裝成一個個的對象硼砰,然后通過對對象的操作來完成相應(yīng)的功能。
舉個栗子:廚師炒菜
以面向過程的思想來分析應(yīng)該分為下面幾個步驟:
? 1.檢查食材是否齊全 2.如果不不夠欣硼,去菜市場買菜 3.洗菜 4.開火 5.按炒菜(按順序放入相應(yīng)的食材题翰,調(diào)料等) 6.出鍋裝盤
以面向?qū)ο蟮乃枷敕治鰟t是這樣的:
? 1.廚師,檢查食材,炒菜 2.采購員豹障,去菜市場買菜 3.墩子冯事,洗菜,切菜血公,備菜
? 通過調(diào)用上面對象中的行為方法即可完成炒菜的整個過程
從上面的例子可以看出昵仅,面向?qū)ο蠛兔嫦蜻^程最大的不同在于,面向?qū)ο箨P(guān)心的是由哪些對象累魔,每個對象應(yīng)該有哪些功能摔笤,而面向過程關(guān)心的是實現(xiàn)過程中的每個步驟。
那么這兩種思想到底孰優(yōu)孰劣呢垦写?從表面上看吕世,貌似面向?qū)ο蟾茫瑸槭裁茨靥萃叮恳驗樗耆衔覀兊恼K季S方式命辖,所以在接受度方面,面向?qū)ο蟮乃枷肟隙ㄊ歉猛砘铩5敲嫦蜻^程也有他的優(yōu)勢吮龄,就是靈活便捷,而面向?qū)ο笙鄬碚f會更耗資源咆疗,更慢一點漓帚。
所以,至于以后使用哪一種午磁,這就需要看我們的具體需求尝抖,根據(jù)不同的需求做不同的選擇。
面向?qū)ο缶幊痰南嚓P(guān)概念
通過上面的分析迅皇,我們知道面向?qū)ο蟮闹攸c在于功能分析和對象的封裝上昧辽,那么最終我們得到的對象的結(jié)構(gòu)是怎樣的,我們繼續(xù)往下學(xué)習(xí)登颓。
比如搅荞,我通過對人的分析得到,每個人都有姓名框咙,年齡咕痛,性別等屬性,同時也有吃飯睡覺等行為喇嘱,那么用JS可以做如下的封裝:
var p = {
name : “xiao song”,
age : 10,
sex : 1,
eat : function () {
console.log(“吃飯”);
},
sleep : function () {
console.log(“睡覺”);
}
}console.log(p.name);//訪問對象的屬性
p.eat();//訪問對象的方法
上面的p則表示一個對象,其中的name / age / sex稱之為對象的屬性腔丧,eat / sleep 稱之為對象的方法,我們通過訪問該對象的屬性或者方法達到相應(yīng)的目的即可愉粤。
DOM操作相關(guān)知識點復(fù)習(xí)
在學(xué)習(xí)了html之后我們發(fā)現(xiàn)砾医,html文檔中的內(nèi)容其實就是由一堆的標簽組成科汗,由于在后面的課程中需要使用到html绷雏,所以我們先大致的回顧一下它的結(jié)構(gòu)头滔。
H5-JS面向?qū)ο?/p>
div h3:元素節(jié)點 id class:屬性節(jié)點 H5-JS面向?qū)ο螅何谋竟?jié)點 一個html文檔主要由這三部分組成,DOM(文檔對象模型)是對操作這些元素的屬性或者方法進行了封裝涎显,從而達到方便快捷的操作html的目的坤检。 獲取元素對象:document.getElementById("div1") 訪問元素的屬性:div1.className 訪問元素的文本內(nèi)容:div1.innerText 增刪改元素:div1.appendChild(newNode) 下面早歇,我們就通過這些API來講解說明面向?qū)ο笙嚓P(guān)的內(nèi)容讨勤。 創(chuàng)建并設(shè)置標簽(面向過程) 需求1:創(chuàng)建三個div元素,并設(shè)置邊框谱姓,背景色,文本及字體顏色 for (var i = 0; i < 3; i++) { var div = document.createElement("div"); div.innerText="div"+i; div.style.backgroundColor="green"; div.style.border="1px solid #000"; div.style.color="white"; document.body.appendChild(div); }
需求2:為頁面中存在的三個P元素設(shè)置邊框屉来,背景色茄靠,文本及字體顏色
我是P1
我是P2
我是P3
需求3:獲取頁面上三個class=“test”的元素蝶桶,設(shè)置邊框,背景色脐雪,文本及字體顏色
我是標題1
我是標題2
我是標題3
上面的代碼是以面向過程的思想完成的喂江,可以看到旁振,兩個需求中的每個步驟都是我們一步一步完成的涨岁,問題很明顯吉嚣,代碼大量的冗余,這種代碼后期不好維護秉撇。
創(chuàng)建并設(shè)置標簽(函數(shù)封裝)
對于上面重復(fù)的代碼秋泄,我們可以使用函數(shù)對其進行封裝
封裝了三個函數(shù):
setStype(eles,bgcolor):為元素設(shè)置樣式
eles:哪些元素
bgcolor:背景色
getElementsByTagName(tagName):根據(jù)元素名稱獲取指定的元素
tagName:元素名
getElementsByClassName(className):根據(jù)class屬性名獲取指定的元素
className:class屬性名
接下來就是調(diào)用三個方法完成了上面的需求,解決了第一種方式中大量的重復(fù)代碼的問題瘦麸。
但是歧胁,這種方式仍然存在問題。在前面JS基礎(chǔ)中說過屠缭,我們應(yīng)該盡量避免大量使用全局變量崭参,這會降低程序的執(zhí)行效率,在上面的程序中阵翎,我們就出現(xiàn)了5個(包括函數(shù))郭卫。所以需要繼續(xù)優(yōu)化。
創(chuàng)建并設(shè)置標簽(面向?qū)ο螅?br>
使用面向?qū)ο蟮乃枷雭斫鉀Q上面的問題贰军,我們可以將上面的三個函數(shù)都裝到一個對象中
var .getElementsByTagName(“p”)
.setStype(ps,"green");vartests=
.setStype(ps,"green");vartests=.getElementsByClassName(“test”);
.setStype(tests,"red");后面如果我們還都需要封裝其他功能词疼,可以直接在
.setStype(tests,"red");后面如果我們還都需要封裝其他功能贰盗,可以直接在這個對象中添加即可
如,根據(jù)元素的id屬性獲取元素舵盈,并未其設(shè)置樣式
getElementById:function (eleId) {
return [document.getElementById(eleId)];
}
需要注意的是,在設(shè)置樣式方法中瓦糟,我們默認是將傳遞進來的元素當(dāng)做數(shù)組進行處理的菩浙,所以,在這里陆淀,我們在getElementById方法中斋竞,手動將獲取到的元素添加到數(shù)組中返回。
通過觀察坝初,在 = {
getElements:{
byTagName: function (tagName) {
return document.getElementsByTagName(tagName);
},
byClassName:function (className) {
return document.getElementsByClassName(className);
},
byId:function (eleId) {
return [document.getElementById(eleId)];
}
},
setStype:function (eles,bgcolor) {
for (var i = 0; i < eles.length; i++) {
eles[i].style.backgroundColor=bgcolor;
eles[i].style.border=“1px solid #000”;
eles[i].style.color=“white”;
}
}
}
將獲取元素的方法封裝到$對象的getElements屬性中樱哼,今后如果還有其他獲取元素的方法,都應(yīng)該是添加到getElements屬性中阅束,其他類型的方法也應(yīng)該按照這種思想進行封裝茄唐。
面向?qū)ο缶幊痰娜筇匦?br>
面向?qū)ο蟮奶匦裕?br>
封裝
作用:復(fù)用和信息隱藏
封裝,也就是把客觀事物封裝成抽象的類呼盆,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對象操作蚁廓,對不可信的進行信息隱藏。
繼承
它可以使用現(xiàn)有類的所有功能腿时,并在無需重新編寫原來的類的情況下對這些功能進行擴展。
通過繼承創(chuàng)建的新類稱為“子類”或“派生類”漠另。
被繼承的類稱為“基類”跃赚、“父類”或“超類”。
繼承的過程满败,就是從一般到特殊的過程叹括。
多態(tài)
當(dāng)存在繼承關(guān)系時,允許將父類對象看成為和它的一個或多個子類對象等同.
這樣,可以根據(jù)當(dāng)前賦給父類對象的子對象的具體特性以不同的方式進行運行.
用字面量方式創(chuàng)建對象
直接使用字面量方式創(chuàng)建對象比較方面汁雷,以鍵值對的格式來定義數(shù)據(jù)
var book1 = {
name:“JavaScript權(quán)威指南”,
price:100,
author:“tim”,
showInfo:function () {
console.log(this.name,this.price,this.author);
}
}console.log(book1);
上面定義了一個書對象,并為其添加了屬性和方法挖藏,我們也可以直接訪問其中的屬性和方法厢漩。
這種方式的弊端是,如果需要創(chuàng)建多個類似的對象宵膨,就顯得不太方便了炸宵,會出現(xiàn)大量的重復(fù)代碼。
也就是說鸿脓,這種方式不適合創(chuàng)建大量的相同或相似的對象涯曲。
內(nèi)置構(gòu)造函數(shù)和簡單工廠創(chuàng)建對象
使用new關(guān)鍵字+內(nèi)置的構(gòu)造函數(shù)創(chuàng)建對象
var book2 = new Object();
book2.name=“JS”;
book2.price=10;
book2.author=“作者”;
book2.showInfo=function () {
console.log(this.name,this.price,this.author);
}
book2.showInfo();
這種方式和字面量方式創(chuàng)建對象存在的問題差不多幻件,在大量創(chuàng)建對象的時候都會存在大量重復(fù)的代碼。
那么篱蝇,利用前面的封裝的思想,我們應(yīng)該可以想到零截,當(dāng)有重復(fù)代碼的時候涧衙,我們可以將這些重復(fù)代碼抽取到函數(shù)中來解決。
function createBook(name, price, author) {
var book = new Object();
book.name=name;
book.price=price;
book.author=author;
book.showInfo=function () {
console.log(this.name,this.price,this.author);
}
return book;
}var book3 = createBook(“bookName1”,10,“author1”);var book4 = createBook(“bookName2”,10,“author2”);console.log(book3);console.log(book4);
我們將創(chuàng)建book對象的代碼封裝到createBook函數(shù)中雁比,當(dāng)需要創(chuàng)建一個book對象的時候撤嫩,直接調(diào)用該函數(shù),將函數(shù)需要的參數(shù)傳遞過去即可茴她。
那么两踏,相同的思想,如果我們需要創(chuàng)建其他的對象,一樣可以使用封裝函數(shù)的方法來解決帕识,這是沒問題的遂铡。
function createPerson(name, age) {
var p = new Object();
p.name = name;
p.age = age;
return p;
}console.log(createPerson(“Neld”, 10))
利用上面的函數(shù),我們可以創(chuàng)建一個Person對象出來伪货,但是通過打印對比钾怔,我們無法通過創(chuàng)建出來的對象判斷該對象的類型宗侦,而在實際開發(fā)中,判斷對象的類型是我們經(jīng)常需要執(zhí)行的姑裂,所以我們繼續(xù)看下面的自定義構(gòu)造函數(shù)創(chuàng)建對象。
自定義構(gòu)造函數(shù)創(chuàng)建對象
構(gòu)造函數(shù)和普通的函數(shù)的定義方式完全一樣欣鳖,如下茴厉,我們定義一個創(chuàng)建Person的構(gòu)造函數(shù)
function createPerson(name, age, sex) {
this.name=name;
this.age=age;
this.sex=sex;
}var p = new createPerson(“Neld”, 10, 1);var p2 = new createPerson(“Song”, 12, 0);console.log§;console.log(p2);
自定義函數(shù)和工廠函數(shù)非常相似,但是還是存在很大的區(qū)別
構(gòu)造函數(shù)名的首字母要求大寫
需要使用new關(guān)鍵字和構(gòu)造函數(shù)一起創(chuàng)建對象
在函數(shù)中师痕,不需要手動創(chuàng)建對象進行數(shù)據(jù)封裝而账,會自動創(chuàng)建并封裝數(shù)據(jù)
在函數(shù)最后泞辐,不需要手動返回創(chuàng)建好的對象,會自動返回
到這里咐吼,大家肯定會有疑問锯茄,自定義構(gòu)造函數(shù)到底是如何創(chuàng)建并封裝對象呢?
在函數(shù)內(nèi)部默認會創(chuàng)建一個空對象 var obj = new Object();
默認把創(chuàng)建好的對象賦值給this this = obj;
默認設(shè)置this的原型對象為當(dāng)前構(gòu)造函數(shù)的原型對象
通過this添加屬性和方法
默認會把內(nèi)部創(chuàng)建的對象返回 return this;
通過上面的分析晚碾,相信大家已經(jīng)能夠理解自定義構(gòu)造函數(shù)的基本使用以及基本的原理了喂急。
構(gòu)造函數(shù)創(chuàng)建對象的返回值
默認情況下廊移,構(gòu)造函數(shù)內(nèi)部會返回新創(chuàng)建好的對象(this)
主動返回:
如果返回值類型的數(shù)據(jù),仍然返回創(chuàng)建好的對象(this)狡孔,不做任何修改
如果返回引用類型的數(shù)據(jù)步氏,則返回指定的數(shù)據(jù),不再返回this芋类。
函數(shù)作為構(gòu)造函數(shù)參數(shù)使用
在JS世界里,函數(shù)屬于一等公民胖喳,擁有最高特權(quán)贮竟,在使用過程中可以作為變量賦值,可以作為參數(shù)傳遞技健,也可以作為函數(shù)的返回值惰拱,下面我們具體來看看他的使用。
函數(shù)作為參數(shù)使用
function f1(name,age,fn) {
console.log(“name:”,name,“age:”,age);
fn();
}function fn(){
console.log(“Hello H5”);
}
f1(“Neld”, 10, fn);
輸出結(jié)果:
? name: Neld age: 10
? Hello H5
在上面的代碼中欣孤,我們將函數(shù)fn作為參數(shù)傳遞給了函數(shù)f1,并且在函數(shù)f1中調(diào)用降传,得到的相應(yīng)的打印輸出勾怒。
函數(shù)作為返回值使用
function f1(name,age,fn) {
console.log(“name:”,name,“age:”,age);
return fn;
}function fn(){
console.log(“Hello H5”);
}var retFun = f1(“Neld”, 10, fn);
retFun();
在函數(shù)f1中將傳遞進來的fn作為返回值返回,接收到調(diào)用f1之后的返回值得到的是返回的函數(shù)泽论,然后在調(diào)用retFun得到打印結(jié)果卡乾。
此時的f1為高階函數(shù)幔妨,即參數(shù)中有一個或多個函數(shù)谍椅,并且把函數(shù)作為返回值。
此時的fn為回調(diào)函數(shù)锁施,fn作為參數(shù)傳遞給函數(shù)f1,在f1內(nèi)部調(diào)用。
函數(shù)作為構(gòu)造函數(shù)的參數(shù)使用
function createPerson(name, age, sex, say) {
this.name=name;
this.age=age;
this.sex=sex;
this.say=say;
}var p = new createPerson(“Neld”, 10, 1, function () {
console.log(“say hello”);
});var p2 = new createPerson(“Song”, 12, 0,function () {
console.log(“say bye”);
});
p.say();
p2.say();
在構(gòu)造函數(shù)中也可以對方法進行封裝肩狂,如果方法的實現(xiàn)是由調(diào)用者決定的傻谁,那么可以在構(gòu)造函數(shù)中接收一個函數(shù)對象列粪,然后在構(gòu)造函數(shù)中進行封裝。
如上面的函數(shù)say态蒂,在創(chuàng)建p和p2對象的時候傳遞并賦值給形參say费什,然后在構(gòu)造函數(shù)中賦值給當(dāng)前對象。
構(gòu)造器屬性
前面說到工廠函數(shù)創(chuàng)建對象是比較方便的赘那,但是存在一個問題就是無法得知創(chuàng)建出來的對象的類型募舟,所以我們選擇使用自定義的構(gòu)造函數(shù)來創(chuàng)建闻察,構(gòu)造函數(shù)創(chuàng)建對象我們已經(jīng)會使用了,那么如何通過他得知創(chuàng)建對象的類型呢呢灶?這里我們提供兩種方式钉嘹。
constructor屬性
function Person(name) {
this.name = name;
}function Dog(name) {
this.name = name;
}var p = new Person(“p”);var d = new Dog(“d”);console.log(p.constructor);//打印得到Person函數(shù)對象console.log(d.constructor);//打印得到Dog函數(shù)對象if(p.constructor == Person){
console.log(“是Person對象”);
}if(d.constructor == Dog){
console.log(“是Dog對象”);
}
- 使用constructor屬性可以獲取到創(chuàng)建對象使用的構(gòu)造器函數(shù)對象跋涣,所以我們可以通過判斷構(gòu)造器的類型來得知創(chuàng)建的對象的類型
instanceof關(guān)鍵字
function Person(name) {
this.name = name;
}function Dog(name) {
this.name = name;
}var p = new Person(“p”);var d = new Dog(“d”);console.log(p instanceof Person);//trueconsole.log(d instanceof Person);//false
instanceof關(guān)鍵字可以直接用來判斷對象的類型,如果是指定的類型奖年,返回true沛贪,反之返回false。
構(gòu)造函數(shù)的調(diào)用和命名
在學(xué)習(xí)了構(gòu)造函數(shù)之后水评,有的同學(xué)對于它和普通函數(shù)的區(qū)別還是不太清楚之碗,這里我們就再對構(gòu)造函數(shù)做一個說明。
構(gòu)造函數(shù)和普通函數(shù)在定義語法上沒有任何區(qū)別
function 函數(shù)名(參數(shù)列表){代碼塊幽纷;}
為了和普通函數(shù)區(qū)分開博敬,我們約定將構(gòu)造函數(shù)的名稱首字母大寫
構(gòu)造函數(shù)一樣可以直接調(diào)用偏窝,此時內(nèi)部的this執(zhí)行window,這種方式不太安全祭往,有可能會在函數(shù)內(nèi)部修改當(dāng)前的全局變量硼补,不建議使用,而且這樣做也不能創(chuàng)建對象
想要創(chuàng)建對象离钝,必須使用new和構(gòu)造函數(shù)一起使用
函數(shù)上下文和this指針
在JS編程的過程中發(fā)現(xiàn)褪储,我們大量使用到this關(guān)鍵字鲤竹,用好了this,能讓我們的代碼更加優(yōu)雅瑟啃。
this總是執(zhí)行一個對象(引用類型)揩尸,但是具體執(zhí)行誰屁奏,需要根據(jù)我們在哪里使用this有關(guān)。這里主要分為下面幾種情況:
函數(shù)外部
函數(shù)外部的作用域是全局作用域(window)勇边,所以粒褒,在全局作用域中使用的this指向window
普通函數(shù)內(nèi)部
函數(shù)內(nèi)部的作用域是局部的,屬于調(diào)用當(dāng)前函數(shù)的對象祥款,所以this執(zhí)向調(diào)用當(dāng)前函數(shù)的對象
構(gòu)造函數(shù)內(nèi)部
在構(gòu)造函數(shù)中月杉,this直接執(zhí)行當(dāng)前創(chuàng)建出來的新對象
在開發(fā)中,我們也可以使用call或者apply函數(shù)修改this的執(zhí)行桨昙,這一點我們在后面繼續(xù)說明腌歉。
自定義構(gòu)造函數(shù)存在的問題
自定義構(gòu)造函數(shù)可以解決工廠函數(shù)帶來的對象類型不確定的問題翘盖,在開發(fā)中用得非常多,那么目前我們的自定義構(gòu)造函數(shù)又是否存在問題呢藐俺?先來看看下面的對象內(nèi)存結(jié)構(gòu)分析泥彤。
function Person(name, age, say) {
this.name = name;
this.age = age;
this.say = function(){
console.log(“say hello”);
}
}var p = new Person(“zs”, 10, say);console.log§;
上面創(chuàng)建的p對象的內(nèi)存結(jié)構(gòu)圖:
可以看出吟吝,我們沒創(chuàng)建一個Person對象凿试,都會在內(nèi)存中分配如0x22和0x33這樣的內(nèi)存來存儲數(shù)據(jù)蛹磺,但是通過觀察發(fā)現(xiàn),0x33中存儲的是一個函數(shù)裙品,而這個函數(shù)在每個對象中都是相同
所以從內(nèi)存資源分配考慮市怎,我們無需為每個對象創(chuàng)建并分配一份新的函數(shù)對象(完全相同),這種函數(shù)大家最好共享同一份干像。
如何實現(xiàn)多個對象共享同一份數(shù)據(jù)呢驰弄,這就需要使用到原型相關(guān)的知識點了揩懒。
這些內(nèi)容也只是我對JavaScript面向?qū)ο蟮睦斫猓渲幸矔泻芏嗖蛔愦蠹铱梢蕴岢龀剂停?dāng)然智亮,如果有問題也可直接留言阔蛉,我會盡力幫助你,希望我們可以共同進步状原,在JavaScript的道路上越走越遠颠区。