JavaScript Garden筆記

PS : 這是一篇針對javascript garden做的筆記,更多內(nèi)容請查看原文鏈接 : http://bonsaiden.github.io/JavaScript-Garden/zh/

對象

  1. JavaScript 中所有變量都可以當作對象使用,除了null 和 undefined;
  2. 2.toString(); // 出錯:SyntaxError,因為解析器試圖將2.作為浮點數(shù)的一部分來解析,可以使用(2).toString()或者2..toString()或2 .toString();
  3. 使用{}可以創(chuàng)建一個新的對象 ,新的對象繼承自Object.prototype;
  4. 訪問對象的屬性,可以通過 “.”或者“[]”操作符,刪除屬性則通過delete操作符;

原型

  1. Javascript使用的是基于prototype原型模型,基于原型鏈的繼承方式;
  2. 屬性查找:會向上遍歷原型鏈,直到找到為止,找不到則返回undefined;
  3. 性能:提防原型鏈過長帶來的性能問題,盡量縮短原型鏈、不要擴展內(nèi)置類型的原型鏈;

hasOwnProperty

用途:為了判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性

for in

作用:和 in操作符一樣,for in循環(huán)同樣在查找對象屬性時遍歷原型鏈上的所有屬性。(盡量使用hasOwnProperty來避免原型鏈上的屬性)

函數(shù)

函數(shù)聲明

foo(); // 正常運行,因為foo在代碼運行前已經(jīng)被創(chuàng)建
function foo() {}

函數(shù)賦值與表達式

foo; // 在代碼運行之前,缺省值為'undefined'
foo(); // 出錯:TypeError,因為foo指向的是一個函數(shù)
var foo = function() {};
var foo = function bar() {
    bar(); // 正常運行
}
bar(); // 出錯:ReferenceError

this的工作原理

  • 當使用 Function.prototype 上的 call 或者 apply方法時,函數(shù)內(nèi)的 this將會被 顯式設置為函數(shù)調(diào)用的第一個參數(shù)。
  • 常見誤解
Foo.method = function() {
    function test() {
        // this 將會被設置為全局對象(譯者注:瀏覽器環(huán)境中也就是 window 對象)
    }
    test();
}
  • 局部變量that
Foo.method = function() {
    var that = this;
    function test() {
        // 使用 that 來指向 Foo 對象
    }
    test();
}
  • this 的晚綁定特性,當 method 被調(diào)用時,this 將會指向 Bar 的實例對象。
function Foo() {}
Foo.prototype.method = function() {};

function Bar() {}
Bar.prototype = Foo.prototype;

new Bar().method(); 

閉包和引用

閉包是 JavaScript 一個非常重要的特性,這意味著當前作用域總是能夠訪問外部作用域中的變量。 因為 函數(shù) 是 JavaScript 中唯一擁有自身作用域的結構,因此閉包的創(chuàng)建依賴于函數(shù)。

  • 模擬私有變量
function Counter(start) {
  var count = start;
  return {
    increment: function() {
        count++;
    },

    get: function() {
      return count;
    }
  }
}

調(diào)用的方法如下:

var foo = Counter(4);
foo.increment();
foo.get(); // 5
var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
} 

上面的代碼不會改變定義在 Counter 作用域中的 count 變量的值,因為 foo.hack 沒有 定義在那個作用域內(nèi)。它將會創(chuàng)建或者覆蓋全局變量 count。

  • 循環(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的。

  • 避免引用錯誤
    使用匿名包裝器
    外部的匿名函數(shù)會立即執(zhí)行,并把 i 作為它的參數(shù),此時函數(shù)內(nèi) e 變量就擁有了 i 的一個拷貝。
for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}
for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

arguments對象

JavaScript 中每個函數(shù)內(nèi)都能訪問一個特別變量 arguments。這個變量維護著所有傳遞到這個函數(shù)中的參數(shù)列表。

  • 轉換為數(shù)組
Array.prototype.slice.call(arguments);
  • 傳遞參數(shù)
 function foo() {
    bar.apply(null, arguments);
}
function bar(a, b, c) {
    // 干活
}

