JavaScript子集
大多數(shù)語言都會定義它們的子集,用以更安全地執(zhí)行不信任的第三方代碼贰剥。
精華(The Good Parts)
Douglas Crockford曾寫過一本很簿的書《JavaScript: The Good Parts》蚌成,專門介紹JavaScript中值得發(fā)揚(yáng)光大的精華部分凛捏。這個語言子集的目標(biāo)是規(guī)避語言中的怪癖葵袭、缺陷部分坡锡,最終編程更輕松鹉勒、程序更健壯。
- 使用函數(shù)定義表達(dá)式而不是函數(shù)定義語句來定義函數(shù)锯厢。
- 循環(huán)體和條件分支都使用花括號括起來实辑。
- 任何語句只要不是以花括號結(jié)束都應(yīng)該使用分號做結(jié)尾剪撬。
- 推薦使用"==="和"!=="残黑,不推薦使用"=="和"!="(因為比較時會涉及到類型轉(zhuǎn)換)梨水。
- 由于JavaScript并不包含塊級作用域疫诽,var語句最好出現(xiàn)在函數(shù)體的頂部踊沸。
- 禁止使用全局變量社证。
Crockford寫過一個在線代碼質(zhì)量檢測工具JSLint追葡,可通過這個工具對代碼進(jìn)行檢查宜肉。
子集的安全性
子集的設(shè)計目的是能在一個容器或"沙箱"中更安全地運行不可信的第三方JavaScript代碼谬返。所有能破壞這個沙箱并影響全局執(zhí)行環(huán)境的語言特性和API在這個安全子集中都是禁止的遣铝。
為了讓JavaScript代碼靜態(tài)地通過安全檢查,必須移除一些JavaScript特性:
- 禁止使用this關(guān)鍵字涨冀,因為函數(shù)(在非嚴(yán)格模式中)可能通過this訪問全局對象鹿鳖。
- 禁止使用with語句壮莹,因為with語句增加了靜態(tài)代碼檢查的難度命满。
- 靜態(tài)分析可以有效地防止帶有點(.)運算符的屬性存取表達(dá)式去讀寫特殊屬性周荐。但我們無法對方括號([])內(nèi)的字符串表達(dá)式做靜態(tài)分析概作⊙堕牛基于這個原因愚屁,安全子集禁止使用方括號霎槐,除非括號內(nèi)是一個數(shù)字或字符串直接量送浊。
- eval()和Function()構(gòu)造函數(shù)在任何子集里都是禁止使用的,因為它們可以執(zhí)行任意代碼丘跌,而且JavaScript無法對這些代碼做靜態(tài)分析袭景。
- 禁止使用全局變量,因此代碼中不能有對Window對象的引用和對Document的引用闭树。
- 禁止使用某些屬性和方法耸棒,以免在水箱中的代碼擁有過多的權(quán)限。如:arguments對象的兩個屬性caller和callee报辱、函數(shù)的call()和apply()方法、以及constructor和prototype兩個屬性。
JavaScript擴(kuò)展
常量和局部變量
在JavaScript1.5及后續(xù)版本中可以使用const關(guān)鍵字來定義常量幅疼,常量是不可重復(fù)賦值的變量米奸。
const pi = 3.14;
pi = 4; // 對常量賦值會報"TypeError"
在JavaScript1.7中,添加了關(guān)鍵字let衣屏,可以定義帶有作用域的變量:
下面列舉各種場景使用let的作用域:
- 全局作用域(定義在函數(shù)之外)
let me = 'go'; // 全局作用域
var i = 'able'; // 全局作用域
- 函數(shù)作用域
function ingWithinEstablishedParameters() {
let terOfRecommendation = 'awesome worker!'; // 函數(shù)作用域
var sityCheerleading = 'go!'; // 函數(shù)作用域
};
- 塊作用域
function allyIlliterate() {
// tuce在這里不可見
for( let tuce = 0; tuce < 5; tuce++ ) {
// tuce在這里可見
};
// tuce在這里不可見
};
function byE40() {
// nish在這里可見
for( var nish = 0; nish < 5; nish++ ) {
// nish在這里可見
};
// nish在這里可見
};
更多可參考:“l(fā)et” keyword vs “var” keyword
例子如下:
o = {x:1, y:2};
for each(let v in o)
console.log(v); // 輸出1和2
console.log(v); // 錯誤:v is not defined
在for循環(huán)中使用let關(guān)鍵字:
// 輸出 5, 5, 5, 5, 5
for (var i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// 輸出 1, 2, 3, 4, 5
for (let i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i);
}, 1000);
}
更多可參考:Explanation of let and block scoping with for loops
注:var聲明的變量在它們所聲明的函數(shù)內(nèi)始終是存在的躏升,但直到代碼執(zhí)行行到var語句時才初始化變量辩棒,在var語句執(zhí)行之前它的值是undefined狼忱。通過let聲明的變量也與之類似。
解構(gòu)賦值(destructuring assignment)
在解構(gòu)賦值中一睁,等號右側(cè)是一個數(shù)組或?qū)ο?一個結(jié)構(gòu)化的值)钻弄,指定左側(cè)一個或多個變量的語法和右側(cè)的數(shù)組和對象直接量的語法保持格式一致。
數(shù)組解構(gòu)賦值
- 簡單的解構(gòu)賦值
let [x, y] = [1, 2]; // 等價于let x=1, y=2
[x, y] = [x+1, y+1]; // 等價于x=x+1, y=y+1
[x, y] = [y, x]; // 交換2個變量的值
console.log([x, y]); // => [3, 2]
- 解構(gòu)賦值左右的變量不一定要一一對應(yīng)者吁,左側(cè)多余的變量賦值為undefined窘俺,而右側(cè)多余的值則會忽略。左側(cè)的變量列表可以包含連續(xù)的逗號用以跳過右側(cè)對應(yīng)的值复凳。
let [x, y] = [1]; // x = 1, y = undefined
[x, y] = [1, 2, 3]; // x = 1, y = 2
[, x, y] = [1, 2, 3, 4];// x = 2, y = 3
注:JavaScript并未提供將右側(cè)多余的值以數(shù)組的形式賦值給左側(cè)變量的語法瘤泪。比如,上面代碼的第2行育八,并不能將[2,3]賦值給y对途。
- 解構(gòu)賦值的返回值是右側(cè)的整個數(shù)據(jù)結(jié)構(gòu),而不是從中取出的某個值髓棋。
let first, second, all;
all = [first, second] = [1, 2, 3, 4]; // first=1, second=2, all=[1,2,3,4]
- 解構(gòu)賦值可使用數(shù)組嵌套的語法实檀。
let [one, [twoA, twoB]] = [1, [2, 2,5], 3]; // one=1, twoA=2, twoB=2.5
對象解構(gòu)賦值
- 在這種情況下,解構(gòu)賦值的左側(cè)看起來是一個對象直接量按声,對象中是一個名值對的列表膳犹,冒號右側(cè)的值是變量名。
let transparent = {r:0.0, g:0.0, b:0.0, a:1.0};
let {r:red, g:green, b:blue} = transparent; // red=0.0, green=0.0, blue=0.0
- 解構(gòu)賦值可使用對象嵌套的語法签则。
let data = {
name: "destructuring assignment",
type: "extension",
impl: [{engine: "spidermonkey", version: 1.7},
{engine: "rhino", version: 1.7}]
};
let ({name: feature, impl: [{engine:impl1, version: v1}, {engine:impl2}]} = data) {
console.log(feature); // => "destructuring assignment"
console.log(impl1); // => "spidermonkey"
console.log(v1); // => 1.7
console.log(impl2); // => "rhino"
}
迭代
for VS for/each
for循環(huán)是遍歷對象的屬性须床,而for/each遍歷對象屬性的值。
a = ["one", "two", "three"];
for(let p in a) console.log(p); // => 0, 1, 2
for each(let v in a) console.log(v) // => "one", "two", "three"
迭代器
迭代器是一個對象渐裂,這個對象允許對它的值集合進(jìn)行遍歷豺旬,并保持任何必要的狀態(tài)以便能夠跟蹤到當(dāng)前遍歷的"位置"。迭代器必須包含next()方法芯义,每一次調(diào)用next()都返回集合中的下一個值哈垢。
下面的counter()返回一個迭代器對象:
function counter(start) {
let nextValue = Math.round(start);
return { next: function() { return nextValue++; }}; // 返回迭代器對象
}
let serialNumberGenerator = counter(1000);
let sn1 = serialNumberGenerator.next(); // => 1000
let sn2 = serialNumberGenerator.next(); // => 1001
注:當(dāng)?shù)饔糜谟邢薜募蠒r,沒有多余的值可迭代時耘分,next()會拋出StopIterator,StopIterator是全局對象的屬性,一個普通的對象(它自身沒有屬性)求泰,只是為了終結(jié)迭代而保留的一個對象央渣。
可迭代對象
- 可迭代對象表示一組可迭代處理的值,可迭代對象必須包含一個
_iterator_()
的方法渴频,用以返回這個集合的迭代器對象芽丹。 - for/in循環(huán)會自動調(diào)用它的
_iterator_()
方法來獲得一個迭代器對象(類型自動轉(zhuǎn)換),然后調(diào)用迭代器的next()方法卜朗。for/in循環(huán)會自動處理StopIteration異常拔第,而且處理過程對開發(fā)者是不可見的。
function range(min, max) {
return {
get min() { return min; },
get max() { return max; },
includes: function(x) { return min <= x && x <= max; },
toString: function() { return "[" + min + "," + max + "]"; },
_iterator_: function() {
let val = Math.ceil(min);
return { next: function() {
if(val > max)
throw StopIteration;
return val++;
}
};
}
};
}
for(let i in range(1, 10)) console.log(i); // => 輸出1~10之間的數(shù)字
Iterator()函數(shù)
- 如果這個函數(shù)的參數(shù)是一個可迭代對象场钉,那么它將返回這個對象的
_iterator_()
方法的調(diào)用結(jié)果蚊俺,返回一個迭代器對象。 - 如果傳入的對象或者數(shù)組沒有定義
_iterator_()
方法逛万,它會返回這個對象的一個可迭代的自定義迭代器泳猬。每次調(diào)用這個迭代器的next()方法都會返回包含2個值的數(shù)組,第1個數(shù)組元素是屬性名宇植,第2個數(shù)組元素是屬性的值得封。
for(let [k, v] in Iterator({a:1, b:2}))
console.log(k + "=" + v); // => "a=1", "b=2"
- Iterator()函數(shù)返回的迭代器還有2個重要的特性。第一指郁,它只對自有屬性進(jìn)行遍歷而忽略繼承的屬性忙上。第二,如果給Iterator()傳入第2個參數(shù)true坡氯,返回的迭代器只對屬性名進(jìn)行遍歷晨横,而忽略屬性值。
o = {x:1, y:2};
Object.prototype.z = 3;
for(p in o) console.log(p); // => "x", "y", "z"
for(p in Iterator(o)) console.log(p); // => ["x", 1], ["y", 2]箫柳,注:不會遍歷繼承屬性
for(p in Iterator(o, true)) console.log(p); // => "x", "y"手形, 注:只遍歷屬性名
生成器函數(shù) Generator function
任何使用關(guān)鍵字yield的函數(shù)都稱為"生成器函數(shù)"。
- yield和return的區(qū)別在于悯恍,使用yield的函數(shù)可保持函數(shù)內(nèi)部狀態(tài)的值库糠。
- 生成器函數(shù)通過yield返回值,不建議使用return返回值涮毫。
- 和普通函數(shù)一樣瞬欧,生成器函數(shù)也通過關(guān)鍵字function聲明,typeof運算符返回"function"罢防,并從Function.prototype繼承屬性和方法艘虎。
- 對生成器函數(shù)的調(diào)用不會執(zhí)行函數(shù)體,而是返回一個生成器對象咒吐。
- 如果不再使用生成器對象野建,可通過close()方法來釋放它属划。調(diào)用close()相當(dāng)于在函數(shù)運行掛起的位置執(zhí)行了一條return語句轧叽,如果如果當(dāng)前掛起的位置在try語句塊中贼陶,那么將首先執(zhí)行finally語句肠牲,再執(zhí)行close()返回藻治,如果finally語句塊產(chǎn)生了異常,這個異常會傳播給close()盼忌。
下面是一個普通的生成器函數(shù):
function range(min, max) {
for(let i=Math.ceil(min); i <= max; i++) {
yield i;
}
}
var f = range(3, 8); // 返回一個生成器對象
console.log(f.next()); // 3赘方,返回單獨值
console.log(f.next()); // 4铛楣,返回單獨值
如果在生成器函數(shù)聲明時加個"*"目溉,則yield會返回對象值:
// function后添加"*"
function *range(min, max) {
for(let i=Math.ceil(min); i <= max; i++) {
yield i;
}
}
var f = range(3, 8); // 返回一個生成器對象
console.log(f.next()); // { value=3, done=false, z=3}明肮,返回對象
console.log(f.next()); // { value=4, done=false, z=3},返回對象
yield不僅可以返回值停做,還可以用于接收值晤愧,可通過next()或send()傳遞yield的接收值:
function *test() {
var i = yield 2;
yield i;
};
var f = test();
console.log(f.next()); // { value=2, done=false, z=3}, 第1次調(diào)用時大莫,沒有執(zhí)行到y(tǒng)ield語句蛉腌,不需要接收值
console.log(f.next(5)); // { value=5, done=false, z=3}, yield接收變量5,并賦值給i
console.log(f.next(6)); // { done=true, z=3, value=undefined}只厘,最后一個值為undefined
更多可參考:
es6-generators
如何理解ES6的yield
數(shù)組推導(dǎo) array comprehension
數(shù)組推導(dǎo)是一種利用另外一個數(shù)組或可迭代對象來初始化數(shù)組元素的技術(shù)烙丛。
數(shù)組推導(dǎo)的語法如下:
[expression for ( variable in object ) if ( condition )]
定義一個數(shù)組:
let evensquares = [x*x for (x in range(0,10)) if (x % 2 === 0)]
與下面的代碼等價:
let evensquares = [];
for(x in range(0,10)) {
if(x % 2 === 0)
evensquares.push(x*x);
}
數(shù)組推導(dǎo)表達(dá)式特點:
- 在變量之前沒有關(guān)鍵字var和let,其實這里使用了隱式的let羔味。
- if語句是可選的河咽,如果省略的話,相當(dāng)于給數(shù)組推導(dǎo)補(bǔ)充一條if(true)從句赋元。
生成器表達(dá)式 generator expression
將數(shù)組推導(dǎo)中的方括號替換成圓括號忘蟹,它就成了一個生成器表達(dá)式。比如:
let h = (f(x) for(x in g));
這段代碼與下面的代碼等價:
function map(i, g) {
for(let x in g) yield f(x);
}
函數(shù)簡寫
如果函數(shù)只計算一個表達(dá)式并返回它的值搁凸,關(guān)鍵字return和花括號都可以省略媚值。
比如,對數(shù)組按降序排列:
data.sort(function(a,b) b-a); // 在"b-a"的前面省略了"{}"和return
多catch從句
try/catch語句中可以使用多catch從句护糖,在catch從句的參數(shù)中可以添加關(guān)鍵字if進(jìn)行異常類型判斷褥芒。
try {
// 這里可能會拋出多種類型的異常
throw 1;
}
catch(e if e instanceof ReferenceError) {
// 這里處理引用錯誤
}
catch(e if e === "quit") {
// 這里處理拋出的字符串是"quit"的情況
}
catch(e if typeof e === "string") {
// 處理其他字符串的情況
}
catch(e) {
// 處理余下的異常情況
}
finally {
// finally從句正常執(zhí)行
}
注:如果catch從句沒有一個是true,那么程序會向上拋出這個未捕獲的異常嫡良。
E4X: ECMAScript for XML
E4X為處理XML文檔定義了一系列強(qiáng)大的特性锰扶。XML對象和原始的JavaScript對象不同,對它們進(jìn)行typeof運算的結(jié)果是"xml"寝受。XML對象和DOM對象沒有任何關(guān)系坷牛。
XML對象創(chuàng)建
- 直接量創(chuàng)建
// 創(chuàng)建一個XML對象
var pt = <periodictable>
<element id="1"><name>Hydrogen</name></element>
<element id="2"><name>Helium</name></element>
</periodictable>;
// 添加一個新元素
pt.element += <element id="3"><name>Lithium</name></element>;
- 構(gòu)造函數(shù)構(gòu)造
// 創(chuàng)建單個節(jié)點 XML
pt.element += new XML('<element id="4"><name>Boron</name></element>');
// 創(chuàng)建多個節(jié)點 XMLList
pt.element += new XMLList('<element id="4"><name>Boron</name></element>' +
'<element id="5"><name>Lithium</name></element>');
XML對象訪問
- 普通訪問
// 得到所有<element>標(biāo)簽的 列表
var elements = pt.element;
// 得到所有的<name>標(biāo)簽的 列表
var names = pt.element.name;
// "Hydrogen",name的第0個標(biāo)簽內(nèi)容
var n = names[0];
- 通配符(*)訪問
// 得到所有<element>標(biāo)簽的所有子節(jié)點(即<name>標(biāo)簽列表)
var names = pt.element.*;
- 使用字符@區(qū)分屬性和標(biāo)簽名
var pt = new XML('<periodictable><element id="1"><name>Hydrogen</name></element><element id="2"><name>Helium</name></element></periodictable>');
// 訪問id屬性很澄,輸出"2"
var a = pt.element[1].@id;
// 獲取所有的<element>標(biāo)簽的所有屬性
var b = pt.element.@*;
- 過濾列表
// 對所有的<element>元素組成的列表進(jìn)行過濾
// 過濾出那些id屬性小于3的元素
var c = pt.element.(@id < 3);
- 刪除標(biāo)簽和屬性
delete pt.element; // 刪除所有的<element>標(biāo)簽
delete pt.element[0].@id; // 刪除一個屬性
命名空間
E4X是支持命名空間的京闰,它為使用XML命名空間提供了語法支持和API支持锨亏。
// 聲明默認(rèn)的命名空間
default xml namespace = "http://www.w3.org/1999/xhtml";