成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JS語言缺陷

I_Am / 616人閱讀

摘要:語言缺陷是一門在極短時(shí)間里創(chuàng)造的腳本語言,它存在很多的不足,這使得在學(xué)習(xí)時(shí)無形加大了學(xué)習(xí)的難度,本文就將這些內(nèi)容進(jìn)行總結(jié),以防繼續(xù)掉坑。

JS語言缺陷
js是一門在極短時(shí)間里創(chuàng)造的腳本語言,它存在很多的不足,這使得在學(xué)習(xí)時(shí)無形加大了學(xué)習(xí)的難度,本文就將這些內(nèi)容進(jìn)行總結(jié),以防繼續(xù)掉坑。
1.變量提升 1.1 案例分析

先來說一下變量提升,它其實(shí)就是先用后聲明,經(jīng)常被拿來說明的一個(gè)例子是:

console.log(a);
var a = 10;//undefined

這是由于這段代碼在執(zhí)行的時(shí)候,js解析器會(huì)先把var聲明放在前面,然后順序執(zhí)行對(duì)應(yīng)的語句,執(zhí)行到console的時(shí)候,由于a變量已經(jīng)聲明提升但未進(jìn)行賦值操作,在js中這種情況就會(huì)報(bào)undefined
上面是對(duì)出錯(cuò)的解釋,接下來就細(xì)細(xì)說明一下變量提升的具體內(nèi)容
先來說一下什么是變量,變量就是存放數(shù)據(jù)的空間,在這個(gè)空間里,可以存放具體的數(shù)據(jù),也可以存放數(shù)據(jù)對(duì)應(yīng)的地址,這實(shí)際上是對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)中的堆棧,棧數(shù)據(jù)少可以直接將數(shù)據(jù)存放進(jìn)來,堆數(shù)據(jù)多,所以另開空間存放,然后把數(shù)據(jù)對(duì)應(yīng)的內(nèi)存地址放在棧內(nèi),在賦值時(shí),棧類型的數(shù)據(jù)會(huì)直接把數(shù)據(jù)拷貝一份然后進(jìn)行賦值,而堆類型的數(shù)據(jù)會(huì)把地址復(fù)制一份,然后不同的變量會(huì)指向同一個(gè)地址,在js中對(duì)象,函數(shù),數(shù)組等都是堆類型數(shù)據(jù),也叫引用類型數(shù)據(jù),下面直接在控制臺(tái)寫個(gè)小例子看看:

//基本類型
//ab彼此修改值的時(shí)候相互不影響
var a = 1,b;
b=a;
console.log(a,b)//1 1
a = 2;
console.log(a,b)//2,1 
//引用類型
//obj修改值時(shí)會(huì)相互影響
var obj1 = new Object();
var obj2 = new Object();
obj1.name="kk"
obj2=obj1
obj2.sex="male"
console.log(obj1)//{name: "kk", sex: "male"}

弄清楚了堆棧的區(qū)別,就可以來繼續(xù)看變量提升的問題了,在js中變量包括基本的數(shù)據(jù)類型和引用的數(shù)據(jù)類型,并且function被設(shè)置為一等公民,也就是說,在聲明變量時(shí)函數(shù)變量的等級(jí)比其他變量的等級(jí)高,函數(shù)的創(chuàng)建又有兩種方式一種是函數(shù)聲明,另一種是函數(shù)表達(dá)式,在變量提升的時(shí)候,只會(huì)提升聲明而不會(huì)提升表達(dá)式:

//函數(shù)聲明
function say(){
  console.log("saying");
}

//函數(shù)表達(dá)式
var say = function(){
  console.log("saying");
}

到這里先來總結(jié)一下,為了理解變量提升,先了解了變量是什么,變量類型有哪些,函數(shù)創(chuàng)建的形式有什么,接下來就可來檢驗(yàn)一下,看是否真的懂了:

var name = "kk";

function say(){
  console.log(name); //輸出:undefined
  var name = "zoe";
  console.log(name); //輸出:"zoe"
}

say();

來解釋一下為什么:

//1.var name;
//2.發(fā)現(xiàn)有函數(shù)聲明,函數(shù)等級(jí)高所以function say();var name;
//3.function say();var name;var name;
//4.say()調(diào)用函數(shù)
//5.此時(shí)name聲明未賦值,所以是undefined
//6.var name = "zoe"
//7.由于此時(shí)name被賦值了,直接打印zoe
//8.var name = "kk"

再來看一個(gè)例子:

var say = function(){
  console.log("1");
};

function say(){
  console.log("2");
};

say(); //輸出:"1"

說一下為什么:

//1.var say;
//2.函數(shù)聲明比變量聲明等級(jí)高,所以function say();var say;
//3.function say()聲明未賦值
//4.var say = function(){}賦值
//5.console.log(1)
//6.function say()賦值
1.2編譯器和解析器

為什么會(huì)出現(xiàn)這種現(xiàn)象呢?從js代碼到瀏覽器識(shí)別js代碼發(fā)生了什么?這個(gè)涉及到編譯原理,大概分成兩個(gè)部分,一是將js代碼生成AST樹,二是將AST樹變成瀏覽器能理解的內(nèi)容,前者叫編譯器,后者叫解釋器,如果自己來設(shè)計(jì),你會(huì)如何處理js代碼呢?這里提供一種思路,那就是把所有的js代碼的信息都記錄下來,然后把他生成一個(gè)樹狀結(jié)構(gòu),也就是我們所說的AST樹,這樣說太抽象了,舉例看看:

//js代碼
if (1 > 0) {
  alert("aa");
}
//ast樹
{
  "type": "Program",
  "start": 0,
  "end": 29,
  "body": [
    {
      "type": "IfStatement",
      "start": 0,
      "end": 29,
      "test": {
        "type": "BinaryExpression",
        "start": 4,
        "end": 9,
        "left": {
          "type": "Literal",
          "start": 4,
          "end": 5,
          "value": 1,
          "raw": "1"
        },
        "operator": ">",
        "right": {
          "type": "Literal",
          "start": 8,
          "end": 9,
          "value": 0,
          "raw": "0"
        }
      },
      "consequent": {
        "type": "BlockStatement",
        "start": 11,
        "end": 29,
        "body": [
          {
            "type": "ExpressionStatement",
            "start": 15,
            "end": 27,
            "expression": {
              "type": "CallExpression",
              "start": 15,
              "end": 26,
              "callee": {
                "type": "Identifier",
                "start": 15,
                "end": 20,
                "name": "alert"
              },
              "arguments": [
                {
                  "type": "Literal",
                  "start": 21,
                  "end": 25,
                  "value": "aa",
                  "raw": ""aa""
                }
              ]
            }
          }
        ]
      },
      "alternate": null
    }
  ],
  "sourceType": "module"
}

可以在 https://astexplorer.net/ 試試

先來定個(gè)任務(wù),那就是只實(shí)現(xiàn)解析if (1 > 0) {alert("aa");}這句話,因?yàn)閖s的內(nèi)容太多了,所以只實(shí)現(xiàn)上面這句話從js-ast-執(zhí)行,再次聲明,其他所有可能存在的問題都不考慮,只是完成解析上面的一句話,開始:

這句話對(duì)于計(jì)算機(jī)來說就是個(gè)字符串,那如何識(shí)別它呢?首先把這句話拆分,然后把拆分的內(nèi)容組合,這個(gè)實(shí)際叫做詞法解析和語法組合,生成對(duì)應(yīng)的類型和值,那這句話中有什么?

