前端筆記之ES678&Webpack&Babel(下)AMD|CMD規范&模塊&webpack&Promise對象&Generator函數


一、AMDCMD規范(了解)

1.1傳統的前端開發多個js文件的關系

yuan.js中定義了一個函數

function mianji(r){
    return 3.14 * r * r
}

 

main.js文件中調用這個函數:

alert(mianji(10))

 

在頁面上按順序引入這兩個js文件:

<html>
<head>
    <title>Document</title>
</head>
<body>

</body>
<script type="text/javascript" src="yuan.js"></script>
<script type="text/javascript" src="main.js"></script>
</html>

 

能運算,yuan.js中定義的函數,是window對象的屬性,所以main.js中能放同一個window全局函數,但每個js文件之間沒有“強的引用”關系,它們都是向window暴露自己的函數,其他人通過window周轉。


1.2 CMD規范

function mianji(r){
    return 3.14 * r * r
}
//暴露
exports.mianji = mianji;
var yuan = require("./yuan.js");
console.log(yuan.mianji(10))

exportsrequire(),在瀏覽器會報錯,因為這兩個對象是Nodejs才有的,在客戶端瀏覽器不認識。

 

實際上在(比Nodejs還早),就有美國的程序員發明了Common.js,中國程序員阿里巴巴玉伯發明了sea.js,都解決了exportsrequire的識別問題。因為這個規范是Commonjs提出的,所以叫CMD規范,Common Module Definition,通用模塊定義Nodejs也遵循CMD規范,但Nodejs並不是CMD規范的發明人。NodejsCommonJS規范的實現,后面學習的webpack也是以CommonJS的形式來書寫。

所以,Node.js的模塊系統,就是參照CommonJS規范實現的。


1.3 AMD規范-require.js(過時)

概述:比如前期學習js基礎時,經常引入其他的js文件,但是有一個文件之間的先后順序問題:

比如haha.js里面有xixi.js需要的某一個函數,那么引入haha.js文件必須在引入xixi.js文件之前;

因此有人,研究出一些規范,這些規范是規范引用js文件的先后順序。

 

require.js庫是AMD規范的代表。

官方網站http://requirejs.org/  

 

 AMDAsynchronous Module Definition,即異步模塊定義。它是適用於瀏覽器端的一種模塊加載方式。從名字可知,AMD采用的是異步加載方式(js中最典型的異步例子就是ajax)。瀏覽器需要使用的js文件(第一次加載,忽略緩存)都存放在服務器端,從服務器端加載文件到瀏覽器是受網速等各種環境因素的影響的,如果采用同步加載方式,一旦js文件加載受阻,后續在排隊等待執行的js語句將執行出錯,會導致頁面的‘假死’,用戶無法使用一些交互。所以在瀏覽器端是無法使用CommonJS的模式的。而CommonJS是適用於服務器端的,著名的Node執行環境就是采用的CommonJS模式。

AMDCMD規范和你開發什么業務無關,和用什么設計模式無關。

AMDCMDJS文件之間的互相引用關系,設計模式是js類和類之間的關系。

 


二、ES6中的模塊(CMD規范)

重點:在ES6中新增了js文件的暴露和引入的新寫法:(importexport

2.1 importexport基本使用

之前學習過Nodejs,用了requireexports來引入和暴露。

W3C那些老頭想:Ryan Dahi那個小孩挺牛逼,在Nodejs發明了require()exports挺好用,我們也制定一個標准吧!於是研究出了這樣的語法:

require()          →    import
exports.***    →     export
module.exports →  default

使用export const暴露函數,import {} from "./路徑"; 接收函數。

 

yuan.js文件暴露

export const mianji = (r) => 3.14 * r * r;
export const zhouchang = (r) => 2 * 3.14 * r;

 

main.js文件接收:

import {mianji,zhouchang} from "./yuan.js"; //引入
console.log(mianji(10));
console.log(zhouchang(10));

 

注意兩個問題:

暴露時是什么名字,接收時一定是什么名字,比如暴露的是mianji,接收就叫mianji

路徑必須./”開頭

 

W3C新發明的CMD規范(exportimport)關鍵字,到今天為止,你會發現如今的瀏覽器和Node平台都不支持。

Google黑板報說2018年將原生支持(exportimport

那現在不支持怎么辦?

 

webpack應運而生,webpack是一個很智能的文件打包器,你只需告訴它主入口文件是誰,它就能順着import鏈進行打包,最后能合並成為一個新的js文件,將不再有importexport的語法。

 

webpack打包命令

webpack main.js all.js

webpack進行了3個事情:
1)從main.js出發,順着import鏈尋找每一個被引用的js文件
2)將每一個js文件智能合並打包。比如一個export暴露的函數沒有被使用,將不會被打包;
3)去import、export化,也就是說all.js是IE8都兼容的。

