一、in 的用法
- for...in
枚舉一個對象的所有可枚舉屬性
-
檢測 DOM/BOM 屬性
if ("onclick" in elem) { // 元素支持 onclick } if ("onerror" in window) { // window 支持 onerror } -
檢測 js 對象的原型屬性(結合 hasOwnProperty() 函數)
if ("attr" in obj && !obj.hasOwnProperty("attr")) { // obj 有原型屬性 attr }
注意:原型屬性會被同名的實例屬性屏蔽掉,所以無法檢測這樣的:
function Super(){
this.attr = 1;
}
function Sub(){
this.attr = 2;
}
Sub.prototype = new Super();
var obj = new Sub();
alert(("attr" in obj && !obj.hasOwnProperty("attr"))); /// false
二、|| 運算符和&&運算符
-
|| 可以用來填充默認值,例如:
var obj = {}; obj.name = ""; // 注意 js 有一堆假值:+0、-0、0、NaN、null、undefined、""、''、false var name = obj.name || "ayqy"; alert(name);
P.S. 個人認為這個東西並不好用,除非能確定原屬性值不可能是任何形式的假值,否則默認值會覆蓋原值
-
&& 可以用來避免從 undefined 值讀取屬性引起 TypeError,例如:
var obj = {}; //var attr = obj.obj.attr; // 報錯,TypeError,不能從 undefined 值讀取屬性 var attr = obj.obj && obj.obj.attr; // 只有 obj.obj 存在且不是假值時才會取其 attr
P.S. 個人認為和&&差不多,不如直接用 if 檢測,不僅能展現更清晰的邏輯流,還便於添加更多的檢測條件
P.S. 這兩種用法是在書的第一部分介紹的,可能更偏向於展示語言特性,並不是推薦用法
個人建議不要這樣用,當然,看到這樣的代碼要能明白其作用與漏洞
三、減少全局變量污染
-
用「命名空間」,即空對象,實質是把創建的全局變量減少到 1 個,比如 YUI 的 Y 對象,JQuery 的 JQuery 和$對象。。例如:
var app = {}; // 命名空間:app app.Consts = { // 子命名空間:常量 URL : { // 子子命名空間:URL } } app.Modules = { // 子命名空間:模塊 } app.Data = { // 子命名空間:數據 } -
用 IIFE(匿名函數立即執行),實質完全不創建全局變量了,0 污染
(function(){ // Module1 })(); (function(){ // Module2 })();
但缺點很明顯,無法實現對象緩存和共享,出了閉包就沒了
所以一般做法是結合命名空間和 IIFE,整體用命名空間組織起來,在模塊內部合適的地方用 IIFE
四、4 種調用模式
-
方法調用模式:obj.fun(); 或者 obj"fun";
-
函數調用模式:fun(); 此時 this 指向 global 對象
-
構造器調用模式:new Fun(); 新對象的 prototype 指向 Fun 的 prototype,this 指向這個新對象
-
apply 調用模式:fun.apply(this, arrArgs);
五、關於 arguments 對象
arguments 對象不是數組對象,也不支持所有數組方法,只是一個有點特殊的普通對象,特殊在其 length 屬性(動態更新)
可以做如下測試:
function fun(){
var x = Object.prototype.toString.call(arguments);
alert(x);
var arr = [];
alert(Object.prototype.toString.call(arr));
};
fun();
// IE8:[object Object] 和 [object Array],Chrome:[object Arguments] 和 [object Array]
// 不一樣不要緊,反正都不是 Array
一個小技巧:可以用 slice 方法把 arguments 對象轉換為數組,例如:
function fun(){
//arguments.sort(); // 報錯,不支持 sort() 函數
var arr = Array.prototype.slice.call(arguments); // 轉換
arr.sort(); // 不報錯,說明轉換成功
alert(arr);
};
fun(3, 2); // 2, 3
注意:只有 slice 有這種奇效,concat 就沒有,雖然無參的 slice 和無參的 concat 用於數組的效果一樣(都是複製一份)
六、級聯(鏈式調用)的實現方式
讓沒有返回值的函數返回 this,所以支持鏈式調用,比如 JQuery 中的:
$("#adBlock").removeClass("active").hide();
七、構造函數調用方式不當會引起作用域污染
如果不用 new 操作符,直接調用構造函數,比如 Fun(); 此時 this 指向 global 屬性,即瀏覽器環境下的 window 對象,可能會污染全局作用域
八、把形參列表簡化為單一對象
例如:
function fun(height, width, margin, padding){ // 參數太長,順序記不住
// do something
}
/*
* @param {number} arg.height 高度
* @param {number} arg.width 寬度
* @param {number} arg.margin 留白
* @param {number} arg.padding 補白
*/
function fun(arg){ // 不用記參數順序了
// do something
}
好處如下:
-
調用時不用再關心參數順序了,接口變得更加易用
-
可以直接傳個 JSON 對象進去
缺點:要寫一堆注釋說明具體需要哪些參數,因為通過一個 arg 完全看不出來
九、防偽對象(持久性的對象)
防偽對象的屬性可以被替換或者刪除,但該對象的完整性不會受到損害
也被稱為持久性的對象,一個持久性對象就是一個簡單功能函數的集合
P.S. 防偽對象的概念出現在書的 [函數化] 部分,目前還不能完全理解函數化想要表達的意思,把自己養肥了再看
函數化部分請查看 [黯羽輕揚:《JavaScript 語言精粹》之函數化](http://ayqy.net/blog/《JavaScript 語言精粹》之函數化/),內容已經補充完整了
十、數組
本質是鍵值對,也就是對象,所以並沒有訪問速度上的優勢
區別是 Array.prototype 提供了很多數組操作函數,此外還有特殊的 length 屬性
注意:
-
數組實際佔用的內存空間
var arr = [1]; arr[99] = 100;
此時 arr 指向的值並沒有佔用 100 個元素的空間,因為上面的結果相當於:
var arr = {
"0" : 1,
"99" : 100
};
2. length 屬性是可寫的
可以用 arr.length = n; 刪掉下標值 >= n 的所有元素
- 常見的 length 屬性用法
省計數器的方式:arr[arr.length] = value;
或者更簡單的:arr.push(value);
- 數組類型檢測
因為 typeof arr 返回"object",而且 instanceof 操作符在跨 frame 時失效,所以檢測數組類型不太容易
書上給了一種超麻煩的方式:
function isArray(value){
return value &&
typeof value === "object" &&
typeof value.length === "number" &&
typeof value.splice === "function" &&
!(value.propertyIsEnumerable("length"));
}
其實有在作者寫書的時候還沒出現的簡單方法:
可以用 Object.prototype.toString.call(value) === '[object Array/Function...]' 來做類型檢查,也可以用來區分原生對象和自定義對象,例如:
[object JSON] // 原生 JSON 對象
[object Object] // 自定義 JSON 對象
關於類型檢測的更多信息請查看 [黯羽輕揚:JS 學習筆記 11_高級技巧](http://ayqy.net/blog/JS 學習筆記 11_高級技巧/)
十一、正則表達式
js 的正則表達式功能並不完整,但基本夠用,比如只支持 3 種模式:g/i/m ~ 全局模式/忽略大小寫模式/多行模式
而且支持的特殊元字符(\d、\s 之類的)也比較少,但好在支持非捕獲型分組和正則環視,還是很不錯的
所以在 js 中使用正則表達式需要做更多的測試,更多關於正則表達式的信息請查看 黯羽輕揚:正則表達式學習筆記
還有一個不常用的點:RegExp 對象的屬性
-
global:是否開了 g 模式
-
ignoreCase:返回是否開了 i 模式
-
lastIndex:返回下一次 exec 匹配的起始位置
-
multiline:返回是否開了多行模式
-
source:返回正則表達式串
需要特別注意:用字面量方式創建的 RegExp 對象可能引用同一個實例,例如:
var regex1 = /abc/g;
var regex2 = /abc/g;
var str = "aaa\r\nabc\r\naaa";
alert([regex1.lastIndex, regex2.lastIndex]); // 0, 0
regex1.test(str);
alert([regex1.lastIndex, regex2.lastIndex]); // 8, 0
alert(regex1 === regex2); // false
老版本瀏覽器最後兩行會輸出 8, 8 和 true,據說正則字面量共享實例是 ES3 的規定,本機���試發現 IE8 不存在問題,但理論上 IE6 就存在這個問題(出 IE6 的時候,ES5 還沒出)
十二、原生對象操作函數
P.S. 此處只介紹需要特別注意的點,完整的各種操作函數請查看 [黯羽輕揚:JS 學習筆記 1_基礎與常識](http://ayqy.net/blog/JS 學習筆記 1_基礎與常識/)
1. 數組操作函數
-
arr1.push(arr2); 會把 arr2 作為單個元素插入,例如:
var arr1 = [1]; var arr2 = [2, 3]; arr1.push(arr2); alert(arr1[2]); // undefined alert(arr1[1][1]); // 3 -
arr.shift() 比 arr.pop() 慢很多,因為刪掉首元需要更新所有元素的索引,而刪掉尾元不需要
-
arr.unshift(),在數組頭部插入元素,IE6 返回 undefined,本來應該返回新數組的長度
2. 對象操作函數
obj.hasOwnProperty("hasOwnProperty") 返回 false
3. 正則表達式對象操作函數
regex.exec(str) 函數功能最強大,當然,執行速度也最慢
regex.test(str) 最簡單,最快,注意:不要開 g 模式,因為純屬浪費(test 只返回匹配/不匹配,一次掃描就夠了)
4. 字符串操作函數
- str.replace(regex, fun); 是很好用的一個函數,可以用 regex 匹配目標部分,還可以用 fun 進一步處理匹配的部分
特別注意:如果 regex 沒有開 g 模式,那麼只替換第一個匹配部分,並不是全串替換
fun 的各個參數含義如下:
<table class="fullwidth-table"><tbody><tr><td class="header">Possible name</td><td class="header">Supplied value</td></tr><tr><td><code>match</code></td><td>The matched substring. (Corresponds to <code>$&</code> above.)</td></tr><tr><td><code>p1, p2, ...</code></td><td>The <em>n</em>th parenthesized submatch string, provided the first argument to <code>replace()</code> was a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp" title="The RegExp constructor creates a regular expression object for matching text with a pattern."><code>RegExp</code></a> object. (Corresponds to <code>$1</code>, <code>$2</code>, etc. above.) For example, if <code>/(\a+)(\b+)/</code>, was given, <code>p1</code> is the match for <code>\a+</code>, and <code>p2</code> for <code>\b+</code>.</td></tr><tr><td><code>offset</code></td><td>The offset of the matched substring within the total string being examined. (For example, if the total string was <code>'abcd'</code>, and the matched substring was <code>'bc'</code>, then this argument will be 1.)</td></tr><tr><td><code>string</code></td><td>The total string being examined.</td></tr></tbody></table>
-
str.replace(regex, replacement); 的 replacement 部分中的$有特殊含義:
-
$$:表示$,如果替換部分中有$需要用$來轉義
-
$&:表示整個匹配的文本
-
$n:例如$1, $2, $3... 表示捕獲到的文本
-
$`:表示位於匹配部分之前的文本
-
$':表示位於匹配部分之後的文本
-
更多詳細示例請查看 W3School:JavaScript replace() 方法
-
str.split(regex); 存在一個特例,需要特別注意:
var str = "a & b & c"; // &是分隔符 var arr = str.split(/\s*(&)\s*/); // 含捕獲 var arr2 = str.split(/\s*&\s*/); // 不含捕獲 var arr3 = str.split(/\s*(?:&)\s*/); // 含非捕獲 alert(arr); // [a, &, b, &, c] alert(arr2); // [a, b, c] alert(arr3); // [a, b, c]如果不小心用了含捕獲型分組的正則表達式,結果可能與預期的不同
十三、糟粕
P.S. 這裡不打算原樣照搬書上的內容,只給出筆者同意的最糟糕的地方
-
自動插入分號。確實不好,比如典型的 ASI 錯誤:
function fun(){ return { attr : 1; } } alert(fun().attr); // TypeError: Cannot read property 'attr' of undefined -
typeof。不好用,這是設計上的失誤,歷史原因
-
parseInt() 與八進制。一個很隱蔽的錯誤:
var year = "2015"; var month = "08"; var day = "09"; alert(parseInt(year)); // IE8:2015 Chrome:2015 alert(parseInt(month)); // IE8:0 Chrome:8 alert(parseInt(day)); // IE8:0 Chrome:9
因為 0 開頭的數值被認為是八進制,省略第二個參數的 parseInt() 會把 08/09 當作八進制來解析,所以結果是 0
處理時間日期太容易引起解析錯誤了,所以最好不要省略 parseInt() 的第二個參數
P.S. Chrome 中正常了,可能是某個 ES 版本對 parseInt() 做了修改,Chrome 做了實現。所以糟粕並不是永遠存在的,當然,是不是糟粕也要看程序員怎麼用。。
- Unicode。js 對 Unicode 的支持不好,雖然也是歷史原因,但不支持就是不支持
P.S. 出 js 的時候 Unicode 自己也沒想到能有 100 萬個字符,js 的字符是 16 位的,能對應前 65536 個 Unicode 字符,而 Unicode 為了擴容,就把剩下的每個字符都用一對字符來表示,但 js 會把這樣的字符當作兩個字符,所以。。
十四、雞肋
P.S. 同上,只列筆者同意的
-
continue 性能低。可以 if 消除
-
位運算性能低。(其它的書都沒有提到這一點)因為要折騰:double -> int -> 做位運算 -> double
-
new 到底應該用還是不用。用的話可能污染作用域
-
void 運算符。單目運算,返回 undefined,所以可以用來實現 IIFE,例如:
void function(){ // do something }();
作者建議不要用 void,因為沒什麼人知道它是做什麼的
十五、JSON
整數的首位不能是 0,道格拉斯(發明 JSON 的大爺)本人說的,數值可以是整數、實數或者科學計數,但首位不能是 0,為避免八進制歧義
測試代碼如下:
var json = '{"number" : 9}';
var obj = JSON.parse(json);
alert(obj.number); // 9
var json = '{"number" : 09}'; // 注意前導 0
var obj = JSON.parse(json);
alert(obj.number); // 報錯
// Chrome:SyntaxError: Unexpected number
// IE8:語法錯誤
// FF:SyntaxError: JSON.parse: expected ',' or '}' after property value in object
參考資料:
- 《JavaScript 語言精粹》
暫無評論,快來發表你的看法吧