JavaScript面向?qū)ο蟮某绦蛟O(shè)計(犯迷糊的小羊)
導(dǎo)語
前面的系列文章,基本把JavaScript的核心知識點的基本語法、標(biāo)準(zhǔn)庫等章節(jié)講解完;
本章開始進(jìn)入JavaScript核心知識點的高級部分——面向?qū)ο蟮某绦蛟O(shè)計,這一部分的內(nèi)容將會對對象這一數(shù)據(jù)類型做進(jìn)一步的深化理解,并且講述幾種創(chuàng)建對象的設(shè)計模式以及JavaScript獨特的繼承機制;
1.理解對象和面向?qū)ο蟮某绦蛟O(shè)計
1.1 面向?qū)ο蟮某绦蛟O(shè)計
"面向?qū)ο缶幊?(Object Oriented Programming,縮寫為OOP)本身是一種編程的思維模式,它把世界的一切看作是對象的集合,世界的運轉(zhuǎn)就是靠一個個對象分工、合作的結(jié)果,體現(xiàn)一切皆“對象”思想;
而在程序設(shè)計當(dāng)中,面向?qū)ο缶幊叹涂梢钥醋鼍帉懜鱾€具有特定功能的對象(模塊)并將它們進(jìn)行有機的分工合作,即目前的模塊化編程就是面向?qū)ο蟮某绦蛟O(shè)計的實際應(yīng)用;
1.2 理解對象
對象在前面的系列文章中曾經(jīng)提到,從數(shù)據(jù)特征上看,對象是無序?qū)傩裕ㄦI值對)的集合;
我們可以使用字面量和構(gòu)造函數(shù)的方式去創(chuàng)建一個最為簡單的對象:
var person = new Object(); person.name = "teren"; person.age = 18; person.greet = function(){ console.log("hello "+this.name); } var teren = { name:"teren", age:18, greet:function(){ console.log("hello "+this.name); } }
通常創(chuàng)建一個簡單的對象,都是采用字面量的方式;
上面的對象就是對現(xiàn)實對象的一種抽象表達(dá);
1.3 對象的屬性類型
前面章節(jié)中我們使用delete命令可以刪除一些對象的屬性,有一些又不可以,使用Object.keys()方法只能遍歷可枚舉屬性,那么對象的屬性是否有一些特性是我們尚未了解的呢?
ES5提供了一種只有內(nèi)部才用的特性(attribute)去描述對象的屬性(property)的各種特性,使用[[attribute]]表示,在JavaScript中不能直接訪問它們;
一個我們非常熟悉的栗子就是Number構(gòu)造函數(shù)構(gòu)造出來的實例對象;
我們無法直接訪問num.[[PrimitiveValue]]
,這一屬性,只能通過num.valueOf()
訪問該值;
ES5中定義對象屬性的兩種特性,數(shù)據(jù)特性和訪問器特性,對象屬性可以兼?zhèn)溥@兩種特性;
數(shù)據(jù)特性定義對象的屬性值的特性,一個屬性值可以包括以下四個數(shù)據(jù)特性:
[[Value]]:存放屬性值; [[Writable]]:是否可寫屬性; [[Enumerable]]:是否為可枚舉屬性; [[Configurable]]:是否可用delete命令刪除;
訪問器特性定義對象的屬性在訪問屬性和設(shè)置屬性時調(diào)用的兩個函數(shù)getter和setter;
[[Get]]:訪問屬性時調(diào)用的函數(shù);
[[Set]]:設(shè)置屬性時調(diào)用的函數(shù);
下面以一個實例對象直接講解這兩個特性:
//數(shù)據(jù)特性; var teren = {}; Object.defineProperty(teren,{ value:"teren", writable:false, enumerable:true, configurable:true }) //訪問器特性; //html <div id="name"></div> //js var obj = Object.defineProperty({},"name",{ set:function(name){ document.getElementById('name').innerHTML=name }, get:function(){ console.log( document.getElementById('name').innerHTML ) }, }) obj.name = "hello world" obj.name
Object.defineProperties可以一次性配置對象的多個屬性;
2. 創(chuàng)建對象的方式
上一節(jié)我們對面向?qū)ο蟮某绦蛟O(shè)計思想和對象有了初步理解,這一節(jié)我們深入探討一下對象的創(chuàng)建方式及其優(yōu)缺點;
創(chuàng)建對象的不同方式也可以簡單的稱作設(shè)計模式,不同的設(shè)計模式在實際編程應(yīng)用中起到不同的作用;
2.1 單例模式
單例模式就是產(chǎn)生一個類的唯一實例對象,它能夠確保您只有一個對象實例能夠?qū)嶋H派上用場;
單例模式下,創(chuàng)建對象方式如下:
var singleton = { attr:1, method:function(){ return this.attr } } var ex1 = singleton; var ex2 = singleton; ex1 === ex2//true
上述創(chuàng)建單例的方式:
優(yōu)點:使用非常簡捷;
缺點:缺乏封裝,成員暴露,初始化時占用資源;
可以使用閉包方式解決這一問題:
var substance = (function(){ var unique; function init(){ var type; return { setType:function(t){ return type = t; } } } return { getInstance:function(){ if(!unique){ unique = init(); } return unique; } } })(); var Adam = substance.getInstance(); var Eve = substance.getInstance(); Adam === Eve Adam.setType('Man')//Man
2.2 工廠模式
單例模式只能創(chuàng)作單個實例對象,也就是說如果將該實例對象賦予多個變量時,會存在對象的引用問題,即修改其中一個變量會影響到另一個變量;
有時我們需要創(chuàng)造多個結(jié)構(gòu)相似的對象,只有部分屬性有所區(qū)別,這時候工廠模式派上用場;
工廠模式的設(shè)計思想就是能夠像工廠一樣批量生產(chǎn)出相似屬性和方法的對象,使用工廠模式能解決多個相似的問題,例如創(chuàng)造多個彈窗(只是標(biāo)題不同);
function person(name,age){ var obj = new Object(); obj.name = name; obj.age = age; obj.greet = function(){ return "hello "+this.name; }; return obj } var Adam = person("Adam",18); var Eve = person("Eve",20);
上述工廠模式:
優(yōu)點:能批量生產(chǎn)結(jié)構(gòu)類似的對象;封裝創(chuàng)建對象的細(xì)節(jié);
缺點:未能解決對象的類型,即由哪個構(gòu)造函數(shù)創(chuàng)建的;
2.3 構(gòu)造函數(shù)模式
構(gòu)造函數(shù)可以創(chuàng)建特定類型的對象,類似之前的Array、RegExp等原生對象都能創(chuàng)造特定類型的實例對象;
function Person(name,age){ this.name = name; this.age = age; this.greet = function(){ return "hello "+this.name; } } var p1 = new Person('Adam',18); var p2 = new Person('Eve',20);
使用構(gòu)造函數(shù)模式就能夠解決實例對象由誰創(chuàng)建的問題;
上述代碼和工廠模式的區(qū)別在于:
1.沒有顯示創(chuàng)建新對象;
2.直接將屬性和方法賦給this對象;
3.沒有return語句;
4.函數(shù)名開頭大寫以區(qū)別普通函數(shù);
5.使用new操作符去創(chuàng)建對象實例;
new操作符的原理
使用new操作符去調(diào)用函數(shù)和直接調(diào)用函數(shù)不同,其new操作符的運行函數(shù)的過程為:
創(chuàng)建一個新對象;
將構(gòu)造函數(shù)的作用域賦給新對象并執(zhí)行構(gòu)造函
內(nèi)的代碼;
返回新對象;
使用代碼表示如下:
function Person(name,age){ this.name = name; this.age = age; this.greet = function(){ return "hello "+this.name; } } function createPerson(name,age){ var o = new Object(); Person.call(o,name,age); return o; } var p1 = createPerson('Adam',18); var p2 = createPerson('Eve',20);
使用構(gòu)造函數(shù)模式創(chuàng)建對象的優(yōu)缺點在于:
優(yōu)點:能夠識別對象屬于的構(gòu)造函數(shù);
缺點:如果存在不同實例對象共享的屬性和方法,使用構(gòu)造函數(shù)模式則會浪費內(nèi)存;
【注】
關(guān)于this關(guān)鍵字的更多知識點可以參見【what's this】;
構(gòu)造函數(shù)如果不用new操作符調(diào)用和普通函數(shù)是一樣的;
2.4 原型模式
每個函數(shù)都有一個prototype原型屬性,這個原型屬性可以部署特定類型的實例共享的屬性和方法;
function Person(){} } Person.prototype.greet = function(){ return "hello "+this.name;
將原來的greet函數(shù)部署在Person函數(shù)的prototype原型屬性上,這樣p1和p2可以共享該方法,而不像構(gòu)造函數(shù)模式每創(chuàng)建一個實例就增加一個greet方法浪費內(nèi)存;
【注】
關(guān)于原型對象的更多理解詳見下一節(jié)——JavaScript的繼承機制;
使用原型模式創(chuàng)建對象的優(yōu)缺點在于:
優(yōu)點:對于每個實例的共享屬性和方法能夠較好實現(xiàn);
缺點:單獨采用原型模式將無法區(qū)分不同實例的私有屬性;
2.5 混合模式
混合模式,就是綜合構(gòu)造函數(shù)模式和原型模式的優(yōu)缺點,構(gòu)造函數(shù)模式部署實例的私有屬性,原型模式部署實例的公有屬性;
function Person(name,age){ this.name = name; this.age = age; } Person.prototype.greet = function(){ return "hello "+this.name; } var p1 = new Person("Adam",18); var p2 = new Person("Eve",20);
混合模式是目前使用最廣泛、認(rèn)同度最高的一種創(chuàng)建自定義類型(類)的設(shè)計模式;
【注】
當(dāng)然,設(shè)計模式不僅僅上述所提到,還有更加精深可以參考《設(shè)計模式》一書以及之前小羊?qū)懙囊黄恼?a target="_blank" rel="external nofollow" >《設(shè)計模式梗概》;
3.JavaScript的繼承機制
上一節(jié)我們通過創(chuàng)建對象的不同模式,隱式引出了原型對象的概念,這一節(jié)中我們將詳細(xì)了解一下原型對象、原型鏈及其實現(xiàn)的繼承機制;
前面,我們從數(shù)據(jù)特征上看,知道對象是無序?qū)傩裕ㄦI值對)的集合;
現(xiàn)在,我們可以從面向?qū)ο蟮慕嵌瓤?,任何對象都是更為抽象的對象的實例,可以理解為類的概念?br />
從這個角度理解,我們現(xiàn)在可以重新定義一下對象和類的含義:
對象可以說是對現(xiàn)實事物的抽象,對象封裝了屬性和方法,屬性值指的是對象的狀態(tài),方法指的是對象的行為;
類是提供一種模板的‘對象',它是對象的抽象;
舉個簡單的栗子:
function Person(name,age){ this.name = name; this.age = age; } Person.prototype.greet = function(){ return "hello "+this.name; } var p1 = new Person("Adam",18); var p2 = new Person("Eve",20);
上述代碼表明,p1和p2兩個實例是對現(xiàn)實Adam和Eve的抽象,而“類”Person又是對2個實例的抽象,為創(chuàng)建相似結(jié)構(gòu)的人提供標(biāo)準(zhǔn)的模板;
[注]ES6之前JavaScript中沒有類,在ES6中定義了類;
3.1 原型對象
在上一節(jié)的原型模式中,我們提到每個函數(shù)都有一個prototype屬性,這個屬性指向函數(shù)的原型對象,可以部署特定類型的實例共享的屬性和方法;
更為深入理解prototype原型對象,prototype原型對象不僅可以部署特定類型的實例共享的屬性和方法,而且還是實現(xiàn)JavaScript繼承的關(guān)鍵;
只要創(chuàng)建一個新函數(shù)就會為該函數(shù)創(chuàng)建一個prototype屬性,每個prototype屬性自動獲得一個constructor屬性,該屬性指向prototype屬性所在的函數(shù)指針;
當(dāng)使用構(gòu)造函數(shù)創(chuàng)建一個實例時,該實例內(nèi)部包含一個內(nèi)部屬性__proto__指向構(gòu)造函數(shù)的原型對象;
由此,一個簡單的繼承便產(chǎn)生了;
以下面代碼為例:
function Person(name,age){ this.name = name; this.age = age; } Person.prototype.greet = function(){ return "hello "+this.name; } var p1 = new Person("Adam",18); var p2 = new Person("Eve",20);
構(gòu)造函數(shù)創(chuàng)建之后,自動創(chuàng)建一個prototype屬性,prototype原型對象下有一個默認(rèn)的constructor屬性指向prototype屬性所在的函數(shù)Person中;
在prototype原型對象上部署greet方法,實例p1的內(nèi)部屬性__proto__
指向構(gòu)造函數(shù)Person.prototype,由此繼承了構(gòu)造函數(shù)的原型對象上的greet方法;
【注意】
- 實例的__proto__指向構(gòu)造函數(shù)的prototype原型對象實現(xiàn)繼承,這種聯(lián)系存在于實例與構(gòu)造函數(shù)的原型對象之間而不是構(gòu)造函數(shù)之間;
- 當(dāng)js引擎解析對象的屬性時,先會搜索對象本身的屬性,如果沒有則會去搜索__proto__指向的原型對象上的屬性,直到找到為止,如果在對象本身定義的屬性和原型對象上的具有相同屬性名,則在讀取該屬性時,自身屬性會屏蔽原型對象上的屬性;
function Person(name,age){ this.name = name; this.age = age; } Person.prototype.greet = function(){ return "hello "+this.name; } var p1 = new Person("Adam",18); p1.greet()//hello Adam; p1.greet = function(){ return "hello world" }

