(轉)容易遺忘的JS知識點整理

1.hasOwnProperty相關

爲了判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性,咱們須要使用繼承自 Object.prototype 的 hasOwnProperty方法。
hasOwnProperty 是 JavaScript 中惟一一個處理屬性可是不查找原型鏈的函數。javascript

  1.  
    // 修改Object.prototype
  2.  
    Object.prototype.bar = 1;
  3.  
    var foo = {goo: undefined};
  4.  
     
  5.  
    foo.bar; // 1
  6.  
    'bar' in foo; // true
  7.  
     
  8.  
    foo.hasOwnProperty('bar'); // false
  9.  
    foo.hasOwnProperty('goo'); // true
注意: 經過判斷一個屬性是否  undefined 是不夠的。 由於一個屬性可能確實存在,只不過它的值被設置爲  undefined

hasOwnProperty 做爲屬性

JavaScript 不會保護 hasOwnProperty 被非法佔用,所以若是一個對象碰巧存在這個屬性, 就須要使用外部的 hasOwnProperty 函數來獲取正確的結果。css

  1.  
    var foo = {
  2.  
    hasOwnProperty: function() {
  3.  
    return false;
  4.  
    },
  5.  
    bar: 'Here be dragons'
  6.  
    };
  7.  
     
  8.  
    foo.hasOwnProperty( 'bar'); // 老是返回 false
  9.  
     
  10.  
    // 使用其它對象的 hasOwnProperty,並將其上下文設置爲foo
  11.  
    ({}).hasOwnProperty.call(foo, 'bar'); // true

當檢查對象上某個屬性是否存在時,hasOwnProperty 是惟一可用的方法。 同時在使用 for in loop遍歷對象時,推薦老是使用 hasOwnProperty 方法, 這將會避免原型對象擴展帶來的干擾。java

for in 循環

和 in 操做符同樣,for in 循環一樣在查找對象屬性時遍歷原型鏈上的全部屬性。web

  1.  
    // 修改 Object.prototype
  2.  
    Object.prototype.bar = 1;
  3.  
     
  4.  
    var foo = {moo: 2};
  5.  
    for(var i in foo) {
  6.  
    console.log(i); // 輸出兩個屬性:bar 和 moo
  7.  
    }
注意: 因爲 for in 老是要遍歷整個原型鏈,所以若是一個對象的繼承層次太深的話會影響性能。

因爲不可能改變 for in 自身的行爲,所以有必要過濾出那些不但願出如今循環體中的屬性, 這能夠經過 Object.prototype 原型上的 hasOwnProperty 函數來完成。swift

使用 hasOwnProperty 過濾

  1.  
    // foo 變量是上例中的
  2.  
    for(var i in foo) {
  3.  
    if (foo.hasOwnProperty(i)) {
  4.  
    console.log(i);
  5.  
    }
  6.  
    }
推薦老是使用  hasOwnProperty。不要對代碼運行的環境作任何假設,不要假設原生對象是否已經被擴展了。

2.命名函數的賦值表達式

另一個特殊的狀況是將命名函數賦值給一個變量。數組

  1.  
    var foo = function bar() {
  2.  
    bar(); // 正常運行
  3.  
    }
  4.  
    bar(); // 出錯:ReferenceError

bar 函數聲明外是不可見的,這是由於咱們已經把函數賦值給了 foo; 然而在 bar 內部依然可見。這是因爲 JavaScript 的命名處理所致, 函數名在函數內老是可見的。瀏覽器

注意:在 IE8及IE8如下版本瀏覽器bar在外部也是可見的,是由於瀏覽器對命名函數賦值表達式進行了錯誤的解析, 解析成兩個函數  foo 和 bar

3.方法的賦值表達式

另外一個看起來奇怪的地方是函數別名,也就是將一個方法賦值給一個變量。安全

  1.  
    var test = someObject.methodTest;
  2.  
    test();

上例中,test 就像一個普通的函數被調用;所以,函數內的 this 將再也不被指向到 someObject 對象。而是指向了windowruby

4.循環中的閉包

一個常見的錯誤出如今循環中使用閉包,假設咱們須要在每次循環中調用循環序號閉包

  1.  
    for(var i = 0; i < 10; i++) {
  2.  
    setTimeout(function() {
  3.  
    console.log(i);
  4.  
    }, 1000);
  5.  
    }

上面的代碼不會輸出數字 0 9,而是會輸出數字10 十次。

當 console.log 被調用的時候,匿名函數保持對外部變量i的引用,此時 for循環已經結束,i的值被修改爲了10.

爲了獲得想要的結果,須要在每次循環中建立變量 i的拷貝。

