欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

前端笔记之ES678&Webpack&Babel(下)AMD|CMD规范&模块&webpack&Promise对象&Generator函数

程序员文章站 2022-05-14 12:23:28
一、AMD和CMD规范(了解) 1.1传统的前端开发多个js文件的关系 yuan.js中定义了一个函数 main.js文件中调用这个函数: 在页面上按顺序引入这两个js文件: 能运算,yuan.js中定义的函数,是window对象的属性,所以main.js中能放同一个window全局函数,但每个js ......

一、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))

前端笔记之ES678&Webpack&Babel(下)AMD|CMD规范&模块&webpack&Promise对象&Generator函数

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规范的代表。

官方网站  

 

 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

前端笔记之ES678&Webpack&Babel(下)AMD|CMD规范&模块&webpack&Promise对象&Generator函数

webpack进行了3个事情:
1)从main.js出发,顺着import链寻找每一个被引用的js文件
2)将每一个js文件智能合并打包。比如一个export暴露的函数没有被使用,将不会被打包;
3)去import、export化,也就是说all.js是ie8都兼容的。

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

 前端笔记之ES678&Webpack&Babel(下)AMD|CMD规范&模块&webpack&Promise对象&Generator函数


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))

会报错,因为函数名重复了:

 前端笔记之ES678&Webpack&Babel(下)AMD|CMD规范&模块&webpack&Promise对象&Generator函数

 

解决方法就是命名空间:用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的简介和安装

官网:

webpack 是一个模块打包器。它的主要目标是将 javascript 文件打包在一起,打包后的文件用于在浏览器中使用,但它也能够胜任转换(transform)、打包(bundle)或包裹(package)任何资源(resource or asset)

 前端笔记之ES678&Webpack&Babel(下)AMD|CMD规范&模块&webpack&Promise对象&Generator函数

 

安装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文件的配置:

 

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

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());
        });
    });
});

前端笔记之ES678&Webpack&Babel(下)AMD|CMD规范&模块&webpack&Promise对象&Generator函数

 

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对象,就是优雅的解决回调函数的黑洞问题。


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();

前端笔记之ES678&Webpack&Babel(下)AMD|CMD规范&模块&webpack&Promise对象&Generator函数

任何一个函数都可以在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());