function Person(name,age){ this.name = name; this.age = age; } Person.prototype = { constructor:Person, greet1:function(){}, greet2:function(){}, greet3:function(){} }; var p1 = new Person("Adam",18); Person.prototype.constructor.name//"Object"
需要注意的是,重置原型對象后,要重新為原型對象的constructor的屬性指回Person構(gòu)造函數(shù);
如果不重置constructor的話,那么此時的Person.prototype是由字面量創(chuàng)建的對象,字面量創(chuàng)建的對象默認(rèn)的構(gòu)造函數(shù)是Object;
3.2 原型鏈
上面我們只定義一個構(gòu)造函數(shù),實現(xiàn)一次繼承;如果存在多個構(gòu)造函數(shù),它們之間也存在繼承關(guān)系,那么就會形成一條關(guān)于繼承的原型鏈;
function SuperType(name,age){ this.name = name; this.age = age } SuperType.prototype.greet = function(){ return "hello "+this.name } function SubType(name,age,height){ SuperType.call(this,name,age); this.height = height; } SubType.prototype = Object.create(SuperType.prototype); SubType.prototype.constructor = SubType; SubType.prototype.method = function(){return 1;} var instance = new SubType('teren',18,180)
上面就是一個最為常用的實現(xiàn)多個類間繼承的設(shè)計模式;
使用Object.create(SuperType.prototype)的優(yōu)缺點在于:
優(yōu)點:能夠創(chuàng)建一個新的SuperType.prototype對象賦給SubType.prototype,修改SubType.prototype這個而不影響原來構(gòu)造函數(shù)SuperType.prototype;
缺點:雖然擁有子類的prototype和父類的prototype值是相同的,但內(nèi)存不同,從而切斷子類和父類之間的類型;
還可以使用SubType.prototype =new SuperType()實現(xiàn)相同效果,其優(yōu)缺點在于:
優(yōu)點:能夠體現(xiàn)子類和父類的繼承關(guān)系;
缺點:子類具有父類的私有屬性;
所以,一般在實際實現(xiàn)原型鏈時使用Object.create()方法,而理解原型鏈時使用new SuperType()方法;
3.3 與原型對象相關(guān)的方法
遍歷對象屬性方法
Object.keys()
和Object.getOwnPropertyNames()
用于遍歷對象自身而不是繼承的屬性名,返回一個數(shù)組,其中Object.keys()
只返回可枚舉屬性;
in
用于檢查一個對象是否具有某個屬性。它不區(qū)分該屬性是對象自身的屬性,還是繼承的屬性;
for...in
用于遍歷對象的所有可枚舉屬性(不管是自身的還是繼承的)
如果只遍歷自身的屬性,可以使用如下代碼:
for (var key in instance){ if(instance.hasOwnProperty(key)){ console.log(key) } }
判斷屬性是否為自身的方法
Object.prototype.hasOwnProperty()返
回一個布爾值,用于判斷某個屬性定義在對象自身,還是定義在原型鏈上;
設(shè)置和獲取實例對象的原型對象的方法
Object.getPropertyOf()
返回一個實例對象的原型對象;
Object.setPropertyOf(obj,prototype)
可傳兩個參數(shù),第1個為現(xiàn)有參數(shù),第2個為原型對象;
判斷一個對象是否為另一個對象的原型對象
Object.prototype.isPrototypeOf()
用于判斷一個對象是否是另一個對象的原型;
小結(jié)
通讀本文,我們可以知道:
- 面向?qū)ο缶幊虝r一種思維模式,它把世界的一切看做是對象的集合,世界的運作是一個個對象分工合作的結(jié)果;映射到程序設(shè)計中,就是編寫各個具有特定功能的對象(模塊),并將它們有機整合使程序得以運作;
- 對象從數(shù)據(jù)特征角度看,是無序鍵值對的集合,對象的屬性具有兩種特性——數(shù)據(jù)特性和訪問器特性;
- 創(chuàng)建對象的不同方式可以稱為設(shè)計模式,本文簡單講解了單例模式、工廠模式、構(gòu)造函數(shù)模式、原型模式、混合模式等;
- 從面向?qū)ο蠼嵌瓤?,對象可以說是對現(xiàn)實事物的抽象,類是對對象的抽象;
- 每個函數(shù)都有一個原型對象prototype,既可以部署特定類型實例的共享屬性和方法,也是JavaScript實現(xiàn)繼承的關(guān)鍵;
- prototype原型對象有一個constructor屬性,該屬性指向prototype所在的函數(shù)指針;
- 每當(dāng)使用構(gòu)造函數(shù)創(chuàng)建一個實例時,該實例內(nèi)部包含一個內(nèi)部屬性__proto__指向構(gòu)造函數(shù)的原型對象,由此實現(xiàn)簡單的繼承;
- 當(dāng)A構(gòu)造函數(shù)是B構(gòu)造函數(shù)的實例時,由此就會形成一條原型鏈,即
A構(gòu)造函數(shù)的實例對象C的__proto__指向A構(gòu)造函數(shù)的原型對象prototype,A構(gòu)造函數(shù)prototype的__proto__指向B構(gòu)造函數(shù)的原型對象prototype,B構(gòu)造函數(shù)prototype的__proto__指向Function構(gòu)造函數(shù)的prototype,F(xiàn)unction的prototype的__proto__指向Object的prototype; - 與原型對象的相關(guān)方法包括:Object.keys()和Object.getPropertyNames()、for...in方法,Object.getPrototypeOf()和Object.setPrototypeOf()方法,Object.prototype.hasOwnProperty()和Object.prototype.isPrototypeOf()方法;
參考資料
《JavaScript高級程序設(shè)計(第3版)》
《JavaScript標(biāo)準(zhǔn)參考教程》——阮一峰
- JavaScript求一組數(shù)的最小公倍數(shù)和最大公約數(shù)常用算法詳解【面向?qū)ο?,回歸迭代和循環(huán)】
- javascript 面向?qū)ο骹unction詳解及實例代碼
- JS 面向?qū)ο笾^承---多種組合繼承詳解
- JS面向?qū)ο缶幊淘斀?/a>
- 詳解JS面向?qū)ο缶幊?/a>
- 詳解JavaScript基于面向?qū)ο笾^承實例
- 詳解JavaScript基于面向?qū)ο笾^承
- 詳解JavaScript基于面向?qū)ο笾畡?chuàng)建對象(2)
- 詳解JavaScript基于面向?qū)ο笾畡?chuàng)建對象(1)
- js面向?qū)ο笾?、私有、靜態(tài)屬性和方法詳解
- JS Pro-深入面向?qū)ο蟮某绦蛟O(shè)計之繼承的詳解
- JAVASCRIPT THIS詳解 面向?qū)ο?/a>
- JS面向?qū)ο蟮某绦蛟O(shè)計相關(guān)知識小結(jié)
相關(guān)文章
用js腳本控制asp.net下treeview的NodeCheck的實現(xiàn)代碼
根據(jù)TreeView2.js修改后的TreeView父節(jié)點與子節(jié)點的CheckBox聯(lián)動.2010-03-03javascript 進(jìn)度條 實現(xiàn)代碼
這個例子是通過測試的。是真真正正根據(jù)記錄的條數(shù)掛鉤的。2009-07-07JavaScript中Location.search處理使用方法
本文主要介紹了JavaScript中Location.search處理使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04詳解如何使用JavaScript中Promise類實現(xiàn)并發(fā)任務(wù)控制
在JavaScript中,Promise是一種用于管理異步操作的強大工具,但是,有時候需要更高級的控制,以限制同時執(zhí)行的任務(wù)數(shù)量,以避免系統(tǒng)資源超負(fù)荷,本文將深入探討JavaScript中的并發(fā)任務(wù)控制,并介紹如何創(chuàng)建一個自定義的Promise類——ConcurrentPromise2023-08-08JavaScript 中實現(xiàn) use strict的方法及優(yōu)勢
本教程將討論JavaScript中的use strict特性,在這里,我們將通過不同的示例了解如何在JavaScript代碼語句中創(chuàng)建和執(zhí)行use strict關(guān)鍵字,需要的朋友可以參考下2023-09-09