爲了不引用錯誤,爲了正確的得到循環序號,最好使用 匿名包裝器(注:其實就是咱們一般說的自執行匿名函數)。

  1.  
    for(var i = 0; i < 10; i++) {
  2.  
    ( function(e) {
  3.  
    setTimeout( function() {
  4.  
    console.log(e);
  5.  
    }, 1000);
  6.  
    })( i);
  7.  
    }

外部的匿名函數會當即執行,並把 i 做爲它的參數,此時函數內 e 變量就擁有了 i 的一個拷貝。

當傳遞給 setTimeout 的匿名函數執行時,它就擁有了對 e 的引用,而這個值是不會被循環改變的。

有另外一個方法完成一樣的工做,那就是從匿名包裝器中返回一個函數。這和上面的代碼效果同樣。

  1.  
    for(var i = 0; i < 10; i++) {
  2.  
    setTimeout(( function(e) {
  3.  
    return function() {
  4.  
    console.log(e);
  5.  
    }
  6.  
    })( i), 1000)
  7.  
    }

5.對象使用和屬性

JavaScript 中全部變量均可以看成對象使用,除了兩個例外 null 和 undefined

  1.  
    false.toString(); // 'false'
  2.  
    [ 1, 2, 3].toString(); // '1,2,3'
  3.  
     
  4.  
    function Foo(){}
  5.  
    Foo.bar = 1;
  6.  
    Foo.bar; // 1

一個常見的誤解是數字的字面值(literal)不能看成對象使用。這是由於 JavaScript 解析器的一個錯誤, 它試圖將點操做符解析爲浮點數字面值的一部分。

2.toString(); // 出錯:SyntaxError

有不少變通方法可讓數字的字面值看起來像對象。

  1.  
    2..toString(); // 第二個點號能夠正常解析
  2.  
    2 .toString(); // 注意點號前面的空格
  3.  
    ( 2).toString(); // 2先被計算

刪除屬性的惟一方法是使用 delete 操做符;設置屬性爲 undefined 或者 null 並不能真正的刪除屬性, 而僅僅是移除了屬性和值的關聯。

  1.  
    var obj = {
  2.  
    bar: 1,
  3.  
    foo: 2,
  4.  
    baz: 3
  5.  
    };
  6.  
    obj.bar = undefined;
  7.  
    obj.foo = null;
  8.  
    delete obj.baz;
  9.  
     
  10.  
    for(var i in obj) {
  11.  
    if (obj.hasOwnProperty(i)) {
  12.  
    console.log(i, '' + obj[i]);
  13.  
    }
  14.  
    }

上面的輸出結果有 bar undefined 和 foo null - 只有 baz 被真正的刪除了,因此從輸出結果中消失。

6.arguments 對象

JavaScript 中每一個函數內都能訪問一個特別變量 arguments。這個變量維護着全部傳遞到這個函數中的參數列表。

arguments 變量不是一個數組(Array)。 儘管在語法上它有數組相關的屬性 length,但它不從 Array.prototype 繼承,實際上它是一個對象(Object)。

所以,沒法對 arguments 變量使用標準的數組方法,好比 pushpop 或者 slice。 雖然使用 for循環遍歷也是能夠的,可是爲了更好的使用數組方法,最好把它轉化爲一個真正的數組。

轉化爲數組

下面的代碼將會建立一個新的數組,包含全部 arguments 對象中的元素。

Array.prototype.slice.call(arguments);Array.prototype.slice.call(arguments);

arguments 對象爲其內部屬性以及函數形式參數建立 getter 和 setter 方法。

所以,改變形參的值會影響到 arguments 對象的值,反之亦然。

  1.  
    function foo(a, b, c) {
  2.  
    arguments[0] = 2;
  3.  
    a; // 2
  4.  
     
  5.  
    b = 4;
  6.  
    arguments[1]; // 4
  7.  
     
  8.  
    var d = c;
  9.  
    d = 9;
  10.  
    c; // 3
  11.  
    }
  12.  
    foo( 1, 2, 3);

以下一個例子:

  1.  
    function sidEffecting(ary) {
  2.  
    ary[0] = ary[2];
  3.  
    }
  4.  
    function bar(a,b,c) {
  5.  
    c = 10
  6.  
    sidEffecting(arguments);
  7.  
    return a + b + c;
  8.  
    }
  9.  
    bar(1,1,1)

這裏全部的更改都將生效,a和c的值都爲10,a+b+c的值將爲21。

7.類型相關

測試爲定義變量

typeof foo !== 'undefined'

上面代碼會檢測 foo 是否已經定義;若是沒有定義而直接使用會致使 ReferenceError 的異常。 這是 typeof 惟一有用的地方。固然也能判斷出來基本類型。