webpack是一個構建工具,在html中只需要引入all.js一個文件即可

 


2.2命名空間

比如增加一個fang.js也向外暴露兩個函數,也叫mianjizhouchang

export const mianji = (a)=> 3.14 * a * a;
export const zhouchang = (a)=> 3.14 * a * 4;

 

此時主入口文件:

import {mianji,zhouchang} from "./yuan.js";
import {mianji,zhouchang} from "./fang.js";
console.log(mianji(15))
console.log(zhouchang(15))

會報錯,因為函數名重復了:

 

 

解決方法就是命名空間:用import * as fang from "./路徑"; 來接收函數

import * as yuan from "./yuan.js";
import * as fang from "./fang.js";

console.log(yuan.mianji(10))
console.log(fang.mianji(10))

此時函數必須通過yuan打點,或fang打點調用,因為接收到的是一個JSON

也就是說import * as fang from "./文件";的語法,可以有效避免函數的名字沖突。

注意:import * as的這個名字,必須和文件名相同。


2.3默認暴露

如果js文件中是一個類(構造函數),此時不希望有命名空間,用默認暴露:

export default 

相當於nodejs中的module.exports

 

比如寫一個文件叫People.js這個文件向外暴露一個類:

export default class People {
    constructor(name,age,sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    change(){
        alert(`啦啦啦!我是賣報的${this.name},今年${this.age}`)
    }
}

 

在主入口文件,就不用{}引用這個類:

import People from "./People.js"; //類叫什么名字,import后就叫什么名字

var xiaoming = new People("小明",12,"男");
xiaoming.change();

 

一個文件可以有多個普通暴露,但是只能有一個默認暴露

// 默認暴露
export default class People {
    constructor(name,age,sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    change(){
        alert(`啦啦啦!我是賣報的${this.name},今年${this.age}`)
    }
}

//以下是普通暴露
export const a = 100;
export const b = 200;
export const f1 = function(){
    alert("我是普通的暴露函數f1")
};
export const f2 = function(){
    alert("我是普通的暴露函數f2")
};

 

注意:引入時,接收默認的暴露,不能加{},接收普通暴露的加{}

// import People from "./People.js"; //類叫什么名字,import后就叫什么名字
import People, {a,b,f1,f2} from "./People.js";

console.log(a) console.log(b) f1(); f2(); var xiaoming = new People("小明",12,"男");
xiaoming.change();

 暴露模塊:

普通暴露

export const

默認暴露

export default

 

 引用模塊:

普通引用

import {mianji,zhouchang} "./yuan.js"

import * as yuan "./yuan.js"

默認引用(類的引用)

import People from "./People.js"

默認暴露和普通暴露一起引用

import People,{a,b,f1,f2} from "./People.js";

 


三、webpack(模塊打包器)

3.1 webpack的簡介和安裝

官網:https://doc.webpack-china.org/

webpack 是一個模塊打包器。它的主要目標是將 JavaScript 文件打包在一起,打包后的文件用於在瀏覽器中使用,但它也能夠勝任轉換(transform)、打包(bundle)或包裹(package)任何資源(resource or asset)

 

 

安裝webpack工作流工具:

npm install -g webpack@3.10.0 

webpack4代版本,優點是:打包合並文件速度快,快了90%


3.2 webpack基本使用

在項目文件夾中運行CMD命令,webpack打包命令:

webpack main.js all.js

main.js是主入口文件

all.js 是出口文件(被打包的)

 

沒有yuan.js的事情,因為在main.jsimport {mianji} from "./yuan.js"; 所以webpack就鏈着import尋找yuan.js進行打包合並。


3.3 webpack.config.js文件

可以在項目中的根目錄寫webpack.config.js文件,來指導webpack工作。

就如同.babelrc文件一樣,指導babel工作。

webpack.config.js文件的配置:

https://webpack.docschina.org/configuration/

 

先配置一個文件,以后用就行了:

const path = require('path');

module.exports = {
    //程序的入口文件
    entry:"./www/app/main.js",

    //程序的出口(打包的文件)
    output:{
        //打包文件輸出的路徑
        path: path.resolve(__dirname, "./www/dist"),
        //打包文件的名稱
        filename: 'all.js'
    },
    //自動監聽文件的變化
    watch:true
}

webpack.config.js文件配置好后,就可以使用webpack命令打包了。


3.4 babel-loader

webpack只是幫我們智能合並打包文件,但是沒有翻譯ES678語法為ES5語法。

所以現在的任務是,在webpack打包的過程中,對每一個js文件用babel進行翻譯。

webpack提供了babel-loader功能,可以讓webpack在打包文件時,順便的翻譯ES678語法。

 

安裝依賴:

npm install --save-dev babel-core@6
npm install --save-dev babel-loader@7
npm install --save-dev babel-preset-es2015
npm install --save-dev babel-preset-es2016