1."if" 
2." " 
3."(" 
4."1" 
5." " 
6.">" 
7." " 
8."0" 
9.")" 
10." " 
11."{" 
12."
 " 
13."alert" 
14."(" 
15."aa" 
16.")" 
17.";" 
18."
" 
19."}"

知道了有什么,就可以開始解析,把他們對(duì)應(yīng)的類型和值標(biāo)好,具體看代碼:

function tokenizeCode(code) {
        var tokens = [];  // 保存結(jié)果數(shù)組
        for (var i = 0; i < code.length; i++) {
          // 從0開始 一個(gè)個(gè)字符讀取
          var currentChar = code.charAt(i);
          if (currentChar === ";") {
            tokens.push({
              type: "sep",
              value: currentChar
            });
            // 該字符已經(jīng)得到解析了,直接循環(huán)下一個(gè)
            continue;
          }
          if (currentChar === "(" || currentChar === ")") {
            tokens.push({
              type: "parens",
              value: currentChar
            });
            continue;
          }
          if (currentChar === "{" || currentChar === "}") {
            tokens.push({
              type: "brace",
              value: currentChar
            });
            continue;
          }
          if (currentChar === ">" || currentChar === "<") {
            tokens.push({
              type: "operator",
              value: currentChar
            });
            continue;
          }
          if (currentChar === """ || currentChar === """) {
            // 如果是單引號(hào)或雙引號(hào),表示一個(gè)字符的開始
            var token = {
              type: "string",
              value: currentChar
            };
            tokens.push(token);
            var closer = currentChar;

            // 表示下一個(gè)字符是不是被轉(zhuǎn)譯了
            var escaped = false;
            // 循環(huán)遍歷 尋找字符串的末尾
            for(i++; i < code.length; i++) {
              currentChar = code.charAt(i);
              // 將當(dāng)前遍歷到的字符先加到字符串內(nèi)容中
              token.value += currentChar;
              if (escaped) {
                // 如果當(dāng)前為true的話,就變?yōu)閒alse,然后該字符就不做特殊的處理
                escaped = false;
              } else if (currentChar === "") {
                // 如果當(dāng)前的字符是 , 將轉(zhuǎn)譯狀態(tài)變?yōu)閠rue,下一個(gè)字符不會(huì)被做處理
                escaped = true;
              } else if (currentChar === closer) {
                break;
              }
            }
            continue;
          }

          // 數(shù)字做處理 
          if (/[0-9]/.test(currentChar)) {
            // 如果數(shù)字是以 0 到 9的字符開始的話
            var token = {
              type: "number",
              value: currentChar
            };
            tokens.push(token);
            // 繼續(xù)遍歷,如果下一個(gè)字符還是數(shù)字的話,比如0到9或小數(shù)點(diǎn)的話
            for (i++; i < code.length; i++) {
              currentChar = code.charAt(i);
              if (/[0-9.]/.test(currentChar)) {
                // 先不考慮多個(gè)小數(shù)點(diǎn) 或 進(jìn)制的情況下
                token.value += currentChar;
              } else {
                // 如果下一個(gè)字符不是數(shù)字的話,需要把i值返回原來的位置上,需要減1
                i--;
                break;
              }
            }
            continue;
          }
          // 標(biāo)識(shí)符是以字母,$, _開始的 做判斷
          if (/[a-zA-Z$\_]/.test(currentChar)) {
            var token = {
              type: "identifier",
              value: currentChar
            };
            tokens.push(token);
            // 繼續(xù)遍歷下一個(gè)字符,如果下一個(gè)字符還是以字母,$,_開始的話
            for (i++; i < code.length; i++) {
              currentChar = code.charAt(i);
              if (/[a-zA-Z0-9$\_]/.test(currentChar)) {
                token.value += currentChar;
              } else {
                i--;
                break;
              }
            }
            continue;
          }

          // 連續(xù)的空白字符組合在一起
          if (/s/.test(currentChar)) {
            var token = {
              type: "whitespace",
              value: currentChar
            }
            tokens.push(token);
            // 繼續(xù)遍歷下一個(gè)字符
            for (i++; i < code.length; i++) {
              currentChar = code.charAt(i);
              if (/s/.test(currentChar)) {
                token.value += currentChar;
              } else {
                i--;
                break;
              }
            }
            continue;
          }
          // 更多的字符判斷 ......
          // 遇到無法理解的字符 直接拋出異常
          throw new Error("Unexpected " + currentChar);
        }
        return tokens;
      } 
      var tokens = tokenizeCode(`
        if (1 > 0) {
          alert("aa");
        }
      `);
      console.log(tokens);

測(cè)試一下:

解析結(jié)果如下:

0: {type: "whitespace", value: "?        "}
1: {type: "identifier", value: "if"}
2: {type: "whitespace", value: " "}
3: {type: "parens", value: "("}
4: {type: "number", value: "1"}
5: {type: "whitespace", value: " "}
6: {type: "operator", value: ">"}
7: {type: "whitespace", value: " "}
8: {type: "number", value: "0"}
9: {type: "parens", value: ")"}
10: {type: "whitespace", value: " "}
11: {type: "brace", value: "{"}
12: {type: "whitespace", value: "?          "}
13: {type: "identifier", value: "alert"}
14: {type: "parens", value: "("}
15: {type: "string", value: ""aa""}
16: {type: "parens", value: ")"}
17: {type: "sep", value: ";"}
18: {type: "whitespace", value: "?        "}
19: {type: "brace", value: "}"}
20: {type: "whitespace", value: "?      "}

有了詞法分析得出來的內(nèi)容下一步就是要把他們語義化,也就是知道他們代表的是什么意思,有什么聯(lián)系?比如說括號(hào)的范圍是什么?變量之間的關(guān)系是什么?具體看代碼,先寫一下大概的結(jié)構(gòu):

