JavaScript忍者祕籍

1. JavaScript經常使用測試框架:

  • QUnit
  • YUI Test
  • JsUnit
  • Jasmine

2. 斷言:

單元測試框架的核心是斷言方法,一般叫作assert()。該方法一般接受一個值——須要斷言的值,以及一個表示該斷言目的的描述。若是該值執行結果爲true,斷言就會經過;不然斷言就會被認爲是失敗的。

3. 函數是第一型對象:

對象在JavaScript中有以下功能:javascript

  • 它們能夠經過字面量進行建立;
  • 它們能夠賦值給變量、數組或其餘對象的屬性;
  • 它們能夠做爲參數傳遞給函數;
  • 它們能夠做爲函數的返回值進行返回;
  • 它們能夠擁有動態建立並賦值的屬性;

在JavaScript中,函數擁有所有這些功能,也就是說能夠像這門語言中的其餘對象同樣使用。所以,咱們說函數是第一型對象。函數還有一個特殊的功能,它們能夠被調用。java

4. 瀏覽器的事件輪詢:

瀏覽器的事件輪詢是單線程的,每一個事件都是按照在隊列中所放置的順序來處理的。

5. 回調概念:

當咱們定義一個函數稍後執行時,不管什麼時候定義,在瀏覽器執行仍是在其餘地方執行,咱們定義的就是所謂的回調。

6. 函數聲明:

JavaScript函數是使用函數字面量進行聲明從而建立函數值的,就像使用數字字面量建立數字值同樣。
函數字面量由四個部分組成:json

  • function關鍵字;
  • 可選名稱;
  • 括號內部的參數;
  • 函數體,包含在大括號內的一系列JavaScript語句;

7. 做用域和函數:

在Javascript中,做用域是由function進行聲明的,而不是代碼塊。聲明的做用域建立於代碼塊,但不是終結於代碼塊。
例如:數組

if(window){
   var x= 213;    
}
alert(x);//213

8. 使用apply()和call()方法:

在函數調用的時候,JavaScript爲咱們提供了一種方式,能夠顯示指定任何一個對象做爲其函數的上下文。JavaScript的每一個函數都有apply()和call()方法。
經過函數的apply()方法來調用函數,咱們要給apply()傳入兩個參數:一個是做爲函數上下文的對象,另一個是做爲函數參數所組成的數組。call()方法的使用方式相似,惟一不一樣的是,給函數傳入的參數是一個參數列表,而不是單個數組。
代碼示例:瀏覽器

<script type = "text/javascript">
function juggle() {
    var result = 0;
    for (var n = 0; n < arguments.length; n++) {
        result += arguments[n];
    }
    this.result = result;
}
var ninja1 = {};
var ninja2 = {};
juggle.apply(ninja1,[1,2,3,4]);
juggle.call(ninja2,5,6,7,8);
console.log(ninja1.result == 10);//true
console.log(ninja2.result == 26);//true
</script>

9. arguments.callee屬性:

代碼示例:緩存

var ninja = {
    chirp: function(n) {
        return n > 1 ? arguments.callee(n - 1) + "-chirp" : "chirp";
    }
}

callee屬性引用的是當前所執行的函數。該屬性能夠做爲一個可靠的方法引用函數自身。服務器

10. 自記憶函數:

記憶以前計算出的值:
function isPrime(value) {
    if (!isPrime.answers) {
        isPrime.answers = {};
    }
    if (isPrime.answers[value] != null) {
        return isPrime.answers[value];
    }
    var prime = value != 1;
    for (var i = 2; i < value; i++) {
        if (value % i == 0) {
            prime = false;
            break;
        }
    }
    return isPrime.answers[value] = prime;
}
緩存記憶DOM元素
function getElements(name) {
    if (!getElements.cache) {
        getElements.cache = {};
    }
    return getElements.cache[name] = getElements.cache[name] || document.getElementsByTagName(name);
}

11. 模擬相似數組的方法

var elems = {
    length: 0,
    add: function (elem) {
        Array.prototype.push.call(this, elem);
    },
    gather: function (id) {
        this.add(document.getElementById(id));
    }
};

12. 使用apply()支持可變參數

在這裏指定Math爲上下文是沒有必要的
function smallest(array) {
    return Math.min.apply(Math, array);
}
function largest(array) {
    return Math.max.apply(Math, array);
}

重載函數的方法:閉包

function addMethod(object, name, fn) {
    var old = object[name];//保存原有的函數,由於調用的時候可能不匹配傳入的參數個數
    object[name] = function () {
        if (fn.length == arguments.length) {
        //若是該匿名函數的形參個數和實數個數匹配,就調用該函數
            return fn.apply(this, arguments);
        } else if (typeof old == 'function') {
        //若是傳入的參數個數不匹配,則調用原有的參數
            return old.apply(this, arguments);
        }
    }
}

