爲了判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性,咱們須要使用繼承自 Object.prototype
的 hasOwnProperty
方法。hasOwnProperty
是 JavaScript
中惟一一個處理屬性可是不查找原型鏈的函數。數組
// 修改Object.prototype Object.prototype.bar = 1; var foo = {goo: undefined}; foo.bar; // 1 'bar' in foo; // true foo.hasOwnProperty('bar'); // false foo.hasOwnProperty('goo'); // true
注意: 經過判斷一個屬性是否undefined
是不夠的。 由於一個屬性可能確實存在,只不過它的值被設置爲undefined
。
JavaScript 不會保護 hasOwnProperty
被非法佔用,所以若是一個對象碰巧存在這個屬性, 就須要使用外部的 hasOwnProperty
函數來獲取正確的結果。瀏覽器
var foo = { hasOwnProperty: function() { return false; }, bar: 'Here be dragons' }; foo.hasOwnProperty('bar'); // 老是返回 false // 使用其它對象的 hasOwnProperty,並將其上下文設置爲foo ({}).hasOwnProperty.call(foo, 'bar'); // true
當檢查對象上某個屬性是否存在時,hasOwnProperty
是惟一可用的方法。 同時在使用 for in loop
遍歷對象時,推薦老是使用 hasOwnProperty
方法, 這將會避免原型對象擴展帶來的干擾。安全
和 in
操做符同樣,for in
循環一樣在查找對象屬性時遍歷原型鏈上的全部屬性。閉包
// 修改 Object.prototype Object.prototype.bar = 1; var foo = {moo: 2}; for(var i in foo) { console.log(i); // 輸出兩個屬性:bar 和 moo }
注意: 因爲 for in 老是要遍歷整個原型鏈,所以若是一個對象的繼承層次太深的話會影響性能。
因爲不可能改變 for in
自身的行爲,所以有必要過濾出那些不但願出如今循環體中的屬性, 這能夠經過 Object.prototype
原型上的 hasOwnProperty
函數來完成。函數
// foo 變量是上例中的 for(var i in foo) { if (foo.hasOwnProperty(i)) { console.log(i); } }
推薦老是使用
hasOwnProperty
。不要對代碼運行的環境作任何假設,不要假設原生對象是否已經被擴展了。
另一個特殊的狀況是將命名函數賦值給一個變量。oop
var foo = function bar() { bar(); // 正常運行 } bar(); // 出錯:ReferenceError
bar
函數聲明外是不可見的,這是由於咱們已經把函數賦值給了 foo
; 然而在 bar
內部依然可見。這是因爲 JavaScript
的命名處理所致, 函數名在函數內老是可見的。性能
注意:在IE8及IE8如下
版本瀏覽器bar在外部也是可見的,是由於瀏覽器對命名函數賦值表達式進行了錯誤的解析, 解析成兩個函數foo 和 bar
另外一個看起來奇怪的地方是函數別名,也就是將一個方法賦值給一個變量。測試
var test = someObject.methodTest; test();
上例中,test
就像一個普通的函數被調用;所以,函數內的 this
將再也不被指向到 someObject
對象。而是指向了window
。this
一個常見的錯誤出如今循環中使用閉包,假設咱們須要在每次循環中調用循環序號prototype
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); }
上面的代碼不會輸出數字 0
到 9
,而是會輸出數字10
十次。
當 console.log
被調用的時候,匿名函數保持對外部變量i的引用,此時 for循環已經結束,i的值被修改爲了10
.
爲了獲得想要的結果,須要在每次循環中建立變量 i
的拷貝。
爲了不引用錯誤,爲了正確的得到循環序號,最好使用 匿名包裝器(注:其實就是咱們一般說的自執行匿名函數)。
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); }, 1000); })(i); }
外部的匿名函數會當即執行,並把 i 做爲它的參數,此時函數內 e 變量就擁有了 i 的一個拷貝。
當傳遞給 setTimeout 的匿名函數執行時,它就擁有了對 e 的引用,而這個值是不會被循環改變的。
有另外一個方法完成一樣的工做,那就是從匿名包裝器中返回一個函數。這和上面的代碼效果同樣。
for(var i = 0; i < 10; i++) { setTimeout((function(e) { return function() { console.log(e); } })(i), 1000) }
JavaScript 中全部變量均可以看成對象使用,除了兩個例外 null
和 undefined
。
false.toString(); // 'false' [1, 2, 3].toString(); // '1,2,3' function Foo(){} Foo.bar = 1; Foo.bar; // 1
一個常見的誤解是數字的字面值
(literal)不能看成對象使用
。這是由於 JavaScript 解析器的一個錯誤, 它試圖將點操做符
解析爲浮點數字面值
的一部分。
2.toString(); // 出錯:SyntaxError
有不少變通方法可讓數字的字面值看起來像對象。
2..toString(); // 第二個點號能夠正常解析 2 .toString(); // 注意點號前面的空格 (2).toString(); // 2先被計算
刪除屬性的惟一方法是使用 delete
操做符;設置屬性爲 undefined
或者 null
並不能真正的刪除屬性, 而僅僅是移除了屬性和值的關聯。
var obj = { bar: 1, foo: 2, baz: 3 }; obj.bar = undefined; obj.foo = null; delete obj.baz; for(var i in obj) { if (obj.hasOwnProperty(i)) { console.log(i, '' + obj[i]); } }
上面的輸出結果有 bar undefined
和 foo null
- 只有 baz
被真正的刪除了,因此從輸出結果中消失。
JavaScript 中每一個函數內都能訪問一個特別變量 arguments
。這個變量維護着全部傳遞到這個函數中的參數列表。
arguments
變量不是一個數組
(Array)。 儘管在語法上它有數組相關的屬性 length
,但它不從 Array.prototype
繼承,實際上它是一個對象
(Object)。
所以,沒法對 arguments
變量使用標準的數組方法,好比 push
, pop
或者 slic
e。 雖然使用 for
循環遍歷也是能夠的,可是爲了更好的使用數組方法,最好把它轉化爲一個真正的數組。
下面的代碼將會建立一個新的數組,包含全部 arguments
對象中的元素。
Array.prototype.slice.call(arguments);
arguments
對象爲其內部屬性以及函數形式參數建立 getter
和 setter
方法。
所以,改變形參的值會影響到 arguments 對象的值,反之亦然。
function foo(a, b, c) { arguments[0] = 2; a; // 2 b = 4; arguments[1]; // 4 var d = c; d = 9; c; // 3 } foo(1, 2, 3);
以下一個例子:
function sidEffecting(ary) { ary[0] = ary[2]; } function bar(a,b,c) { c = 10 sidEffecting(arguments); return a + b + c; } bar(1,1,1)
這裏全部的更改都將生效,a和c的值都爲10,a+b+c的值將爲21。
typeof foo !== 'undefined'
上面代碼會檢測 foo
是否已經定義;若是沒有定義而直接使用會致使 ReferenceError
的異常。 這是 typeof
惟一有用的地方。固然也能判斷出來基本類型。
爲了檢測一個對象的類型,強烈推薦使用 Object.prototype.toString
方法
以下例子:
Object.prototype.toString.call([]) // "[object Array]" Object.prototype.toString.call({}) // "[object Object]" Object.prototype.toString.call(2) // "[object Number]"
內置類型(好比 Number
和 String
)的構造函數在被調用時,使用或者不使用 new
的結果徹底不一樣。
new Number(10) === 10; // False, 對象與數字的比較 Number(10) === 10; // True, 數字與數字的比較 new Number(10) + 0 === 10; // True, 因爲隱式的類型轉換
轉換爲字符串
'' + 10 === '10'; // true
將一個值加上空字符串能夠輕鬆轉換爲字符串類型。
轉換爲數字
+'10' === 10; // true
使用一元的加號操做符,能夠把字符串轉換爲數字。
轉換爲布爾型
經過使用 否
操做符兩次,能夠把一個值轉換爲布爾型。
!!'foo'; // true !!''; // false !!'0'; // true !!'1'; // true !!'-1' // true !!{}; // true !!true; // true
eval
函數會在當前做用域中執行一段 JavaScript 代碼字符串。
var foo = 1; function test() { var foo = 2; eval('foo = 3'); return foo; } test(); // 3 foo; // 1
可是 eval
只在被直接調用而且調用函數就是 eval
自己時,纔在當前做用域中執行。
var foo = 1; function test() { var foo = 2; var bar = eval; bar('foo = 3'); return foo; } test(); // 2 foo; // 3
上面的代碼等價於在全局做用域中調用 eval
,和下面兩種寫法效果同樣:
// 寫法一:直接調用全局做用域下的 foo 變量 var foo = 1; function test() { var foo = 2; window.foo = 3; return foo; } test(); // 2 foo; // 3 // 寫法二:使用 call 函數修改 eval 執行的上下文爲全局做用域 var foo = 1; function test() { var foo = 2; eval.call(window, 'foo = 3'); return foo; } test(); // 2 foo; // 3
在任何狀況下咱們都應該避免使用 eval
函數。99.9% 使用 eval
的場景都有不使用 eval
的解決方案。
eval
也存在安全問題
,由於它會執行任意傳給它的代碼, 在代碼字符串未知或者是來自一個不信任的源時,絕對不要使用 eval
函數。
var id = setTimeout(foo, 1000); clearTimeout(id);
因爲沒有內置的清除全部定時器的方法,能夠採用一種暴力的方式來達到這一目的。
// 清空"全部"的定時器 for(var i = 1; i < 1000; i++) { clearTimeout(i); }
可能還有些定時器不會在上面代碼中被清除(注:若是定時器調用時返回的 ID 值大於 1000), 所以咱們能夠事先保存全部的定時器 ID,而後一把清除。
建議不要在調用定時器函數時,爲了向回調函數傳遞參數而使用字符串的形式。
function foo(a, b, c) {} // 不要這樣作 setTimeout('foo(1,2, 3)', 1000) // 可使用匿名函數完成相同功能 setTimeout(function() { foo(1, 2, 3); }, 1000)
絕對不要使用字符串做爲setTimeout
或者setInterval
的第一個參數, 這麼寫的代碼明顯質量不好。當須要向回調函數傳遞參數時,能夠建立一個匿名函數,在函數內執行真實的回調函數。另外,應該避免使用 setInterval,由於它的定時執行不會被 JavaScript 阻塞。
後續逐漸添加