var parser = function(tokens){
    const ast = {
        type:"Program",
        body:[]
    };
    // 逐條解析頂層語句
    while (i < tokens.length) {
      const statement = nextStatement();
      if (!statement) {
        break;
      }
      ast.body.push(statement);
    }

    return ast;
    
}
var ast = parse([
          {type: "whitespace", value: "
"},
          {type: "identifier", value: "if"},
          {type: "whitespace", value: " "},
          {type: "parens", value: "("},
          {type: "number", value: "1"},
          {type: "whitespace", value: " "},
          {type: "operator", value: ">"},
          {type: "whitespace", value: " "},
          {type: "number", value: "0"},
          {type: "parens", value: ")"},
          {type: "whitespace", value: " "},
          {type: "brace", value: "{"},
          {type: "whitespace", value: "
"},
          {type: "identifier", value: "alert"},
          {type: "parens", value: "("},
          {type: "string", value: ""aa""},
          {type: "parens", value: ")"},
          {type: "sep", value: ";"},
          {type: "whitespace", value: "
"},
          {type: "brace", value: "}"},
          {type: "whitespace", value: "
"}
                ]);

具體解析過程,生成ast樹:

var parse = function(tokens) {
        let i = -1;     // 用于標(biāo)識(shí)當(dāng)前遍歷位置
        let curToken;   // 用于記錄當(dāng)前符號(hào)
        // 讀取下一個(gè)語句
        function nextStatement () {

          // 暫存當(dāng)前的i,如果無法找到符合條件的情況會(huì)需要回到這里
          stash();
          
          // 讀取下一個(gè)符號(hào)
          nextToken();
          if (curToken.type === "identifier" && curToken.value === "if") {
            // 解析 if 語句
            const statement = {
              type: "IfStatement",
            };
            // if 后面必須緊跟著 (
            nextToken();
            if (curToken.type !== "parens" || curToken.value !== "(") {
              throw new Error("Expected ( after if");
            }

            // 后續(xù)的一個(gè)表達(dá)式是 if 的判斷條件
            statement.test = nextExpression();

            // 判斷條件之后必須是 )
            nextToken();
            if (curToken.type !== "parens" || curToken.value !== ")") {
              throw new Error("Expected ) after if test expression");
            }

            // 下一個(gè)語句是 if 成立時(shí)執(zhí)行的語句
            statement.consequent = nextStatement();

            // 如果下一個(gè)符號(hào)是 else 就說明還存在 if 不成立時(shí)的邏輯
            if (curToken === "identifier" && curToken.value === "else") {
              statement.alternative = nextStatement();
            } else {
              statement.alternative = null;
            }
            commit();
            return statement;
          }

          if (curToken.type === "brace" && curToken.value === "{") {
            // 以 { 開頭表示是個(gè)代碼塊,我們暫不考慮JSON語法的存在
            const statement = {
              type: "BlockStatement",
              body: [],
            };
            while (i < tokens.length) {
              // 檢查下一個(gè)符號(hào)是不是 }
              stash();
              nextToken();
              if (curToken.type === "brace" && curToken.value === "}") {
                // } 表示代碼塊的結(jié)尾
                commit();
                break;
              }
              // 還原到原來的位置,并將解析的下一個(gè)語句加到body
              rewind();
              statement.body.push(nextStatement());
            }
            // 代碼塊語句解析完畢,返回結(jié)果
            commit();
            return statement;
          }
          
          // 沒有找到特別的語句標(biāo)志,回到語句開頭
          rewind();

          // 嘗試解析單表達(dá)式語句
          const statement = {
            type: "ExpressionStatement",
            expression: nextExpression(),
          };
          if (statement.expression) {
            nextToken();
            if (curToken.type !== "EOF" && curToken.type !== "sep") {
              throw new Error("Missing ; at end of expression");
            }
            return statement;
          }
        }
        // 讀取下一個(gè)表達(dá)式
        function nextExpression () {
          nextToken();
          if (curToken.type === "identifier") {
            const identifier = {
              type: "Identifier",
              name: curToken.value,
            };
            stash();
            nextToken();
            if (curToken.type === "parens" && curToken.value === "(") {
              // 如果一個(gè)標(biāo)識(shí)符后面緊跟著 ( ,說明是個(gè)函數(shù)調(diào)用表達(dá)式
              const expr = {
                type: "CallExpression",
                caller: identifier,
                arguments: [],
              };

              stash();
              nextToken();
              if (curToken.type === "parens" && curToken.value === ")") {
                // 如果下一個(gè)符合直接就是 ) ,說明沒有參數(shù)
                commit();
              } else {
                // 讀取函數(shù)調(diào)用參數(shù)
                rewind();
                while (i < tokens.length) {
                  // 將下一個(gè)表達(dá)式加到arguments當(dāng)中
                  expr.arguments.push(nextExpression());
                  nextToken();
                  // 遇到 ) 結(jié)束
                  if (curToken.type === "parens" && curToken.value === ")") {
                    break;
                  }
                  // 參數(shù)間必須以 , 相間隔
                  if (curToken.type !== "comma" && curToken.value !== ",") {
                    throw new Error("Expected , between arguments");
                  }
                }
              }
              commit();
              return expr;
            }
            rewind();
            return identifier;
          }
          if (curToken.type === "number" || curToken.type === "string") {
            // 數(shù)字或字符串,說明此處是個(gè)常量表達(dá)式
            const literal = {
              type: "Literal",
              value: eval(curToken.value),
            };
            // 但如果下一個(gè)符號(hào)是運(yùn)算符,那么這就是個(gè)雙元運(yùn)算表達(dá)式
            stash();
            nextToken();
            if (curToken.type === "operator") {
              commit();
              return {
                type: "BinaryExpression",
                left: literal,
                right: nextExpression(),
              };
            }
            rewind();
            return literal;
          }
          if (curToken.type !== "EOF") {
            throw new Error("Unexpected token " + curToken.value);
          }
        }
        // 往后移動(dòng)讀取指針,自動(dòng)跳過空白
        function nextToken () {
          do {
            i++;
            curToken = tokens[i] || { type: "EOF" };
          } while (curToken.type === "whitespace");
        }
        // 位置暫存棧,用于支持很多時(shí)候需要返回到某個(gè)之前的位置
        const stashStack = [];
        function stash () {
          // 暫存當(dāng)前位置
          stashStack.push(i);
        }
        function rewind () {
          // 解析失敗,回到上一個(gè)暫存的位置
          i = stashStack.pop();
          curToken = tokens[i];
        }
        function commit () {
          // 解析成功,不需要再返回
          stashStack.pop();
        }
        const ast = {
          type: "Program",
          body: [],
        };
        // 逐條解析頂層語句
        while (i < tokens.length) {
          const statement = nextStatement();
          if (!statement) {
            break;
          }
          ast.body.push(statement);
        }
        return ast;
      };

測(cè)試一下:

解析出來的ast的具體結(jié)構(gòu)如下:

{
  "type": "Program",
  "body": [
    {
      "type": "IfStatement",
      "test": {
        "type": "BinaryExpression",
        "left": {
          "type": "Literal",
          "value": 1
        },
        "right": {
          "type": "Literal",
          "value": 0
        }
      },
      "consequent": {
        "type": "BlockStatement",
        "body": [
          {
            "type": "ExpressionStatement",
            "expression": {
              "type": "CallExpression",
              "caller": {
                "type": "Identifier",
                "value": "alert"
              },
              "arguments": [
                {
                  "type": "Literal",
                  "value": "aa"
                }
              ]
            }
          }
        ]
      },
      "alternative": null
    }
  ]
}

至此生成ast樹,這樣就有了代碼的相關(guān)的信息,下一步就是把這些信息轉(zhuǎn)化成執(zhí)行代碼,這就是遍歷ast,然后eval處理就行了,具體看代碼:

const types = {
  Program (node) {
     var code = node.body.map(child => {
      return generate(child)
    });
    // console.log(code)
    return code;
  },

  IfStatement (node) {
    let code = `if ( ${generate(node.test)} ) { ${generate(node.consequent)} } `;
    if (node.alternative) {
      code += `else ${generate(node.alternative)}`;
    }    
    return code;

  },

  BinaryExpression(node){
    let code = `${generate(node.left)} > ${generate(node.right)} `;    
    return code;
  },


  Literal (node) {
    let code = node.value;
    return code;
  },

  BlockStatement(node){
    let code = node.body.map(child => {
      return generate(child)

    });

    return code;
  },

  ExpressionStatement(node){
    let code = `${generate(node.expression)}`;    
    return code;
  },

  CallExpression(node){
    let alert = `${generate(node.caller)}`; 
    let value = generate(node.arguments[0]);
    return `${alert}("${value}")`;
  },

  Identifier(node){
    let code = node.value;    
    return code;
  }
};


function generate(ast) {
  return types[ast.type](ast).toString();
}

var code = generate({
  "type": "Program",
  "body": [
    {
      "type": "IfStatement",
      "test": {
        "type": "BinaryExpression",
        "left": {
          "type": "Literal",
          "value": 1
        },
        "right": {
          "type": "Literal",
          "value": 0
        }
      },
      "consequent": {
        "type": "BlockStatement",
        "body": [
          {
            "type": "ExpressionStatement",
            "expression": {
              "type": "CallExpression",
              "caller": {
                "type": "Identifier",
                "value": "alert"
              },
              "arguments": [
                {
                  "type": "Literal",
                  "value": "aa"
                }
              ]
            }
          }
        ]
      },
      "alternative": null
    }
  ]
});

// console.log(code)
eval(code)

把代碼放在控制臺(tái)試試:

至此,通過解析一句話了解了js到底是如何處理代碼的。

2.閉包 2.1閉包基礎(chǔ)

接著來看閉包,閉包是函數(shù)內(nèi)的函數(shù),實(shí)際是作用域內(nèi)的作用域,在js中為什么會(huì)出現(xiàn)閉包這個(gè)概念呢?是因?yàn)閖s中只有局部變量和全局變量,局部變量放在函數(shù)作用域內(nèi),全局變量放在全局作用域內(nèi),局部可以訪問全局但全局無法訪問局部,如果想要讓全局能夠訪問到局部,就需要通過閉包來實(shí)現(xiàn),具體看代碼:

function f(){
  var a=1;
}
console.log(a)

`此時(shí)console處于全局,a處于局部,全局無法訪問局部,所以必然會(huì)報(bào)錯(cuò):
`

這時(shí)可以利用閉包來進(jìn)行解決,具體看代碼:

function f(){
  var a=1;
  function g(){
    console.log(a)
  };
  return g;
}
f()()

此時(shí)就能夠訪問到局部的變量了,分析一下,我在f()中寫了g(),g()屬于f(),所以能訪問a,然后在外層把f返回,此時(shí)的f實(shí)際就是需要訪問的變量,看到這里有點(diǎn)疑惑,直接把a(bǔ)進(jìn)行return不也能達(dá)到這樣的目的么,為什么還要加一層?這個(gè)問題可以用一個(gè)例子來解釋,假設(shè)a是一個(gè)局部變量,但有需要被訪問到,同時(shí)還不希望所有的人都訪問到,那使用閉包包裝過的變量,只有知道包裝形式的人才能使用它,這個(gè)就是為什么需要多保障一層的原因

2.1閉包應(yīng)用 2.1.1 保護(hù)私有變量

以上就是對(duì)閉包的解釋,來想一想閉包幫助我們擁有了訪問局部變量的能力,那它怎么用呢?
首先就是用于保護(hù)私有變量,導(dǎo)出公有變量,以jquery源碼入口結(jié)構(gòu)來進(jìn)行說明:

( function( global, factory ) {
        "use strict";
        if ( typeof module === "object" && typeof module.exports === "object" ) {
            module.exports = global.document ? factory( global, true ) :
                function( w ) {
                    if ( !w.document ) {
                        throw new Error( "jQuery requires a window with a document" );
                    }
                    return factory( w );
                };
        } else {
            factory( global );
        }
    } )( 
        typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
        //具體代碼
        return jQuery;
    }

把這個(gè)結(jié)構(gòu)抽離出來,如下:

( function() { }) ( )

第一個(gè)括號(hào)有兩個(gè)作用:

讓js解析器把后面的function當(dāng)作函數(shù)表達(dá)式而不是函數(shù)定義

形成一個(gè)作用域,類似在上面閉包例子中的f函數(shù)

第二個(gè)括號(hào)

觸發(fā)函數(shù)并傳參

2.1.2 定時(shí)器

接著是定時(shí)器相關(guān)的應(yīng)用:

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

這個(gè)代碼的本意是要每隔1秒輸出01234,但實(shí)際上它會(huì)每隔1秒輸出5,因?yàn)閒or循環(huán)會(huì)很快執(zhí)行完,i的值固定為5,但setTimeout是異步操作會(huì)被掛起,等到異步操作完成的時(shí)候,i已經(jīng)是5,所以會(huì)輸出5,利用閉包來改造一下:

for( var i = 0; i < 5; i++ ) {
    ((j) => {
        setTimeout(() => {
            console.log( j );
        }, 1000 * j)
    })(i)    
}

setTimeout的父級(jí)作用域自執(zhí)行函數(shù)中的j的值就會(huì)被記錄,實(shí)現(xiàn)目標(biāo)

2.1.3 DOM綁定事件

再來看個(gè)例子,如果需要給頁面上多個(gè)div綁定點(diǎn)擊事件時(shí),一般是這樣寫:




    
    test


    
a
b
c

但這樣寫會(huì)導(dǎo)致alert()的內(nèi)容都是c,原因和上面差不多,所以需要保存每次循環(huán)的內(nèi)容,所以可以這樣來寫:




    
    test


    
a
b
c

2.2 內(nèi)存泄露

上面說了什么是閉包以及閉包怎么用,那閉包會(huì)不會(huì)帶來一些不好的影響呢?
答案是內(nèi)存泄露,意思就是變量不被使用但還占用空間未被清除,對(duì)于局部的變量,它的生命周期是局部作用域被調(diào)用開始---局部作用域被調(diào)用完成,對(duì)于全局的變量,它的生命周期是整個(gè)應(yīng)用結(jié)束,比如關(guān)閉瀏覽器,函數(shù)中的變量毫無疑問是局部變量,但是由于使用了閉包,所以它被全局的某處使用,導(dǎo)致js的垃圾回收機(jī)制并不會(huì)將它回收,在不注意的情況下就會(huì)造成內(nèi)存泄露,在js中有兩種垃圾回收的方法:

一種是標(biāo)記回收,當(dāng)局部作用域生效開始,就會(huì)把局部作用域的變量進(jìn)行標(biāo)記,等到局部作用域失效時(shí),被標(biāo)記的內(nèi)容就會(huì)被清除

一種是引用回收,當(dāng)進(jìn)入作用域時(shí),會(huì)在變量上添加引用計(jì)數(shù),當(dāng)同一個(gè)值被賦給另一個(gè)變量時(shí)計(jì)數(shù)加1,當(dāng)該變量值修改時(shí)計(jì)數(shù)減1,如果在回收周期到來時(shí),計(jì)數(shù)為0,則會(huì)被回收

js本身實(shí)現(xiàn)了垃圾自動(dòng)回收,但是系統(tǒng)實(shí)際分配給瀏覽器的內(nèi)存總量是有限的,如果因?yàn)殚]包導(dǎo)致垃圾變量不被回收就會(huì)導(dǎo)致崩潰,具體看代碼:

function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

result實(shí)際上就是閉包f2函數(shù)。它一共運(yùn)行了兩次,第一次的值是999,第二次的值是1000。這證明,函數(shù)f1中的局部變量n一直保存在內(nèi)存中,并沒有在f1調(diào)用后被自動(dòng)清除,原因就在于f1是f2的父函數(shù),而f2被賦給了一個(gè)全局變量,這導(dǎo)致f2始終在內(nèi)存中,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中,不會(huì)在調(diào)用結(jié)束后,被垃圾回收機(jī)制(garbage collection)回收,那怎么解決呢? result = null;手動(dòng)解除占用

function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

    result = null;
    
  nAdd();

  result(); // 1000
3.類 3.1原生實(shí)現(xiàn)類功能

在js語言中原本是沒有類這個(gè)概念的,但是隨著業(yè)務(wù)復(fù)雜又需要編寫面向?qū)ο蟮拇a,那怎么辦呢?創(chuàng)造一下,所以js就有了構(gòu)造函數(shù),原型對(duì)象,作用域鏈等一系列的概念,在其他高級(jí)語言中,類就是模板,具有對(duì)應(yīng)的屬性和方法,并且支持公有、私有,靜態(tài)屬性和方法等,一個(gè)類還必須滿足封裝繼承和多態(tài)三大性質(zhì),這樣的話根據(jù)類就能就能創(chuàng)造出實(shí)例對(duì)象了.
在js中默認(rèn)存在nativeobject(Function,Date..),built-in object(Global/Math)和host object(DOM/BOM),這些實(shí)例對(duì)象直接就能使用,但js的強(qiáng)大在于自己定制的類和實(shí)例化的對(duì)象,所以這就是接下來寫文的目的,如果自己來創(chuàng)造類的功能,你會(huì)怎么來做呢?