addMethod方法共接受三個參數:app

  • 要綁定方法的對象
  • 綁定方法所用的屬性名稱
  • 要綁定的方法

13. 什麼是閉包:

閉包是一個函數在建立時容許該自身函數訪問並操做該自身函數以外的變量時所建立的做用域。換句話說,閉包可讓函數訪問全部的變量和函數,只要這些變量和函數聲明時的做用域內就行。

14. 在封閉的做用域內,強制使用一個名稱:

(function ($) {
    $("img").on("click", function (e) {
        $(e.target).addClass("clickOn");
    })
})(jQuery);//jQuery做爲參數綁定到$上

15. 利用即時函數妥善處理迭代問題:

for (var i = 0; i < length; i++) {
    (function (n) {
        alert(n);
    })(i);
}

16. 使用hasOwnProperty()方法辨別Object原型擴展:

Object.prototype.keys = function () {
    var keys = [];
    for (var i in this) {
        //使用hasOwnProperty()忽略原型對象上的屬性
        if (this.hasOwnProperty(i)) {
            keys.push(i);
        }
    }
    return keys;
};

var obj = {a: 1};
console.log(obj.keys());

17. 在Number原型上添加一個方法

Number.prototype.add = function (num) {
        return this + num;
    };
    var n = 5;

    console.log(n.add(3));

18. 子類化Array對象:

function MyArray() {}

MyArray.prototype = new Array();
var mine = new MyArray();
mine.push(1, 2, 3);
console.log(mine.length == 3);//true

19. 模擬Array功能,而不是擴展出子類:

function MyArray() {}
    MyArray.prototype.length = 0;
    (function () {
        var methods = ['push', 'pop', 'shift', 'unshift', 'slice', 'splice', 'join'];
        for (var i = 0; i < methods.length; i++) {
            (function (name) {
                MyArray.prototype[name] = function () {
                    return Array.prototype[name].apply(this, arguments);
                }
            })(methods[i])
        }
    })();
    var mine = new MyArray();
    mine.push(1, 2, 3);
    console.log(mine);//[1, 2, 3]

20. 判斷函數是不是做爲構造器進行調用的

function Test(){
        return this instanceof arguments.callee;
    }
  • 經過arguments.callee能夠獲得當前執行函數的引用;
  • 「普通」函數的上下文是全局做用域(除非有人作了強制修改);
  • 利用instanceof操做符測試已構建對象是否構建於指定的構造器;

基於這些事實,函數在做爲構造器進行執行的時候,表達式:this instanceof arguments.callee;的結果是true,若是做爲普通函數執行,則返回false。框架

21. 用eval()方法進行求值:

基本功能:

  • 該方法將執行傳入代碼的字符串;
  • 在調用eval()方法的做用域內進行代碼求值;
console.log(eval("5+5") == 10);//true

求值結果:
eval()方法將返回傳入字符串中最後一個表達式的執行結果。

console.log(eval("3+4;5+6"));//11

在使用eval()方法求值的時候,求值執行的做用域就是調用eval()時的做用域,而不是全局做用域。

22. 用函數構造器進行求值:

JavaScript中的全部函數都是Function的實例,能夠直接使用Function構造器來實例化函數。

var add = new Function("a", "b", "return a + b;");
console.log(add(3, 4));//7

Function構造器可變參數列表的最後一個參數,始終是要建立函數的函數體內容。前面的參數則表示函數的形參名稱。

因此,上面的示例代碼等價於以下代碼:

var add = function(a, b) { return a + b;}

雖然這些代碼在功能上是等同的,但採用Function構造器方式有一個明顯的區別,函數體由運行時的字符串所提供。

另一個極其重要的實現區別是,使用Function構造器建立函數的時候,不會建立閉包。在不想承擔任何不相關閉包的開銷時,這多是一件好事。

23. 將JSON字符串轉化成JavaScript對象

var json = '{"name":"Ninja"}';
    var object = eval("(" + json + ")");
    console.log(object);//Object {name: "Ninja"}

使用eval()作JSON解析時須要注意的是:一般,JSON數據來自於遠程服務器,並且,盲目執行遠程服務器上的不可信代碼,基本是不可取的。

最受歡迎的JSON轉換器腳本是由JSON標記的創造者Douglas Crockford所編寫。在該轉換器中,他作了一些初步的JSON字符串解析,以防止任何惡意信息經過。Github:JSON-js

24. with語句

with語句是一個強大的、但常常被誤解的、有爭議的Javascript特性。with語句容許咱們將一個對象的全部屬性引用到當前做用域,容許咱們無需使用擁有者對象的前綴,就能夠對這些屬性進行引用和賦值操做。
var use = "other";//定義一個全局變量
    var katana = {//建立一個對象
        isSharp: true,
        use: function () {
            this.isSharp != this.isSharp;
        }
    };
    with (katana) {//創建一個with做用域,在該做用域內,能夠直接引用屬性名稱,而沒必要使用katana前綴,好像它們就是全局變量和全局方法同樣
        console.log(use !== "other" && typeof use == "function");//做用域內進行測試,true
        console.log(this !== katana);//true
    }

    console.log(use === "other");//做用域外進行測試,true
    console.log(typeof isSharp === "undefined");//true
