〔總結〕容易遺忘的JS知識點整理

1.hasOwnProperty相關

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

// 修改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 函數來獲取正確的結果。瀏覽器

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 循環

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 函數來完成。函數

使用 hasOwnProperty 過濾

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

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

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

var foo = function bar() {
    bar(); // 正常運行
}
bar(); // 出錯:ReferenceError

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

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

3.方法的賦值表達式

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

var test = someObject.methodTest;
test();

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

4.循環中的閉包

一個常見的錯誤出如今循環中使用閉包,假設咱們須要在每次循環中調用循環序號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)
}

5.對象使用和屬性

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

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 被真正的刪除了,因此從輸出結果中消失。

6.arguments 對象

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

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

所以,沒法對 arguments 變量使用標準的數組方法,好比 push, pop 或者 slice。 雖然使用 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。

7.類型相關

測試爲定義變量

typeof foo !== 'undefined'

上面代碼會檢測 foo 是否已經定義;若是沒有定義而直接使用會致使 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]"

類型轉換

內置類型(好比 NumberString)的構造函數在被調用時,使用或者不使用 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

8.爲何不要使用 eval

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 函數。

9.定時器

手工清空定時器

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 阻塞。

後續逐漸添加

相關文章
相關標籤/搜索