 babel-core babel的核心

 babel-loader babelwebpack一起使用的橋梁

 babel-preset-es2015 是翻譯的字典

 

注意:babel-loader默認安裝的是8代版本,但babel-core默認安裝的是6,所以版本不兼容,會報錯。解決方法是babel-loader降級安裝7代,或者babel-core升級安裝為7代。

 

安裝完3個依賴,並配置好webpack.config.js文件后,webpack不但能打包,還能翻譯了。

webpack.config.js文件配置

const path = require('path');

module.exports = {
    //程序的入口文件
    entry:"./www/app/main.js",

    //程序的出口(打包的文件)
    output:{
        //打包文件輸出的路徑
        path: path.resolve(__dirname, "./www/dist"),
        //打包文件的名稱
        filename: 'all.js'
    },
    //監聽文件的變化(自動打包)
    watch:true,
    //配置webpack模塊插件
    module:{
        //關於模塊的配置規則
        rules:[{
            // 模塊規則(配置 loader、解析器等選項)
            test: /\.js$/,  //解析的時候匹配js文件
            loader:"babel-loader",
            //翻譯什么文件夾中的文件
            include: [ path.resolve(__dirname, "www/app")],
            //不翻譯什么文件夾中的文件
            exclude: [ path.resolve(__dirname, "node_modules")],
            //配置翻譯語法
            options:{
                presets:["es2015","es2016"] } }] }
}

之前寫的.babelrc文件,現在webpackwebpack.config.js文件中提供了,所以可以刪除了。


四、Promise對象(重點)

Promise對象是關於解決異步的書寫美觀問題。

4.1復習同步和異步

CPU面對一個長時間、設備處理的諸如文件讀取、磁盤I/O、數據庫查詢、數據庫的寫入等等操作的時候,此時有兩種模式:

① 同步模式:死等這個設備處理完成,此時CPU自己被阻塞

② 異步模式:不等這個設備處理完成,而是先執行后面的語句,等這個設備處理完成的時候,執行回調函數。

 

比如下面的代碼,讓CPU先執行一段計算,然后命令硬盤異步讀取文件,讀取的過程中不阻塞CPUCPU提前執行后面的計算語句,等硬盤讀取完畢之后,執行回調函數。

var fs = require("fs");

for(var i = 0; i < 100; i++){
    i++;
}
console.log(i); 
//異步讀取文件
fs.readFile("./txt/1.txt", (err,data) =>{
    console.log(data.toString());  
});

for(var i = 0; i < 100; i++){
    i++;
}
console.log(i); 

4.2異步回調語法不美觀

先讀1.txt,然后讀2.txt,最后讀3.txt

fs.readFile("./data/1.txt", (err,data) =>{
    console.log(data.toString());
    fs.readFile("./data/2.txt", (err,data) =>{
        console.log(data.toString());
        fs.readFile("./data/3.txt", (err,data) =>{
            console.log(data.toString());
        });
    });
});

 

Ajax請,順序讀取3api地址,分別是1.json2.json3.json

$.get("data/1.json", function(data1){
   $.get("data/2.json", function(data2){
       $.get("data/3.json", function(data3){
           console.log(data1,data2,data3)
       })
   })
})

通過以上兩個案例,我們遇見了回調黑洞問題(一層嵌套一層)

回調就是當有多個異步的事情,要排隊進行的時候,此時必須回調嵌套回調。

 

之所以回調函數,因為fs.readFile()ajax是異步的,需要有回調函數告訴主程序它讀取完畢了。

那么有沒有方法,讓異步語句形式變的更好看?從嵌套寫法,變為火車寫法,而不是一層嵌套一層的縮進?

ES6推出了Promise對象,就是優雅的解決回調函數的黑洞問題。

http://es6.ruanyifeng.com/#docs/promise


4.3 ES6Promise對象

ES6 規定,Promise對象是一個構造函數,用來生成Promise實例。

下面代碼創造了一個Promise實例。

const promise = new Promise(function(resolve, reject){
      if(/* 異步操作成功 */){
            resolve(value);
      }else{
            reject(error);
      }
});

Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolvereject。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。

resolve  表示成功執行的回調函數

reject  表示失敗執行的回調函數

 

封裝一個函數,叫讀文件duwenjian(),這個函數接收url作為路徑參數

var fs = require("fs");

function duwenjian(url){
    return new Promise(function(resolve,reject){
        fs.readFile(url, (err,data)=>{
            if(err){
                reject(); //失敗執行
                return;
            }
            resolve(data.toString()); //成功執行
        })
   })
}

duwenjian("./data/1.txt").then((data)=>{
    console.log(data);
    return duwenjian("./data/2.txt");
}).then((data)=>{
    console.log(data);
    return duwenjian("./data/3.txt");
}).then((data)=>{
    console.log(data);
})

回調黑洞問題解決了,將原來的“嵌套模式”變為“隊列模式”

可以認為這是一根語法糖,因為沒有改變原理,只改變了寫法。

 

Promise實例生成以后,可以用then方法分別指定resolved狀態和rejected狀態的回調函數。

then方法可以接受兩個回調函數作為參數。第一個回調函數是Promise對象的狀態變為resolved時調用,第二個回調函數是Promise對象的狀態變為rejected時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受Promise對象傳出的值作為參數。

 

紅色表示成功執行的回調函數,藍色表示失敗執行的回調函數:

duwenjian("./data/1.txt").then((data)=>{ console.log(data); },()=>{
    console.log("讀取失敗")
})

then表示然后。

 

這個函數返回一個Promise對象,這個對象是構造函數,ES6新增的。

Promise一定是某個函數的唯一返回值,Promise自己調用沒有任何意義,必須當做一個函數的返回值才有意義!本例中,Promise就是duwenjian函數的返回值!!

這個函數中,一定是一個異步語句,我們的案例是fs.readFile()

