《JavaScript模式》讀書筆記html
模式是針對廣泛問題的解決方案。更進一步地說,模式是解決一類特定問題的模版。node
在軟件開發過程當中,模式是指一個通用問題的解決方案。 一個模式不只僅是一個能夠用來複制粘貼的代碼解決方案,更多地是提供一個更好的實踐經驗、有用的抽象化表示和解決一類問題的模版。關鍵詞: 表示、解決 程序員
學習模式的好處:
web
三種類型的模式:正則表達式
對象有兩種類型設計模式
全局變量的問題: 1. 全局變量會在整個JavaScript應用或web頁面內共享。 他們生存在同一個命名空間內,總有可能發生命名衝突。尤爲是一個應用程序中兩個獨立的部分(如兩個js文件)定義了同名的全局變量,但卻有不一樣目的時。 而咱們只要減小全局變量的數目,就能夠減小發生衝突的可能性。數組
使用var 定義的全局變量不能夠刪除; 不使用var的全局變量能夠刪除。說明,使用var 定義就是一個變量, 不使用var定義就是一個對象的屬性。
瀏覽器
2. 單一var模式緩存
即便用一個 var 聲明多個變量。安全
優點: 聲明爲局部變量、 更少的代碼、 更簡潔的操做。
考慮下面的代碼:
log(c); var c = 1000; log(c);
第一個log輸出 undefined; 第二個log輸出 1000;
上面的代碼等同於:
var c; log(c); c = 1000; log(c);
由於開始c只是聲明瞭,沒有初始化。在 c = 1000;才初始化。
for循環主要用於兩種狀況,遍歷 數組 和 類數組對象(HTML容器對象)的。
數組就是Array對象, var arr = [1, 2, 3, 4]; 這就是一個數組。
而HTML容器對象是DOM方法返回的對象,如: document.getElementByName() document.getElementsByClassName() document.getElementsByTagName()都是 dom方法返回的HTML容器對象。
HTML容器對象的麻煩之處在於: 他們都是document(HTML頁面)下活動的查詢。也就是說每次再訪問任何容器的長度時,都是在查詢活動的DOM,而且DOM是很是耗時的。
因而,下面的循環是不可取得:
for (var i = 0; i < myArray.length; i++) { // 對myArray進行操做 }
若是myArray是一個數組還好,每次讀取它的長度耗時並非很嚴重。
可是若是myArray是一個HTML容器對象(類數組對象),那麼操做DOM就會很是耗時。
好的方法:
for (var i = 0, len = myArray.length; i < len; i++ ) { // 對myArray進行操做 }
這種方法裏,咱們現將myArray.length緩存了起來,這樣在後續的循環過程當中, 就不會每次循環都讀取length,進而減小了大量的DOM訪問,這樣的方法在safari中速度提升兩倍,在IE7中速度提升170倍。
剛剛提到,for循環主要是用於遍歷數組和類數組對象的(通通與數組有關),而for-in循環主要是用於遍歷一個普通對象的屬性的(與數組無關)。
雖然,有時候混用是可行的,可是這樣咱們的代碼邏輯就會大大下降。
在使用 for 循環時,咱們最好使用 hasOwnProperty()方法。
增長構造函數的原型屬性是一個加強功能性的強大方法,可是這樣會嚴重影響可維護性,由於你的同伴每每但願內置的JavaScript的方法使用是一致的,而不指望您增長本身的方法。
可是若是咱們發現 將來的ECMASCRIPT版本將某一方法做爲統一的實現,只是如今還有一些不支持時,咱們就能夠本身添加構造函數上的方法。 另外,若是其餘地方已經支持了此方法,您這不支持,也能夠添加。
爲原型添加自定義的方法:
if (typeof Object.prototype.myMethod !== "function") {
Object.prototype.myMethod = function () {
// implementation...
};
}
7. 避免使用隱式類型轉換
咱們應當儘可能採用 == 而不是 === 來判斷是否相等。
咱們認爲使用 == 來判斷是否相等就是反模式(可能產生的問題比解決的問題要多), 而 === 是一種更爲標準的模式。
其實這個仍是很好理解的,由於大多數時候,咱們不多見到使用eval()方法的狀況。
而且記住:eval()是一個魔鬼
爲何? 由於eval()能夠將任意字符串看成一個JavaScript代碼來執行。
大多數狀況下,parseInt顧名思義就是將接收的參數解析出整數, 通常,咱們可能給傳遞一個字符串,可是,最佳實踐是傳遞第二個參數表示把這個字符串中的數字當作幾進制。ES3和ES5的表現是不一致的。
下面兩個函數一個是函數聲明的方式定義,另外一個是匿名函數的形式定義,注意空格的區別所在:
function addTwoNumber() { return 10 + 20; } var addTwoNumber = function () { return 10 + 20; }
這即是最好的空格的保留習慣 --- 在函數聲明時,函數名和圓括號之間沒有空格; 在匿名函數時, function 與 ()之間是有一個空格的。
舉例來講, 若是有一個名爲reverse()的函數, 該函數能夠將字符串翻轉過來,該函數有一個字符串參數並返回另外一個字符串,那麼能夠按照以下的方式來記錄文檔:
/** * 翻轉一個字符串 * * @param {String} 輸入須要翻轉的字符串 * @return {String} 翻轉後的字符串 */ var reverse = function () { //... return ouput;
};
12. 注意一個變量若是沒有使用var,那麼它將成爲全局的,對於函數也是同樣的,一個函數在定義時沒有使用var,那麼它也將是全局的,由於函數名就是一個指針,指針就是變量。
1. 相對於使用構造函數建立對象,咱們更傾向於使用對象字面量的方式建立對象。
緣由有二:
(1). 字面量表示法的顯著優勢在於它僅須要輸入更少更短的字符,這是一種優美的對象建立方式
(2). 與使用object構造函數相對, 使用字面量的另外一個緣由在於它並無做用域的解析。由於可能以一樣的名字建立了一個局部構造函數,解析器須要從Object()的位置開始一直向上查找做用域鏈,直到發現全局object構造函數,可是對象字面量的方法就不會有這樣的解析過程。
2. 相對於使用 new Array() 方式建立數組,咱們更傾向於使用數組字面量的方式建立數組。
JavaScript中的數組也是對象, 雖然能夠經過 new Array() 來建立,但這不是咱們所推薦的。
緣由有二:
(1). 數組字面量表示法簡單、明確、優美。
(2). 使用 new Array() 會出現陷阱 --- 如 var arr = new Array(3); 這裏的3是數組的長度, 而這個數組中卻什麼都沒有。 又如使用 var arr = new Array (3.14); 那麼就會報錯,由於3.14不是合法的長度。
注: 通常狀況下, 爲了將數組和通常的對象區分開,咱們能夠檢測它是否具備length屬性和slice()方法,有的就是數組,不然是對象。另外ES5中出現了Array.isArray(arr)方法,若是arr是數組,則返回true,不然返回false。
上面的說法是錯的! 由於不只僅 對象中的Array又length屬性, 對象中的 Function也是具備length屬性的。Function的length屬性時這個函數指望接收的參數的個數!!!
3. 相對於使用 new RegExp() 構造函數的方法建立正則表達式, 更但願是正則表示式字面量的方法。
如 var re = /ad\\f/gm; 是字面量的方法。 var re = new RegExp("ad\\\\f","gm"); 是構造函數的方式。
緣由有二(實則是一):
(1). 使用字面量的方式看上去就很簡潔。
(2). 能夠看到若是想要匹配 \ , 在字面量中須要使用 \ 來轉義,可是在構造函數中須要用四個才能達到相同的效果。
4. 對於String、Number、Boolean,咱們儘可能使用簡單形式,而不要用包裝對象。
var a = "zzw"; 就是簡單的形式,而 var a = new String("zzw"); 就是複雜的使用了包裝對象的形式。 二者的區別有二:
1. 前者使用typeof判斷獲得的是 string ,然後者使用 typeof 判斷獲得的是 obect。
2. 二者均可以只用方法,只是前者可用的方法存在的聲明週期更短, 然後者會始終存在。
5. 錯誤對象
try { 什麼 throw { name: "myError", message: "some error!", } } catch (e) { alert(e.name); }
如上所示: 若是出現了錯誤,咱們在throw 就拋出來這個錯誤對象, 而後再catch。
注意:一個程序中最好要有 try 和 catch 的語句!
總結:
在通常狀況下, 除了Date()構造函數之外,不多須要其餘的內置構造函數,而是使用字面量的方法去建立。
a 函數是對象中的第一等公民。
b 函數能夠提供做用域。
(a)函數固然是對象,由於咱們能夠用new Function()來建立對象,其次函數有本身的屬性(name)和方法(toString),最後函數能夠做爲參數向其餘的函數傳遞。注:雖然函數能夠用構造函數的方法建立,可是不推薦,由於咱們得傳遞字符串,這和eval()同樣糟糕。
(b)在js中,只有函數能夠提供做用域,而其餘如if、while等有{}的都不能提供,由於在js中,沒有塊級做用域這一說。
(a) 函數聲明
function add () { return 1+2; }
注意:函數聲明能夠將函數的定義提高到其所在做用域的頂部。
(b) 命名函數表達式
var add = function addSomething() { return 1+2; }
注意:前者和後者是能夠相同也能夠不一樣的, 若是調用其name屬性能夠獲得addSomething而不是add,另一個好處是用於求階乘的時候(參看《JavaScript函數之美》),命名函數表達式無提高效果。
(c) 匿名函數表達式
var add = function () { return 1+2; }
注意: 無提高效果。且函數.name是空的字符串。
(a) 函數聲明只能出如今「程序代碼」中, 這表示它僅能在其餘函數體的內部或全局空間中。
正確區分下面的幾種形式是頗有必要的。
callMe(function () { // 剛剛說了,函數聲明只能出如今「程序代碼」中,顯然這裏是匿名函數表達式。 }); callMe(function add() { // 一樣,函數聲明不可能做爲一個參數,這裏是命名函數表達式 });
var myObject = {
say: function () {
// 顯然這裏是匿名函數表達式
}
}
(b) 在不使用函數聲明的時候,咱們推薦使用匿名函數表達式。
var foo = function bar() {};
雖然這個在語法上沒有什麼問題,可是表達起來不夠方便,且在IE中可能會出現問題。
什麼使回調函數?當把一個函數的引用 看成一個參數傳遞給另外一個函數時,另外一個函數在執行過程當中可能會執行這個函數,那麼這個函數就成爲了回調函數,又稱回調。
看下面的這個例子(很是重要!)
大意: 先找到所需的nodes,而後隱藏這些nodes。 咱們能夠用兩個獨立的函數完成這個任務 --- 這樣代碼可重用!
var findNodes = function () { var nodes = [], found, status = true; for (var i = 0; i < 1000; i++) { // 複雜的邏輯 if (status) { nodes.push(found); } } return nodes; }; var hide = function (nodes) { var i = 0, max = nodes.length; for (var i = 0; i < max; i++) { nodes[i].style.display = "none"; } } hide(findNodes());
存在的問題:1. 雖然這是兩個函數,能夠保證代碼的重用,可是咱們發現每一個函數都要進行一個循環 --- 很消耗性能。
2. 注意: findNodes()並非回調函數,由於咱們能夠看到 傳入的是一個結果,而不是函數的引用。
由於屢次循環消耗性能,咱們能夠將隱藏的步驟放在findNodes裏啊, 問題是: 若是這樣,咱們就不能重用代碼了。
解決方法: 將hide函數做爲回調函數傳入findNodes中。以下:
var findNodes = function (callback) { var i = 1000, nodes = [], found; if (typeof callback !== "function") { callback = false; } while (i) { i -= 1; // 複雜的邏輯 if (callback) { callback(found); } nodes.push(); } return nodes; }; //回調函數 var hide = function (node) { node.style.display = "none"; } findNodes(hide);
這樣問題就解決了 --- 兩個函數既能夠重用, 還無需屢次循環消耗性能。
雖然在不少狀況下這種方法都是簡單且有效的,可是也常常存在一些場景,其回調並非一次性的匿名函數或者全局函數(其中若是有this就會指向全局),而是對象的方法(那麼它的this就會指向對象),這時若是做爲回調函數被應用時,由於函數的特殊之處(函數時存在堆裏的),這時this就會指向全局, 不會指向對象,就會出錯。 方法: 使用call或者apply進行綁定this到對象上。
另外異步事件監聽器也是回調函數的模式。
document.addEventListener("click", console.log, false);
其中console.log就是回調函數。
另外超時也是回調函數的模式。
var add = function () { return 1+3; }; setTimeout(add, 100);
其中setTImeout的第一個參數傳遞的是函數的引用(不帶圓括號),若是帶上圓括號,傳遞的就是一個結果了。
(注意: 咱們知道傳遞迴調函數的引用的目的是在這個函數中調用這個回調函數,因此能夠猜想setTimeout這個函數的內部必定有調用add這個函數的語句!)
庫中的回調模式
若是咱們但願函數的重用,咱們就要考慮回調模式。
什麼是自定義函數?
函數能夠動態定義(即函數聲明的方式),也能夠分配給變量(即匿名函數表達式的方式)。 若是建立了一個新函數而且將其分配給保存了另外函數的同一個變量,那麼就以一個新函數覆蓋了舊函數,這一切發生在舊函數體的內部。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>callback</title> </head> <body> <script> var selfDefine = function () { console.log("good"); selfDefine = function () { console.log("better"); } } selfDefine(); //good selfDefine(); //better selfDefine(); //better selfDefine(); //better selfDefine(); //better </script> </body> </html>
這就是自定義函數, 咱們能夠看到雖然五次調用了一樣的函數,但第一次的卻不同,這是由於在第一次調用函數的時候, 咱們將這個函數名(指針)指向了一個新的函數,因爲沒有使用var, 因此這個函數就是全局的,便覆蓋了第一次的函數。
可是若是咱們使用var,那麼這個就不會覆蓋了,也就不是自定義函數了,
var selfDefine = function () { console.log("good"); var selfDefine = function () { console.log("better"); } } selfDefine(); //good selfDefine(); //good selfDefine(); //good selfDefine(); //good selfDefine(); //good
這是由於咱們每次再調用的時候,在函數內部的同名函數做爲外部函數的私有函數,根據做用域鏈的規則,顯然咱們是不能訪問到裏面的函數的。
優勢: 當您的函數有一些初始化工做要作,而且只須要執行一次,那麼這種模式就很是有用。 由於並無理由去執行本能夠避免的重複工做,即該函數的一些部分可能並再也不須要(很是好)。
即時函數模式(Immediate Function Pattern)是一種能夠支持在定義函數後當即執行該函數的語法。
思考: 既然你這個函數在定義的時候就執行了,那麼你直接寫成一堆語句不就好了,幹嗎非要寫成即時函數的形式呢?
回答: 說得好! 即時函數的惟一目的就是模仿塊級做用域!
我: 嗯? 也不徹底對,後面我來告訴你!
(function () { alert("good"); }());
這就是一個即時函數。下面的替代方法也是很常見的。
(function () { alert("good"); })();
其實即便函數的目的不只僅是模仿塊級做用域!由於即便函數也能夠傳遞參數啊!以下:
(function (who, when) { // 能夠看到和變量相接的字符串咱們都使用一個空格來空開,這一點意識到是很是重要的。 console.log("I met " + who + " on " + when); }("zzw", "now")); // I met zzw on now
通常來講,不該該傳遞過多的參數到即時函數中,由於這樣將迅速成爲一種閱讀負擔!
即便函數的返回值:
var str = (function (who, when) { // 能夠看到和變量相接的字符串咱們都使用一個空格來空開,這一點意識到是很是重要的。 return "I met " + who + " on " + when; }("zzw", "now")); console.log(str); // I met zzw on now
咱們能夠看到這裏即時函數的返回值賦值給了 str ,很是有效。
在即時函數中定義的變量將會是用於自調用函數的局部變量,而且不會擔憂全局空間被臨時變量所污染。
注意: 即時函數的其餘名稱包括 「自調用」以及「自執行」函數,由於該函數在定義以後當即執行。
可使用下面的模版來定義一個功能,讓咱們稱之爲module1:
// 文件module.js中定義的模塊 module1 (function () { //模塊1中的全部代碼... }());
遵循這個模版,能夠編碼其餘的模塊,而後,當將這些代碼發佈到在線站點時,能夠決定哪些功能應用於黃金時間,而且使用構建腳本將對應文件合併。
注意: 好比在開發一個較大的網站時,咱們每每須要引入一些通用的js文件,這時咱們就能夠將這些通用的文件使用即時函數封裝起來,這同時也遵循咱們以前所講的儘可能減小全局變量的原則。
這種模式使用帶有init()方法的對象,該方法在建立對象以後將會當即執行,init函數須要負責全部的初始化任務。
下面是一個即時對象模式的實例:
({ // 在這裏能夠定義設定值 // 又名配置常數 maxWidth: 600, maxHeight: 800, // 還能夠定義一些實用的方法 gimmeMax: function () { return this.maxWidth + "x" + this.maxHeight; }, // 初始化 init: function () { console.log(this.gimmeMax()); // 更多初始化任務 } }).init();
這裏是使用對象字面量建立了一個對象,而後用括號包裹起來是由於這樣就肯定是一個對象了,而不是相似與if和while的代碼塊, 對象一旦建立就會初始化。
配置對象模式是一種提供更簡潔的API的方法,尤爲是在創建一個庫或者是任何將被其餘程序使用的代碼的狀況。
舉例說明:
想象一下, 若是正在編寫一個名爲addPerson()的函數, 該函數接收人員的名和姓參數,而且將這我的添加到列表中,可使用:
function addPerson(first, last) { //... }
後來,瞭解到實際上還要存儲人員的出生日期、以及可選的性別和住址等信息,所以,能夠修改函數並添加新的函數信息(將可選參數放在末尾),
function addPerson(first, last, dob, gender, address) {};
在這一點上, 該函數的參數列表就變得有點長,而後,知道須要添加一個用戶名,而且這是絕對必要的而非可選的,如今的函數調用者必須傳遞參數,並且可選參數也要傳遞,同時還要注意不要混淆了參數的順序。
addPerson("zzw", "wadf", new Date(), null, null, "batman");
使用者須要傳遞大量的參數並非很方便,一個更好地辦法就是僅僅使用一個參數對象來替代全部的參數,讓咱們稱該參數爲conf, 也就是配置的意思。
addPerson(conf);
而後,該函數的使用者能夠這麼作:
var conf = { username: "batman", first: "Bruce", last: "Wary" }; addPerson(conf);
這就是配置對象了,配置對象的優勢在於:
而配置對象的不利之處在於:
須要注意的地方:
注:這一部分一直不是很理解,但願之後真正須要用到的時候能夠邊學邊用。
前言:
JavaScript是一種簡潔明瞭的語言,其中並無在其餘語言中常用的一些特殊的語法結構,好比命名空間(namespace)、模塊(module)、包(package)、私有屬性(private property)、以及靜態成員等語法。
本章節會經過一些常見的模式來實現、替換那些語法特徵,或者僅僅以不一樣於那些語法特徵的方式來思考問題。
JavaScript並無內置命名空間,命名空間(namespace)有助於減小程序中所須要的全局變量的數量。 它的用途和使用即時函數有類似之處,都是爲了減小全局變量的數量, 由此帶來的好處就是有助於避免命名衝突或着過長的名字前綴。
補充:顯然,過長的名字前綴也是一種避免衝突的方法,可是這種方法並很差。
使用js模仿命名空間仍是很簡單的。
以下面的例子:
// 警告:反模式 // 構造函數 function Parent() {}; function Child() {}; // 一個變量 var some_var = 1; // 一些對象 var module1 = {}; module1.data = { a: 1, b: 2 }; var module2 = {};
上面的代碼不是不能夠這麼寫,可是壞處在於: 該模塊的變量和引入的其餘模塊的變量頗有可能會衝突,或者全局變量愈來愈多的時候,自身也會引起衝突。
能夠經過爲應用建立一個全局對象這種方式來重構上面的代碼,好比建立全局對象MYAPP,而後改變全部的函數和變量以使其成爲您的全局對象的屬性。
// 全局變量,最好只有一個 var MYAPP = {}; // 構造函數 MYAPP.Parent = function () { }; MYAPP.Child = function () { }; // 一個變量 MYAPP.some_var = {}; // 一個容器對象 MYAPP.modules = {}; // 嵌套對象 MYAPP.modules.module1 = {}; MYAPP.modules.module1.data = { a: 1, b: 2 }; MYAPP.modules.module2 = {};
這就是 模仿命名空間 的模式,這裏在這個項目上,只有一個全局變量MYAPP,所有大寫是由於 一般程序員都會根據公約以所有大寫的方式來命名全局變量,故全局變量是很是引人注目的。(別忘了,通常狀況下一個常量也是使用這種方式來命名的)
主要使用場景: 在引入第三方庫的時候,好比js和窗口widget的衝突,強烈推薦使用這種方式。
缺點(從強哥的APP.js就能夠看出來): 1. 須要輸入更多的字符,每一個變量和函數都要加前綴,整體上增長了須要下載的代碼量。 2. 僅有一個全局實例意味着任何部分的代碼均可以修改全局實例。 3. 長嵌套名字意味着更長(更慢)的屬性解析查詢時間。
這一部分不作過多的介紹,只說重點的部分。
咱們知道:
因爲程序複雜性的增長、代碼的某些部分被分隔成了不一樣的文件,以及使用添加包含語句等多個因素,僅假設您的代碼是第一個定義某個命名空間或者它內部的一個屬性(也許實際上不是的),這種作法就會致使覆蓋以前的命名空間,或者本身的命名空間被覆蓋 --- 總之,這都是不安全的。 所以,在添加一個屬性或者建立一個命名空間以前,最好是先檢查它是否已經存在,以下所示:
// 不安全的代碼 --- 可能會有衝突 var MYAPP = {}; // 更好的代碼風格1 if (typeof MYAPP === "undefined") { var MYAPP = {}; } // 更好的代碼風格2 if (typeof MYAPP !== "object") { var MYAPP = {}; } // 或者使用更短的語句 var MYAPP = MYAPP || {};
這都只是咱們給的名稱, 私有成員通常指對象的屬性在外部不能訪問,特權方法通常指對象的方法能夠訪問對象的屬性(有特權)。
Javascript Page 97