摘要:命名函數(shù)的賦值表達式另外一個特殊的情況是將命名函數(shù)賦值給一個變量。這是由于的命名處理所致,函數(shù)名在函數(shù)內(nèi)總是可見的。當需要向回調(diào)函數(shù)傳遞參數(shù)時,可以創(chuàng)建一個匿名函數(shù),在函數(shù)內(nèi)執(zhí)行真實的回調(diào)函數(shù)。
1.hasOwnProperty相關
為了判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性,我們需要使用繼承自 Object.prototype 的 hasOwnProperty方法。
hasOwnProperty 是 JavaScript 中唯一一個處理屬性但是不查找原型鏈的函數(shù)。
// 修改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。hasOwnProperty 作為屬性
JavaScript 不會保護 hasOwnProperty 被非法占用,因此如果一個對象碰巧存在這個屬性, 就需要使用外部的 hasOwnProperty 函數(shù)來獲取正確的結果。
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 方法, 這將會避免原型對象擴展帶來的干擾。
for in 循環(huán)和 in 操作符一樣,for in 循環(huán)同樣在查找對象屬性時遍歷原型鏈上的所有屬性。
// 修改 Object.prototype Object.prototype.bar = 1; var foo = {moo: 2}; for(var i in foo) { console.log(i); // 輸出兩個屬性:bar 和 moo }
注意: 由于 for in 總是要遍歷整個原型鏈,因此如果一個對象的繼承層次太深的話會影響性能。
由于不可能改變 for in 自身的行為,因此有必要過濾出那些不希望出現(xiàn)在循環(huán)體中的屬性, 這可以通過 Object.prototype 原型上的 hasOwnProperty 函數(shù)來完成。
使用 hasOwnProperty 過濾// foo 變量是上例中的 for(var i in foo) { if (foo.hasOwnProperty(i)) { console.log(i); } }
推薦總是使用 hasOwnProperty。不要對代碼運行的環(huán)境做任何假設,不要假設原生對象是否已經(jīng)被擴展了。2.命名函數(shù)的賦值表達式
另外一個特殊的情況是將命名函數(shù)賦值給一個變量。
var foo = function bar() { bar(); // 正常運行 } bar(); // 出錯:ReferenceError
bar 函數(shù)聲明外是不可見的,這是因為我們已經(jīng)把函數(shù)賦值給了 foo; 然而在 bar 內(nèi)部依然可見。這是由于 JavaScript 的命名處理所致, 函數(shù)名在函數(shù)內(nèi)總是可見的。
注意:在IE8及IE8以下版本瀏覽器bar在外部也是可見的,是因為瀏覽器對命名函數(shù)賦值表達式進行了錯誤的解析, 解析成兩個函數(shù) foo 和 bar3.方法的賦值表達式
另一個看起來奇怪的地方是函數(shù)別名,也就是將一個方法賦值給一個變量。
var test = someObject.methodTest; test();
上例中,test 就像一個普通的函數(shù)被調(diào)用;因此,函數(shù)內(nèi)的 this 將不再被指向到 someObject 對象。而是指向了window。
4.循環(huán)中的閉包一個常見的錯誤出現(xiàn)在循環(huán)中使用閉包,假設我們需要在每次循環(huán)中調(diào)用循環(huán)序號
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); }
上面的代碼不會輸出數(shù)字 0到 9,而是會輸出數(shù)字10 十次。
當 console.log 被調(diào)用的時候,匿名函數(shù)保持對外部變量i的引用,此時 for循環(huán)已經(jīng)結束,i的值被修改成了10.
為了得到想要的結果,需要在每次循環(huán)中創(chuàng)建變量 i的拷貝。
為了避免引用錯誤,為了正確的獲得循環(huán)序號,最好使用 匿名包裝器(注:其實就是我們通常說的自執(zhí)行匿名函數(shù))。
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); }, 1000); })(i); }
外部的匿名函數(shù)會立即執(zhí)行,并把 i 作為它的參數(shù),此時函數(shù)內(nèi) e 變量就擁有了 i 的一個拷貝。
當傳遞給 setTimeout 的匿名函數(shù)執(zhí)行時,它就擁有了對 e 的引用,而這個值是不會被循環(huán)改變的。
有另一個方法完成同樣的工作,那就是從匿名包裝器中返回一個函數(shù)。這和上面的代碼效果一樣。
for(var i = 0; i < 10; i++) { setTimeout((function(e) { return function() { console.log(e); } })(i), 1000) }5.對象使用和屬性
JavaScript 中所有變量都可以當作對象使用,除了兩個例外 null 和 undefined。
false.toString(); // "false" [1, 2, 3].toString(); // "1,2,3" function Foo(){} Foo.bar = 1; Foo.bar; // 1
一個常見的誤解是數(shù)字的字面值(literal)不能當作對象使用。這是因為 JavaScript 解析器的一個錯誤, 它試圖將點操作符解析為浮點數(shù)字面值的一部分。
2.toString(); // 出錯:SyntaxError
有很多變通方法可以讓數(shù)字的字面值看起來像對象。
2..toString(); // 第二個點號可以正常解析 2 .toString(); // 注意點號前面的空格 (2).toString(); // 2先被計算
刪除屬性的唯一方法是使用 delete 操作符;設置屬性為 undefined 或者 null 并不能真正的刪除屬性, 而僅僅是移除了屬性和值的關聯(lián)。
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 被真正的刪除了,所以從輸出結果中消失。
6.arguments 對象JavaScript 中每個函數(shù)內(nèi)都能訪問一個特別變量 arguments。這個變量維護著所有傳遞到這個函數(shù)中的參數(shù)列表。
arguments 變量不是一個數(shù)組(Array)。 盡管在語法上它有數(shù)組相關的屬性 length,但它不從 Array.prototype 繼承,實際上它是一個對象(Object)。
因此,無法對 arguments 變量使用標準的數(shù)組方法,比如 push, pop 或者 slice。 雖然使用 for 循環(huán)遍歷也是可以的,但是為了更好的使用數(shù)組方法,最好把它轉化為一個真正的數(shù)組。
轉化為數(shù)組下面的代碼將會創(chuàng)建一個新的數(shù)組,包含所有 arguments 對象中的元素。
Array.prototype.slice.call(arguments);
arguments 對象為其內(nèi)部屬性以及函數(shù)形式參數(shù)創(chuàng)建 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。
7.類型相關 測試為定義變量typeof foo !== "undefined"
上面代碼會檢測 foo 是否已經(jīng)定義;如果沒有定義而直接使用會導致 ReferenceError 的異常。 這是 typeof 唯一有用的地方。當然也能判斷出來基本類型。
Object.prototype.toString檢測一個對象的類型為了檢測一個對象的類型,強烈推薦使用 Object.prototype.toString 方法
如下例子:
Object.prototype.toString.call([]) // "[object Array]" Object.prototype.toString.call({}) // "[object Object]" Object.prototype.toString.call(2) // "[object Number]"類型轉換
內(nèi)置類型(比如 Number 和 String)的構造函數(shù)在被調(diào)用時,使用或者不使用 new 的結果完全不同。
new Number(10) === 10; // False, 對象與數(shù)字的比較 Number(10) === 10; // True, 數(shù)字與數(shù)字的比較 new Number(10) + 0 === 10; // True, 由于隱式的類型轉換
轉換為字符串
"" + 10 === "10"; // true
將一個值加上空字符串可以輕松轉換為字符串類型。
轉換為數(shù)字
+"10" === 10; // true
使用一元的加號操作符,可以把字符串轉換為數(shù)字。
轉換為布爾型
通過使用 否 操作符兩次,可以把一個值轉換為布爾型。
!!"foo"; // true !!""; // false !!"0"; // true !!"1"; // true !!"-1" // true !!{}; // true !!true; // true8.為什么不要使用 eval
eval 函數(shù)會在當前作用域中執(zhí)行一段 JavaScript 代碼字符串。
var foo = 1; function test() { var foo = 2; eval("foo = 3"); return foo; } test(); // 3 foo; // 1
但是 eval 只在被直接調(diào)用并且調(diào)用函數(shù)就是 eval 本身時,才在當前作用域中執(zhí)行。
var foo = 1; function test() { var foo = 2; var bar = eval; bar("foo = 3"); return foo; } test(); // 2 foo; // 3
上面的代碼等價于在全局作用域中調(diào)用 eval,和下面兩種寫法效果一樣:
// 寫法一:直接調(diào)用全局作用域下的 foo 變量 var foo = 1; function test() { var foo = 2; window.foo = 3; return foo; } test(); // 2 foo; // 3 // 寫法二:使用 call 函數(shù)修改 eval 執(zhí)行的上下文為全局作用域 var foo = 1; function test() { var foo = 2; eval.call(window, "foo = 3"); return foo; } test(); // 2 foo; // 3
在任何情況下我們都應該避免使用 eval 函數(shù)。99.9% 使用 eval 的場景都有不使用 eval 的解決方案。
eval 也存在安全問題,因為它會執(zhí)行任意傳給它的代碼, 在代碼字符串未知或者是來自一個不信任的源時,絕對不要使用 eval 函數(shù)。
9.定時器 手工清空定時器var id = setTimeout(foo, 1000); clearTimeout(id);清除所有定時器
由于沒有內(nèi)置的清除所有定時器的方法,可以采用一種暴力的方式來達到這一目的。
// 清空"所有"的定時器 for(var i = 1; i < 1000; i++) { clearTimeout(i); }
可能還有些定時器不會在上面代碼中被清除(注:如果定時器調(diào)用時返回的 ID 值大于 1000), 因此我們可以事先保存所有的定時器 ID,然后一把清除。
建議不要在調(diào)用定時器函數(shù)時,為了向回調(diào)函數(shù)傳遞參數(shù)而使用字符串的形式。
function foo(a, b, c) {} // 不要這樣做 setTimeout("foo(1,2, 3)", 1000) // 可以使用匿名函數(shù)完成相同功能 setTimeout(function() { foo(1, 2, 3); }, 1000)
絕對不要使用字符串作為 setTimeout 或者 setInterval 的第一個參數(shù), 這么寫的代碼明顯質(zhì)量很差。當需要向回調(diào)函數(shù)傳遞參數(shù)時,可以創(chuàng)建一個匿名函數(shù),在函數(shù)內(nèi)執(zhí)行真實的回調(diào)函數(shù)。另外,應該避免使用 setInterval,因為它的定時執(zhí)行不會被 JavaScript 阻塞。
后續(xù)逐漸添加
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92157.html
摘要:筆記說明重學前端是程劭非前手機淘寶前端負責人在極客時間開的一個專欄,每天分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入的專欄學習原文有的語音,如有侵權請聯(lián)系我,郵箱。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以...
摘要:筆記說明重學前端是程劭非前手機淘寶前端負責人在極客時間開的一個專欄,每天分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入的專欄學習原文有的語音,如有侵權請聯(lián)系我,郵箱。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以...
摘要:筆記說明重學前端是程劭非前手機淘寶前端負責人在極客時間開的一個專欄,每天分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入的專欄學習原文有的語音,如有侵權請聯(lián)系我,郵箱。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以...
閱讀 818·2023-04-25 22:13
閱讀 2335·2019-08-30 15:56
閱讀 2217·2019-08-30 11:21
閱讀 650·2019-08-30 11:13
閱讀 2014·2019-08-26 14:06
閱讀 1950·2019-08-26 12:11
閱讀 2282·2019-08-23 16:55
閱讀 530·2019-08-23 15:30