Object.prototype.toString檢測一個對象的類型

爲了檢測一個對象的類型,強烈推薦使用 Object.prototype.toString 方法

以下例子:

  1.  
    Object.prototype.toString.call([]) // "[object Array]"
  2.  
    Object.prototype.toString.call({}) // "[object Object]"
  3.  
    Object.prototype.toString.call(2) // "[object Number]"

類型轉換

內置類型(好比 Number 和 String)的構造函數在被調用時,使用或者不使用 new 的結果徹底不一樣。

  1.  
    new Number(10) === 10; // False, 對象與數字的比較
  2.  
    Number(10) === 10; // True, 數字與數字的比較
  3.  
    new Number(10) + 0 === 10; // True, 因爲隱式的類型轉換

轉換爲字符串

'' + 10 === '10'; // true

將一個值加上空字符串能夠輕鬆轉換爲字符串類型。

轉換爲數字

+'10' === 10; // true

使用一元的加號操做符,能夠把字符串轉換爲數字。

轉換爲布爾型

經過使用 否 操做符兩次,能夠把一個值轉換爲布爾型。

  1.  
    !! 'foo'; // true
  2.  
    !! ''; // false
  3.  
    !! '0'; // true
  4.  
    !! '1'; // true
  5.  
    !! '-1' // true
  6.  
    !!{}; // true
  7.  
    !! true; // true

8.爲何不要使用 eval

eval 函數會在當前做用域中執行一段 JavaScript 代碼字符串。

  1.  
    var foo = 1;
  2.  
    function test() {
  3.  
    var foo = 2;
  4.  
    eval('foo = 3');
  5.  
    return foo;
  6.  
    }
  7.  
    test(); // 3
  8.  
    foo; // 1

可是 eval 只在被直接調用而且調用函數就是 eval 自己時,纔在當前做用域中執行。

  1.  
    var foo = 1;
  2.  
    function test() {
  3.  
    var foo = 2;
  4.  
    var bar = eval;
  5.  
    bar('foo = 3');
  6.  
    return foo;
  7.  
    }
  8.  
    test(); // 2
  9.  
    foo; // 3

上面的代碼等價於在全局做用域中調用 eval,和下面兩種寫法效果同樣:

  1.  
    // 寫法一:直接調用全局做用域下的 foo 變量
  2.  
    var foo = 1;
  3.  
    function test() {
  4.  
    var foo = 2;
  5.  
    window.foo = 3;
  6.  
    return foo;
  7.  
    }
  8.  
    test(); // 2
  9.  
    foo; // 3
  10.  
     
  11.  
    // 寫法二:使用 call 函數修改 eval 執行的上下文爲全局做用域
  12.  
    var foo = 1;
  13.  
    function test() {
  14.  
    var foo = 2;
  15.  
    eval.call(window, 'foo = 3');
  16.  
    return foo;
  17.  
    }
  18.  
    test(); // 2
  19.  
    foo; // 3

在任何狀況下咱們都應該避免使用 eval 函數。99.9% 使用 eval 的場景都有不使用 eval 的解決方案。

eval 也存在安全問題,由於它會執行任意傳給它的代碼, 在代碼字符串未知或者是來自一個不信任的源時,絕對不要使用 eval 函數。

9.定時器

手工清空定時器

  1.  
    var id = setTimeout(foo, 1000);
  2.  
    clearTimeout( id);

清除全部定時器

因爲沒有內置的清除全部定時器的方法,能夠採用一種暴力的方式來達到這一目的。

  1.  
    // 清空"全部"的定時器
  2.  
    for(var i = 1; i < 1000; i++) {
  3.  
    clearTimeout(i) ;
  4.  
    }

可能還有些定時器不會在上面代碼中被清除(注:若是定時器調用時返回的 ID 值大於 1000), 所以咱們能夠事先保存全部的定時器 ID,而後一把清除。

建議不要在調用定時器函數時,爲了向回調函數傳遞參數而使用字符串的形式。

  1.  
    function foo(a, b, c) {}
  2.  
     
  3.  
    // 不要這樣作
  4.  
    setTimeout( 'foo(1,2, 3)', 1000)
  5.  
     
  6.  
    // 可使用匿名函數完成相同功能
  7.  
    setTimeout( function() {
  8.  
    foo( 1, 2, 3);
  9.  
    }, 1000)
絕對不要使用字符串做爲  setTimeout 或者  setInterval 的第一個參數, 這麼寫的代碼明顯質量不好。當須要向回調函數傳遞參數時,能夠建立一個匿名函數,在函數內執行真實的回調函數。

另外,應該避免使用 setInterval,由於它的定時執行不會被 JavaScript 阻塞。

相關文章
相關標籤/搜索