另一個技巧是同時使用 call 和 apply,創(chuàng)建一個快速的解綁定包裝器。

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// 創(chuàng)建一個解綁定的 "method"
// 輸入?yún)?shù)為: this, arg1, arg2...argN
Foo.method = function() {

    // 結果: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};

等同于下面的代碼:

Foo.method = function() {
    var args = Array.prototype.slice.call(arguments);
    Foo.prototype.method.apply(args[0], args.slice(1));
};
  • 自動更新
    arguments 對象為其內(nèi)部屬性以及函數(shù)形式參數(shù)創(chuàng)建 getter 和 setter 方法。
    因此,改變形參的值會影響到 arguments 對象的值,反之亦然。
  • 性能
    arguments 的 getters 和 setters 方法總會被創(chuàng)建;因此使用 arguments 對性能不會有什么影響。 除非是需要對 arguments 對象的屬性進行多次訪問。
    ES5 提示: 這些 getters 和 setters 在嚴格模式下(strict mode)不會被創(chuàng)建。
    使用 arguments.callee會顯著的影響現(xiàn)代 JavaScript 引擎的性能。
function foo() {
    arguments.callee; // do something with this function object
    arguments.callee.caller; // and the calling function object
}

function bigLoop() {
    for(var i = 0; i < 100000; i++) {
        foo(); // Would normally be inlined...
    }
}

上面代碼中,foo 不再是一個單純的內(nèi)聯(lián)函數(shù) inlining(譯者注:這里指的是解析器可以做內(nèi)聯(lián)處理), 因為它需要知道它自己和它的調(diào)用者。 這不僅抵消了內(nèi)聯(lián)函數(shù)帶來的性能提升,而且破壞了封裝,因此現(xiàn)在函數(shù)可能要依賴于特定的上下文。

因此強烈建議大家不要使用 arguments.callee 和它的屬性。

構造函數(shù)

在構造函數(shù)內(nèi)部 - 也就是被調(diào)用的函數(shù)內(nèi) - this 指向新創(chuàng)建的對象 Object。 這個新創(chuàng)建的對象的 prototype 被指向到構造函數(shù)的 prototype。
如果被調(diào)用的函數(shù)沒有顯式的 return 表達式,則隱式的會返回 this 對象 - 也就是新創(chuàng)建的對象。
顯式的 return 表達式將會影響返回結果,但僅限于返回的是一個對象。

為了創(chuàng)建新對象,我們可以創(chuàng)建一個工廠方法,并且在方法內(nèi)構造一個新對象。不好的地方:

  1. 會占用更多的內(nèi)存,因為新創(chuàng)建的對象不能共享原型上的方法。
  2. 為了實現(xiàn)繼承,工廠方法需要從另外一個對象拷貝所有屬性,或者把一個對象作為新創(chuàng)建對象的原型。
  3. 放棄原型鏈僅僅是因為防止遺漏 new 帶來的問題,這似乎和語言本身的思想相違背。

作用域

盡管 JavaScript 支持一對花括號創(chuàng)建的代碼段,但是并不支持塊級作用域; 而僅僅支持 函數(shù)作用域。
每次引用一個變量,JavaScript 會向上遍歷整個作用域直到找到這個變量為止。 如果到達全局作用域但是這個變量仍未找到,則會拋出 ReferenceError 異常。
變量聲明提升(Hoisting): JavaScript 會提升變量聲明。這意味著 var 表達式和 function 聲明都將會被提升到當前作用域的頂部。

名稱解析順序

JavaScript 中的所有作用域,包括全局作用域,都有一個特別的名稱 this 指向當前對象。
函數(shù)作用域內(nèi)也有默認的變量 arguments,其中包含了傳遞到函數(shù)中的參數(shù)。
比如,當訪問函數(shù)內(nèi)的 foo 變量時,JavaScript 會按照下面順序查找:

  1. 當前作用域內(nèi)是否有 var foo 的定義。
  2. 函數(shù)形式參數(shù)是否有使用 foo 名稱的。
  3. 函數(shù)自身是否叫做 foo。
  4. 回溯到上一級作用域,然后從 #1 重新開始。

命名空間

只有一個全局作用域?qū)е碌某R婂e誤是命名沖突。在 JavaScript中,這可以通過 匿名包裝器 輕松解決。

(function() {
    // 函數(shù)創(chuàng)建一個命名空間

    window.foo = function() {
        // 對外公開的函數(shù),創(chuàng)建了閉包
    };

})(); // 立即執(zhí)行此匿名函數(shù)

匿名函數(shù)被認為是 表達式;因此為了可調(diào)用性,它們首先會被執(zhí)行。

( // 小括號內(nèi)的函數(shù)首先被執(zhí)行
function() {}
) // 并且返回函數(shù)對象
() // 調(diào)用上面的執(zhí)行結果,也就是函數(shù)對象

數(shù)組

數(shù)組遍歷與屬性