最開始想到的方法是Object,可以有屬性和方法,能否用它來實(shí)現(xiàn)?來試一試:

function showColor() {
  alert(this.color);
}
function createCar() {
  var oTempCar = new Object;
  oTempCar.color = "blue";
  oTempCar.doors = 4;
  oTempCar.mpg = 25;
  oTempCar.showColor = showColor;
  return oTempCar;
}

var oCar1 = createCar();
var oCar2 = createCar();

注意到上面除了object還用了一個(gè)function createCar,其實(shí)這是創(chuàng)建類的一種設(shè)計(jì)模式,叫做工廠模式,避免了重復(fù)去new object,同時(shí)內(nèi)部的方法以屬性的形式來進(jìn)行關(guān)聯(lián),避免了每次調(diào)用工廠函數(shù)的時(shí)候重復(fù)生成對(duì)應(yīng)的方法

3.2構(gòu)造+原型實(shí)現(xiàn)類功能

會(huì)發(fā)現(xiàn)雖然上述的工廠函數(shù)實(shí)現(xiàn)了屬性和方法的功能,但是屬性和方法是分離開的啊,有沒有辦法解決呢?用構(gòu)造函數(shù)+原型對(duì)象,構(gòu)造函數(shù)本質(zhì)上就是一個(gè)首字母大寫的函數(shù),只不過調(diào)用的時(shí)候是用new關(guān)鍵字來進(jìn)行生成,原型對(duì)象是為了解決類的方法重復(fù)創(chuàng)建的問題,所以將方法保存在原型對(duì)象中,然后在調(diào)用時(shí)沿著作用域鏈去尋找,那如何把方法綁定在原型對(duì)象上呢?每個(gè)構(gòu)造函數(shù)都可以通過prototype找到原型對(duì)象,具體看代碼,

function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.drivers = new Array("Mike","John");
}

Car.prototype.showColor = function() {
  alert(this.color);
};

var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);

oCar1.drivers.push("Bill");

alert(oCar1.drivers);    //輸出 "Mike,John,Bill"
alert(oCar2.drivers);    //輸出 "Mike,John"

會(huì)發(fā)現(xiàn)在構(gòu)造函數(shù)內(nèi)沒有創(chuàng)建對(duì)象,而是使用 this 關(guān)鍵字,新建實(shí)例時(shí)使用new 運(yùn)算符,那他們都干了啥?

1.new先新建了個(gè)空對(duì)象,就像在剛才的工廠函數(shù)中new Object()一樣,怎么證明呢?在控制臺(tái)測(cè)試一下

var Test = function(){}
console.log(typeof Test)//function
var test = new Test()
console.log(typeof test)//object

會(huì)發(fā)現(xiàn)經(jīng)過new后test的類型變成了object

2.接著Car.__proto__=car.prototype,將實(shí)例的原型對(duì)象指向構(gòu)造函數(shù)的原型對(duì)象,為什么這么做呢,因?yàn)樵诠S函數(shù)中我們給對(duì)象添加方法是直接通過oTempCar.showColor = showColor;,但通過構(gòu)造+原型的方式來進(jìn)行添加函數(shù)時(shí),函數(shù)是被放在構(gòu)造函數(shù)的原型對(duì)象里的,這是為了在調(diào)用時(shí)避免重復(fù)生成方法,所以實(shí)例對(duì)象要想訪問到構(gòu)造函數(shù)的方法,就必須要將自己的原型對(duì)象指向構(gòu)造函數(shù)的原型對(duì)象,此時(shí)就可以訪問到對(duì)應(yīng)的方法了
3.再接著car.call(Car),把this指向當(dāng)前的對(duì)象,這是因?yàn)樵谄胀ê铮瑃his指向的是全局,只有進(jìn)行修改后才能指向當(dāng)前對(duì)象,這樣的話就能像工廠函數(shù)那樣的進(jìn)行屬性賦值了
這樣說太抽象,做了張圖大家看看:

前面曾經(jīng)說過this需要進(jìn)行綁定,因?yàn)樵诓煌淖饔糜蛳聇his所指代的內(nèi)容是不同的,所以在這里看一下this到底會(huì)指向什么東西
首先是全局作用域下的this:

console.log(this)
//Window?{postMessage: ?, blur: ?, focus: ?, close: ?, parent: Window,?…}

接著是對(duì)象內(nèi)的this:

var obj = {
    user:"kk",
    a:function(){
        console.log(this.user)
        },
    b: {
        user: "gg",
        fn:function(){
            console.log(this.user);
        }
    }
    
}
obj.a();//kk
obj.b.fn();//gg

再來是函數(shù)內(nèi)的this:

var a = 1;
function test(){
    console.log(this.a)
}
test();//1

還有構(gòu)造函數(shù)中的this:

function Main(){
    this.def = function(){
                console.log(this === main);
            };
}
Main.prototype.foo = function(){
    console.log(this === main);
}
var main = new Main();
main.def(); //true
main.foo();//true

得出了什么結(jié)論呢?this永遠(yuǎn)指向最后調(diào)用他的對(duì)象

3.3類的使用案例

前面說了這么多的類的創(chuàng)建,實(shí)戰(zhàn)一下,看看學(xué)這么多到底有什么用?
1.字符串連接的性能,要先來知道一下,ECMAScript 的字符串是不可變的,要想對(duì)它做修改,必須經(jīng)過以下的幾個(gè)步驟:

var str = "hello ";
str += "world";

創(chuàng)建存儲(chǔ) "hello " 的字符串。

創(chuàng)建存儲(chǔ) "world" 的字符串。

創(chuàng)建存儲(chǔ)連接結(jié)果的字符串。

把 str 的當(dāng)前內(nèi)容復(fù)制到結(jié)果中。

把 "world" 復(fù)制到結(jié)果中。

更新 str,使它指向結(jié)果。

如果代碼匯中只有幾次字符串拼接,那還沒什么影響,但如果有幾千次幾萬次呢,上面這些流程在每修改一次的時(shí)候就會(huì)執(zhí)行一遍,非常的耗費(fèi)性能,解決方法是用 Array 對(duì)象存儲(chǔ)字符串,然后用 join() 方法(參數(shù)是空字符串)創(chuàng)建最后的字符串,把它直接封裝成類來使用:

function StringBuffer () {
  this._strings_ = new Array();
}

