JavaScript 開發總結(一)

  1. 數據類型:JavaScript定義的數據類型有字符串、數字、布爾、數組、對象、Null、Undefined,但typeof有區分可判別的數據分類是number、string、boolean、object(null / array)、function和undefined。undefined 這個值表示變量不含有值,null 能夠用來清空變量
    let a = 100;
    typeof a;//number
    a = undefined;
    typeof a;//undefined
    a = null;
    typeof a;//object
    a instanceof Object;//false. instanceof 運算符檢測 Object.prototype 是否存在於 a 的原型鏈上
  2. 默認類型轉換:這是個讓人頭疼的問題,對於「==」最好換成「===」、「!=」最好換成「!==」,以防止默認類型轉換而致使邏輯錯誤。這裏列舉一部分
    5 == true;//false。true會先轉換爲1,false轉換爲0
    '12' + 1;//'123'。數字先轉換成字串
    '12' - 1;//11。字串先轉換成數字
    [] == false;//true。數組轉換爲其餘類型時,取決於另外一個比較者的類型
    [] == 0;//true
    [2] == '2';//true
    [2] == 2;//true
    [] - 2;//-2
    12 + {a:1};//"12[object Object]"。這裏對象先被轉換成了字串
    null == false;//false
    null == 0;//false
    null - 1;//-1
  3. Number計算精度問題:在含有小數部分的數值運算中可能會存在精度問題,如0.03 - 0.02 = 0.009999999999999998,結果不是0.01,這在金額計算中將是一個很大的問題。固然金額計算一般放在後端來作,但一樣的問題恐怕依舊難以免,因此要麼以分而不是元爲單位進行計算。總之,這兩種方法的本質都是同樣的,那就是經過整數運算來避免運算精度問題。
  4. JS對象與字串互轉換:若是要複製對象屬性,可經過JSON.stringify()轉換成字符串類型(JSON.stringify(obj, null, 2)可轉換成格式化字符串輸出),賦值給複製變量後再經過JSON.parse()轉換成對象類型,但這種轉換會致使原對象方法丟失,只有屬性能夠保留下來;若是在發生對象賦值後再賦新值,其將指向新的地址空間。關於JSON與JavaScript之間的關係:JSON基於 JavaScript 語法,但不是JavaScript 的子集
    > a = {"name":"cenze","age":28}
    
    > JSON.stringify(a,null,2)
        {
          "name": "cenze",
          "age": 28
        }

     

  5. 大小寫切換
    let str = '23abGdH4d4Kd';
    str = str.replace(/([a-z]*)([A-Z]*)/g, function(match, $1 , $2){return $1.toUpperCase() + $2.toLowerCase()});//"23ABgDh4D4kD"
    
    str = 'web-application-development';
    str = str.replace(/-([a-z])/g, (replace)=>replace[1].toUpperCase());//"webApplicationDevelopment"(駝峯轉換)
  6. 生成隨機數
    str = Math.random().toString(36).substring(2) 
  7. | 0 、^ 0、>>、 ~~ 取整數去除小數部分:這裏沒有列出& 1,是由於其操做沒法保持符號不變
    3.2 / 1.7 | 0 = 1;
    ~~(3.2 / 1.7) = 1;//~~可還原原數
    3.2 / 1.7 >> 0 = 1;
    3.2 / 1.7 ^ 0 = 1;
    -3.2 / 1.7 & 1 = 1;//& 1操做沒法保持符號不變
    if (!~str.indexOf(substr)) {//~按位取反,等價於符號取反而後減一   //do some things }
  8. 整數判斷:對於parseInt(010,10)結果值爲8,parseInt("010",10)結果值爲10,這說明parseInt會自動斷定數值基數並優先於用戶定義。0x10+''結果爲字串"16",010+''結果爲字串"8"
    let a = 2,
        b = 2.3;
    
    //方法一:Math.round()
    Math.round(a) === a;//true
    Math.round(b) === b;//false
    
    //方法二:模1運算
    typeof a == "number" && (a % 1) === 0;//true
    typeof b == "number" && (b % 1) === 0;//false
    
    //方法三:ES6 Number.isInteger()
    Number.isInteger(a);//true
    Number.isInteger(b);//false
    
    //方法四:parseInt()
    parseInt(a, 10) === a;//true
    parseInt(b, 10) === b;//false
    
    //方法五:異或0運算
    a ^ 0 === a;//true
    b ^ 0 === b;//false
  9. NOT(!)、AND(&&) 和 OR(||):OR(||) 和 AND(&&) 運算結果是運算終止時當前運算數(表達式)的值,結果不必定是true或false;可是NOT(!)運算結果要麼是true,要麼是false。比較難理解的是[ ] == ![ ]
    true && 0;//0
    false && 2;//false
    1 && 2;//2 1 || 2;//1 0 || 1;//1
    ![] || !{} || 3;//3
    [] == ![];//true
    !NaN && !undefined && !null && !0 && !'' && 6;//6
  10. 無第三變量交換值:下邊的作法未必在空間和時間上都比聲明第三變量來的更好,寫在這裏僅僅爲了拓展一下思惟
    //方法一:經過中間數組完成交換
    let a = 1,
        b = 2;
    a = [b, b = a][0];
    
    //方法二:經過加減運算完成交換
    let a = 1,
        b = 2;
    a = a + b;
    b = a - b;
    a = a - b;
    
    //方法三:經過加減運算完成交換
    let a = 1,
        b = 2;
    a = a - b;
    b = a + b;
    a = b - a;
    
    //方法四:經過兩次異或還原完成交換。另外,這裏不會產生溢出
    let a = 1,
        b = 2;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    
    //方法五:經過乘法運算完成交換
    let a = 1,
        b = 2;
    a = b + (b = a) * 0; 
    
    //方法六:經過ES6解構賦值
    let a = 1,
        b = 2;
    [a, b] = [b, a];
  11. /、%、**運算符html

    5 / 3;//1.6666666666666667
    4.53 % 2;//0.5300000000000002
    5 % 3;//2
    2 ** 3;//8,相比於Math.pow(2,3)要簡潔多了
  12. Array.prototype.sort(compareFunction)compareFunction可選,用來指定按某種順序進行排列的函數。若是省略,元素按照轉換爲的字符串的諸個字符的Unicode位點進行排序。好比:
    [1, 10, 21, 2].sort();//[1, 10, 2, 21]
    [1, 10, 21, 2].sort((a, b) => a - b);//[1, 2, 10, 21]
    // 須要被排序的數組
    var list = ['Delta', 'alpha', 'CHARLIE', 'bravo'];
    
    // 對須要排序的數字和位置的臨時存儲
    var mapped = list.map(function(el, i) {
      return { index: i, value: el.toLowerCase() };
    })
    
    // 按照多個值排序數組
    mapped.sort(function(a, b) {
      return +(a.value > b.value) || +(a.value === b.value) - 1;
    });
    
    // 根據索引獲得排序的結果
    var result = mapped.map(function(el){
      return list[el.index];
    });

     

  13. 數組去重
    //方法一:先排序後去重
    Array.prototype.unique = function(){
        this.sort(); //先排序
        let res = [this[0]];
        for(let i = 1; i < this.length; i++){
            if(this[i] !== res[res.length - 1]){
                res.push(this[i]);
            }
        }
        
        return res;
    }
    
    //方法二:利用對象屬性惟一性避免重複元素
    Array.prototype.unique = function(){
        let res = [],
            deduplication = {};
        for(let i = 0; i < this.length; i++){
            if(!deduplication[this[i]]){
                res.push(this[i]);
                deduplication[this[i]] = 1;
            }
        }
    
        return res;
    }
    
    //方法三:利用過濾函數遞代去重處理,該方法可保持數組原序不變
    Array.prototype.unique = function () {
        let self = this,
            res = [],
            target;
        while (self.length) {
            target = self.shift();
            res.push(target);
            self = self.filter(item => target !== item);
        }
    
        return res;
    }
    
    //方法四:利用ES6新增長的Set數據類型作中間轉換可去重
    Array.prototype.unique = function () {
        return [...new Set(this)];
    }
  14. 求數組最大最小數
    Math.max.apply(null, [2,5,30,1,20]);//30
    Math.min.apply(null, [2,5,30,1,20]);//1
    Math.min(...[2,5,30,1,20]);//1.ES6
  15. encodeURI() 與 encodeURIComponent():它們和escape()同樣都不會對 ASCII 字母和數字進行編碼,但escape()除了不對前述字符和「* @ - _ + . / 」這些 ASCII 標點符號進行編碼外,其餘全部的字符都會被轉義序列替換 。關於URL的標準定義可參考: Uniform Resource Locators (URL)
    1. encodeURI()對 URI 進行完整的編碼,但不會對「;/?:@&=+$,#」這些在 URI 中具備特殊含義的 ASCII 標點符號進行編碼,同時也不編碼「- _ . ! ~ * ' ( )」這些ASCII字符。但decodeURI("%27") == unescape("%27") == "'";
    
    2. encodeURIComponent()可把字符串做爲 URI 組件進行編碼,會對「;/?:@&=+$,#」這些在 URI 中具備特殊含義的 ASCII 標點符號進行編碼,但不編碼「- _ . ! ~ * ' ( )」這些ASCII字符。
  16. setTimeout與setInterval: setTimeout 函數會在一個時間段過去後在隊列中添加一個消息,這個時間段做爲函數的第二個參數被傳入。若是隊列中沒有其它消息,消息會被立刻處理;若是有其它消息,setTimeout消息必須等待其它消息處理完。所以第二個參數僅僅表示最少的時間而非確切的時間。
    瀏覽器的內核是多線程的,它們在內核控制下相互配合以保持同步,一個瀏覽器至少實現三個常駐線程:JavaScript引擎線程、GUI渲染線程、事件觸發線程

    JavaScript引擎線程:一直等待着任務隊列中任務的到來,而後按優先級加以處理。瀏覽器不管何時都只有一個JavaScript線程在運行JavaScript程序。 GUI渲染線程:負責渲染瀏覽器界面,當界面須要重繪(Repaint)或因爲某種操做引起迴流(Reflow)時,該線程就會執行。但須要注意,GUI渲染線程與JavaScript引擎是互斥的,當JavaScript引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JavaScript引擎空閒時當即被執行。 事件觸發線程:在一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待JavaScript引擎的處理。這些事件可來自JavaScript引擎當前執行的代碼塊如setTimeout、也可來自瀏覽器內核的其餘線程如鼠標點擊、Ajax異步請求等,但因爲JavaScript的單線程關係全部這些事件都得排隊等待JavaScript引擎處理(當線程中沒有執行任何同步代碼的前提下才會執行異步代碼)。
    1. setTimeout()和setInterval()共用一個編號池,clearTimeout()和 clearInterval() 在技術上能夠互換;可是爲了不混淆,不要混用取消定時函數
    2. 須要注意的是setTimeout()和setInterval()延遲執行的函數或代碼中的this會指向一個錯誤的值,由於那些函數或代碼是在一個分離的環境空間裏被執行的,這些代碼中包含的 this 關鍵字在非嚴格模式會指向 window (或全局)對象,嚴格模式下爲 undefined
    3. IE < 9環境下setTimeout() 或者 setInterval()都不支持傳遞額外的參數,能夠考慮自行從新定義兼容代碼同名取代這兩個函數
    4. setTimeout()的最小延遲時間與瀏覽器及操做系統有關,但不是0。在John Resig的《Javascript忍者的祕密》中:Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.另外Firefox中定義的最小時間間隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定義的最小時間間隔是4毫秒
    5. 延遲只是代表可能的最小延遲時間,而不保證確切延遲時間。若是任務隊列很長,耗時不少,最後延遲時間可能遠遠超出參數中指定的值
  17. 原型鏈(Prototype Chaining)與繼承: 原型鏈是ECMAScript 中實現繼承的方式。JavaScript 中的繼承機制並非明確規定的,而是經過模仿實現的,全部的繼承細節並不是徹底由解釋程序處理,你有權決定最適用的繼承方式,好比使用對象冒充(構造函數定義基類屬性和方法)、混合方式(用構造函數定義基類屬性,用原型定義基類方法)
    function ClassA(sColor) {
        this.color = sColor;
    }
    
    ClassA.prototype.sayColor = function () {
        alert(this.color);
    };
    
    function ClassB(sColor, sName) {
        ClassA.call(this, sColor);
        this.name = sName;
    }
    
    ClassB.prototype = new ClassA();
    
    ClassB.prototype.sayName = function () {
        alert(this.name);
    };
    
    var objA = new ClassA("blue");
    var objB = new ClassB("red", "John");
    objA.sayColor();    //輸出 "blue"
    objB.sayColor();    //輸出 "red"
    objB.sayName();    //輸出 "John"
  18. Object.creat()和Object.assign():Object.create(proto, [ propertiesObject ])可用來實現類式單繼承,從指定的原型對象及其屬性去建立一個新的對象;Object.assign(target, ...sources)用於將全部可枚舉的屬性的值從一個或多個源對象複製到目標對象。另外,JSON.parse(JSON.stringify(obj))也能夠用來實現對obj的複製。
    var obj = {
      foo: 1,
      get bar() {
        return 2;
      }
    };
    
    var copy = Object.assign({}, obj); 
    // { foo: 1, bar: 2 }
    // copy.bar的值來自obj.bar的getter函數的返回值 
  19. call、apply和bind:call和apply的用途都是用來調用當前函數,且用法類似,它們的第一個參數都用做 this 對象,但其餘參數call要分開列舉,而apply要以一個數組形式傳遞;bind給當前函數定義預設參數後返回這個新的函數(初始化參數改造後的原函數拷貝),其中預設參數的第一個參數是this指定(當使用new 操做符調用新函數時,該this指定無效),新函數調用時傳遞的參數將位於預設參數以後與預設參數一塊兒構成其所有參數,bind最簡單的用法是讓一個函數不論怎麼調用都有一樣的 this 值。下邊的list()也稱偏函數(Partial Functions):node

    function list() {
      return Array.prototype.slice.call(arguments);
    }
    
    var list1 = list(1, 2, 3); // [1, 2, 3]
    
    // Create a function with a preset leading argument
    var leadingThirtysevenList = list.bind(undefined, 37);
    
    var list2 = leadingThirtysevenList(); // [37]
    var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
  20. Memoization技術: 替代函數中太多的遞歸調用,是一種能夠緩存以前運算結果的技術,這樣咱們就不須要從新計算那些已經計算過的結果。In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. Although related to caching, memoization refers to a specific case of this optimization, distinguishing it from forms of caching such as buffering or page replacement. In the context of some logic programming languages, memoization is also known as tabling.web

    function memoizer(fundamental, cache) {   
      let cache = cache || {},   
          shell = function(arg) {   
              if (! (arg in cache)) {   
                  cache[arg] = fundamental(shell, arg);   
              }   
              return cache[arg];   
          };   
      return shell;   
    } 
  21. 閉包(Closure):詞法表示包括不被計算的變量(上下文環境中變量,非函數參數)的函數,函數可使用函數以外定義的變量。下面以單例模式爲例來說述如何建立閉包
    let singleton = function(){
      let obj;
      return function(){
           return obj || (obj = new Object());
      }
    }();
  22. New Function():若是要用一個字串來新建一個函數,用函數參數來指定this對象:
    let variables = {
        key1: 'value1',
        key2: 'value2'
    },
        fnBody = 'return this.key1 + ":" + this.key2',
        fn = new Function(fnBody);
    console.log(fn.apply(variables));
  23. 高階函數:若是函數定義的部分或所有參數是另外一個函數,或者返回值是另外一個函數,便可認爲該函數是高階函數,如函數柯里化(currying)和反柯里化(uncurrying)。柯里化是把接受多個參數的函數轉換成接受部分參數的函數,並在該函數中返回接受餘下的參數且返回結果的新函數的技術,它可用於延遲計算到最後一次調用
    var currying = function (fn) {
        var _args = [];
        return function () {
            if (arguments.length === 0) {
                return fn.apply(this, _args);
            }
            Array.prototype.push.apply(_args, [].slice.call(arguments));
            return arguments.callee;
        }
    };
    
    var add=function () {
        var total = 0;
        for (var i = 0, c; c = arguments[i++];) {
            total += c;
        }
        return total;
    };
    
    var sum = currying(add);  
      
    sum(100,200)(300);//ƒ () { ... }
    sum(400);//ƒ () { ... }
    sum();//1000
    反柯里化是把函數簽名形式obj.func(arg1, arg2)轉化成新的函數簽名形式func(obj, arg1, arg2),這樣後者比前者更具備通常適應性,而再也不是特殊對象方法
    Function.prototype.uncurrying = function (){
        var _this = this;
        return function (){
            return Function.prototype.call.apply(_this,arguments);
        }
    };
    
    function fn(){
        console.log(`Hello ${this.value},${[].slice.call(arguments)}`);
    }
    
    var uncurryfn = fn.uncurrying();
    
    uncurryfn({value:'World'},'Javascript');  //Hello World,Javascript
  24. DocumentFragment: Roughly speaking, a DocumentFragment is a lightweight container that can hold DOM nodes. 在維護頁面DOM樹時,使用文檔片斷document fragments 一般會起到優化性能的做用shell

    let ul = document.getElementsByTagName("ul")[0],
        docfrag = document.createDocumentFragment();
    
    const browserList = [
        "Internet Explorer", 
        "Mozilla Firefox", 
        "Safari", 
        "Chrome", 
        "Opera"
    ];
    
    browserList.forEach((e) => {
        let li = document.createElement("li");
        li.textContent = e;
        docfrag.appendChild(li);
    });
    
    ul.appendChild(docfrag);
  25.  forEach()遍歷:另外,適當時候也能夠考慮使用for 或 for ... in 或 for ... of 語句結構後端

    1. 數組實例遍歷: arr.forEach(function(item, key){
            //do some things
        })
    2. 非數組實例遍歷: Array.prototype.forEach.call(obj, function(item, key){
            //do some things
        })
    3. 非數組實例遍歷: Array.from(document.body.childNodes[0].attributes).forEach(function(item, key){
            //do some things. Array.from()是ES6新增長的
        })
  26. DOM事件流:Graphical representation of an event dispatched in a DOM tree using the DOM event flow.If the bubbles attribute is set to false, the bubble phase will be skipped, and if stopPropagation() has been called prior to the dispatch, all phases will be skipped.數組

     

    (1) 事件捕捉(Capturing Phase):event經過target的祖先從window傳播到目標的父節點。IE不支持Capturing
    (2) 目標階段(Target Phase):event到達event的target。若是事件類型指示事件不冒泡,則event在該階段完成後將中止
    (3) 事件冒泡(Bubbling Phase):event以相反的順序在目標祖先中傳播,從target的父節點開始,到window結束瀏覽器


    事件綁定:緩存

    1. Tag事件屬性綁定:<button onclick="do some things"></button>
    2. 元素Node方法屬性綁定:btnNode.onclick = function(){//do some things}
    3. 註冊EventListener:btnNode.addEventListener('click', eventHandler, bubble);另外,IE下實現與W3C有點不一樣,btnNode.attachEvent(‘onclick’, eventHandler)
  27. 遞歸與棧溢出(Stack Overflow):遞歸很是耗費內存,由於須要同時保存成千上百個調用幀,很容易發生「棧溢出」錯誤;而尾遞歸優化後,函數的調用棧會改寫,只保留一個調用記錄,但這兩個變量(func.arguments、func.caller,嚴格模式「use strict」會禁用這兩個變量,因此尾調用模式僅在嚴格模式下生效)就會失真。在正常模式下或者那些不支持該功能的環境中,採用「循環」替換「遞歸」,減小調用棧,就不會溢出多線程

    function Fibonacci (n) {
      if ( n <= 1 ) {return 1};
    
      return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
    Fibonacci(10); // 89
    Fibonacci(50);// 20365011074,耗時10分鐘左右才能出結果
    Fibonacci(100);// 這裏就一直出不告終果,也沒有錯誤提示
    
    
    function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
      if( n <= 1 ) {return ac2};
    
      return Fibonacci2 (n - 1, ac2, ac1 + ac2);
    }
    Fibonacci2(100) // 573147844013817200000
    Fibonacci2(1000) // 7.0330367711422765e+208
    Fibonacci2(10000) // Infinity. "Uncaught RangeError:Maximum call stack size exceeded"

    可見,經尾遞歸優化以後,性能明顯提高。若是不能使用尾遞歸優化,可以使用蹦牀函數(trampoline)將遞歸轉換爲循環:蹦牀函數中循環的做用是申請第三者函數來繼續執行未完成的任務,而保證本身函數能夠順利退出。另外,這裏的第三者和本身多是同一函數定義閉包

    function trampoline(f) {
      while (f && f instanceof Function) {
        f = f();
      }
      return f;
    }
    
    //改造Fibonacci2()的定義
    function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
      if( n <= 1 ) {return ac2};
    
      return Fibonacci2.bind(undefined, n - 1, ac2, ac1 + ac2);
    }
    
    trampoline(Fibonacci2(100));//573147844013817200000
  28.  預取與延遲加載:預取如提早加載圖片,當用戶須要查看時可直接從本地渲染,而不須要去服務端下載,它保證了圖片快速、無縫地發佈,讓用戶在瀏覽時得到更好的用戶體驗;延遲加載如先加載小圖或示例圖做爲佔位圖,只有進入瀏覽器可視區域內時,才加載完整圖片並顯示出來,在頁面圖片數量多且比較大的時候很是有用

  29. Debounce 和 Throttle:Debounce 字面意思是去彈跳消除抖動,強制某個連續動做只觸發一次,避免屢次觸發。咱們但願在用戶中止某個操做一段時間以後才執行相應的監聽函數,而不是在用戶操做的過程中,就執行多少次監聽函數。好比你在 3s 內連續地移動了鼠標,瀏覽器會觸發多個 mousemove 事件,執行屢次監聽函數;但若是對監聽函數用 100ms 的Debounce去彈跳後,那麼瀏覽器只會在第 3.1s 的時候執行這個監聽函數一次。下邊是Debounce的實現:
    /**
    *
    * @param fn {Function}   該函數定義了事件觸發後的業務處理邏輯
    * @param delay {Number}  延遲時間,單位是毫秒(ms)
    *
    * @return {Function}     目標事件將要觸發執行的監聽函數
    */
    function debounce(fn, delay) {
      // 定時器,用來 setTimeout
      let timer
      // 返回一個「去彈跳」了的監聽函數
      return function () {
        // 保存函數調用時的上下文和參數,傳遞給 fn
        let context = this,
            args = arguments;
        // 先清除定時器,保證 delay 毫秒內 fn 不執行屢次
        clearTimeout(timer)
        // 用戶中止當前連續操做後的第 delay 毫秒執行 fn
        timer = setTimeout(function () {
          fn.apply(context, args)
        }, delay)
      }
    }
    Throttle 字面意思是節流閥,限制監聽函數執行的頻率,與Debounce不一樣的是,它不是要避免連續動做引發監聽函數屢次執行:
    /**
    *
    * @param fn {Function}   該函數定義了事件觸發後的業務處理邏輯
    * @param delay {Number}  執行間隔,單位是毫秒(ms)
    *
    * @return {Function}     目標事件將要觸發執行的監聽函數
    */
    function throttle(fn, threshhold) {
      // 記錄上次執行的時間
      let last,
      // 定時器
          timer;
      // 默認間隔爲 250ms
      threshhold || (threshhold = 250);
    // 返回一個「經節流」了的監聽函數 return function () { // 保存函數調用時的上下文和參數,傳遞給 fn let context = this, args = arguments, now = +new Date(); // 若是距離上次執行 fn 函數的時間小於 threshhold,那麼就放棄 // 執行 fn,並從新計時 if (last && now < last + threshhold) { clearTimeout(timer) timer = setTimeout(function () { last = now fn.apply(context, args) }, threshhold) } else {// 在連續動做剛開始或超過threshhold間隔時當即執行 fn last = now fn.apply(context, args) } } }
相關文章
相關標籤/搜索