由于 for in 循環(huán)會枚舉原型鏈上的所有屬性,唯一過濾這些屬性的方式是使用 hasOwnProperty 函數(shù), 因此會比普通的 for 循環(huán)慢上好多倍。
length 屬性的 getter 方式會簡單的返回數(shù)組的長度,而 setter 方式會截斷數(shù)組。
為了更好的性能,推薦使用普通的 for 循環(huán)并緩存數(shù)組的 length 屬性。 使用 for in 遍歷數(shù)組被認為是不好的代碼習慣并傾向于產(chǎn)生錯誤和導致性能問題。

Array 構造函數(shù)

由于 Array 的構造函數(shù)在如何處理參數(shù)時有點模棱兩可,因此總是推薦使用數(shù)組的字面語法 - [] - 來創(chuàng)建數(shù)組。
new Array(3); 這種調(diào)用方式,并且這個參數(shù)是數(shù)字,構造函數(shù)會返回一個 length 屬性被設置為此參數(shù)的空數(shù)組。 需要特別注意的是,此時只有 length 屬性被設置,真正的數(shù)組并沒有生成。

類型

相等與比較

""           ==   "0"           // false
0            ==   ""            // true
0            ==   "0"           // true
false        ==   "false"       // false
false        ==   "0"           // true
false        ==   undefined     // false
false        ==   null          // false
null         ==   undefined     // true
" \t\r\n"    ==   0             // true

上面的表格展示了強制類型轉換,這也是使用 == 被廣泛認為是不好編程習慣的主要原因, 由于它的復雜轉換規(guī)則,會導致難以跟蹤的問題。

此外,強制類型轉換也會帶來性能消耗,比如一個字符串為了和一個數(shù)字進行比較,必須事先被強制轉換為數(shù)字。

嚴格等于操作符

如果兩個操作數(shù)類型不同就肯定不相等也有助于性能的提升。

{} === {};                   // false

強烈推薦使用嚴格等于操作符。如果類型需要轉換,應該在比較之前顯式的轉換, 而不是使用語言本身復雜的強制轉換規(guī)則。

typeof 操作符

盡管 instanceof 還有一些極少數(shù)的應用場景,typeof 只有一個實際的應用(譯者注:這個實際應用是用來檢測一個對象是否已經(jīng)定義或者是否已經(jīng)賦值), 而這個應用卻不是用來檢查對象的類型。

Object.prototype.toString.call([])    // "[object Array]"
Object.prototype.toString.call({})    // "[object Object]"
Object.prototype.toString.call(2)    // "[object Number]"
// IE8
Object.prototype.toString.call(null)    // "[object Object]"
Object.prototype.toString.call(undefined)    // "[object Object]"

// Firefox 4
Object.prototype.toString.call(null)    // "[object Null]"
Object.prototype.toString.call(undefined)    // "[object Undefined]"
typeof foo !== 'undefined'

為了檢測一個對象的類型,強烈推薦使用 Object.prototype.toString 方法; 因為這是唯一一個可依賴的方式。正如上面表格所示,typeof 的一些返回值在標準文檔中并未定義, 因此不同的引擎實現(xiàn)可能不同。

除非為了檢測一個變量是否已經(jīng)定義,我們應盡量避免使用 typeof 操作符。

instanceof 操作符

instanceof 操作符用來比較兩個操作數(shù)的構造函數(shù)。只有在比較自定義的對象時才有意義。 如果用來比較內(nèi)置類型,將會和 typeof 操作符 一樣用處不大。

new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true

'foo' instanceof String; // false
'foo' instanceof Object; // false

instanceof 操作符應該僅僅用來比較來自同一個 JavaScript 上下文的自定義對象。 正如 typeof 操作符一樣,任何其它的用法都應該是避免的。

類型轉換

使用內(nèi)置類型 Number 作為構造函數(shù)將會創(chuàng)建一個新的 Number 對象, 而在不使用 new 關鍵字的 Number 函數(shù)更像是一個數(shù)字轉換器。

將一個值加上空字符串可以輕松轉換為字符串類型。

使用一元的加號操作符,可以把字符串轉換為數(shù)字。

字符串轉換為數(shù)字的常用用法:

+'010' === 10
Number('010') === 10
parseInt('010', 10) === 10  // 用來轉換為整數(shù)

+'010.2' === 10.2
Number('010.2') === 10.2
parseInt('010.2', 10) === 10

轉換為布爾型:
通過使用 否 操作符兩次,可以把一個值轉換為布爾型。

!!'foo';   // true
!!'';      // false
!!'0';     // true
!!'1';     // true
!!'-1'     // true
!!{};      // true
!!true;    // true

