JavaScript 學習記錄

在此以做記錄,有什么問題還望大家指正。
關鍵字:算法、模塊化、閉包、隨機數(shù)、浮動、bind、正則、DOM分析

閉包理解和問題解決

  <ul>
      <li>one</li>
      <li>two</li>
      <li>three</li>
      <li>four</li>
  </ul>

下面的這種寫法會始終彈出 4
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    lists[ i ].onmouseover = function(){
        alert(i);    
    };
}

解決方法一:
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    (function(index){
        lists[ index ].onmouseover = function(){
            alert(index);    
        };                    
    })(i);
}

解決方法二:
var lists = document.getElementsByTagName('li');
for(var i = 0, len = lists.length; i < len; i++){
    lists[ i ].$$index = i;    //通過在Dom元素上綁定$$index屬性記錄下標
    lists[ i ].onmouseover = function(){
        alert(this.$$index);    
    };
}

解決方法三:
function eventListener(list, index){
    list.onmouseover = function(){
        alert(index);
    };
}
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    eventListener(lists[ i ] , i);
} 

當你輸入一個網(wǎng)址的時候,實際會發(fā)生什么?

英文地址
翻譯地址
stackoverflow

  • 第一步當然是輸入網(wǎng)址
  • 第二步瀏覽器查找域名對于的IP地址
  • 第三步瀏覽器給web服務器發(fā)送一個HTTP請求
  • 第四步 facebook服務的永久重定向響應 服務器給瀏覽器響應一個301永久重定向響應,這樣瀏覽器就會訪問“http://www.facebook.com/” 而非“http://facebook.com/”。
  • 第五步瀏覽器跟蹤重定向地址 現(xiàn)在,瀏覽器知道了“http://www.facebook.com/”才是要訪問的正確地址,所以它會發(fā)送另一個獲取請求
  • 第六步服務器"處理"請求 服務器接收到獲取請求,然后處理并返回一個響應。
  • 第七步服務器發(fā)回一個HTML響應 報頭中把Content-type設置為“text/html”。報頭讓瀏覽器將該響應內(nèi)容以HTML形式呈現(xiàn),而不是以文件形式下載它。瀏覽器會根據(jù)報頭信息決定如何解釋該響應,不過同時也會考慮像URL擴展內(nèi)容等其他因素。
  • 第八步瀏覽器開始顯示HTML 在瀏覽器沒有完整接受全部HTML文檔時,它就已經(jīng)開始顯示這個頁面了:
  • 第九步瀏覽器發(fā)送獲取嵌入在HTML中的對象
  • 第十步瀏覽器發(fā)送異步(AJAX)請求

算法說明

  • 時間復雜度指的是一個算法執(zhí)行所耗費的時間
  • 空間復雜度指運行完一個程序所需內(nèi)存的大小
  • 穩(wěn)定指,如果a=b,a在b的前面,排序后a仍然在b的前面
  • 不穩(wěn)定指,如果a=b,a在b的前面,排序后可能會交換位置
//數(shù)組去重
方法1:
function unique(arr){
    var obj = {};
    var temp = [];
    for(var i = 0; i < arr.length; i++){
        if(!obj[arr[i]]){
            obj[arr[i]] = true;
            temp.push(arr[i]);
        }
    }
    return temp;
}

