以下內(nèi)容來(lái)自張榮銘《JavaScript設(shè)計(jì)模式》第2章。
兩種編程風(fēng)格 -- 面向過(guò)程與面向?qū)ο蟆?/p>
面向?qū)ο缶幊淌侵笇⑿枨蟪橄蟪梢粋€(gè)對(duì)象,然后針對(duì)這個(gè)對(duì)象分析其特征(屬性)與動(dòng)作(方法),這個(gè)對(duì)象我們稱之為類。
面向?qū)ο缶幊趟枷胫?,其中一個(gè)特點(diǎn)就是封裝,即把需要的功能放在一個(gè)對(duì)象里,便于管理。但由于JavaScript屬于解釋性的弱類語(yǔ)言,沒(méi)有經(jīng)典強(qiáng)類型語(yǔ)言中那種通過(guò)class等關(guān)鍵字實(shí)現(xiàn)的類的封裝方式。
1 封裝
1-1 創(chuàng)建一個(gè)類
首先聲明一個(gè)函數(shù),保存在一個(gè)變量?jī)?nèi)。按照編程習(xí)慣,代表類的首字母一般會(huì)用大寫;
然后這個(gè)函數(shù)(類)的內(nèi)部通過(guò)對(duì)this(函數(shù)內(nèi)部自帶的一個(gè)變量,用于指向當(dāng)前這個(gè)對(duì)象)變量添加屬性或者方法來(lái)實(shí)現(xiàn)對(duì)類添加屬性或者方法。
也可以通過(guò)在類的原型(類也是一個(gè)對(duì)象,也有原型prototype)上添加屬性和方法。兩種方法:一種是一一為原型對(duì)象屬性賦值,另一種則是將一個(gè)對(duì)象賦值給類的原型對(duì)象。但兩種方法不建議混用。
var Book = function (id, bookname, price) {
this.id = id;
this.bookname = bookname;
this.price = price;
}
Book.prototype.display = function () {};
// 或者
Book.prototype = {
display: function () {}
}
使用:
用 new 關(guān)鍵字來(lái)實(shí)例化(創(chuàng)建)新的對(duì)象。使用實(shí)例化對(duì)象的屬性或者方法時(shí),可以通過(guò)點(diǎn)語(yǔ)法訪問(wèn)。
JavaScript是一種基于原型prototype的語(yǔ)言,所以在創(chuàng)建一個(gè)對(duì)象時(shí)都有一個(gè)原型prototype用于指向其繼承的屬性、方法。這樣通過(guò)prototype繼承的方法并不是對(duì)象自身的,在使用這些方法時(shí)需要通過(guò)prototype一級(jí)一級(jí)查找得到。
通過(guò) this 添加的屬性、方法是在當(dāng)前對(duì)象上添加的,是該對(duì)象自身?yè)碛械?,通過(guò)類創(chuàng)建一個(gè)新對(duì)象時(shí),this 指向的屬性和方法都會(huì)得到相應(yīng)的創(chuàng)建。通過(guò)prototype繼承的屬性或者方法是每個(gè)對(duì)象通過(guò)prototype訪問(wèn)到,每次通過(guò)類創(chuàng)建一個(gè)新對(duì)象時(shí)這些屬性和方法不會(huì)再次創(chuàng)建。
1-2 constructor屬性
當(dāng)創(chuàng)建一個(gè)函數(shù)或者對(duì)象時(shí)都會(huì)為其創(chuàng)建一個(gè)原型對(duì)象prototype,在prototype對(duì)象中會(huì)創(chuàng)建一個(gè)constructor屬性。constructor屬性指向的就是擁有整個(gè)原型對(duì)象的函數(shù)或?qū)ο蟆?/p>
1-3 屬性與方法封裝
通過(guò)JS函數(shù)級(jí)作用域的特征來(lái)實(shí)現(xiàn)在函數(shù)內(nèi)部創(chuàng)建外界訪問(wèn)不到的私有化變量和私有化方法。
// 私有屬性 與 私有方法 , 特權(quán)方法, 對(duì)象公有屬性和對(duì)象共有方法, 構(gòu)造器
var Book = functiion (id, name, price) {
// 私有屬性
var num = 1;
// 私有方法
function checkId () {};
// 特權(quán)方法
this.getName = function () {};
this.getPrice = function () {};
this.setName = function () {};
this.setPrice = function () {};
// 對(duì)象公有屬性
this.id = id;
// 對(duì)象公有方法
this.copy = function () {};
// 構(gòu)造器
this.setName(name);
this.setPrice(price);
}
通過(guò)new關(guān)鍵字實(shí)例化對(duì)象時(shí),由于對(duì)類執(zhí)行一次,所以類的內(nèi)部 this上定義的屬性和方法復(fù)制到新創(chuàng)建的對(duì)象上,成為對(duì)象公有化的屬性和方法,而其中的一些方法能訪問(wèn)到類的私有屬性和方法。
通過(guò)new關(guān)鍵字創(chuàng)建的新對(duì)象,無(wú)法獲取類外面通過(guò)點(diǎn)語(yǔ)法添加的屬性和方法,但是可以通過(guò)類來(lái)使用。因此在類外面通過(guò)點(diǎn)語(yǔ)法定義的屬性以及方法被稱為類的靜態(tài)共有屬性和類的靜態(tài)共有方法。
而類通過(guò)prototype創(chuàng)建的屬性或者方法在類實(shí)例的對(duì)象中是可以通過(guò)this訪問(wèn)到的,所有prototype對(duì)象中的屬性和方法稱為共有屬性和共有方法。
// 類靜態(tài)公有屬性(對(duì)象不能訪問(wèn))
Book.isChinese = true;
// 類靜態(tài)公有方法(對(duì)象不能訪問(wèn))
Book.resetTime = function () {
console.log('new-Time');
}
Book.prototype = {
// 公有屬性
isJSBook: false,
// 公有方法
display: function () {}
}
聽(tīng)過(guò)new關(guān)鍵字創(chuàng)建的對(duì)象實(shí)質(zhì)是對(duì)新對(duì)象this的不斷賦值,并將prototype指向類的prototype所指向的對(duì)象,而類的構(gòu)造函數(shù)外面通過(guò)點(diǎn)語(yǔ)法定義的屬性方法是不會(huì)添加到新創(chuàng)建的對(duì)象上去的。因此想要在新創(chuàng)建的對(duì)象中使用 isChinese需要通過(guò)Book類,而不能通過(guò)this, 如 Book.isChinese。
通過(guò)類的原型prototype上定義的屬性在新對(duì)象里可以直接使用點(diǎn)方法訪問(wèn)。原因:新對(duì)象的prototype和類的prototype指向的是同一個(gè)對(duì)象。
例:
var b = new Book(11, 'javascript設(shè)計(jì)模式', 50);
console.log(b.num); // undefined
console.log(b.isJSBook); // false
console.log(b.id); // 11
console.log(b.isChinese); // undefined
console.log(Book.isChinese); // true
Book.resetTime(); // new-Time
1-4 閉包
閉包是有權(quán)訪問(wèn)另外一個(gè)函數(shù)作用域中變量的函數(shù),即在一個(gè)函數(shù)內(nèi)部創(chuàng)建另外一個(gè)函數(shù)。
// 利用閉包實(shí)現(xiàn)
var Book = (function () {
// 靜態(tài)私有變量
var bookNum = 0;
// 靜態(tài)私有方法
function checkBook (name) { };
// 返回構(gòu)造函數(shù)
return function (newId, newName, newPrice) {
// 私有變量
var name, price;
// 私有方法
function checkID (id) { };
// 特權(quán)方法
this.getName = function () {};
this.getPrice = function () {};
this.setName = function () {};
this.setPrice = function () {};
// 公有屬性
this.id = newId;
// 公有方法
this.copy = function () {};
bookNum++;
if (bookNum > 100) throw new Error('我們僅出版100本書');
// 構(gòu)造器
this.setName(name);
this.setPrice(price);
}
})();
Book.prototype = {
// 靜態(tài)公有屬性
isJSBook: false;
// 靜態(tài)公有方法
display: function () {}
}
// 利用閉包實(shí)現(xiàn)
var Book = (function () {
// 靜態(tài)私有變量
var bookNum = 0;
// 靜態(tài)私有方法
function checkBook (name) { };
// 創(chuàng)建類
function book(newId, newName, newPrice) {
// 私有變量
var name, price;
// 私有方法
function checkID (id) { };
// 特權(quán)方法
this.getName = function () {};
this.getPrice = function () {};
this.setName = function () {};
this.setPrice = function () {};
// 公有屬性
this.id = newId;
// 公有方法
this.copy = function () {};
bookNum++;
if (bookNum > 100) throw new Error('我們僅出版100本書');
// 構(gòu)造器
this.setName(name);
this.setPrice(price);
}
// 構(gòu)建原型
_book.prototype = {
// 靜態(tài)公有屬性
isJSBook: false;
// 靜態(tài)公有方法
display: function () {}
}
// 返回類
return _book;
})();
1-5 安全模式
// 圖書安全類
var Book = function (title, time, type) {
// 判斷執(zhí)行過(guò)程中, this是否是當(dāng)前這個(gè)對(duì)象(如果是說(shuō)明是用new創(chuàng)建的)
if (this instanceof Book) {
this.title = title;
this.time = time;
this.type = type;
// 否則重新創(chuàng)建這個(gè)對(duì)象
} else {
return new Book(title, time, type);
}
}
var book = Book('JavaScript', '2014', 'js');