核心

為什么不要使用

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

在任何情況下我們都應該避免使用 eval 函數(shù)。99.9% 使用 eval 的場景都有不使用 eval 的解決方案。

undefined 和 null

undefined 是一個值為 undefined 的類型。

這個語言也定義了一個全局變量,它的值是 undefined,這個變量也被稱為 undefined。 但是這個變量不是一個常量,也不是一個關鍵字。這意味著它的值可以輕易被覆蓋。

  • 處理 undefined 值的改變
    由于全局變量 undefined 只是保存了 undefined 類型實際值的副本, 因此對它賦新值不會改變類型 undefined 的值。

為了避免可能對 undefined 值的改變,一個常用的技巧是使用一個傳遞到匿名包裝器的額外參數(shù)。 在調(diào)用時,這個參數(shù)不會獲取任何值。

另外一種達到相同目的方法是在函數(shù)內(nèi)使用變量聲明。

null的用處

它在 JavaScript 內(nèi)部有一些使用場景(比如聲明原型鏈的終結 Foo.prototype = null),但是大多數(shù)情況下都可以使用 undefined 來代替。

自動分號插入

JavaScript 不是一個沒有分號的語言,恰恰相反上它需要分號來就解析源代碼。 因此 JavaScript 解析器在遇到由于缺少分號導致的解析錯誤時,會自動在源代碼中插入分號。

建議絕對不要省略分號,同時也提倡將花括號和相應的表達式放在一行, 對于只有一行代碼的 if 或者 else 表達式,也不應該省略花括號。 這些良好的編程習慣不僅可以提到代碼的一致性,而且可以防止解析器改變代碼行為的錯誤處理。

其它

setTimeout 和 setInterval

作為第一個參數(shù)的函數(shù)將會在全局作用域中執(zhí)行,因此函數(shù)內(nèi)的 this 將會指向這個全局對象。

function Foo() {
    this.value = 42;
    this.method = function() {
        // this 指向全局對象
        console.log(this.value); // 輸出:undefined
    };
    setTimeout(this.method, 500);
}
new Foo();
  • setInterval 的堆調(diào)用

當回調(diào)函數(shù)的執(zhí)行被阻塞時,setInterval 仍然會發(fā)布更多的回調(diào)指令。在很小的定時間隔情況下,這會導致回調(diào)函數(shù)被堆積起來。

  • 隱藏使用 eval
    setTimeout 和 setInterval 也接受第一個參數(shù)為字符串的情況。 這個特性絕對不要使用,因為它在內(nèi)部使用了 eval。
function foo() {
    // 將會被調(diào)用
}

function bar() {
    function foo() {
        // 不會被調(diào)用
    }
    setTimeout('foo()', 1000);
}
bar();

由于 eval 在這種情況下不是被直接調(diào)用,因此傳遞到 setTimeout 的字符串會自全局作用域中執(zhí)行; 因此,上面的回調(diào)函數(shù)使用的不是定義在 bar 作用域中的局部變量 foo。

絕對不要使用字符串作為 setTimeout 或者 setInterval 的第一個參數(shù), 這么寫的代碼明顯質(zhì)量很差。當需要向回調(diào)函數(shù)傳遞參數(shù)時,可以創(chuàng)建一個匿名函數(shù),在函數(shù)內(nèi)執(zhí)行真實的回調(diào)函數(shù)。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 第5章 引用類型 引用類型的值(對象)是引用類型的一個示例。在ECMAScript 中,引用類型是一種數(shù)據(jù)結構,用...
    力氣強閱讀 815評論 0 0
  • 在世界盡頭,有一只睡覺的狗。 其實美麗不是因為她手里的煙,而是因為她的沉默吧。 她的沉默像是外婆家里每個清晨看起來...
    孔七二閱讀 178評論 0 0
  • 大家一提到學習,總是和專心致志,一心一意分不開。學習嘛,就是要“心無旁騖”,才能達到“求知問學”的目的。王羲之練字...
    蕉蔬醬閱讀 621評論 0 3
  • 明明懶 倦倦聽風 一夜雨 雨打藩籬 山水不執(zhí)念朝夕 去留無意 桂中廣寒 月下潮汐 相互寵溺 花謝花開 春去秋來 皆...
    知了___閱讀 361評論 3 6
  • 原文作者 |ChristopherPhin 翻譯 | 牙牙 去年的十一月,我辭掉了CreativeBloq 的姊妹...
    風信子Freecens閱讀 809評論 0 2

友情鏈接更多精彩內(nèi)容