注意:在with語句的做用域內,對象屬性的優先級絕對高於在更高層做用域內定義的同名變量。函數上下文(this)是不受with語句影響的。

25. 判斷一個瀏覽器是否支持opacity:

var div = document.createElement("div");
    div.setAttribute("style", "opacity:0.5");
    var OPACITY_SUPPORTED = div.style.opacity === "0.5";
    console.log(OPACITY_SUPPORTED);

26. 獲取計算樣式的值:

function fetchComputedStyle(element, property) {//定義新函數
        if (window.getComputedStyle) {
            var computedStyles = window.getComputedStyle(element);//獲取接口
            if (computedStyles) {
                property = property.replace(/[A-z]/g, '-$1').toLowerCase();//獲取樣式值
                return computedStyles.getPropertyValue(property);
            }
        } else if (element.currentStyle) {//使用專有方式,IE瀏覽器
            property = property.replace(/-([a-z])/ig, function (all, letter) {
                return letter.toUpperCase();
            });
            return element.currentStyle[property];
        }
    }
    window.onload = function () {
        var div = document.getElementsByTagName("div")[0];
        console.log(fetchComputedStyle(div, "display"));
    };

27. 綁定和解綁事件處理程序:

在DOM2下,對於兼容於DOM的現代瀏覽器,咱們綁定和解綁事件處理程序使用的是addEventListener()和removeEventListener()方法;而對IE老版本使用的則是attachEvent()和detachEvent()方法。
<br/>

注意:IE沒有提供事件捕獲階段的監聽方式,只支持時間處理過程當中的冒泡階段。此外,IE的實現給綁定處理程序設置了錯誤的上下文,使得處理程序內的this引用的是全局上下文,而不是事件目標元素。另外,IE沒有將事件信息傳遞給處理程序,而是將事件信息定死在全局上下文了——window對象。

這意味着,在處理以下類型的事件時,咱們須要根據不一樣的瀏覽器來指定不一樣的方式。

  • 綁定一個處理程序時;
  • 解綁一個處理程序時;
  • 獲取事件信息時;
  • 獲取事件目標時;
如何解決多API的問題,以及IE不能正確設置上下文問題:
代碼:綁定事件處理程序時,設置正確的上下文
if (document.addEventListener) {
        this.addEvent = function (elem, type, fn) {
            elem.addEventListener(type, fn, false);//false表示使用冒泡處理
            return fn;
        };
        this.removeEvent = function (elem, type, fn) {//DOM解綁函數
            elem.removeEventListener(type, fn, false);
        }
    } else if (document.attachEvent) {//檢測是否支持IE
        this.addEvent = function (elem, type, fn) {
            var bound = function () {
                return fn.apply(elem, arguments);//改變上下文
            };
            elem.attachEvent("on" + type, bound);
            return bound;
        };
        this.removeEvent = function (elem, type, fn) {
            elem.detachEvent("on" + type, fn);
        }
    }

28. 將事件委託給祖先元素:

假設一個表格在初始化加載的時候,全部的單元格都是白色背景,咱們很想直觀地代表,表格中的哪一個單元格在與用戶交互的時候被單機了。

醜陋的代碼:
    var cells = document.getElementsByTagName("td");
    for (var n = 0; n < cells.length; n++) {
        addEvent(cells[n], "click", function () {
            this.style.backgroundColor = "yellow";
        });
    }

優雅的寫法:
    var table = document.getElementById("#someTable");
    addEvent(table, "click", function (event) {
        if (event.target.tagName.toLowerCase() == "td") {
            event.target.style.backgroundColor = "yellow";//一個元素被單擊的時候能夠經過event.target獲取該元素的引用
        }
    });

存在一個問題:在老版本IE瀏覽器中,submit和change事件根本沒有冒泡,那麼如何進行事件委託?

29. 事件冒泡檢測代碼:

console.log(isEventSupported("click"));//true
function isEventSupported(eventName) {
    //建立一個div用於測試,一般各個事件均可以冒泡到div上來,包括change和submit
    var element = document.createElement("div"), isSupported;
    eventName = "on" + eventName;
    isSupported = (eventName in element);//檢測元素是否有一個屬性表示該事件
    //若是檢測到沒有這個屬性,那就建立一個ontype並插入一點代碼,而後判斷該元素是否能夠將其轉換爲一個函數
    //若是轉換爲一個函數,說明該元素知道如何解釋冒泡事件
    if (!isSupported) {
        element.setAttribute(eventName, "return;");
        isSupported = typeof element[eventName] == 'function';
    }
    element = null;//刪除臨時元素
    return isSupported;
}
相關文章
相關標籤/搜索