StringBuffer.prototype.append = function(str) {
  this._strings_.push(str);
};

StringBuffer.prototype.toString = function() {
  return this._strings_.join("");
};

封裝好了,可以來對(duì)比一下傳統(tǒng)的字符串拼接和我們封裝的這種類之間的性能差異:








下面是兩者進(jìn)行1百萬次操作的耗時(shí)對(duì)比

Concatenation with plus: 568 milliseconds
Concatenation with StringBuffer: 388 milliseconds
3.4對(duì)象冒充繼承

上面已經(jīng)實(shí)現(xiàn)了js中類的創(chuàng)建,下一步要解決是類的繼承,最常用的有對(duì)象冒充繼承,原型鏈繼承和混合繼承
首先說對(duì)象冒充繼承,本質(zhì)就是把父類作為子類的一個(gè)方法,然后來調(diào)用它,具體看代碼:

function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}
function ClassB(sColor, sName) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;

    this.name = sName;
    this.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"

父類作為子類的一個(gè)方法時(shí)當(dāng)調(diào)用這個(gè)方法實(shí)際上父類的屬性和方法就被子類繼承了,同時(shí)我們還會(huì)發(fā)現(xiàn)delete this.newMethod;這句話,這是避免子類中新拓展的屬性或者方法覆蓋掉父類的屬性方法,經(jīng)過這樣的冒用,就實(shí)現(xiàn)了子類的繼承,同時(shí)這種方法還可以實(shí)現(xiàn)多重繼承,也就是一個(gè)子類繼承多個(gè)父類,da但是,這樣繼承的父類中若果有重復(fù)的屬性或者方法,會(huì)按照繼承順序來確定優(yōu)先級(jí),后繼承的優(yōu)先級(jí)高,具體看代碼:

function ClassZ() {
    this.newMethod = ClassX;
    this.newMethod();
    delete this.newMethod;

    this.newMethod = ClassY;
    this.newMethod();
    delete this.newMethod;
}

這種繼承方法非常的流行,以至于官方后來擴(kuò)展了call()和apply()來簡(jiǎn)化上面的操作,call()第一個(gè)參數(shù)就是子類,第二個(gè)參數(shù)就是需要傳遞的參數(shù)[字符串],而apply()和call()的區(qū)別是,apply接受的參數(shù)形式為數(shù)組

//call
function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}
function ClassB(sColor, sName) {
    ClassA.call(this, sColor);

    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}
//apply
function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}
function ClassB(sColor, sName) {
    ClassA.apply(this, new Array(sColor));

    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

做了張圖,大家看看:

3.5原型鏈繼承

除了對(duì)象冒充繼承,還可以使用原型鏈繼承,原理是原型鏈最終會(huì)指向原型對(duì)象,換句話說,原型對(duì)象上的屬性方法能被對(duì)象實(shí)例訪問到,利用這個(gè)特性就可以實(shí)現(xiàn)繼承,怎么做呢?ClassB.prototype = new ClassA();搞定,但要記住,子類的所有新屬性和方法必須寫在這句話后面,因?yàn)榇藭r(shí)子類的原型對(duì)象實(shí)際上已經(jīng)是A的實(shí)例所指向的原型對(duì)象,如果寫在這句話前面,那新屬性和方法就被掛載到了B的原型對(duì)象上去了,經(jīng)過這句話賦值,那掛載的內(nèi)容就相當(dāng)于全被刪了,切記切記,還有一點(diǎn)要知道,原型鏈繼承并不能實(shí)現(xiàn)多重繼承,這是因?yàn)樵蛯?duì)象只有一個(gè),采用A的就不能用B的,否則就相當(dāng)于把前一個(gè)刪了。

function ClassA() {
}

ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB() {
}

ClassB.prototype = new ClassA();

ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
    alert(this.name);
};
var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName();

ClassB.prototype = new ClassA();是最重要的,它將ClassB 的 prototype 屬性設(shè)置成 ClassA 的實(shí)例,獲得了ClassA 的所有屬性和方法

3.6混合繼承

對(duì)象冒充的主要問題是必須使用構(gòu)造函數(shù)方式,使用原型鏈,就無法使用帶參數(shù)的構(gòu)造函數(shù)了,所以可以將兩者結(jié)合起來:

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"
3.7多態(tài)

一個(gè)預(yù)語言能使用類這個(gè)功能,說明它至少滿足了類的三個(gè)特點(diǎn),封裝,繼承和多態(tài),前面說過了封裝和繼承,現(xiàn)在來說一下多態(tài),多態(tài):同一操作作用于不同的對(duì)象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果??戳艘院蟾杏X很抽象,老辦法,舉例子,某人家里養(yǎng)了一只雞,一只鴨,當(dāng)主人向他們發(fā)出‘叫’的命令時(shí)。鴨子會(huì)嘎嘎的叫,而雞會(huì)咯咯的叫,轉(zhuǎn)換成代碼如下:

var makeSound = function(animal) {
    animal.sound();
}

var Duck = function(){}
Duck.prototype.sound = function() {
    console.log("嘎嘎嘎")
}
var Chiken = function() {};
Chiken.prototype.sound = function() {
    console.log("咯咯咯")
}

makeSound(new Chicken());
makeSound(new Duck());

JavaScript中大多是通過子類重寫父類方法的方式實(shí)現(xiàn)多態(tài),具體看代碼:

//使用es6 class簡(jiǎn)化代碼
class Parent {
    sayName() {
        console.log("Parent");
    }
}
class Child extends Parent{
    sayName() {
        console.log("Child");
    }
}
function sayAge(object) {
    if ( object instanceof Child ){
        console.log( "10" );
    }else if ( object instanceof Parent ){
        console.log( "30" );
    }
}

sayAge(child);   // "10"
sayAge(parent);  // "30"

很好玩,通過相同的操作但卻得到了不同的結(jié)果,這個(gè)就是多態(tài),這里以后再深入學(xué)習(xí)后會(huì)再補(bǔ)充的,留坑

3.8私有/靜態(tài)屬性和方法

我們前面寫的類的屬性和方法都是公有的,但其實(shí)一個(gè)真正的類是包含只提供內(nèi)部使用的私有屬性方法和只提供類本身使用的靜態(tài)屬性和方法,接下來就一一實(shí)現(xiàn)一下:
首先是靜態(tài)屬性和方法,這個(gè)實(shí)現(xiàn)很簡(jiǎn)單,直接在類中添加就好了

function Person(name) {

}
//添加靜態(tài)屬性
Person.mouth = 1; 
//添加靜態(tài)方法
Person.cry = function() {
    alert("Wa wa wa …"); 
}; 
var me = new Person("Zhangsan"); 

me.cry(); //Uncaught TypeError: me.cry is not a function

接著是私有屬性和方法,其中私有方法又叫特權(quán)方法,它既可以訪問共有變量又可以訪問私有變量:

function Person(name) {
    //公有變量
    this.name = name;
    //私有變量
    let privateValue = 1;
    //私有方法
    let privateFunc = function(){
        console.log(this.name,privateValue)
    };
    privateFunc()

}
console.log(new Persion("kk"))
3.9ES6類的創(chuàng)建繼承

前面說了這么多才把js的類實(shí)現(xiàn)好,但每次寫代碼都要這么麻煩么?幸好ed6中已經(jīng)將剛才所說的內(nèi)容封裝好了,也就是常說的class和extends,大家叫他們是語法糖,實(shí)際原理就是上面講的內(nèi)容,那來看看到底怎么用es6來實(shí)現(xiàn)類的創(chuàng)建與繼承
首先是創(chuàng)建:

class Animal{
    constructor(name){
        this.name = name;
    };
    sayNmae(){
        console.log(this.name)
    }
}
let animal = new Animal("小狗");
console.log(animal.name);
animal.sayNmae("小汪")

會(huì)發(fā)現(xiàn)多了一些關(guān)鍵字class和constructor,并且方法也寫在了類里面,其中class和原來的function對(duì)比來看,說明在使用時(shí)只能有new這一種調(diào)用方式,而不是像以前一樣技能當(dāng)構(gòu)造函數(shù)又能當(dāng)普通函數(shù),constructor和原來的this差不多都是指向了當(dāng)前的對(duì)象做完了就把對(duì)象返回

接著是繼承:

class Dog extends Animal{
    constructor(name,type){
        super(name);
        this.type = type

    }
    sound(content){
        console.log(content);
    }
}

let dog = new Dog("小狗","aaa");
console.log(dog.name)
dog.sayNmae()
console.log(dog.type)
dog.sound("汪汪汪")

同樣發(fā)現(xiàn)多了一些關(guān)鍵字extends和super(),其中extends相當(dāng)于原來的Parent.apply(this),super相當(dāng)于原來的ClassB.prototype = new ClassA();,也就是指向存放屬性和方法的原型對(duì)象
ok,至此,關(guān)于類的內(nèi)容告一段落,其實(shí)還有很多內(nèi)容可以說,比如設(shè)計(jì)模式,但它包含的內(nèi)容太多了,以后多帶帶開一篇來說。

4.異步 4.1回調(diào)函數(shù)

js是單線程語言,所以出現(xiàn)了耗時(shí)的操作時(shí)候,腳本會(huì)被卡死,這就需要處理異步的操作的機(jī)制,在最開始,js處理異步的方法是采用回調(diào)函數(shù),比如下面這個(gè)例子:

function test(){
    setTimeout(() => {
        console.log("a")
    },2000)
}

test()
console.log("b")


期望的結(jié)果是先a后b,但打印的結(jié)果是先b后a如何解決呢?

function test(f){
    setTimeout(() => {
        console.log("a")
        f()
    },2000)
}

test(() => {
    console.log("b")
})


確實(shí)達(dá)到了目的,但是如果需要嵌套的層數(shù)特別多的時(shí)候會(huì)導(dǎo)致地獄回調(diào),不利于代碼維護(hù),所以es6提出了promise來解決這個(gè)問題

4.2Promise
function test(){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log("a");
            resolve()
        },2000)
    })
}


test()
  .then(() => {
        return new Promise((resolve,reject) => {
            setTimeout(()=> {
                let a = 1;
                if(a){
                    reject()
                }else{
                    console.log("b");
                    resolve();
                }
            })    
        },1000)
    })
  .then(() => {
            console.log("c")
        }).catch((err) => {
            console.log("error")
        })

4.3async/await

通過這樣的方法確實(shí)實(shí)現(xiàn)了操作并且將邏輯拆開了避免了callback hell,但是這樣寫還是不舒服,看著很難受,所以可以用async、await來進(jìn)行書寫:

function a(){
            setTimeout(() => {
                console.log("a")
                
            },2000)
        }

function b(){
            setTimeout(() => {
                console.log("b")
            },1000)
        }

async function test(){
    try {
        await a();
        await b();

    }catch(ex){
        console.log("error")
    }
}



test()


ok,完美解決

5.模塊化 5.1原生模塊化

首先說一下,為什么需要模塊化,在es6之前,如果有多個(gè)文件,文件彼此之間相互依賴,最簡(jiǎn)單的就是后一個(gè)文件要調(diào)用前一個(gè)文件的變量,怎么做呢?前一個(gè)文件就會(huì)將該變量綁定在window頂層對(duì)象上暴露出去,這樣做確實(shí)達(dá)到了目的,但是同時(shí)也帶來了新的問題,如果一個(gè)項(xiàng)目是多人開發(fā)的,其他人不知道你到底定義了什么內(nèi)容,很有可能會(huì)把原先你定義好的變量給覆蓋掉,這是第一個(gè)致命,的地方除此以外,當(dāng)自己寫了一個(gè)模塊,在導(dǎo)入的時(shí)候,有可能因?yàn)槟K文件過大導(dǎo)致加載速度很慢,這是第二個(gè)致命的地方,前面兩點(diǎn)在開發(fā)時(shí)定好開發(fā)的規(guī)范,盡量拆分模塊為單一的體積小的內(nèi)容還是可以解決的,但是還有一點(diǎn)就是模塊之間的加載順序,如果調(diào)用在前而加載在后,那肯定會(huì)報(bào)錯(cuò),這是第三個(gè)致命的地方,并且這種出錯(cuò)還不好排查
為了解決這些問題,先后有很多的模塊化規(guī)范被提出,那想想,一個(gè)良好的模塊應(yīng)該是什么樣的?總結(jié)了一下,應(yīng)該具有:

1.保證不與其他模塊發(fā)生變量名沖突

2.只暴露特定的模塊成員

3.模塊與模塊之間語義分明

4.支持異步加載

5.模塊加載順序不會(huì)影響調(diào)用

5.2瀏覽器模塊化AMD

首先是AMD(Asynchronous Module Definition),它是專門為瀏覽器中JavaScript環(huán)境設(shè)計(jì)的規(guī)范,使用方法如下:
1.新建html引入requirejs并通過data-main="main.js"指定主模塊

//index.html



    
    requirejs


    
    
    

2.接著在主模塊中加載需要用到的其他模塊,比如math.js,加載模塊固定使用require(),第一個(gè)參數(shù)是個(gè)數(shù)組指定加載的模塊,第二個(gè)是個(gè)回調(diào)函數(shù),當(dāng)加載完成后具體的執(zhí)行就在這里

//main.js
require(["math"], function (math){

    alert(math.foo());

  });

3.被引用的模塊寫在define函數(shù)中,如果還有引用的模塊,就把第一個(gè)參數(shù)寫成數(shù)組來調(diào)用

//math.js
define(["num"], function(num){
    function foo(){
        return num.number();
    }
    return {
      foo : foo
    };

  });
//num.js
define(function (){
    var number = function (){
        var a = 5; 
      return a;
    };

    return {
      number: number
    };

  });

好多自己以前寫的模塊并沒有使用define來定義,所以并不支持AMD的規(guī)范,那如何來加載這些內(nèi)容呢?可以通過require.config({ })來進(jìn)行加載:

//main.js
require.config({
         paths:{
             "NotAmd":"./jutily"
         },
          shim:{
              "NotAmd":{
                  exports:"NotAmd"
              }
          }
      });
require(["math","NotAmd"], function (math){
    alert(math.foo());
    console.log(NotAmd())
  });
//jutily.js
(function(global) {
     global.NotAmd = function() {
         return "c, not amd module";
     }
 })(window);
5.3ES6模塊化

不管是AMD還是CMD,說到底它們都是加載的外來模塊實(shí)現(xiàn)js代碼的規(guī)范,但這樣寫也太麻煩了,于是es6中本身就開始支持模塊化了,具體如下:

//export.js
let myName="laowang";
let myAge=90;
let myfn=function(){
    return "我是"+myName+"!今年"+myAge+"歲了"
}