方法2:
[...new Set([2,1,2,1,2,3,1])]
//冒泡排序 兩兩比較 最大或者最小的排在了最后
function bubbleSort(arr){
    for(let i = 0; i < arr.length;i++){
        for(let j = 0; j < arr.length - 1 - i;j++){
            let temp;
            if(arr[j] > arr[j+1]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    return arr;
}
//快速排序  元素為基準, 小的放左邊 大的放右邊
function quickSort(arr){
    if(arr.length <= 1) return arr;
    var pivotIndex = Math.floor(arr.length/2);
    //找基準,并把基準從原數(shù)組刪除 從index開始刪除1個元素
    var pivot = arr.splice(pivotIndex,1)[0];

    var left = [];
    var right = [];

    // 比基準小的放在left 大的放在right
    for(var i = 0; i < arr.length; i++){
        if(arr[i] <= pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    return quickSort(left).concat([pivot],quickSort(right));
}   
// 選擇排序 把每一個數(shù)都與第一個數(shù)比較,如果小于第一個數(shù),就把它們交換位置;這樣一輪下來,最小的數(shù)就排到了最前面;重復n-1輪
function selectSort(arr){
    let len = arr.length;
    let temp;
    for(let i = 0; i < len -1;i++){
        let minIndex = i;
        for(let j = i + 1; j < len; j++){
            if(arr[j] < arr[minIndex]){ //尋找最小的數(shù)
                minIndex = j;           //將最小數(shù)的索引保存
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}
// 插入排序:

// 解析:

//  (1) 從第一個元素開始,該元素可以認為已經(jīng)被排序

//  (2) 取出下一個元素,在已經(jīng)排序的元素序列中從后向前掃描

//  (3) 如果該元素(已排序)大于新元素,將該元素移到下一位置

//  (4) 重復步驟3,直到找到已排序的元素小于或者等于新元素的位置

//  (5)將新元素插入到下一位置中

//  (6) 重復步驟2

function insertSort(arr){
// 假設第0個元素是一個有序的數(shù)列,第1個以后是無序的序列
// 所以第一個元素開始講無序序列的元素插入到有序數(shù)列中
    for(let i = 1; i < arr.length;i++){
        // 升序
        if(arr[i] < arr[i - 1]){
            // 取出無序序列的第i個元素作為被插入元素
            var guard = arr[i];
            // 記住有序序列的最后一個位置 并且將有序序列位置擴大一個
            var j = i - 1;
            arr[i] = arr[j];
            while(j > 0 && guard < arr[j]){
                arr[j+1] = arr[j];
                j--;
            }
            arr[j+1] = guard;
        }
    }
    return arr;
}

清除浮動

  • 01
.classfix{
    clear:both;
}
<div class="clearfix"></div>
  • 02:
    clearfix的方法:使用overflow進行包裹所有的浮動子元素。有誤傷。
    Ckearfix的使用場景:
    1、父盒子要把所有的子盒子包裹住。
    2、父盒子是包裹正行的 div元素,需要前后進行清除浮動。
.clearfix {
    display:table;//觸發(fā)bfc,div具有的包裹性
}
.clearfix:before,.clearfix:after{
    conotent:'';
    display: block;
    clear:both;
    height:0;
    visibility:hidden;
}
.clearfix {
    _zoom:1;
}

模塊化

// Javascript模塊化編程
// 1. 對于早期來說 我們一般函數(shù)的寫法如下

function m1() {}

function m2() {}
// 函數(shù)m1 m2組成了一個模塊, 直接使用調(diào)用即可, 但是
// 這樣的話會污染全局變量,無法保證不和其他模塊的變量
// 名沖突,而且也看不出和其他模塊成員的關系

// 2. 那我們將模塊寫成對象,所以模塊成員放到這個對象當中
var modul1 = new Object({
        _count: 0,
        m1: function() {},
        m2: function() {}
    })
    // 這里講m1 m2封裝到module1里面,使用的時候調(diào)用就可以
module.m1();
// 這樣的話會暴露所有模塊的成員,內(nèi)部狀態(tài)可以被改寫,比如
// 外部可以直接修改內(nèi)部計數(shù)器的數(shù)值
modul1._count = 5;

// 3. 立即執(zhí)行函數(shù)可以達到不暴露私有成員的目的
var module1 = (function() {
    var _count = 1;
    var m1 = function() {};
    var m2 = function() {};
    return {
        m1: m1,
        m2: m2
    }
})();
// 這種寫法 外部代碼就無法訪問內(nèi)部_count 變量
console.info(modul1._count);


// 4. 那么接下來, 如果模塊很大的話, 可能繼承自其他模塊,
// 這個時候可以采用"放大模式"

var module1 = (function(mod) {
    mod.m3 = function() {};
    return mod;
})(module1);

// 在不影響原來模塊的基礎上, 添加新的方法, 返回新的module1模塊

// 5. 但是上面的這種寫法有一個弊病, 在瀏覽器環(huán)境中, 模塊的各個部分通常都是從網(wǎng)上獲取的, 有時無法知道哪個部分會先加載, 如果采用上面的方法, 第一個執(zhí)行的部分有可能加載一個不存在空對象, 這時就要采用 "寬放大模式"。
var module1 = (function(mod) {
    // ...
    return mod;
})(window.modul1 || {});

// 與放大模式相比, 寬放大模式就是立即執(zhí)行函數(shù)的參數(shù)可能是空對象

// 6. 輸入全局變量
// 獨立性是模塊的重要特點,模塊內(nèi)部最好不和程序其他部分直接交互
// 為了在模塊內(nèi)部調(diào)用全局變量,必須顯示將其他變量輸入模塊

var module1 = (function($, YAHOO) {
    //...
})(jQuery, YAHOO);
// 上面的module1模塊需要使用jQuery庫和YUI庫,就把這兩個庫(其實是兩個模塊)當作參數(shù)輸入module1。這樣做除了保證模塊

作用域

一般會問的是 es6 中 let 與 var 的區(qū)別, 或者列舉代碼, 然后通過對代碼的解讀來看你對作用域的掌握比較方便.

  • let所聲明的變量,只在let命令所在的代碼塊內(nèi)有效。
  • var命令會發(fā)生”變量提升“現(xiàn)象,即變量可以在聲明之前使用,值為undefined。這種現(xiàn)象多多少少是有些奇怪的,按照一般的邏輯,變量應該在聲明語句之后才可以使用。

為了糾正這種現(xiàn)象,let命令改變了語法行為,它所聲明的變量一定要在聲明后使用,否則報錯。

// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;

// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;

上面代碼中,變量foo用var命令聲明,會發(fā)生變量提升,即腳本開始運行時,變量foo已經(jīng)存在了,但是沒有值,所以會輸出undefined。變量bar用let命令聲明,不會發(fā)生變量提升。這表示在聲明它之前,變量bar是不存在的,這時如果用到它,就會拋出一個錯誤。

  • 只要塊級作用域內(nèi)存在let命令,它所聲明的變量就“綁定”(binding)這個區(qū)域,不再受外部的影響。
var tmp = 123;
if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面代碼中,存在全局變量tmp,但是塊級作用域內(nèi)let又聲明了一個局部變量tmp,導致后者綁定這個塊級作用域,所以在let聲明變量前,對tmp賦值會報錯。

ES6明確規(guī)定,如果區(qū)塊中存在let和const命令,這個區(qū)塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。

總之,在代碼塊內(nèi),使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區(qū)”(temporal dead zone,簡稱 TDZ)。

“暫時性死區(qū)”也意味著typeof不再是一個百分之百安全的操作。

typeof x; // ReferenceError
let x;

上面代碼中,變量x使用let命令聲明,所以在聲明之前,都屬于x的“死區(qū)”,只要用到該變量就會報錯。因此,typeof運行時就會拋出一個ReferenceError。

作為比較,如果一個變量根本沒有被聲明,使用typeof反而不會報錯。

typeof undeclared_variable // "undefined"

上面代碼中,undeclared_variable是一個不存在的變量名,結果返回“undefined”。所以,在沒有l(wèi)et之前,typeof運算符是百分之百安全的,永遠不會報錯?,F(xiàn)在這一點不成立了。這樣的設計是為了讓大家養(yǎng)成良好的編程習慣,變量一定要在聲明之后使用,否則就報錯。

// 不報錯
var x = x;

// 報錯
let x = x;
// ReferenceError: x is not defined

上面代碼報錯,也是因為暫時性死區(qū)。使用let聲明變量時,只要變量在還沒有聲明完成前使用,就會報錯。上面這行就屬于這個情況,在變量x的聲明語句還沒有執(zhí)行完成前,就去取x的值,導致報錯”x 未定義“。

ES6 規(guī)定暫時性死區(qū)和let、const語句不出現(xiàn)變量提升,主要是為了減少運行時錯誤,防止在變量聲明前就使用這個變量,從而導致意料之外的行為。這樣的錯誤在 ES5 是很常見的,現(xiàn)在有了這種規(guī)定,避免此類錯誤就很容易了。

總之,暫時性死區(qū)的本質(zhì)就是,只要一進入當前作用域,所要使用的變量就已經(jīng)存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量。

  • let不允許在相同作用域內(nèi),重復聲明同一個變量。
// 報錯
function () {
  let a = 10;
  var a = 1;
}
// 報錯
function () {
  let a = 10;
  let a = 1;
}

因此,不能在函數(shù)內(nèi)部重新聲明參數(shù)。

function func(arg) {
  let arg; // 報錯
}

function func(arg) {
  {
    let arg; // 不報錯
  }
}

AMD CMD CommonJs

AMD/CMD/CommonJs是JS模塊化開發(fā)的標準,目前對應的實現(xiàn)是RequireJs/SeaJs/nodeJs.

CommonJs主要針對服務端,AMD/CMD主要針對瀏覽器端,所以最容易混淆的是AMD/CMD。(順便提一下,
針對服務器端和針對瀏覽器端有什么本質(zhì)的區(qū)別呢?服務器端一般采用同步加載文件,也就是說需要某個模塊,
服務器端便停下來,等待它加載再執(zhí)行。這里如果有其他后端語言,如java,經(jīng)驗的‘玩家’應該更容易理解。
而瀏覽器端要保證效率,需要采用異步加載,這就需要一個預處理,提前將所需要的模塊文件并行加載好。)

 AMD/CMD區(qū)別,雖然都是并行加載js文件,但還是有所區(qū)別,AMD是預加載,在并行加載js文件同時,還會
解析執(zhí)行該模 塊(因為還需要執(zhí)行,所以在加載某個模塊前,這個模塊的依賴模塊需要先加載完成);
而CMD是懶加載,雖然會一開 始就并行加載js文件,但是不會執(zhí)行,而是在需要的時候才執(zhí)行。


AMD/CMD的優(yōu)缺點.一個的優(yōu)點就是另一個的缺點, 可以對照瀏覽。
AMD優(yōu)點:加載快速,尤其遇到多個大文件,因為并行解析,所以同一時間可以解析多個文件。
 AMD缺點:并行加載,異步處理,加載順序不一定,可能會造成一些困擾,甚至為程序埋下大坑。

CMD優(yōu)點:因為只有在使用的時候才會解析執(zhí)行js文件,因此,每個JS文件的執(zhí)行順序在代碼中是有體現(xiàn)的,是可控的。
CMD缺點:執(zhí)行等待時間會疊加。因為每個文件執(zhí)行時是同步執(zhí)行(串行執(zhí)行),因此時間是所有文件
解析執(zhí)行時間之和,尤其在文件較多較大時,這種缺點尤為明顯。

https://www.zhihu.com/question/20351507/answer/14859415

如何使用?CommonJs的話,因為nodeJs就是它的實現(xiàn),所以使用node就行,也不用引入其他包。
AMD則是通過<script>標簽引入RequireJs,具體語法還是去看官方文檔或者百度一下吧。
CMD則是引入SeaJs。

exports 和 module.exports 的區(qū)別:

  • module.exports 初始值為一個空對象 {}
  • exports 是指向的 module.exports 的引用
  • require() 返回的是 module.exports 而不是 exports
我們經(jīng)常看到這樣的寫法:
exports = module.exports = somethings
上面的代碼等價于:
module.exports = somethings
exports = module.exports
原理很簡單,即 module.exports 指向新的對象時,exports 斷開了與 module.exports 的引用,那么通過 exports = module.exports 讓 exports 重新指向 module.exports 即可。

module.exports區(qū)別

CommonJS是服務器端模塊的規(guī)范,Node.js采用了這個規(guī)范。
根據(jù)CommonJS規(guī)范,一個單獨的文件就是一個模塊。加載模塊使用require方法,該方法讀取一個文件并執(zhí)行,最后返回文件內(nèi)部的exports對象。

CommonJS 加載模塊是同步的,所以只有加載完成才能執(zhí)行后面的操作。像Node.js主要用于服務器的編程,加載的模塊文件一般都已經(jīng)存在本地硬盤,所以加載起來比較快,不用考慮異步加載的方式,所以CommonJS規(guī)范比較適用。但如果是瀏覽器環(huán)境,要從服務器加載模塊,這是就必須采用異步模式。所以就有了 AMD CMD 解決方案。

隨機抽獎

Knuth-Durstenfeld Shuffle
Fisher-Yates 洗牌算法的一個變種是 Knuth Shuffle
每次從未處理的數(shù)組中隨機取一個元素,然后把該元素放到數(shù)組的尾部,即數(shù)組的尾部放的就是已經(jīng)處理過的元素,這是一種原地打亂的算法,每個元素隨機概率也相等,時間復雜度從 Fisher 算法的 O(n2)提升到了 O(n)

選取數(shù)組(長度n)中最后一個元素(arr[length-1]),將其與n個元素中的任意一個交換,此時最后一個元素已經(jīng)確定
選取倒數(shù)第二個元素(arr[length-2]),將其與n-1個元素中的任意一個交換
重復第 1 2 步,直到剩下1個元素為止

functuin shuffle(arr){
  var length = arr.length, temp, random;
  while(0 != length){
    random = Math.floor(Math.random()*length);
    length--;
    temp = arr[length];
    arr[length] = arr[random]l
    arr[random] = temp;
  }
  return arr;
}

Array.sort()
利用Array的sort方法可以更簡潔的實現(xiàn)打亂,對于數(shù)量小的數(shù)組來說足夠。因為隨著數(shù)組元素增加,隨機性會變差。

[1,3,14,11,2,4,5].sort(()=>{
  return .5 - Math.random();
})
Math.round(Math.random() * 1000)
表示產(chǎn)生一個隨機數(shù)并把它放大1000倍再取整,即生成0~1000之間的隨機整數(shù)。

((Math.random() * 10 + 5).toFixed(1) - 0)
表示產(chǎn)生一個5到15之間,包含一位小數(shù)的隨機數(shù)。
分步解釋一下,先產(chǎn)生一個隨機數(shù)把它乘以10再加上5,即生成5~15的隨機數(shù),然后調(diào)用toFixed轉換成保留1位小數(shù)的字符串形式,最后減0是做了隱式轉換,把字符串再轉換為數(shù)字。

不需要洗所有的牌

function draw(amount,n=1){
  const cards = Array(amount).fill().map((_,i)=>i+1);
  for(let i = amount - 1, stop = amount - n - 1;i > stop;i--){
    let rand = Math.floor((i+1)*Math.random());
    [cards[rand],cards[i]] = [cards[i],cards[rand]];
  }
  return cards.slice(-n);
}
console.log(draw(62,10));
function shuffle(amount,n=1){
  const arr = Array(amount).fill().map((_,i)=>i+1); 建立長度為amount的數(shù)組
  for(var i = arr.length - 1,stop = amount - n - 1;i >stop;i--){
    var random = Math.floor((i+1)*Math.random());
    [arr[random],arr[i]] = [arr[i],arr[random]];
  }
  return arr.slice(-n);
}

shuffle(12,10);

Array.prototype.shuffle = function() {
    var input = this;
    for (var i = input.length-1; i >=0; i--) {
        var randomIndex = Math.floor(Math.random()*(i+1)); 
        [input[randomIndex], input[i]] = [input[i], input[randomIndex]] ; 
    }
    return input;
}
var tempArray = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
tempArray.shuffle();
console.log(tempArray);  

上面這個版本是優(yōu)化過的版本,顯然如果取 10 個數(shù),只需要循環(huán) 10 次即可,不需要把 64 張牌都洗了。

要解決可以連續(xù)抽獎的問題,就需要把 cards 提取出來(就像方案 1 的隨機抽取一樣),但是那樣的話就使得函數(shù)有副作用,雖說是臨時寫一個抽獎,也不喜歡設計得太糙?;蛘?,那就加一個構造器執(zhí)行初始化?

function Box(amount){
  this.cards = Array(amount).fill().map((_,i)=>i+1);
}
Box.prototype.draw = function(n = 1){
  let cards = this.cards,amount = this.cards.length;
  for(let i = amount - 1, stop = amount - n - 1; i > stop; i--){
    let rand = Math.floor((i + 1) * Math.random());
    [cards[rand], cards[i]] =  [cards[i], cards[rand]];
 }
 let ret = cards.slice(-n);  //截取最后的十個數(shù)字
 cards.length = amount - n;  //數(shù)組長度減少 去掉部分值
 return ret;
}
var box = new Box(62);
console.log(box.draw(5),box.draw(5));

JS bind

bind()方法創(chuàng)建一個新的函數(shù), 當被調(diào)用時,它的this關鍵字被設置為提供的值 ,在調(diào)用新函數(shù)時,提供任何一個給定的參數(shù)序列。
語法

fun.bind(thisArg[, arg1[, arg2[, ...]]])

參數(shù)

thisArg
當綁定函數(shù)被調(diào)用時,該參數(shù)會作為原函數(shù)運行時的 this 指向。當使用new 操作符調(diào)用綁定函數(shù)時,該參數(shù)無效。
arg1, arg2, ...
當綁定函數(shù)被調(diào)用時,這些參數(shù)將置于實參之前傳遞給被綁定的方法。

返回值
返回由指定的this值和初始化參數(shù)改造的原函數(shù)拷貝

描述
bind() 函數(shù)會創(chuàng)建一個新函數(shù)(稱為綁定函數(shù)),新函數(shù)與被調(diào)函數(shù)(綁定函數(shù)的目標函數(shù))具有相同的函數(shù)體(在 ECMAScript 5 規(guī)范中內(nèi)置的call屬性)。當目標函數(shù)被調(diào)用時 this 值綁定到 bind() 的第一個參數(shù),該參數(shù)不能被重寫。綁定函數(shù)被調(diào)用時,bind() 也接受預設的參數(shù)提供給原函數(shù)。一個綁定函數(shù)也能使用new操作符創(chuàng)建對象:這種行為就像把原函數(shù)當成構造器。提供的 this 值被忽略,同時調(diào)用時的參數(shù)被提供給模擬函數(shù)。

示例
創(chuàng)建綁定函數(shù)
bind() 最簡單的用法是創(chuàng)建一個函數(shù),使這個函數(shù)不論怎么調(diào)用都有同樣的 this 值。JavaScript新手經(jīng)常犯的一個錯誤是將一個方法從對象中拿出來,然后再調(diào)用,希望方法中的 this 是原來的對象。(比如在回調(diào)中傳入這個方法。)如果不做特殊處理的話,一般會丟失原來的對象。從原來的函數(shù)和原來的對象創(chuàng)建一個綁定函數(shù),則能很漂亮地解決這個問題:

this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 返回 81

var retrieveX = module.getX;
retrieveX(); // 返回 9, 在這種情況下,"this"指向全局作用域

// 創(chuàng)建一個新函數(shù),將"this"綁定到module對象
// 新手可能會被全局的x變量和module里的屬性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81

偏函數(shù)(Partial Functions)
bind()的另一個最簡單的用法是使一個函數(shù)擁有預設的初始參數(shù)。這些參數(shù)(如果有的話)作為bind()的第二個參數(shù)跟在this(或其他對象)后面,之后它們會被插入到目標函數(shù)的參數(shù)列表的開始位置,傳遞給綁定函數(shù)的參數(shù)會跟在它們的后面。

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]

配合 setTimeout

在默認情況下,使用 window.setTimeout() 時,this 關鍵字會指向 window (或全局)對象。當使用類的方法時,需要 this 引用類的實例,你可能需要顯式地把 this 綁定到回調(diào)函數(shù)以便繼續(xù)使用實例。

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒鐘后, 調(diào)用'declare'方法

bind和call以及apply一樣,都是可以改變上下文的this指向的。不同的是,call和apply一樣,直接引用在方法上,而bind綁定this后返回一個方法,但內(nèi)部核心還是apply。

var obj = {
  a: 1,
  b: 2,
  getCount: function(c, d) {
    return this.a + this.b + c + d;
  }
};
c = obj.getCount;
console.log(func(3, 4));  // 7

為何會這樣?因為func在上下文中的this是window!bind的存在正是為了改變this指向獲取想要的值:

var obj = {
  a: 1,
  b: 2,
  getCount: function(c, d) {
    return this.a + this.b + c + d;
  }
};
 
window.a = window.b = 0;
var func = obj.getCount.bind(obj);
console.log(func(3, 4));  // 10

低版本ie6~8兼容寫法:

var obj = {
  a:1,
  b:2,
  getCount:function(c,d){
    return this.a + this.b + c + d;
  }
}
Function.prototype.bind = Function.prototype.bind || function(context){
  var that = this;
  return function(){
    return that.apply(context,arguments);
  }
}
window.a = window.b = 0;
var func = obj.getCount.bind(obj);
console.log(func(3,4));

bind的核心是返回一個未執(zhí)行的方法,如果直接使用apply或者call:

var ans = obj.getCount.apply(obj, [3, 4]);
console.log(ans); // 10

函數(shù)前加上波浪號,其作用是把函數(shù)聲明轉換為表達式,這樣就可以直接運行。

for(var i = 0;i < 10; i++){
  ~function(i){
    setTimeout(function(){
      console.log(i);
    },i*1000);
  }(i)
}

bind實現(xiàn)

for(var i = 0; i < 10;i++){
  setTimeout(console.log.bind(console,i),i*1000)
}

bind是和apply、call一樣,是Function的擴展方法,所以應用場景是func.bind(),而傳的參數(shù)形式和call一樣,第一個參數(shù)是this指向,之后的參數(shù)是func的實參,fun.bind(thisArg[, arg1[, arg2[, ...]]])。

正則表達式

簡寫形式的元字符:
//
// \w 文字 \W 代表的是非文字 word 文字 默認表示英文和下劃線
// \d 數(shù)字 \D 代表的是非數(shù)字 digit 數(shù)字
// \s 空白 \S 代表的是非空白 spcae 空白

        . 代表除了換行符以外的任何字符
        // 有些開發(fā)者使用 [\s\S] 表示增強的 .  ---> [\s\S] 代表任意的一個字符
        
        
        var get = function ( str ) {
            
            //            1                   2                 3
            var r = /^(?:#([\w\-]+)|\.([\w\-]+)|([\w\-]+))$/;

// var r = /^(?:#([\w-]+)|.([\w-]+)|([\w-]+)|(*)+)$/;
var m = r.exec( str );

            if ( m[ 1 ]  ) {
                return [ document.getElementById( m[ 1 ] ) ];
            } else if ( m[ 2 ] ) {
                return document.getElementsByClassName( m[ 2 ] );
            } else if ( m[ 3 ] ){
                return document.getElementsByTagName( m[ 3 ] );
            }
            
        };

// 分組
// 1> 基本元字符
// . [] () |
. 代表任意的非 換行字符
[]
() 代表分組 和 提高優(yōu)先級 , 如果僅僅是為了提高優(yōu)先級 不捕獲的話 , 需要使用(?:) 取消捕獲
| 代表二者中的一個

// 2> 限定元字符
// + 前面緊跟的字符或組至少 1 個 {1,}
// * 前面緊跟的字符或組至少 0 個 {0,}
// ? 前面緊跟的字符或組出現(xiàn)0次或1次; 如果跟在其他限定符后面表示取消貪婪模式
// {0,1}
// {n} 前面緊跟的字符或組 n 個
// {n,} 前面緊跟的字符或組至少 n 個
// {n,m} 前面緊跟的字符或組 n 到 m 個

// 3> 首尾元字符
// ^ hat 字符串里表示以什么什么開頭 方括號里面表示以什么開頭
// $ 字符串以什么結尾 引導符

// 4> 簡寫元字符
// \w \W
// \s \S
// \d \D

性能分析

DOM 設置屬性
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            .c {
                border: 1px solid red;
                width: 400px;
                height: 150px;
            }
        </style>
        <script type="text/javascript">
            
            // 2, 在頁面中創(chuàng)建 3 個 div, 要求 設置其邊框與顏色以及大小
            // 1> 直接設置 style 屬性
            // 2> 使用 類樣式
            // -> setAttribute
            // -> .語法
            
//          onload = function () {
//              var i, node;
//              for ( i = 0; i < 3; i++ ) {
//                  node = document.createElement( 'div' );
//                  // node.setAttribute( 'class', 'c' );
//                  node.className = 'c';
//                  document.body.appendChild( node );
//              }
//          };
            
            
            // 1, 方法比較多, 練習的過程的中每一個做法都要熟練
            // 2, 由于每次循環(huán)都使用 document.body.appenChild 因此
            //      會導致每次 for 都要刷新頁面結構. 應該采用一個臨時的數(shù)據(jù)
            //      存儲這些 dom 對象, 在 全部創(chuàng)建完成以后, 再一并加入
                    
                    
            // 只有創(chuàng)建一個 節(jié)點標簽, 才可以不影響 整個頁面布局, 同時允許存儲其他標簽
//          onload = function () {
//              var i, node, container = document.createElement( 'div' );
//              for ( i = 0; i < 3; i++ ) {
//                  node = document.createElement( 'div' );
//                  // node.setAttribute( 'class', 'c' );
//                  node.className = 'c';
//                  container.appendChild( node );
//              }
//              document.body.appendChild( container );
//          };

            // 用于緩存文檔片段的 DOM 對象 DocumentFragment
            onload = function () {
                var i, node, 
                    container = document.createDocumentFragment();
                    
                for ( i = 0; i < 3; i++ ) {
                    node = document.createElement( 'div' );
                    // node.setAttribute( 'class', 'c' );
                    node.className = 'c';
                    container.appendChild( node );
                }
                document.body.appendChild( container );
            };
        </script>
    </head>
    <body>
    </body>
</html>
或者使用下面的innerHTML:
    <script type="text/javascript">
        onload = function () {
            var i,s = '';
            for (var i = 0; i < 10; i++) {
                s += '<div>' + i + '</div>';
            }
            document.body.innerHTML = s;
        }
    </script>
性能分析:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script>
            
            // 在頁面中添加 1000 個div
            
            var count = 50000;
            
            var test1 = function () {
                var start = +new Date(),
                    i, end, node, docfrag;
                
                docfrag = document.createDocumentFragment();
                for ( i = 0; i < count; i++ ) {
                    node = document.createElement( 'div' );
                    docfrag.appendChild( node );
                }
                document.body.appendChild( docfrag );
                
                end = +new Date();
                
                console.log( 'test1 = ' + ( end - start ) );
            };
            
            
            var test2 = function () {
                var start = +new Date(),
                    i, end, s;
                
                s = '';
                for ( i = 0; i < 1000; i++ ) {
                    s += '<div></div>';
                    // document.body.innerHTML += '<div></div>';
                }
                document.body.innerHTML = s;
                
                end = +new Date();
                
                console.log( 'test2 = ' + ( end - start ) );
            };
            
            
            onload = function() {
                // test1();
                test2()
            };
        </script>
    </head>
    <body>
    </body>
</html>
dom中創(chuàng)建代碼:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript">
            
            // jq 中
            // $( '<div style=""></div>' );
            
            // itcast -> I
            // var dom = I( '<a  )
            
            var cElem = function ( html ) {
                // 1, 在內(nèi)部創(chuàng)建一個 docfrag
                var docfrag = document.createDocumentFragment();
                // 2, 創(chuàng)建真正的 div, 然后設置其 innerHTMl 為出入的字符串
                // 然后在遍歷該子元素, 將內(nèi)容加入到 docfrag 中
                var div = document.createElement( 'div' );
                // 3, 將字符串設置為 它的 innerHTML
                div.innerHTML = html;
                // 4, 遍歷div的子元素, 加入 docfrag 下面為什么這么寫呢
                // 因為在 DOM 元素中默認有一個特征, 即元素只允許有一個 父節(jié)點
                // 如果添加元素到另一個節(jié)點中, 該元素會自動的離開原來的父節(jié)點
                //最好不用for循環(huán)
                while( div.firstChild ) {
                    docfrag.appendChild( div.firstChild );
                }
                // 5, 獲得其子元素返回
                return docfrag;
            };
            
        </script>
    </head>
    <body>
    </body>
    <script>
        var dom = cElem( '<a >一個鏈接</a><br />' + 
                         '<a   );
                         
        document.body.appendChild( dom );
        
        // I( ... ).appendTo( 'body' );
    </script>
</html>
解析while 
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript">
            
            var id = function ( id ) {
                return document.getElementById( id );
            };
            
            
            onload = function () {
                var d1 = id( 'dv1' );
                var d2 = id( 'dv2' );
                var list = d1.getElementsByTagName("p");
                var len;
                // dom 節(jié)點只能有一個父節(jié)點 你把這個節(jié)點append到了
                //另一個節(jié)點上 ,當前這個節(jié)點就少了一個節(jié)點
                //i繼續(xù)累加 節(jié)點本身length也在減少 總在變化
                //所以這里讓他固定不動就可以了
                //這種寫法不太好
                //比較好使用while循環(huán)
//              for ( var i = 0, len = list.length; i < len; i++ ) {
//                  
//                  d2.appendChild( list[ 0 ] );
//                  
//              }
                
//              while ( list[ 0 ] ) {
//                  d2.appendChild( list[ 0 ] );
//              }
                
                while ( d1.firstChild ) {
                    d2.appendChild( d1.firstChild );
                }
            };
            
        </script>
    </head>
    <body>
        <div id="dv1">
            <p>p1</p>
            <p>p2</p>
            <p>p3</p>
            <p>p4</p>
        </div>
        <div id="dv2">
            
        </div>
    </body>
</html>
抽離出一種方法, 分析:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript">
            
            // 將字符串轉換成 DOM 對象
            var cElem = function ( html ) {
                var docfrag = document.createDocumentFragment();
                var div = document.createElement( 'div' );
                div.innerHTML = html;
                while( div.firstChild ) {
                    docfrag.appendChild( div.firstChild );
                }
                return {
                    element: docfrag,
                    appendTo: function ( dom ) {
                        dom.appendChild( this.element );
                    }
                };
            };
            
            
            
            // cElem( '...' ).appendTo( document.body );
            // 函數(shù)返回 DOM 對象, 沒有該方法
            // 但是現(xiàn)在需要該方法. 在原型中添加??? 在哪一個原型中添加呢?
            // 首先不確定 dom 對象的共有原型, 同時可能引起原型鏈搜索性能問題
            // 其次開發(fā)的原則是不影響內(nèi)置對象成員
            
            // 因此不應該直接在 DOM 對象上添加成員
            
            // 給 DOM 對象提供一個包裝對象
            // 可以考慮將 cELem 函數(shù)返回的對象做一個修改, 然后其是一個 自定義對象
            // 該對象中有 appendTo 方法
            
            
            onload = function () {
                
                cElem( '<div style="border: 1px solid red; width: 200px; height: 100px;"></div>' )
                    .appendTo( document.body );
                    
            };
        </script>
    </head>
    <body>
    </body>
</html>
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評論 19 139
  • 弘丹參考的是錢穆的《論語新解》以及傅佩榮的《人能弘道-傅佩榮談論語》,綠窗幽夢參考的是朱熹的《四書章句集注》,由弘...
    弘丹閱讀 802評論 0 5
  • (1.14)子曰:君子食無求飽,居無求安,敏于事而慎于言,就有道而正焉,可謂好學也已。 孔子說:一個君子,飲食不求...
    瀚王閱讀 1,144評論 0 1
  • 今天就是個失控的一天。工作、情緒似乎就跟著一個字走一一背! 做為一個物流業(yè)者,跟司機、客戶都似乎要斗智斗勇斗體力!...
    獅子座秋秋閱讀 154評論 3 2

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