 異步語句的成功,要將數據通過resolve(數據)傳出去,這個數據將成為then里面的data

 異步語句的失敗,要將錯誤信息通過reject(err)傳出去,這個數據將成then里面的第二個data

 Promise的實例擁有then()方法,也就是說,then方法是定義在原型對象。

 

Ajax中使用promise對象,按順序讀取三個文件:

為了解決黑洞問題,此時用promise來解決:

function duwenjian(url){
   return new Promise(function(resolve,reject){
      $.ajax({
           url : url,
           success : function(data){
              resolve(data);
           },
           error : function(){
               reject();
           }
      })
   })
}

$("button").click(function(event) {
   duwenjian("data/1.json").then(function(data){
       console.log(data);
       return duwenjian("data/2.json");
   }).then((data)=>{
       console.log(data);
       return duwenjian("data/3.json");
   }).then((data)=>{
       console.log(data);
   })
});

promise表示承諾、契約:當異步完畢之后,一定會執行resolve,從而then被執行,里面能夠得到值。


4.4 ES7asyncawait

ES6中的Promise對象,解決了回調黑洞,但實際上又產生了火車黑洞,then()過多不好看。

所以ES7中提出了async/await可以讓我們用同步的方式寫異步,不寫then了。

async表示異步,await表示異步等待。

 

用寫同步的方法,寫異步函數:

注意:

1)必須寫那個return Promise對象的函數;

2await不能裸用,必須寫在async字樣的函數里;

3)所有異步語句前都要加await,但是后面的函數必須return Promise實例的函數。

