一.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.防偽對象的概念出現在書的[函數化]部分,目前還不能完全理解函數化想要表達的意思,把自己養肥了再看
十.數組
本質是鍵值對,也就是對象,所以並沒有訪問速度上的優勢
區別是Array.prototype提供了很多數組操作函數,此外還有特殊的length屬性
注意: 1. 數組實際占用的內存空間
var arr = [1]; arr[99] = 100;
此時arr指向的值並沒有占用100個元素的空間,因為上面的結果相當於:
var arr = { "0" : 1, "99" : 100 };
-
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_高級技巧
十一.正則表達式
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_基礎與常識
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模式,那么只替換第一個匹配部分,並不是全串替換
-
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。不好用,這是設計上的失誤,歷史原因
-
paseInt()與八進制。一個很隱蔽的錯誤:
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語言精粹》