P8
類
JavaScript 與 C++或 Java 這種傳統(tǒng)的面向?qū)ο笳Z言不同,它實(shí)際上壓根兒沒有類矮燎。該語言的一切都是基于對(duì)象的定血,其依靠的是一套原型(prototype)系統(tǒng)。而原型本身實(shí)際上也是一種對(duì)象诞外。
封裝
我們只需要知道所操作對(duì)象的接口澜沟,而不必去關(guān)心它的具體實(shí)現(xiàn)。
備注
在 JavaScript 中峡谊,盡管所有的方法和屬性都是 public 的茫虽,但該語言還是提供了一些隱藏?cái)?shù)據(jù)的方法,以保護(hù)程序的隱密性既们。
P9
聚合
將幾個(gè)現(xiàn)有對(duì)象合并成一個(gè)新對(duì)象的過程席噩。在 Java 中類的成員變量可以是基本類型(如 int、char)贤壁,也可以是其他類悼枢。
繼承 多態(tài)
和 Java 中的繼承、多態(tài)一樣
P23
基本數(shù)據(jù)類型
數(shù)字
字符串
布爾值
undefined:僅聲明但未賦值脾拆。
null:已經(jīng)賦值馒索,只不過值為 null。用 typeof 查看變量類型輸出的是 'object'名船。
P27
Infinity(無窮大)
在 JavaScript 中绰上,還有一種叫做Infinity 的特殊值。它所代表的是超出了 JavaScript 處理范圍的數(shù)值渠驼。但 Infinity 依然是一個(gè)數(shù)字蜈块,我們可以在控制臺(tái)使用 typeof 來測(cè)試Infinity。當(dāng)我們輸入1e308 時(shí)迷扇,一切正常百揭,但一旦將后面的308 改成 309 就出界了。實(shí)踐證明蜓席,JavaScript所能處理的最大值是1.7976931348623157e+308器一,而最小值為5e-324狡刘。
另外缤言,任何數(shù)除以 0 結(jié)果也為 Infinity
P28
NaN(Not A Number)
實(shí)上它依然屬于數(shù)字類型。運(yùn)算錯(cuò)誤就會(huì)返回 NaN心墅。
P30
字符串轉(zhuǎn)數(shù)字
> var s = 100;
> s = s * 1;
> typeof s;
< "number"
P34
空字符串 ""
null
undefined
數(shù)字 0
數(shù)字 NaN
布爾值 false
這 6 個(gè)值有時(shí)也會(huì)被我們稱為 falsy 值猜敢,而其他值則被稱為 truthy 值(包括字符串""颅围、" "粘我、"false"等)聂薪。
P37
如果 JavaScript 引擎在一個(gè)邏輯表達(dá)式中遇到一個(gè)非布爾類型的操作數(shù),那么該操作數(shù)的值就會(huì)成為該表達(dá)式所返回的結(jié)果方仿。例如:
> true || "something";
< true
> true && "something";
< "something"
> true && "something" && true;
< true
通常情況下述雾,這種行為應(yīng)該盡量避免,因?yàn)樗鼤?huì)使我們的代碼變得難以理解兼丰。但在某些時(shí)候這樣做也是有用的。例如唆缴,當(dāng)我們不能確定某個(gè)變量是否已經(jīng)被定義時(shí)鳍征,就可以像下面這樣,即如果變量 mynumber 已經(jīng)被定義了面徽,就保留其原有值艳丛,否則就將它初始化為 10。
> var mynumber = mynumber || 10;
> mynumber;
< 10
這種做法簡(jiǎn)單而優(yōu)雅趟紊,但是請(qǐng)注意氮双,這也不是絕對(duì)安全的。如果這里的 mynumber 之前被初始化為 0(或者是那 6 個(gè) falsy 值中的任何一個(gè))霎匈,這段代碼就不太可能如我們所愿了戴差。
> var mynumber = 0;
> var mynumber = mynumber || 10;
> mynumber;
< 10
P39
NaN 不等于任何東西,包括它自己
> NaN == NaN;
< false
undefined 與 null
當(dāng)我們嘗試使用一個(gè)不存在的變量時(shí)铛嘱,控制臺(tái)中就會(huì)產(chǎn)生以下錯(cuò)誤信息:
> foo;
< ReferenceError: foo is not defined
但當(dāng)對(duì)不存在的變量使用 typeof 操作符時(shí)則不會(huì)出現(xiàn)這樣的錯(cuò)誤暖释,而是會(huì)返回一個(gè)字符串"undefined"。
> typeof foo;
< "undefined"
如果我們?cè)诼暶饕粋€(gè)變量時(shí)沒有對(duì)其進(jìn)行賦值墨吓,調(diào)用該變量時(shí)并不會(huì)出錯(cuò)球匕,但 typeof操作符依然會(huì)返回"undefined":
> var somevar;
> typeof somevar;
< "undefined"
這是因?yàn)楫?dāng)我們聲明而不初始化一個(gè)變量時(shí),JavaScript 會(huì)自動(dòng)使用 undefined 值來初始化這個(gè)變量帖烘。
> var somevar;
> somevar === undefined;
< true
但 null 值就完全是另一回事了亮曹。它不能由 JavaScript 自動(dòng)賦值,只能交由我們的代碼來完成秘症。
> var somevar = null;
> somevar;
< null
> typeof somevar;
< "object"
P43
如果新元素被添加的位置與原數(shù)組末端之間存在一定的間隔照卦,那么這之間的元素將會(huì)被自動(dòng)設(shè)定為 undefined 值。例如:
> var a = [1,2,3];
> a[6] = 'new';
> a;
< [1, 2, 3, undefined x 3, "new"]
P44
刪除元素
為了刪除特定的元素乡摹,我們需要用到 delete 操作符窄瘟。然而,相關(guān)元素被刪除后趟卸,原數(shù)組的長(zhǎng)度并不會(huì)受到影響蹄葱。從某種意義上來說氏义,該元素被刪除的位置只是被留空了而已。
> var a = [1,2,3];
> delete a[1];
> a;
< [1, undefined, 3]
P45
我們也可以通過這種數(shù)組訪問方式來獲取字符串中特定位置上的字符
> var s = 'one';
> s[0];
< "o"
P63
調(diào)用函數(shù)時(shí)忘了傳遞相關(guān)的參數(shù)图云,JavaScript 引擎就會(huì)自動(dòng)將其設(shè)定為 undefined惯悠。
內(nèi)建變量 arguments
它能返回函數(shù)所接收的所有參數(shù)。
> function args() {
return arguments;
}
> args();
< []
> args( 1, 2, 3, 4, true, 'ninja');
< [1, 2, 3, 4, true, "ninja"]
P65
預(yù)定義函數(shù)
parseInt()
將字符串轉(zhuǎn)換為整數(shù)竣况,支持 8 進(jìn)制克婶、10 進(jìn)制和 16 進(jìn)制。
parseFloat()
將字符串轉(zhuǎn)換為十進(jìn)制數(shù)
isNaN()
用與判斷是否是 NaN
isFinite()
判斷是否是 Infinity
P69
URI 的編碼與反編碼
在 URL(Uniform Resource Locator丹泉,統(tǒng)一資源定位符)或 URI(Uniform ResourceIdentifier情萤,統(tǒng)一資源標(biāo)識(shí)符)中,有一些字符是具有特殊含義的摹恨。如果我們想“轉(zhuǎn)義”這些字符筋岛,就可以去調(diào)用函數(shù) encodeURI()或 encodeURIComponent()。
> var url = 'http://www.packtpub.com/scr ipt.php?q=this and that';
> encodeURI(url);
< "http://www.packtpub.com/scr%20ipt.php?q=this%20and%20that"
> encodeURIComponent(url);
< "http%3A%2F%2Fwww.packtpub.com%2Fscr%20ipt.php%3Fq%3Dthis%20and%20that"
encodeURI()和 encodeURIComponent()分別都有各自對(duì)應(yīng)的反編碼函數(shù):decodeURI() 和 decodeURIComponent()晒哄。
eval()
eval()會(huì)將其輸入的字符串當(dāng)做 JavaScript 代碼來執(zhí)行睁宰。
> eval('var ii = 2;');
> ii;
< 2
所以,這里的 eval('var ii = 2;')與表達(dá)式 var ii = 2;的執(zhí)行效果是相同的寝凌。
安全性方面 — JavaScript 擁有的功能很強(qiáng)大柒傻,但這也意味著很大的不確定性,如果您對(duì)放在 eval()函數(shù)中的代碼沒有太多把握较木,最好還是不要這樣使用红符。
性能方面 — 它是一種由函數(shù)執(zhí)行的“動(dòng)態(tài)”代碼,所以要比直接執(zhí)行腳本要慢伐债。
總結(jié)下來就是功能強(qiáng)大但不安全违孝,而且執(zhí)行速度慢,所以最好別用泳赋。
P72
變量提升
> var a = 123;
> function f() {
alert(a);
var a = 1;
alert(a);
}
> f();
這串代碼執(zhí)行的結(jié)果是:
第一個(gè) alert() 彈出 undefined
第二個(gè) alert() 彈出 1
盡管第一次調(diào)用 alert() 時(shí)變量 a 還沒有被正式定義雌桑,但該變量已經(jīng)存在于本地空間了,而且函數(shù)域始終優(yōu)于全局域祖今,所以第一次彈出 undefined校坑。
其實(shí)我覺得變量提升這個(gè)特性不用去記,這種容易造成誤解的特性一般不會(huì)有人去用千诬。
更多參考: JavaScript中變量提升是語言設(shè)計(jì)缺陷
P73
函數(shù)也是數(shù)據(jù)
這種特殊的數(shù)據(jù)有兩個(gè)重要特性:
1.它們所包含的是代碼
2.它們是可執(zhí)行的
舉個(gè)栗子耍目,我們可以把一個(gè)函數(shù)賦值給一個(gè)變量
> function define() {
return 1;
}
> var express = function() {
return 1;
}
> typeof define;
< "function"
> typeof express;
< "function"
像變量那樣使用函數(shù)
> var sum = function(a, b) {
return a + b;
}
> var add = sum;
> typeof add;
< "function"
> add(1, 2);
< 3
P75
回調(diào)函數(shù)
既然函數(shù)是一種特殊的變量,那么它也能像變量那樣被當(dāng)成參數(shù)傳給其它函數(shù)徐绑。
> function invokeAdd(a, b) {
return a() + b();
}
> invokeAdd(
function () {return 1; },
function () {return 2; }
);
< 3
自己寫了一個(gè)遍歷數(shù)組的回調(diào)函數(shù)
> function each(array, callback) {
for (var i = 0; i < array.length; i++) {
callback(array[i]);
}
}
> var num = [1, 4, 77, 233, 2233];
> each(a, function(item) {
alert(item);
});
P79
即時(shí)函數(shù)
函數(shù)在定義后可以立即使用
> (function() {
// ...
}());
P80
內(nèi)部(私有)函數(shù)
函數(shù)和其他類型本質(zhì)上是一樣的邪驮,所以函數(shù)內(nèi)部也可以定義一個(gè)函數(shù)。
function outer(param) {
function inner(theinput) {
return theinput * 2;
}
return 'The result is ' + inner(param);
}
私有函數(shù)外部不可訪問
> outer(2);
< "The result is 4"
> inner(2);
< ReferenceError: inner is not defined
P81
返回函數(shù)的函數(shù)
函數(shù)始終都會(huì)有一個(gè)返回值傲茄,即便不是顯式返回毅访,它也會(huì)隱式返回一個(gè) undefined沮榜。既然函數(shù)的本質(zhì)是變量,那么它自然能夠作為值被返回喻粹。
function a() {
alert('A!');
return function(){
alert('B!');
};
}
執(zhí)行函數(shù)
> var newFunc = a();
> newFunc();
如果您想讓返回的函數(shù)立即執(zhí)行蟆融,也可以不用將它賦值給變量,直接在該調(diào)用后面再加一對(duì)括號(hào)即可守呜,效果是一樣的:
> a()();
P82
重寫函數(shù)
函數(shù)能夠從內(nèi)部重寫自己
function a() {
alert('A!');
a = function(){
alert('B!');
};
}
執(zhí)行這個(gè)函數(shù)的話除了第一次會(huì)彈出 A型酥,之后只會(huì)彈出 B。這是因?yàn)樵?a() 第一次執(zhí)行前它內(nèi)部是這樣的
> console.info(a);
< function a() {
alert('A!');
a = function() {
alert('B!');
};
}
函數(shù)調(diào)用一次之后內(nèi)部就被重寫了
> a();
> console.info(a);
< function() {
alert('B!');
};
重寫函數(shù)有什么
不同的瀏覽器特性不同查乒,我們可以通過重寫讓函數(shù)根據(jù)當(dāng)前所在的瀏覽器來重定義自己弥喉。這就是所謂的“瀏覽器兼容性探測(cè)”技術(shù)。
P86
閉包
閉包最常見的例子就是利用閉包突破作用域鏈玛迄。在此之前有兩個(gè)重要概念:
1.函數(shù)內(nèi)的函數(shù)能夠訪問函數(shù)內(nèi)的變量由境。
2.所有函數(shù)都能夠訪問全局變量。
從函數(shù)外部訪問函數(shù)內(nèi)的變量
首先在目標(biāo)函數(shù)內(nèi)聲明一個(gè)函數(shù)憔晒,聲明的這個(gè)函數(shù)對(duì)目標(biāo)函數(shù)的所有變量擁有訪問權(quán)限,然后再將聲明的函數(shù)賦值給一個(gè)全局變量蔑舞,這樣就能做到從外部訪問函數(shù)內(nèi)的變量了拒担。
閉包#1
> var F = function() {
var b = 'local variable';
var N = function() {
return b;
};
return N;
}
> var inner = F();
> inner();
< "local variable"
閉包#2
> var inner; // placeholder
> var F = function() {
var b = 'local variable';
var N = function() {
return b;
}
inner = N;
}
> F();
> inner();
< "local variable"
由于 N() 是在 F() 內(nèi)部定義的,它可以訪問 F() 的作用域攻询,所以即使該函數(shù)后來升級(jí)成了全局函數(shù)从撼,但它依然可以保留對(duì) F() 作用域的訪問權(quán)。
閉包#3
每個(gè)函數(shù)都可以被認(rèn)為是一個(gè)閉包钧栖。因?yàn)槊總€(gè)函數(shù)都在其作用域中維護(hù)了某種私有聯(lián)系低零。但在大多數(shù)時(shí)候,該作用域在函數(shù)體執(zhí)行完之后就自行銷毀了— 除非發(fā)生一些有趣的事(比如像上一小節(jié)所述的那樣)拯杠,導(dǎo)致作用域被保持掏婶。
讓我們?cè)賮砜匆粋€(gè)閉包的例子。這次我們使用的是函數(shù)參數(shù)(function parameter)潭陪。該參數(shù)與函數(shù)的局部變量沒什么不同雄妥,但它們是隱式創(chuàng)建的(即它們不需要使用 var 來聲明)。
> function F(param) {
var N = function() {
return param;
};
param++;
return N;
}
> var inner = F(123);
> inner();
< 124;
循環(huán)中的閉包
新手們?cè)陂]包問題上會(huì)犯的典型錯(cuò)誤
> function F(param) {
var arr = [];
for(var i = 0; i < 3; i++) {
arr[i] = function() {
return i;
};
}
return arr;
}
> var arr = F();
> arr[0]();
< 3
> arr[1]();
< 3
> arr[2]();
< 3
為什么返回的都是3
在這串代碼中創(chuàng)建了3個(gè)閉包依溯,它們都指向了同一個(gè)局部變量 i老厌。而 return i;
是引用傳遞而不是值傳遞,傳遞的是 i 的引用而非 i 的值黎炉。執(zhí)行完 F() 函數(shù)之后 i 的值為3枝秤,所以3個(gè)閉包輸出的值都是3。
換一種閉包形式
> function F(param) {
var arr = [];
for(var i = 0; i < 3; i++) {
arr[i] = (function(x) {
return function() {
return x;
}
}(i) );
}
return arr;
}
> var arr = F();
> arr[0]();
< 0
> arr[1]();
< 1
> arr[2]();
< 2
這串代碼其實(shí)也沒有改變引用傳遞的方式慷嗜,只不過是創(chuàng)建了3個(gè)引用淀弹,而且用上了即時(shí)函數(shù)丹壕。
P91
getter 與 setter
為了不將數(shù)據(jù)暴露給外部,我們將數(shù)據(jù)封裝在函數(shù)內(nèi)部垦页。為了操作數(shù)據(jù)雀费,我們提供兩個(gè)接口:getter 與 setter 來獲取和設(shè)置值。
> var getValue, setValue;
> (function() {
var secret = 0;
getValue = function() {
return secret;
};
setValue = function(v) {
if(typeof v === 'number') {
secret = v;
}
};
}() );
> getValue();
< 0
> setValue(123);
> getValue();
< 123
> setValue(false);
> getValue();
< 123
P92
迭代器
我們都知道如何用循環(huán)來遍歷一個(gè)簡(jiǎn)單的數(shù)組痊焊,但是有時(shí)候我們需要面對(duì)更為復(fù)雜的數(shù)據(jù)結(jié)構(gòu)盏袄,它們通常會(huì)有著與數(shù)組截然不同的序列規(guī)則。這時(shí)候就需要將一些“誰是下一個(gè)”的復(fù)雜邏輯封裝成易于使用的 next()函數(shù)薄啥,然后辕羽,我們只需要簡(jiǎn)單地調(diào)用 next() 就能實(shí)現(xiàn)對(duì)于相關(guān)的遍歷操作了。
> function setup(x) {
var i = 0;
return function() {
return x[i++];
};
}
> var next = setup(['a', 'b', 'c']);
> next();
< "a"
> next();
< "b"
> next();
< "c"
P93
練習(xí)題
1.顏色轉(zhuǎn)換器
> function getRGB(color) {
if(typeof color != 'string') {
console.info('請(qǐng)輸入字符串');
return;
}
var reg = /^#?[0-9a-fA-F]{6}$/;
if(reg.test(color)) {
var rgb = color.match(/[0-9a-fA-F]{2}/g);
var r = parseInt(rgb[0], 16);
var g = parseInt(rgb[1], 16);
var b = parseInt(rgb[2], 16);
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
}
}
> var str = '#334aF4';
> getRGB(str);
< "rgb(51, 74, 244)"
2.如果在控制臺(tái)中執(zhí)行以下各行垄惧,分別會(huì)輸出什么內(nèi)容刁愿?
> parseInt(1e1);
< 10
> parseInt('1e1');
< 1
> parseFloat('1e1');
< 10
> isFinite(0/10);
< true
> isFinite(20/0);
< false
> isNaN(parseInt(NaN));
< true
3.下面代碼中,alert()彈出的內(nèi)容會(huì)是什么到逊?
> var a = 1;
> function f() {
function n() {
alert(a);
}
var a = 2;
n();
}
> f();
會(huì)彈出2铣口,應(yīng)該是變量提升的緣故。
4.以下所有示例都會(huì)彈出"Boo觉壶!"警告框脑题,您能分別解釋其中原因嗎?
4.1
var f = alert;
eval('f("Boo!")');
在 JavaScript 中函數(shù)也是變量铜靶,只不過這個(gè)變量包含的是代碼并且可執(zhí)行叔遂。將 alert 函數(shù)賦值給 f,相當(dāng)于是給 alert 取了個(gè)別名争剿,后面加 () 執(zhí)行函數(shù)已艰。直接調(diào)用 f('Boo!');
也能夠達(dá)到同樣的效果。
4.2
var e;
var f = alert;
eval('e=f')('Boo!');
將函數(shù) f 賦值給 e 并且在賦值完之后執(zhí)行函數(shù)蚕苇。
4.3
(function() {
return alert;
})() ('Boo!');
把這串代碼拆分來看哩掺,可以分成兩個(gè)部分:
第一部分
(function() { return alert; })()
這一個(gè)即時(shí)函數(shù),它返回的是 alert
第二部分
('Boo!');
這是一個(gè)方法體
它們合在一起就是 alert('Boo!');
P97
元素涩笤、屬性疮丛、方法與成員
說到數(shù)組的時(shí)候,我們常說其中包含的是元素辆它。而當(dāng)我們說對(duì)象時(shí)誊薄,就會(huì)說其中包含的是屬性。實(shí)際上對(duì)于 JavaScript 來說锰茉,它們并沒有多大的區(qū)別呢蔫,只是在技術(shù)術(shù)語上的表達(dá)習(xí)慣有所不同罷了。這也是它區(qū)別于其他程序設(shè)計(jì)語言的地方。
另外片吊,對(duì)象的屬性也可以是函數(shù)绽昏,因?yàn)楹瘮?shù)本身也是一種數(shù)據(jù)。在這種情況下俏脊,我們稱該屬性為方法全谤。例如下面的 talk 就是一個(gè)方法:
var dog = {
name: 'Benji',
talk: function() {
alert('Woof, woof!');
}
}
如果我們要訪問的屬性名是不確定的,就必須使用中括號(hào)表示法了爷贫,它允許我們?cè)谶\(yùn)行時(shí)通過變量來實(shí)現(xiàn)相關(guān)屬性的動(dòng)態(tài)存取认然。
> var key = 'name';
> dog[key];
< "Benji"
P101
修改屬性與方法
由于 JavaScript 是一種動(dòng)態(tài)語言,所以它允許我們隨時(shí)對(duì)現(xiàn)存對(duì)象的屬性和方法進(jìn)行修改漫萄。其中自然也包括添加與刪除屬性卷员。
> var hero = {};
> typeof hero.breed;
< "undefined"
> hero.breed = 'turtle';
> hero.name = 'Leonardo';
> hero.sayName = function() {
return hero.name;
}
> hero.sayName();
< "Leonardo"
刪除一個(gè)屬性
> delete hero.name;
< true
> hero.sayName();
< "undefined"
P103
構(gòu)造器函數(shù)
> function Hero() {
this.occupation = 'Ninja';
}
> var hero = new Hero();
> hero.occupation;
< "Ninja"
P104
全局對(duì)象
事實(shí)上,程序所在的宿主環(huán)境一般都會(huì)為其提供一個(gè)全局對(duì)象腾务,而所謂的全局變量其實(shí)都只不過是該對(duì)象的屬性罷了毕骡。
> var a = 1;
> window.a;
< 1
> this.a;
< 1
P106
構(gòu)造器屬性
當(dāng)我們創(chuàng)建對(duì)象時(shí),實(shí)際上同時(shí)也賦予了該對(duì)象一種特殊的屬性 — 即構(gòu)造器屬性(constructor property)岩瘦。該屬性實(shí)際上是一個(gè)指向用于創(chuàng)建該對(duì)象的構(gòu)造器函數(shù)的引用未巫。
回到103頁(yè)
> hero.constructor;
< function Hero() {
this.occupation = 'Ninja';
}
簡(jiǎn)單來說,構(gòu)造器屬性是默認(rèn)的屬性启昧,該屬性指向構(gòu)造函數(shù)叙凡。
P107
instanceof 操作符
用于測(cè)試一個(gè)對(duì)象的類型,彌補(bǔ)了 typeof 的不足箫津。
P108
構(gòu)造器函數(shù)默認(rèn)返回的是 this 對(duì)象
function C() {
// var this = {}; //pseudo code, you can't do this
this.a = 1;
// return this;
}
P109
傳遞對(duì)象
當(dāng)我們拷貝某個(gè)對(duì)象或者將它傳遞給某個(gè)函數(shù)時(shí)狭姨,往往傳遞的都是該對(duì)象的引用宰啦。因此我們?cè)谝蒙纤龅娜魏胃膭?dòng)苏遥,實(shí)際上都會(huì)影響它所引用的原對(duì)象。
> var original = {howmany: 100};
> var nullify = function(o) {o.howmany = 0;}
> nullify(original);
> original.howmany;
< 0
P117
Function
之前赡模,我們已經(jīng)了解了函數(shù)是一種特殊的數(shù)據(jù)類型田炭,但事實(shí)還遠(yuǎn)不止如此,它實(shí)際上是一種對(duì)象漓柑。函數(shù)對(duì)象的內(nèi)建構(gòu)造器是 Function()教硫,你可以將它作為創(chuàng)建函數(shù)的一種備選方式(但我們并不推薦這種方式)。
> function sum(a, b) { // function declaration
return a + b;
}
> sum(1, 2)
< 3
> var sum = new Function('a', 'b', 'return a + b;');
> sum(1, 2)
< 3
P120
prototype 屬性
> var ninja = {
name: 'Ninja',
say: function() {
return 'I am a ' + this.name;
}
};
> function F() {};
> typeof F.prototype;
< "object"
如果我們現(xiàn)在對(duì)該 prototype 屬性進(jìn)行修改辆布,就會(huì)發(fā)生一些有趣的變化:當(dāng)前默認(rèn)的空對(duì)象被直接替換成了其他對(duì)象瞬矩。
> F.prototype = ninja;
> var baby_ninja = new F();
> baby_ninja.name;
< "Ninja"
> baby_ninja.say();
< "I am a Ninja"
P121
call() 與 apply()
在 JavaScript 中,每個(gè)函數(shù)都有 call()和 apply()兩個(gè)方法锋玲,您可以用它們來觸發(fā)函數(shù)景用,并指定相關(guān)的調(diào)用參數(shù)。
> var some_obj = {
name: 'Ninja',
say: function(who) {
return 'Haya ' + who + ', I am a ' + this.name;
}
};
> some_obj.say('Dude');
< "Haya Dude, I am a Ninja"
下面惭蹂,我們?cè)賱?chuàng)建一個(gè) my_obj 對(duì)象伞插,它只有一個(gè) name 屬性:
> var my_obj = {name: 'Scripting guru'};
顯然割粮,some_obj 的 say()方法也適用于 my_obj,因此我們希望將該方法當(dāng)做 my_obj 自身的方法來調(diào)用媚污。在這種情況下舀瓢,我們就可以試試 say()函數(shù)中的對(duì)象方法 call():
> some_obj.say.call(my_obj, 'Dude');
> "Haya Dude, I am a Scripting guru"
實(shí)際上是轉(zhuǎn)移了 this 對(duì)象
P122
arguments 實(shí)際上是一個(gè)類數(shù)組對(duì)象,它沒有數(shù)組的 sort()耗美、slice()方法京髓,我們可以用 call 方法讓它使用數(shù)組的方法。
> function f(){
var args = [].slice.call(arguments);
return args.reverse();
}
> f(1,2,3,4);
< [4,3,2,1]
P154
js 中函數(shù)既可以作為普通函數(shù)使用又可以作為構(gòu)造器來創(chuàng)建對(duì)象幽歼。相比于 Java 嚴(yán)謹(jǐn)?shù)恼Z法朵锣,JavaScript 的語法顯得有點(diǎn)亂。
P156
使用原型添加方法或?qū)傩?/p>
function Gadget(name, color) {
this.name = name;
this.color = color;
this.whatAreYou = function() {
return 'I am a ' + this.color + ' ' + this.name;
};
}
Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
Gadget.prototype.getInfo = function() {
return 'Rating: ' + this.rating + ', price: ' + this.price;
};
Gadget.prototype.get = function(what) { // getter
return this[what];
};
Gadget.prototype.set = function(key, value) { // setter
this[key] = value;
};
P164
__proto__與 prototype 并不是等價(jià)的甸私。__proto__實(shí)際上是某個(gè)實(shí)例對(duì)象的屬性诚些,而 prototype 則是屬于構(gòu)造器函數(shù)的屬性。
參考__proto__ 和 prototype 到底有啥區(qū)別
P165
PHP中有一個(gè)叫做in_array()的函數(shù)皇型,主要用于查詢數(shù)組中是否存在某個(gè)特定的值诬烹。JavaScript 中則沒有一個(gè)叫做 inArray()的方法(不過在 ES5 中有 indexOf()方法),因此弃鸦,下面我們通過 Array.prototype 來實(shí)現(xiàn)一個(gè)绞吁。
> Array.prototype.inArray = function(needle) {
for (var i = 0; i < this.length; i++) {
if (this[i] === needle) {
return true;
}
}
return false;
}
> var colors = ['red', 'green', 'blue'];
> colors.inArray('red');
< true
> colors.inArray('yellow');
< false
字符串反轉(zhuǎn)函數(shù)
> String.prototype.reverse = function() {
return Array.prototype.reverse.apply(this.split('')).join('');
}
> "bumblebee".reverse();
< "eebelbmub"
P166
關(guān)于擴(kuò)展內(nèi)建對(duì)象
雖說通過原型來擴(kuò)展內(nèi)建對(duì)象功能強(qiáng)大,但是我們使用的時(shí)候得慎重考慮唬格。我們擴(kuò)展過的函數(shù)沒準(zhǔn)將來出現(xiàn)在內(nèi)置方法中家破,這樣很可能導(dǎo)致無法預(yù)期的錯(cuò)誤。
擴(kuò)展內(nèi)置對(duì)象一般是向下兼容购岗,當(dāng)您用自定義方法擴(kuò)展原型時(shí)汰聋,首先應(yīng)該檢查該方法是否已經(jīng)存在。這樣一來喊积,當(dāng)瀏覽器內(nèi)存在同名內(nèi)建方法時(shí)烹困,我們可以直接調(diào)用原生方法,這就避免了方法覆蓋乾吻。
if (typeof String.prototype.trim !== 'function') {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+&/g, '' );
};
}
> " hello ".trim();
< "hello"
P167
原型陷阱
接下來是終極無敵繞的代碼環(huán)節(jié)
> function Dog() {
this.tail = true;
}
> var benji = new Dog();
> var rusty = new Dog();
即便在 benji 和 rusty 對(duì)象創(chuàng)建之后髓梅,我們也依然能為 Dog() 的原型對(duì)象添加屬性,并且在屬性被添加之前就已經(jīng)存在的對(duì)象也可以隨時(shí)訪問這些新屬性∫锴現(xiàn)在枯饿,讓我們放一個(gè) say() 方法進(jìn)去:
> Dog.prototype.say = function(){
return 'Woof!';
};
> benji.say();
< "Woof!"
> rusty.say();
< "Woof!"
現(xiàn)在,我們用一個(gè)自定義的新對(duì)象完全覆蓋掉原有的原型對(duì)象:
> Dog.prototype = {
paws: 4,
hair: true
};
然后 benji 和 rusty 并不能訪問到新的原型對(duì)象中的屬性诡必。
補(bǔ)充
這里再來回顧一下 constructor 是什么奢方,constructor 是一個(gè)屬性,這個(gè)屬性指向構(gòu)造函數(shù)。benji 的構(gòu)造函數(shù)是 Dog() 袱巨,Dog 的構(gòu)造函數(shù)是 Function()阁谆。
> benji.constructor;
< Dog() {
this.tail = true;
}
> Dog.constructor;
< Function() { [native code] }
__proto__與 prototype 之間的區(qū)別
1.對(duì)象有屬性 __proto__,指向該對(duì)象的構(gòu)造函數(shù)的原型對(duì)象愉老。
2.方法除了有屬性 __proto__场绿,還有屬性 prototype,prototype 指向該方法的原型對(duì)象嫉入。
> benji.prototype
< undefined
> Dog.prototype
< {say: ?, constructor: ?}
say: ? ()
constructor: ? Dog()
__proto__: Object
> benji.__proto__
< {say: ?, constructor: ?}
say: ? ()
constructor: ? Dog()
__proto__: Object
> Dog.__proto__
< ? () { [native code] }
> benji.__proto__ === Dog.prototype
< true
> Dog.__proto__ === Function.prototype
< true
原型對(duì)象和原型的區(qū)別:prototype 并不能獲取到原型焰盗,應(yīng)該用 __proto__,或 Object.getPrototypeOf() 來獲取咒林。prototype只是函數(shù)的一個(gè)特殊屬性熬拒,它指向了new 這個(gè)函數(shù)創(chuàng)造出來的對(duì)象的原型對(duì)象,但并不是原型垫竞,這里很容易混淆澎粟。
P176
將共享屬性遷移到原型中去
function Shape() {}
Shape.prototype.name = 'Shape';
這樣一來,當(dāng)我們?cè)儆?new Shape() 新建對(duì)象時(shí)欢瞪,name 屬性就不再是新對(duì)象的私有屬性了活烙,而是被添加進(jìn)了該對(duì)象的原型中。
P177
我們也可以通過 hasOwnProperty() 方法來明確對(duì)象自身屬性與其原型鏈屬性的區(qū)別遣鼓。
P180
接下來又是一大串的代碼啸盏,看得我頭疼,這和我高中時(shí)候做數(shù)學(xué)題的感受一模一樣骑祟。
function Shape() {}
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function() {
return this.name;
};
function TwoDShape() {}
var F = function() {};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
this.side = side;
this.height = height;
}
var F = function() {};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function() {
return this.side * this.height / 2;
}
下面來測(cè)試一下
> var my = new Triangle(5, 10);
> my.getArea();
< 25
> my.toString();
< "Triangle"
通過這種方法回懦,我們就可以保持住原型鏈:
> my.__proto__ === Triangle.prototype;
< true
> my.__proto__.constructor === Triangle;
< true
> my.__proto__.__proto__ === TwoDShape.prototype;
< true
> my.__proto__.__proto__.__proto__.constructor === Shape;
< true
搞得這么麻煩是為了在改變子對(duì)象屬性的時(shí)候不影響父對(duì)象。
P183
將繼承部分封裝成函數(shù)
function extend(Child, Parent) {
var F = function() {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
下面用一個(gè)完整的實(shí)例來檢驗(yàn)一下
function extend(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
function Shape() {};
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function() {
return this.constructor.uber
? this.constructor.uber.toString() + ', ' + this.name
: this.name;
}
function TwoDShape() {};
extend(TwoDShape, Shape);
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
this.side = side;
this.height = height;
}
extend(Triangle, TwoDShape);
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function() {
return this.side * this.height / 2;
}
測(cè)試
> new Triangle().toString();
< "Shape, 2D shape, Triangle"
P185
屬性拷貝
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for(var i in p) {
c[i] = p[i];
}
c.uber = p;
}
與之前的方法相比次企,這個(gè)方法在效率上略遜一籌怯晕。因?yàn)檫@里執(zhí)行的是子對(duì)象原型的逐一拷貝,而非簡(jiǎn)單的原型鏈查詢抒巢。所以我們必須要記住贫贝,這種方式僅適用于只包含基本數(shù)據(jù)類型的對(duì)象秉犹,所有的對(duì)象類型(包括函數(shù)與數(shù)組)都是不可復(fù)制的蛉谜,因?yàn)樗鼈冎恢С忠脗鬟f。
小結(jié)
學(xué)到這里我對(duì) JavaScript 原型的理解又有了新的深度崇堵,現(xiàn)在我總結(jié)一下原型這個(gè)概念:
1.首先函數(shù)既可以當(dāng)做普通函數(shù)來使用型诚,又可以作為構(gòu)造器。
2.作為構(gòu)造器的函數(shù)只需要使用 new
關(guān)鍵字就能夠創(chuàng)建一個(gè)對(duì)象鸳劳。
3.構(gòu)造函數(shù)和對(duì)象之間的區(qū)別就是有沒有 prototype
這個(gè)屬性狰贯。
4.prototype
屬性指向的是一個(gè)對(duì)象。
5.用一張圖來描述原型,先上代碼
function Person() {}
Person.prototype.name = 'lemon';
Person.prototype.age = 20;
Person.prototype.sayName = function() {
return this.name;
};
var person = new Person();
P190
之前是在原型對(duì)象之間構(gòu)建繼承關(guān)系涵紊,現(xiàn)在拋開原型對(duì)象傍妒,直接在對(duì)象之間構(gòu)建繼承關(guān)系。
function extendCopy(p) {
var c = {};
for(var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
var shape = {
name: 'Shape',
toString: function() {
return this.name;
}
}
var twoDee = extendCopy(shape);
twoDee.name = '2D shape';
twoDee.toString = function() {
return this.uber.toString() + ', ' + this.name;
};
var triangle = extendCopy(twoDee);
triangle.name = 'Triangle';
triangle.getArea = function() {
return this.side * this.height / 2;
};
測(cè)試
> triangle.side = 5;
> triangle.height = 10;
> triangle.getArea();
< 25
> triangle.toString();
< "Shape, 2D shape, Triangle"
P192
深拷貝
之前的方法在拷貝對(duì)象的時(shí)候拷貝的是對(duì)象的引用摸柄,這樣造成的結(jié)果是父對(duì)象和子對(duì)象指向同一個(gè)對(duì)象颤练。深拷貝的原理是拷貝對(duì)象時(shí)新創(chuàng)建一個(gè)空對(duì)象,再將對(duì)象中的屬性一一拷貝到空對(duì)象中驱负。
function deepCopy(p, c) {
c = c || {};
for (var i in p) {
if (p.hasOwnProperty(i)) {
if (typeof p[i] === 'object') {
c[i] = Array.isArray(p[i]) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
}
return c;
}
現(xiàn)在來測(cè)試一下
> var parent = {
numbers: [1, 2, 3],
letters: ['a', 'b', 'c'],
obj: {
prop: 1
},
bool: true
};
> var mydeep = deepCopy(parent);
> mydeep.numbers.push(4,5,6);
> mydeep.numbers;
< [1, 2, 3, 4, 5, 6]
> parent.numbers;
< [1, 2, 3]
ES5 標(biāo)準(zhǔn)中實(shí)現(xiàn)了 Array.isArray() 函數(shù)嗦玖,為了支持低版本環(huán)境,我們需要自己實(shí)現(xiàn)一個(gè) isArray() 方法跃脊。
if (typeof Array.isArray !== 'function') {
Array.isArray = function(candidate) {
return Object.prototype.toString.call(candidate) === '[object Array]';
};
}
P200
構(gòu)造器借用
繼承實(shí)現(xiàn)的一種手段宇挫,原理是子對(duì)象構(gòu)造器可以通過 call() 或 apply() 方法來調(diào)用父對(duì)象的構(gòu)造器。廢話不多說酪术,直接上代碼
先創(chuàng)建一個(gè)父類構(gòu)造器 Shape()
function Shape(id) {
this.id = id;
}
Shape.prototype.name = 'shape';
Shape.prototype.toString = function() {
return this.name;
};
現(xiàn)在我們來定義 Triangle()構(gòu)造器器瘪,在其中通過 apply()方法來調(diào)用 Shape() 構(gòu)造器,并將相關(guān)的 this 值(即 new Triangle() 所創(chuàng)建的示例)和其他一些參數(shù)傳遞該方法绘雁。
function Triangle() {
Shape.apply(this, arguments);
}
Triangle.prototype.name = 'Triangle';
下面娱局,我們來測(cè)試一下,先新建一個(gè) triangle 對(duì)象:
> var t = new Triangle(101);
> t.name;
< "Triangle"
在這里咧七,新的 triangle 對(duì)象繼承了其父對(duì)象的 id 屬性衰齐,但它并沒有繼承父對(duì)象原型中的其他任何東西:
> t.id;
< 101
> t.toString();
< "[object Object]"
之所以 triangle 對(duì)象中不包含 Shape 的原型屬性,是因?yàn)槲覀儚膩頉]有調(diào)用 newShape() 創(chuàng)建任何一個(gè)實(shí)例继阻,自然其原型也從來沒有被用到耻涛。
P228
setTimeout、setInterval
請(qǐng)注意瘟檩,雖然我們有時(shí)意圖讓某個(gè)函數(shù)在數(shù)毫秒后即執(zhí)行抹缕,但 JavaScript 并不保證該函數(shù)能恰好在那個(gè)時(shí)候被執(zhí)行。瀏覽器會(huì)維護(hù)維護(hù)一個(gè)執(zhí)行隊(duì)列墨辛。100 毫秒的計(jì)時(shí)器只是意味著在 100 毫秒后將指定代碼放入執(zhí)行隊(duì)列卓研,但如果隊(duì)列中仍有還在執(zhí)行的代碼,那么剛剛放入的代碼就要等待直到它們執(zhí)行結(jié)束睹簇,從而雖然我們?cè)O(shè)定了 100 毫秒的代碼執(zhí)行延遲時(shí)間奏赘,這段代碼很可能到 120 毫秒以后才會(huì)被執(zhí)行。
P237
檢查元素是否存在屬性
> bd.childNodes[1].hasAttributes();
< true
查看元素屬性個(gè)數(shù)
> bd.childNodes[1].attributes.length;
< 1
獲取屬性名
> bd.childNodes[1].attributes[0].nodeName;
< "class"
獲取屬性值
> bd.childNodes[1].attributes[0].nodeValue;
< "opener"
> bd.childNodes[1].attributes['class'].nodeValue;
< "opener"
> bd.childNodes[1].getAttribute('class');
< "opener"
P242
遍歷DOM
function walkDOM(n) {
do {
console.log(n);
if (n.hasChildNodes()) {
walkDOM(n.firstChild)
}
} while (n = n.nextSibling);
}
P253
document.referrer 中記錄的是我們之前所訪問過的頁(yè)面 URL太惠,它通常用于防盜鏈磨淌。
document.domain 在跨域的時(shí)候需要用到。
P255
addEventListener() 方法為元素綁定監(jiān)聽器凿渊。
P257
捕捉法與冒泡法
事件冒泡和事件捕獲分別由微軟和網(wǎng)景公司提出梁只,這兩個(gè)概念都是為了解決頁(yè)面中事件流(事件發(fā)生順序)的問題缚柳。
事件冒泡
事件會(huì)從最內(nèi)層的元素開始發(fā)生,然后逐級(jí)往上傳播搪锣,最后傳播到 document秋忙。
事件捕獲
與事件冒泡相反,事件會(huì)從最外層的 document 開始發(fā)生构舟,直到最具體的元素翰绊。
參考文章 淺談事件冒泡與事件捕獲
P259
阻止事件冒泡
首先要定義一個(gè)以事件對(duì)象為參數(shù)的函數(shù),并在函數(shù)內(nèi)對(duì)該對(duì)象調(diào)用 stopPropagation() 方法
function paraHandler(e) {
alert('clicked paragraph');
e.stopPropagation();
}
P260
防止默認(rèn)行為
在瀏覽器模型中旁壮,有些事件自身就存在一些預(yù)定義行為监嗜。例如,單擊鏈接會(huì)載入另一個(gè)頁(yè)面抡谐。對(duì)此裁奇,我們可以為該鏈接設(shè)置監(jiān)聽器,并使用 preventDefault()
方法禁用其默認(rèn)行為麦撵。
var all_links = document.getElementsByTagName('a');
for (var i = 0; i < all_links.length; i++) {
all_links[i].addEventListener(
'click',
function(e) {
if (!confirm('Are you sure you want to follow this link?')) {
e.preventDefault();
}
},
false
);
}
注意:并不是所有的默認(rèn)行為都能夠禁止刽肠,只能說大部分的是可以禁止的。
P261
在控制臺(tái)中返回被單擊元素(即目標(biāo)元素)的 nodeName 屬性值
document.addEventListener('click', function(e) {
console.log(e.target.nodeName);
}, false);
P267
在前面的例子中免胃,XHR 對(duì)象都是屬于全局域的音五,myCallback 要根據(jù)這個(gè)全局對(duì)象的存在狀態(tài)來訪問它的 readyState、status 和 responseText 屬性羔沙。除此之外還有一種方法躺涝,可以讓我們擺脫對(duì)全局對(duì)象的依賴,那就是將我們的回調(diào)函數(shù)封裝到一個(gè)閉包中去扼雏。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = (function(myxhr) {
return function() {
myCallback(myxhr);
}
}) (xhr);
xhr.open('GET', 'somefile.txt', true);
xhr.send('');
P269
自己封裝一個(gè) ajax 請(qǐng)求方法
function request(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = (function(myxhr) {
return function() {
if (myxhr.readyState === 4) {
callback(myxhr);
}
}
}) (xhr);
xhr.open('GET', url, true);
xhr.send('');
}
P275
好書推薦
《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》(Design Patterns: Elements of Reusable Object-Oriented Software)是軟件工程領(lǐng)域有關(guān)軟件設(shè)計(jì)的一本書坚嗜,提出和總結(jié)了對(duì)于一些常見軟件設(shè)計(jì)問題的標(biāo)準(zhǔn)解決方案,稱為軟件設(shè)計(jì)模式诗充。該書作者為:ErichGamma, Richard Helm, Ralph Johnson苍蔬,John Vlissides,后以“四人幫”(Gang of Four蝴蜓,GoF)著稱碟绑。
P278
異步的 JavaScript 代碼載入
這種方式就是動(dòng)態(tài)創(chuàng)建 script 節(jié)點(diǎn),然后將它插入 DOM
(function() {
var s = document.createElement('scrit');
s.setAttribute('src', 'behaviors.js');
document.getElementsByTagName('head')[0].appendChild(s);
} ());
命名空間
為了減少命名沖突茎匠,我們通常都會(huì)盡量減少使用全局變量的機(jī)會(huì)格仲。但這并不能根本解決問題,更好的辦法是將變量和方法定義在不同的命名空間中汽抚。這種方法的實(shí)質(zhì)就是只定義一個(gè)全局變量抓狭,并將其他變量和方法定義為該變量的屬性伯病。
// global namespace
var MYAPP = MYAPP || {};
// sub-object
MYAPP.event = {};
// object together with the method declarations
MYAPP.event = {
addListener: function(el, type, fn) {
// .. do the thing
},
removeListener: function(el, type, fn) {
// ...
},
getEvent: function(e) {
// ...
}
// ... other methods or properties
}
Element 構(gòu)造器
MYAPP.dom = {};
MYAPP.dom.Element = function(type, properties) {
var tmp = document.createElement(type);
for (var i in properties) {
if (properties.hasOwnProperty(i)) {
tmp.setAttribute(i, properties[i]);
}
}
return tmp;
};
P281
初始化分支
var MYAPP = {};
MYAPP.event = {
addListener: null,
removeListener: null
}
if (window.addEventListener) {
MYAPP.event.addListener = function(el, type, fn) {
el.addEventListener(type, fn, false);
};
MYAPP.event.removeListener = function(el, type, fn) {
el.removeEventListener(type, fn, false);
};
} else if (document.attachEvent) { // IE
MYAPP.event.addListener = function(el, type, fn) {
el.attachEvent('on' + type, fn);
};
MYAPP.event.removeListener = function(el, type, fn) {
el.detachEvent('on' + type, fn);
};
} else { // older browsers
MYAPP.event.addListener = function(el, type, fn) {
el['on' + type] = fn;
};
MYAPP.event.removeListener = function(el, type, fn) {
el['on' + type] = null;
};
}
P282
惰性初始
原理就是方法重寫自身
var MYAPP = {};
MYAPP.myevent = {
addListener: function(el, type, fn) {
if(el.addEventListener) {
MYAPP.myevent.addListener = function(el, type, fn) {
el.addEventListener(type, fn, false);
};
} else if (el.attachEvent) {
MYAPP.myevent.addListener = function(el, type, fn) {
el.attachEvent('on' + type, fn);
};
} else {
MYAPP.myevent.addListener = function(el, type, fn) {
el['on' + type] = fn;
};
}
MYAPP.myevent.addListener(el, type, fn);
}
};
與前面初始化分支的區(qū)別:
前一個(gè)例子在頁(yè)面加載的時(shí)候就初始化了造烁。
惰性出事僅調(diào)用的時(shí)候才會(huì)初始化否过。
P283
如果參數(shù)列表過長(zhǎng),不妨試試改用對(duì)象
當(dāng)一個(gè)函數(shù)的參數(shù)多于三個(gè)時(shí)惭蟋,使用起來就多少會(huì)有些不太方便苗桂,因?yàn)槲覀儾惶菀子涀∵@些參數(shù)的順序。但我們可以用對(duì)象來代替多個(gè)參數(shù)告组。也就是說煤伟,讓這些參數(shù)都成為某一個(gè)對(duì)象的屬性。這在面對(duì)一些配置型參數(shù)時(shí)會(huì)顯得尤為適合木缝,因?yàn)樗鼈冎型嬖诙鄠€(gè)缺省參數(shù)便锨。
MYAPP.dom.FancyButton = function(text, conf) {
var type = conf.type || 'submit';
var font = conf.font || 'Verdana';
var b = document.createElement('input');
b.value = text;
b.type = type;
b.font = font;
return b;
};
P285
私有屬性和方法
var MYAPP = {};
MYAPP.dom = {};
MYAPP.dom.FancyButton = function(text, conf) {
var styles = {
font: 'Verdana',
border: '1px solid black',
color: 'black',
background: 'grey'
};
function setStyles(b) {
var i;
for (i in styles) {
if (styles.hasOwnProperty(i)) {
b.style[i] = conf[i] || styles[i];
}
}
}
conf = conf || {};
var b = document.createElement('input');
b.type = conf.type || 'submit';
b.value = text;
setStyles(b);
return b;
};
P286
私有函數(shù)的公有化
函數(shù)對(duì)外提供 get 和 set 方法
var MYAPP = {};
MYAPP.dom = (function() {
var _setStyle = function(el, prop, value) {
console.log('setStyle');
};
var _getStyle = function(el, prop) {
console.log('getStyle');
};
return {
setStyle: _setStyle,
getStyle: _getStyle,
yetAnother: _setStyle
};
} ());
P288
模塊
不是很懂,倒是在 Vue 里面看多過 export 和 import
P289
鏈?zhǔn)秸{(diào)用
通過鏈?zhǔn)秸{(diào)用模式我碟,我們可以在單行代碼中一次性調(diào)用多個(gè)方法放案,就好像它們被鏈接在了一起。當(dāng)我們需要連續(xù)調(diào)用若干個(gè)彼此相關(guān)的方法時(shí)矫俺,會(huì)帶來很大的方便吱殉。實(shí)際上,我們就是通過前一個(gè)方法的結(jié)果(即返回對(duì)象)來調(diào)用下一個(gè)方法的厘托,因此不需要中間變量友雳。
var obj = new MYAPP.dom.Element('span');
obj.setText('hello');
obj.setStyle('color', 'red');
obj.setStyle('font', 'Verdana');
document.body.appendChild(obj);
我們已經(jīng)知道,構(gòu)造器返回的是新建對(duì)象的 this
指針铅匹。同樣的押赊,我們也可以讓 setText()
和 setStyle()
方法返回 this
,這樣包斑,我們就可以直接用這些方法所返回的實(shí)例來調(diào)用其他方法考杉,這就是所謂的鏈?zhǔn)秸{(diào)用:
var obj = new MYAPP.dom.Element('span');
obj.setText('hello')
.setStyle('color', 'red');
.setStyle('font', 'Verdana');
document.body.appendChild(obj);
P294
單例模式
書上寫的是單件模式,這里應(yīng)該是翻譯者用詞不當(dāng)舰始,所以我將它改成了單例模式崇棠。
function Logger() {
if (!Logger.single_instance) {
Logger.single_instance = this;
}
return Logger.single_instance;
}
這串代碼能夠保證無論 new
多少次都只有一個(gè)實(shí)例對(duì)象。
缺陷:它的唯一缺陷是 Logger 構(gòu)造器的屬性是公有的丸卷,因此它隨時(shí)有可能會(huì)被覆蓋
工廠模式
除了用關(guān)鍵字 new
創(chuàng)建對(duì)象以外枕稀,還可以用工廠模式創(chuàng)建對(duì)象
var MYAPP = {};
MYAPP.dom = {};
MYAPP.dom.Text = function(url) {
this.url = url;
this.insert = function(where) {
var txt = document.createTextNode(this.url);
where.appendChild(txt);
};
};
MYAPP.dom.Link = function(url) {
this.url = url;
this.insert = function(where) {
var link = document.createElement('a');
link.href = this.url;
link.appendChild(document.createTextNode(this.url));
where.appendChild(link);
};
};
MYAPP.dom.Image = function(url) {
this.url = url;
this.insert = function(where) {
var im = document.createElement('img');
im.src = this.url;
where.appendChild(im);
};
};
給 MYAPP.dom 工具添加一個(gè)工廠方法
MYAPP.dom.factory = function(type, url) {
return new MYAPP.dom[type](url);
};
調(diào)用
var url = 'http://www.phpied.com/images/covers/oojs.jpg';
var image = MYAPP.dom.factory('Image', url);
image.insert(document.body);
P297
裝飾器模式
作用是拓展對(duì)象功能
var tree = {};
tree.decorate = function() {
alert('Make sure the tree won\'t fall');
};
tree.RedBalls = function() {
this.decorate = function() {
this.RedBalls.prototype.decorate();
alert('Put on some red balls');
};
};
tree.BlueBalls = function() {
this.decorate = function() {
this.BlueBalls.prototype.decorate();
alert('Add blue balls');
};
};
tree.Angel = function() {
this.decorate = function() {
this.Angel.prototype.decorate();
alert('An angel on the top');
};
};
添加裝飾器
tree.getDecorator = function(deco) { //裝飾器
tree[deco].prototype = this;
return new tree[deco];
};
使用
tree = tree.getDecorator('BlueBalls');
tree = tree.getDecorator('Angel');
tree = tree.getDecorator('RedBalls');
用文字來解釋上面3串代碼就是將 tree
設(shè)置為 tree.BlueBalls
的原型對(duì)象并且返回 tree.BlueBalls
對(duì)象,再將 tree.BlueBalls
設(shè)置為 tree.Angel
的原型對(duì)象谜嫉,最后將 tree.Angel
設(shè)置為 tree.RedBalls
的原型對(duì)象萎坷。
P299
觀察者模式
當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都將得到通知沐兰。接下來是模板代碼:
var observer = {
addSubscriber: function(callback) {
if (typeof callback === 'function') {
this.subscribers[this.subscribers.length] = callback;
}
},
removeSubscriber: function(callback) {
for (var i = 0; i < this.subscribers.length; i++) {
if (this.subscribers[i] === callback) {
delete this.subscribers[i];
}
}
},
publish: function(what) {
for (var i = 0; i < this.subscribers.length; i++) {
if (typeof this.subscribers[i] === 'function') {
this.subscribers[i](what);
}
}
},
make: function(o) {
for (var i in this) {
if (this.hasOwnProperty(i)) {
o[i] = this[i];
o.subscribers = [];
}
}
}
};
這串代碼有三個(gè)重要且必定包含的方法:
1.將函數(shù)添加進(jìn)入棧的 addSubscriber()
方法
2.將函數(shù)移除棧的 removeSubscriber()
的方法
3.執(zhí)行棧中所有函數(shù)的 publish()
方法
另外我發(fā)現(xiàn)觀察者模式除了能夠監(jiān)聽對(duì)象狀態(tài)變化哆档,還能夠擴(kuò)展函數(shù)功能。將不同函數(shù) push 到同一個(gè)任務(wù)棧中住闯,這樣就能夠?qū)崿F(xiàn)函數(shù)功能增強(qiáng)瓜浸。而且這樣還有一個(gè)好處就是把功能模塊化(好像叫做切片處理)澳淑,每一個(gè)模塊既能獨(dú)立存在又能整合在一起。