function duwenjian(url){
   return new Promise(function(resolve,reject){
      $.ajax({
           url : url,
           success : function(data){
               resolve(data);
           },
           error : function(){
               reject();
           }
      })
   })
}

async function main(){
    var data1 = await duwenjian("data/1.json").then(data=>data);
    var data2 = await duwenjian("data/2.json").then(data=>data);
    var data3 = await duwenjian("data/3.json").then(data=>data);
    console.log(data1)
    console.log(data2)
    console.log(data3)
};
main();

Nodejs讀文件:

var fs = require("fs");

function duwenjian(url){
    return new Promise(function(resolve,reject){
        fs.readFile(url, (err,data)=>{
            if(err){
                reject(); //失敗執行
                return;
            }
            resolve(data.toString()); //成功執行
        })
    })
}

async function main(){
    var data1 = await duwenjian("data/1.txt").then(data=>data);
    var data2 = await duwenjian("data/2.txt").then(data=>data);
    var data3 = await duwenjian("data/3.txt").then(data=>data);
    console.log(data1)
    console.log(data2)
    console.log(data3)
};

main();
讀文件

任何一個函數都可以在function之前加async這個關鍵字,表示這個函數中出現了await

await后面只能跟着一個返回promise對象的實例的函數的調用,then里面的函數的返回值將自動被等號左邊接收

使用async/await的哲學:用寫同步語句的感覺,去寫異步語句。

async寫在function這個之前,await寫在異步語句之前

await表示等待后面的操作執行完畢,再執行下一行代碼。


4.5 ES8fetch()

ES8中繼續提出了fetch方法,這個方法發出ajax請求,返回Promise的實例

 

fetch()函數是ES8中提出的新的特性,它:

1)能夠發出Ajax請求,並且請求機理不是xhr,而是新的機理;

2)天生可以跨域,但是需要設置頭,服務器可以寫腳本識別這個頭部判斷是否應該拒絕它;

3fetch()返回Promise對象,所以可以用then來跟着,then里面的第一個函數就是resolve,這個resolve的返回值將自動被await等號左邊的變量的接收。

4)不要忘記寫asyncawait

async function main(){
    var data1 = await fetch("data/1.json").then(data=>data.json());
    var data2 = await fetch("data/2.json").then(data=>data.json());
    var data3 = await fetch("data/3.json").then(data=>data.json());
    console.log(data1)
    console.log(data2)
    console.log(data3)
};
main();

 

可以不用jQuery

babel沒有任何插件可以翻譯fetch為傳統ajax

fetch只能用在瀏覽器環境,不能用在Nodejs環境。

 


經典面試題:

Promise是什么:

傳統的ajax或者fs寫異步的時候,會遇見回調黑洞,回調套用回調,不美觀。所以ES6中提出了Promise對象,此時可以封裝一個函數,返回Promise的實例,new Promise的時候要傳入一個函數,這個函數里面寫具體的異步,當異步成功調用resolve(),異步失敗調用reject()。此時在外部,我們就可以用.then().then()火車的形式,來實現異步、異步的順序處理。

 

async/await是什么:

Promise雖然解決了回調黑洞,但是變成了火車的黑洞。我們可以給函數加上async的前綴,里面的異步語句加上await前綴,這樣就可以用類似於同步的寫法寫異步。等號左側就能有接收值,語句自然一條一條的執行了。

 

fetch是什么:

fetch就是ajax,但是返回的是promise的實例,可以直接then,而不需要再次封裝一個什么函數了。


 

 

五、Generator函數

產生器。

我們沒有任何理由封裝它,都是別人的框架提供,比如react-sagadva等。

解釋:

l如同async寫在函數前面一樣,*要寫在function函數后面

l如同await寫在語句前面一樣,yield要寫在語句前面,但后面不一定是異步語句,表示“產出”

l函數可以像被打了斷點,逐步執行,到了yield就停止執行。

l函數不能直接調用執行,直接調用返回一個對象,比如下面的hw,然后hw必須用next()調用函數。

function*  helloWorldGenerator() {
    console.log("哈哈");
    yield 'hello';
    yield 'world';
    return 'ending';
}
var hw = helloWorldGenerator();

console.log(hw.next());
console.log(hw.next());
console.log(hw.next());
console.log(hw.next());
console.log(hw.next());

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM