- 相關(guān)推薦
常用的JavaScript模式
常用的JavaScript模式有哪些?模式是解決或者避免一些問(wèn)題的方案。下面YJBYS小編為大家列出幾種常用的JavaScript模式,歡迎大家閱讀學(xué)習(xí)!
在JavaScript中,會(huì)用到一些常用的編碼模式。下面就列出了一些常用的JavaScript編碼模式,有的模式是為了解決特定的問(wèn)題,有的則是幫助我們避免一些JavaScript中容易出現(xiàn)的錯(cuò)誤。
單一var模式
所謂“單一var模式”(Single var pattern)就是指在函數(shù)頂部,只使用一個(gè)var進(jìn)行變量聲明的模式。例如:
function func() {
var a = 1,
b = 2,
sum = a + b,
myObject = {}, i,
j;
// other code}
使用這個(gè)模式的好處:
在函數(shù)頂部展示了所有函數(shù)中使用的局部變量
防止變量提升引起的問(wèn)題
變量提升
JavaScript允許在函數(shù)的任意地方聲明變量,但是效果都等同于在函數(shù)頂部進(jìn)行聲明,這個(gè)是所謂的變量提升(Hoisting)。
看一個(gè)例子:
var num = 10;
function func() {
alert(num); // undefined
var num = 1;
alert(num); // 1}func();
從這個(gè)例子可以看到,第一次alert的值并不是10,而是undefined。所以,應(yīng)該盡量使用“單一var模式”來(lái)避免類似的問(wèn)題。
關(guān)于變量提升的細(xì)節(jié),請(qǐng)參考我前面一篇JavaScript的執(zhí)行上下文。
for-in循環(huán)
在JavaScript中,for-in循環(huán)主要用來(lái)枚舉對(duì)象的屬性。
但是,由于JavaScript中原型鏈的存在,一般都會(huì)結(jié)合hasOwnProperty()來(lái)使用for-in循環(huán),從而過(guò)濾原型鏈上的非該對(duì)象的屬性。
var wilber = {
name: "Wilber",
age: 28,
gender: "male"};Object.prototype.printPersonalInfo = function() { console.log(this.name, "is", this.age, "years old");
};for(var prop in wilber) { if(wilber.hasOwnProperty(prop)) { console.log(prop, ":", wilber[prop]);
}
}
開(kāi)放的大括號(hào)位置
根據(jù)開(kāi)發(fā)人員的習(xí)慣,開(kāi)放大括號(hào)的位置會(huì)有不同的選擇,可以和語(yǔ)句放在同一行,也可以放在新的一行:
var total = 10;if(tatal > 5) { console.log("bigger than 5");
}if(tatal > 5)
{ console.log("bigger than 5");
}
兩種形式的代碼都能實(shí)現(xiàn)同樣的邏輯,但是,JavaScript允許開(kāi)發(fā)人員省略分號(hào),JavaScript的分號(hào)插入機(jī)制(semicolon insertion mechanism)會(huì)負(fù)責(zé)加上省略的分號(hào),這時(shí)開(kāi)放大括號(hào)的位置不同就可能產(chǎn)生不同的結(jié)果。
看一個(gè)例子:
function func() { return
{
name: "Wilber"
};
}
alert(func());// undefined
之所以得到的結(jié)果是undefined就是因?yàn)镴avaScript的分號(hào)插入機(jī)制,在return語(yǔ)句之后自動(dòng)添加了分號(hào)。
調(diào)整一下開(kāi)放的大括號(hào)的位置就可以避免這個(gè)問(wèn)題:
function func() { return {
name: "Wilber"
};
}
alert(func());// [object]
所以,關(guān)于開(kāi)放的大括號(hào)位置,建議將開(kāi)放的大括號(hào)放置在前面語(yǔ)句的同一行。
強(qiáng)制new模式
JavaScript中,通過(guò)new關(guān)鍵字,可以用構(gòu)造函數(shù)來(lái)創(chuàng)建對(duì)象,例如:
function Person(name, city) { this.name = name; this.city = city;
this.getInfo = function() { console.log(this.name, "lives at", this.city);
}
}var will = new Person("Will", "Shanghai");
will.getInfo();// Will lives at Shanghai
但是,如果開(kāi)發(fā)人員忘記了new關(guān)鍵字,那么構(gòu)造函數(shù)中的this將代表全局對(duì)象(瀏覽器中就是window對(duì)象),所有的屬性將會(huì)變成全局對(duì)象的屬性。
function Person(name, city) { this.name = name; this.city = city;
this.getInfo = function() { console.log(this.name, "lives at", this.city);
}
}var will = Person("Will", "Shanghai");console.log(will.name);// Uncaught TypeError: Cannot read property 'name' of undefinedconsole.log(window.name);// Willconsole.log(window.city);// Shanghaiwindow.getInfo();// Will lives at Shanghai
所以,為了避免這類問(wèn)題的方式,首先是從代碼規(guī)范上下手。建議對(duì)于所有的JavaScript構(gòu)造函數(shù)的命名方式都遵循,構(gòu)造函數(shù)使用首字母大寫(xiě)的命名方式。
這樣當(dāng)我們看到首字母大寫(xiě)的函數(shù),就要考慮是不是漏掉了new關(guān)鍵字。
自調(diào)用構(gòu)造函數(shù)
當(dāng)然除了規(guī)范之外,還可以通過(guò)代碼的方式來(lái)避免上面的問(wèn)題。
具體的做法就是,在構(gòu)造函數(shù)中檢查this是否為構(gòu)造函數(shù)的一個(gè)實(shí)例,如果不是,構(gòu)造函數(shù)可以通過(guò)new關(guān)鍵字進(jìn)行自調(diào)用。
下面就是使用自調(diào)用構(gòu)造函數(shù)對(duì)上面的例子進(jìn)行改進(jìn):
function Person(name, city) { if(!(this instanceof Person)) { return new Person(name, city);
}
this.name = name; this.city = city;
this.getInfo = function() { console.log(this.name, "lives at", this.city);
}
}var will = Person("Will", "Shanghai");console.log(will.name);// Willconsole.log(will.city);// Shanghaiwill.getInfo();// Will lives at Shanghaiwindow.getInfo();// Uncaught TypeError: window.getInfo is not a function
結(jié)合構(gòu)造函數(shù)的命名約定和自調(diào)用的構(gòu)造函數(shù),這下就不用擔(dān)心漏掉new關(guān)鍵字的情況了。
數(shù)組性質(zhì)檢查
當(dāng)在JavaScript中判斷一個(gè)對(duì)象是不是數(shù)組的時(shí)候,不能直接使用typeof,因?yàn)槲覀儠?huì)得到object。
在ECMA5中提出了Array.isArray()這個(gè)函數(shù),我們可以直接使用來(lái)判斷一個(gè)對(duì)象是不是數(shù)組類型。
對(duì)于不支持ECMA5的環(huán)境,我們可以通過(guò)下面的方式自己實(shí)現(xiàn)Array.isArray()這個(gè)函數(shù)。
if(typeof Array.isArray === "undefined") { Array.isArray = function(arg){ return Object.prototype.toString.call(arg) === "[object Array]";
};
}var arr = [];console.log(Array.isArray(arr));// true
立即執(zhí)行函數(shù)
立即執(zhí)行函數(shù)是JavaScript中非常常用的一種模式,形式如下:
(function() {
// other code
}());
通過(guò)這個(gè)模式可以提供一個(gè)局部的作用域,所以函數(shù)代碼都會(huì)在局部作用域中執(zhí)行,不會(huì)污染其他作用域。
現(xiàn)在的很多JavaScript庫(kù)都直接使用了這種模式,例如JQuery、underscore等等。
立即執(zhí)行函數(shù)的參數(shù)
關(guān)于立即執(zhí)行函數(shù)另外一點(diǎn)需要注意的地方就是立即執(zhí)行函數(shù)的參數(shù)。
我們可以像正常的函數(shù)調(diào)用一樣進(jìn)行參數(shù)傳遞:
(function(name, city) {
console.log(name, "lives at", city);}("Wilber", "Shanghai"));// Wilber lives at Shanghai
在立即執(zhí)行函數(shù)中,是可以訪問(wèn)外部作用域的(當(dāng)然包括全局對(duì)象),例如:
var name = "Wilber";var city = "Shanghai";
(function() { console.log(name, "lives at", city);
}());// Wilber lives at Shanghai
但是,如果立即執(zhí)行函數(shù)需要訪問(wèn)全局對(duì)象,常用的模式就是將全局對(duì)象以參數(shù)的方式傳遞給立即執(zhí)行函數(shù)。
var name = "Wilber";var city = "Shanghai";
(function(global) { console.log(global.name, "lives at", global.city);
}(this));// Wilber lives at Shanghai
這樣做的好處就是,在立即執(zhí)行函數(shù)中訪問(wèn)全局變量的屬性的時(shí)候就不用進(jìn)行作用域鏈查找了,關(guān)于更多JavaScript作用域鏈的內(nèi)容,可以參考理解JavaScript的作用域鏈。
初始化時(shí)分支
初始化時(shí)分支(Init-time Branching)是一種常用的優(yōu)化模式,就是說(shuō)當(dāng)某個(gè)條件在整個(gè)程序聲明周期內(nèi)都不會(huì)發(fā)生改變的時(shí)候,不用每次都對(duì)條件進(jìn)行判斷,僅僅一次判斷就足夠了。
這里最常見(jiàn)的例子就是對(duì)瀏覽器的檢測(cè),在下面的例子中,每次使用utils.addListener1屬性的時(shí)候都要進(jìn)行瀏覽器判斷,效率比較低下:
var utils = {
addListener: function(el, type, fn) {
if (typeof window.addEventListener === 'function') {
el.addEventListener(type, fn, false);
} else if (typeof document.attachEvent === 'function') { // IE el.attachEvent('on' + type, fn);
} else { // older browsers el['on' + type] = fn;
}
},
removeListener: function(el, type, fn) {
// pretty much the same... }
};
所以,根據(jù)初始化時(shí)分支模式,可以在腳本初始化的時(shí)候進(jìn)行一次瀏覽器檢測(cè),這樣在以后使用utils的時(shí)候就不必進(jìn)行瀏覽器檢測(cè)了:
// the interfacevar utils = {
addListener: null,
removeListener: null};// the implementationif (typeof window.addEventListener === 'function') {
utils.addListener = function(el, type, fn) {
el.addEventListener(type, fn, false);
};
utils.removeListener = function(el, type, fn) {
el.removeEventListener(type, fn, false);
};
} else if (typeof document.attachEvent === 'function') { // IE utils.addListener = function(el, type, fn) {
el.attachEvent('on' + type, fn);
};
utils.removeListener = function(el, type, fn) {
el.detachEvent('on' + type, fn);
};
} else { // older browsers utils.addListener = function(el, type, fn) {
el['on' + type] = fn;
};
utils.removeListener = function(el, type, fn) {
el['on' + type] = null;
};
}
命名空間模式
JavaScript代碼中,過(guò)多的全局變量經(jīng)常會(huì)引發(fā)一些問(wèn)題,比如命名沖突。
結(jié)合命名空間模式就可以一定程度上減少代碼中全局變量的個(gè)數(shù)。
下面就看一個(gè)通用命名空間函數(shù)的實(shí)現(xiàn):
var MYAPP = MYAPP || {};
MYAPP.namespace = function (ns_string) { var parts = ns_string.split('.'), parent = MYAPP,
i; // strip redundant leading global
if (parts[0] === "MYAPP") {
parts = parts.slice(1);
} for (i = 0; i < parts.length; i += 1) { // create a property if it doesn't exist
if (typeof parent[parts[i]] === "undefined") { parent[parts[i]] = {};
} parent = parent[parts[i]];
} return parent;
};
結(jié)合這個(gè)通用命名空間函數(shù)的,就可以實(shí)現(xiàn)代碼的模塊化:
// assign returned value to a local varvar module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2; // true// skip initial `MYAPP`MYAPP.namespace('modules.module51');// long namespaceMYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');
聲明依賴關(guān)系
JavaScirpt庫(kù)通常是通過(guò)命名空間來(lái)進(jìn)行模塊化,當(dāng)我們?cè)诖a中使用第三方的庫(kù)的時(shí)候,可以只引入我們代碼依賴的模塊。
所謂聲明依賴關(guān)系,就是指在函數(shù)或者模塊的頂部是聲明代碼需要依賴哪些模塊,這個(gè)聲明包括創(chuàng)建一個(gè)局部變量,并將它們指向你需要的模塊:
var myFunction = function () { // dependencies
var event = YAHOO.util.Event,
dom = YAHOO.util.Dom; // use event and dom variables
// for the rest of the function...};
通過(guò)聲明依賴關(guān)系這種模式,會(huì)給我們帶來(lái)很多好處:
明確的依賴聲明可以向你的代碼的使用者表明這些特殊的腳本文件需要被確保包含進(jìn)頁(yè)面
數(shù)頭部的聲明解,讓發(fā)現(xiàn)和處理依賴關(guān)系更加簡(jiǎn)單
局部變量(比如:dom)通常比使用全局變量(比如:YAHOO)快,比訪問(wèn)全局對(duì)象的屬性(比如:YAHOO.util.Do)更快,可以得到更好的性能,全局符號(hào)只會(huì)在函數(shù)中出現(xiàn)一次,然后就可以使用局部變量,后者速度更快。
壓縮工具比如YUICompressor 和 Google Closure compiler會(huì)重命名局部變量,產(chǎn)生更小的體積的代碼,但從來(lái)不會(huì)重命名全局變量,因?yàn)槟菢邮遣话踩?/p>
代碼復(fù)用模式
下面就看看JavaScript中的代碼復(fù)用模式。一般來(lái)說(shuō),通常使用下面的方式來(lái)實(shí)現(xiàn)代碼的復(fù)用:
繼承
借用方法
繼承
在JavaScript中可以很方便的通過(guò)原型來(lái)實(shí)現(xiàn)繼承。
關(guān)于原型式繼承,ECMA5通過(guò)新增Object.create()方法規(guī)范化了原型式繼承。這個(gè)方法接收兩個(gè)參數(shù):
一個(gè)用作新對(duì)象原型的對(duì)象
一個(gè)為新對(duì)象定義額外屬性的對(duì)象(可選的)
看一個(gè)使用Object.create()的例子:
utilsLibC = Object.create(utilsLibA, { sub: {
value: function(){
console.log("sub method from utilsLibC");
}
},
mult: {
value: function(){
console.log("mult method from utilsLibC");
}
},
})
utilsLibC.add();// add method from utilsLibA
utilsLibC.sub();// sub method from utilsLibCutilsLibC.mult();
// mult method from utilsLibC
console.log(utilsLibC.__proto__);// Object {add: (), sub: (), __proto__: Object}console.log(utilsLibC.__proto__.constructor);
// function Object() { [native code] }
關(guān)于JavaScript繼承的更多信息,可以參考關(guān)于JavaScript繼承的那些事。
借用方法
有時(shí)候可能只需要一個(gè)已經(jīng)存在的對(duì)象的一個(gè)或兩個(gè)方法,但是又不想通過(guò)繼承,來(lái)建立額外的父子(parent-child)關(guān)系。
這時(shí)就可以考慮使用借用方法模式完成一些函數(shù)的復(fù)用。借用方法模式得益于function的方法call()和apply()。
這種模式一個(gè)常見(jiàn)用法就是借用數(shù)組方法。
數(shù)組擁有有用的方法,那些類數(shù)組對(duì)象(array-like objects)比如arguments類數(shù)組對(duì)象(array-like objects)比如arguments沒(méi)有的方法。所以arguments可以借用數(shù)組的方法,比如slice()方法,看一個(gè)例子:
function f() { var args = [].slice.call(arguments, 1, 3); return args;
}// examplef(1, 2, 3, 4, 5, 6); // returns [2,3]
【常用的JavaScript模式】相關(guān)文章:
JavaScript常用方法匯總03-08
對(duì)javascript的理解03-29
最常用的20個(gè)javascript方法函數(shù)03-10
JavaScript的課堂講解03-31
JavaScript 基礎(chǔ)教學(xué)04-01