export {
    myName,
    myAge,
    myfn
}

//export default= {
    myName,
    myAge,
    myfn
}
import {myfn,myAge,myName} from "./export.js";
//import * as info from "./export.js";
console.log(myfn());//我是laowang!今年90歲了
console.log(myAge);//90
console.log(myName);//laowang

發(fā)現(xiàn)上面有個(gè)export和export default 兩者的區(qū)別是前者可以出現(xiàn)多次后者只能出現(xiàn)一次,可以混合出現(xiàn)這兩種導(dǎo)出方式

5.4commonjs模塊化

前面的規(guī)范適用于瀏覽器端的js編程,但是現(xiàn)在的js早已經(jīng)不再局限在瀏覽器了,在服務(wù)端同樣也能使用,這就需要在服務(wù)端也實(shí)現(xiàn)js的模塊化,這就是commonjs,具體使用如下:

//export.js
var x = 5;
var addX = function (value) {
  return value + x;
};
exports.x = x;
module.exports.addX = addX;
var example = require("./example.js");
console.log(example.x); // 5
console.log(example.addX(1)); // 6

發(fā)現(xiàn)有exports和module.exports,他們的區(qū)別是什么呢?其實(shí)兩者差不多,但是如果要導(dǎo)出的是函數(shù)的時(shí)候就寫在module.exports上
重點(diǎn)要理解一下require的內(nèi)容,它的大概原理是:

檢查 Module._cache,是否緩存之中有指定模塊

緩存之中沒有,就創(chuàng)建一個(gè)新的Module實(shí)例

把它保存到緩存

使用 module.load() 加載指定的模塊文件,讀取文件內(nèi)容之后,使用 module.compile() 執(zhí)行文件代碼

如果加載/解析過程報(bào)錯(cuò),就從緩存刪除該模塊

返回該模塊的 module.exports

參考文章:
1.Babel是如何編譯JS代碼的及理解抽象語法樹(AST):https://www.cnblogs.com/tugen...
2.Babel是如何讀懂JS代碼的:
https://zhuanlan.zhihu.com/p/...
3.用 Chrome 開發(fā)者工具分析 javascript 的內(nèi)存回收(GC)
https://www.oschina.net/quest...
4.ECMAScript 定義類或?qū)ο?
http://www.w3school.com.cn/js...
5.ECMAScript 繼承機(jī)制實(shí)現(xiàn):
http://www.w3school.com.cn/js...
6.js 多態(tài)如何理解,最好能有個(gè)例子
https://segmentfault.com/q/10...
7.Javascript模塊化編程(一):模塊的寫法:
http://www.ruanyifeng.com/blo...
8.Javascript模塊化編程(二):AMD規(guī)范:
http://www.ruanyifeng.com/blo...
9.Javascript模塊化編程(三):require.js的用法
http://www.ruanyifeng.com/blo...
10.CommonJS規(guī)范
http://javascript.ruanyifeng....

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109643.html

相關(guān)文章

  • 的確,Java存在缺陷。但是……

    摘要:是當(dāng)時(shí)唯一的書,而且只有語言規(guī)范。仍然在中使用未來可能被取代,但不是現(xiàn)在。仍然是大學(xué)里教授的主要語言,并且存在于很多優(yōu)秀的庫中,比如。筆者期待積極的討論。的確存在缺陷,但這些缺陷并不妨礙它在世界上最主要的公司和系統(tǒng)內(nèi)全天候地完成工作。 【編者按】本文作者為資深碼農(nóng) Tim Spann,主要講述 Java 讓人無法抗拒的眾多優(yōu)點(diǎn)以及一些些缺陷。本文系國(guó)內(nèi) ITOM 管理平臺(tái) OneAPM...

    wayneli 評(píng)論0 收藏0
  • 換一種思維看待PHP VS Node.js

    摘要:提供一種可選的決策方案換一種思維看待決策能夠做的事情,也可以,反之也是,所以選擇它們很簡(jiǎn)單,如果公司前端多,就選擇,如果公司后端多,就選擇,當(dāng)然這只是個(gè)人觀點(diǎn)哈。 php和javascript都是非常流行的編程語言,剛剛開始一個(gè)服務(wù)于服務(wù)端,一個(gè)服務(wù)于前端,長(zhǎng)久以來,它們都能夠和睦相處,直到有一天,一個(gè)叫做node.js的JavaScript運(yùn)行環(huán)境誕生后,再加上PHP的swoole擴(kuò)...

    Michael_Lin 評(píng)論0 收藏0
  • javascript入門教程(一):基本概念

    摘要:本文建議有基礎(chǔ)的人看,由于內(nèi)容過多,所以建議配合高級(jí)程序設(shè)計(jì)服用。一共由三部分組成,分別是最新版本是,簡(jiǎn)稱,,。 本文建議有html基礎(chǔ)的人看,由于js內(nèi)容過多,所以建議配合《javascript高級(jí)程序設(shè)計(jì)》服用。 在開始前我先簡(jiǎn)單介紹一下javascript這門語言吧。 javascript誕生于1995年,主要是用來表單的驗(yàn)證,雖然名字里面有java,但是和java毫無關(guān)系,甚至...

    Michael_Lin 評(píng)論0 收藏0
  • javascript入門教程(一):基本概念

    摘要:本文建議有基礎(chǔ)的人看,由于內(nèi)容過多,所以建議配合高級(jí)程序設(shè)計(jì)服用。一共由三部分組成,分別是最新版本是,簡(jiǎn)稱,,。 本文建議有html基礎(chǔ)的人看,由于js內(nèi)容過多,所以建議配合《javascript高級(jí)程序設(shè)計(jì)》服用。 在開始前我先簡(jiǎn)單介紹一下javascript這門語言吧。 javascript誕生于1995年,主要是用來表單的驗(yàn)證,雖然名字里面有java,但是和java毫無關(guān)系,甚至...

    jemygraw 評(píng)論0 收藏0
  • JS中創(chuàng)建對(duì)象的幾種設(shè)計(jì)模式

    摘要:構(gòu)造函數(shù)模式定義構(gòu)造函數(shù)模式是語言創(chuàng)建對(duì)象的通用方式。但兩種語言用構(gòu)造函數(shù)創(chuàng)建對(duì)象的方式略有不同在中沒有類的概念,函數(shù)即為一等公民,因此,不必顯式聲明某個(gè)類,直接創(chuàng)建構(gòu)造函數(shù)即可,類的方法和屬性在構(gòu)造函數(shù)中或原型對(duì)象上處理。 工廠模式 定義:工廠模式非常直觀,將創(chuàng)建對(duì)象的過程抽象為一個(gè)函數(shù),用函數(shù)封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié)。通俗地講,工廠模式就是將創(chuàng)建對(duì)象的語句放在一個(gè)函數(shù)里,通...

    Galence 評(píng)論0 收藏0
  • 《C陷阱與缺陷》第三章

    摘要:而對(duì)于二維數(shù)組,因?yàn)閮?nèi)存連續(xù)性的原因,內(nèi)存并不會(huì)真真的開辟一個(gè)二維空間,而是連續(xù)依次存入二維數(shù)組的每個(gè)數(shù)據(jù)。之所以有二維數(shù)組的說法是為了分析問題方便。二維數(shù)組的實(shí)質(zhì)是一維數(shù)組,只是其元素類型是一維數(shù)組類型。 ...

    tyheist 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<