乐趣区

阮一峰ES6全面回顾

9 月抽空重新回顾了下 ES6 所有知识点,整个回顾过程既惊喜又感慨,感慨开发这么久好像真的没有好好的静下心去读一本好的书,大多情况下只是在使用的时候用到了,不熟悉或者感兴趣再去走马观花一通,感慨之余也发现了一些自身的的问题,知识体系还是不够丰富、扎实,一句话:有空多读书总没错,好了不闲扯了,下面我们步入正题

关于 JavaScript

如今前端知识日新月异,越是晚入门小伙伴,很多基础层面的东西,接触的真的是少之又少,各种前端框架层粗不穷 VueReactNg 这三大带有里程碑意义的框架 Api 我想看的这么文字的盆友应该都有接触,基础的 Api 使用应该是没有什么难度,但是在遇到问题,解决问题、以及在原有组件以及自我封装组件,搭建框架过程总会遇到一些莫名其妙的问题,解决这个问题,看源码 是大众都知道的一些方法,但个人认为这是一种进阶的方法,新手入门建议可以先了解咱们 JavaScript 的发展史 可以比较轻松的认识我们整个技术体系的发展和未来方向,避免一些认知错误

发展史

  1. 互联网早期

    • BS 架构:1990 年的 12 月 25 日,西方的圣诞节,Tim Berners-Lee 在他的 NeXT 电脑上部署了第一套 “主机 - 网站 - 浏览器” 构成的 Web 系统,这标志 BS 架构 的网站应用软件的开端,也是前端工程的开端。这个时候前端只是简单的 Html 静态文本 简单到基本动画都没有
    • 1995 年 javascript 的诞生:1995 年,NetScape(网景)公司的工程师 Brendan Eich 设计了 javascript 脚本语言,并集成到了 navigator2.0 版本中。随后微软也意识到了 javascript 的潜力,并模仿开发 VBScript 和 JScript 应用到了 IE 中,这直接开启了NetScape 和微软的浏览器竞争。由于微软的 IE 集成在 windows 操作系统上的优势,NetScape 的 navigator 很快在浏览器市场上落于下风。于是他们把 javascript 提交到了ECMA,推动制订了ECMAScript 标准,成功实现了 javascript 的标准国际化。虽然第一次浏览器战争最后 IE 大胜 Navigator,但是 NetScape 的 javascript 主导了 W3C 的官方标准。
  2. 互联网发展期

    • 动态页面伊始:由于 javascript 推动,前端页面开始走向动态化,那时流行的 跑马灯、悬浮广告 基本是各大门户网站的标配,页面基本都是通过 PHP、JSP、ASP 动态渲染出来,这也直接导致后端代码的逻辑臃肿,服务端为了更好的管理代码,应运而生MVC 模式
    • Ajax 出现:前期的动态性完全由服务端推动,每一次的动态更新都需要将页面进行 reload 操作,不管交互还是性能,都是不值当的 “ 这个问题直到谷歌在 04 年应用 Ajax 技术开发的 Gmail 和谷歌地图的发布,才得到了解决。” 这背后的秘密就是 Ajax 技术中 实现的异步 HTTP 请求,这让页面无需刷新就可以发起 HTTP 请求,用户也不用专门等待请求的响应,而是可以继续网页的浏览或操作。
      Ajax 开启了 web2.0 的时代
    • 前端兼容问题:由于更重浏览器差异性,兼容问题也是层出不穷,不同的浏览器技术标准有不小的差异,不利于兼容开发,这催生了 Dojo、Mooltools、YUIExtJS、jQuery 等前端兼容框架,其中 jQuery 应用最为广泛。这些框架中不知道大家用过几个
    • Html5 诞生:部分浏览器厂商为了解决适配问题提出过 Web Forms 2.0Web Applications 1.0 等规章最后整合成 HTML5、各大浏览器都在为适配浏览器不断改善自己的浏览器
    • Node.js:2009 年,Ryan Dahl 以 Chrome 的 V8 引擎为基础开发了基于 事件循环的异步 I / O 框架 -Node.js。Node.js 使得前端开发人员可以利用 javascript 开发服务器端程序。很快,大量的 Node.js 使用者就建构了一个用 NPM 包管理工具管理的 Node.js 生态系统。node.js 也能开发跨平台的桌面应用

    Node 衍生出的 NPM 包管理工具为整个前端社区提供了一个规范良好的平台

  3. 互联网进击

    • 移动 App、Hybrid App:移动互联网的到来,飞速发展,原生 app 迭代远远满足不了,大家找到折中的方法,损耗部分性能,提高产品产出;jQuery Mobile、Sencha Touch、Framework7如鱼得水;Hybrid 技术指的是利用 Web 开发技术,调用 Native 相关的 API,实现移动与 Web 二者的有机结合,既能利用 Web 开发周期短的优势,又能为用户提供 Native 的体验。
    • ECMAScript6:2015 年 6 月,ECMAScript 6.0发布,该版本增加了很多新的语法,极大的拓展了 javascript 的开发潜力; 一些陈旧的浏览器可以通过 Babel 进行降级转义适配,ES6 将 JavaScript 推向了另一个历史转折点
    • 进行中:经过历史的沉淀,技术演进,交互升级,React、Vue、Anjular三大框架利用 js 集合自身优势,完全实现了目前的前后端分离的开发模式;开发体系发展到:NPM 和 Yarn 为代表的包管理工具;ES6 及 Babel 和 TypeScript 构成的脚本体系;HTML5;CSS3 和相应的处理技术;React、Vue、Anjular 为代表的框架;Webpack 为代表的打包工具;Node.js 为基础的 Express 和 KOA 后端框架;Hybrid 技术。

ECMAScript 简介

提案规则

权重自上到下

  • Stage 0 – Strawman(展示阶段)
  • Stage 1 – Proposal(征求意见阶段)
  • Stage 2 – Draft(草案阶段)
  • Stage 3 – Candidate(候选人阶段)
  • Stage 4 – Finished(定案阶段)

一般走到草案阶段基本可以在正式标准中看到,Tc39 可查看各提案

Tips:这也是在 babel 中配置 presets 的来由(可能我们使用了一些仍然在草案甚至征求意见阶段的 API)的时候需 babel 垫片

Babel 转码器

配置.babelrc/babel.config.js
// 基本格式
{"presets":[]
    "plugins":[]}

不同环境支持不同的转换方法

  • 命令行 //@babel/cli
  • 浏览器环境 //HTML 中引入对应脚本
  • node 环境 //Traceur 模块

一般我们使用的脚手架默认配置好,但是我们需要配置的什么意思,以及为什么要配置

@babel/register、babel-core/register

改写 require 命令,为每个 require 引入的资源进行 Babel 转义

@babel/polyfill

Babel 仅会转换新的语法,但是已有的 Api 中的 Iterator、Generator、Set、Map、Proxy、Reflect、Symbol、Promise 是需要用这个垫片处理

@babel/core

暴露出 Babel 的 Api,进行特殊操作


ES6 基础篇

基础篇我们主要从 es6 中添加的一些命令,以及对已有数据类型拓展的汇总

let & const

let

特性如下

  1. 区别于 var 的块级作用域
  2. 不存在变量提升问题
  3. 具有暂时性死区
  4. 不能重复命名
// 下面这块很好的体现了 ` 块级作用域 `

var a = [];
for (var i = 0; i < 10; i++) {a[i] = function () {console.log(i);
  };
}
a[6](); // 10​
​
var a = [];
for (let i = 0; i < 10; i++) {a[i] = function () {console.log(i);
  };
}
a[6](); // 6

特殊说明下 暂时性死区的概念,就是:在块中,let 声明变量之前都是不能使用的

const

const 和 let 特性基本一致,区别于,使用 const 定义的是 显式的常量,一经定义,不可更改

块级作用域

es5 之前是有 全局作用域、函数作用域 ,es6 推出的块级作用域但凡一个{} 就是一个作用域;
外层块级作用域能在内层使用,内层定义在外层访问不到

解构赋值

Tips:解构赋值 对应的还有 拓展操作符 , 解构不成功则为 undefined; 一看就懂,一用代码就简洁

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值(Iterator 类型),这被称为解构(Destructuring)

数组的解构

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [, , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

对象的解构

let {bar, foo} = {foo: 'aaa', bar: 'bbb'};
foo // "aaa"
bar // "bbb"

let {baz} = {foo: 'aaa', bar: 'bbb'};
baz // undefined

数组对象的解构

let obj = {
  p: [
    'Hello',
    {y: 'World'}
  ]
};

let {p: [x, { y}] } = obj;
x // "Hello"
y // "World"

函数参数的解构

// 设置默认值
function move({x = 0, y = 0} = {}) {return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

注:还有字符串解构(转换成数组)、字符 / 布尔解构(转换成对象),解构遵循的宗旨是模式、数据格式一致,即可解构,解构失败 undefined;进行赋值的过程,不管是数组还是对象解构都可以设置 默认值 ;圆括号() 只能在赋值语句的非模式部分即可

用途优势

  • 结构清晰,代码明了
  • 函数中直接返回多个值

    let [a,b,c] = fn()
  • 函数入参清晰多参数
  • JSON 对象快速提取对应值
  • 函数设置默认值

字符串

字符串中改动还是蛮多的有 添加 Unicode 表示 JSON.stringify() 适配非 UTF- 8 编码,下面介绍日常开发实用改变

添加 Iterator 接口

for of遍历器(es6 新增)仅对拥有 Iterator 接口的适配

模板字符串

“ 反引号 节省我们字符串拼接的痛点

let str1 = 
    'There are <b>' + basket.count + '</b>' +
      'items in your basket,';
let str2 = `There arebasket.count ${variable}</b>items in your basket, `;

上面字符串拼接自上到下的升级,同时也可以使用 ${} 动态注入变量

模板编译

<%...%>中放置 JavaScript 代码,如下

let template = `
<ul>
  <% for(let i=0; i < data.supplies.length; i++) { %>
    <li><%= data.supplies[i] %></li>
  <% } %>
</ul>
`;

可以利用字符串匹配 <%...%> 封装 template 转换函数

字符串方法

  • String.fromCodePoint()// 识别大于 0xFFFF 的字符
  • String.raw() // 返回斜杠都转义的字符
  • codePointAt()

        charAt() // UTF-8
        codePointAt() // UTF-16
  • normalize() // 处理合成字符串
  • includes(), startsWith(), endsWith()

    includes():返回布尔值,表示是否找到了参数字符串。startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  • repeat() // 返回一个新字符,参数是重复次数
  • padStart(),padEnd() // 头 / 尾部补全字符 两个参数第一个长度、第二个填补的 str
  • trimStart(),trimEnd() // 消除头 / 尾部空格
  • matchAll() // 字符正则匹配 参数正则表达式

数值类型

二进制、八进制表示法

0b(0B)二进制;0o(0O)八进制

方法

  • Number.isFinite(), Number.isNaN()
    判断是否有限、判断参数类型是否为 NAN
  • Number.parseInt(), Number.parseFloat()
    将全局方法移植到内置对象下
  • Number.isInteger() 判断是否是整数
  • Number.EPSILON // 常量 表示 1 与大于 1 的最小浮点数之间的差。
  • Math.trunc() // 返回整数
  • Math.sign() // 判断是否正数、负数、零
  • Math.cbrt() // 计算数组的立方根
  • Math.clz32() // 返回 32 位前置零个数
  • Math.imul // 返回两个数以 32 位带符号整数形式相乘的结果

拓展符号 ( 右结合即右边先运算)

****= 操作符 进行指数运算

// 指数拓展符
let a = 2;

a ** 2 **4 // 2 * (2 * 4)

a **= 3 // a = 2 * 2 * 2

数组拓展

展开运算符

...将一个数组转成用逗号分隔的参数排列

console.log(1,2 ...[3,4]) // 1,2,3,4

方法拓展

  • Array.form() // 将类数组转换成数组
  • Array.of() // 用于将一组值,转换为数组

实例方法拓展

  • find()、findIndex() // find 找到第一个满足条件的值,findIndex 和前者类似,但是返回是下标,找不到返回-1
  • fill() 数组填充(params0: 填充的值,params1: 开始下坐标,params2: 结束下坐标)
  • entries(),keys() 和 values() // 返回数组的键值对
  • includes() // 是否包含某个值,返回 bool 值
  • flat()、flatMap() // 扯平数组

        [1, 2, , [4, 5]].flat() //[1, 2, 4, 5] 默认拉平一层,参数 num、Infinity 自定义拉平层数目
        // flatMap() 相当于先进行 Map 再进行 flat 操作
        // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
        [2, 3, 4].flatMap((x) => [x, x * 2])
        // [2, 4, 3, 6, 4, 8]
  • sort() //ES2019 明确要求排序的稳定性

数组的空位

ES6 中明确将空位装换成 undefined;数组实例方法、map、扩展操作符存在差异

总之避免空位的出现是很必要的

对象类型

简洁写法

对象中的属性、方法只要 key、value 名字一致,即可简写成一个如下

// 属性
let a = 'a';
let b = 'b';
let obj = {a, b}
// 等同于
let a = 'a';
let b = 'b';
let obj = {a:a, b:b}

// 方法
let obj = 
{
    a,
    b,
    c(){console.log('这是一个对象方法')
    }
}
// 等同于
let obj = 
{
    a,
    b,
    c:function(){console.log('这是一个对象方法')
    }
}

属性名表达式

let propKey = 'foo';

let obj = {[propKey]: true,
  ['a' + 'bc']: 123
};
// 需要注意的是如果属性名是一个对象默认会装换成[object Object]

拓展操作符

// 合并对象
let ab = {...a, ...b};
// 等同于
let ab = Object.assign({}, a, b);

// 同数组拓展操作符后可跟表达式 如:const obj = {...(x > 1 ? {a: 1} : {}),
  b: 2,
};

拓展方法

可枚举性

每个属性都有一个描述对象,用来控制属性的行为 Object.getOwnPropertyDescriptor(obj,'key') 可获取对应属性的描述行为,
描述中 enumerable 标识是否可遍历

总之:操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用 for…in 循环,而用 Object.keys()代替

遍历方法
  1. for in
  2. Object.getOwnPropertyNames()

    1. 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
  3. Object.getOwnPropertySymbols()

    1. 返回一个数组,包含对象自身的所有 Symbol 属性的键名。
  4. Reflect.ownKeys()

    1. 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
  5. Object.is()

    1. 传统判断值是否相等使用 == 或者===, 前者会强制类型转换,后者NAN 不等于自身,并 + 0 等于 -0, 此方法就解决了上述问题
  6. Object.assign()

    1. Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),第一个参数就是目标对象, 只会处理可枚举的数据类型,并 Symbol 值属性也会被拷贝
    2. 数组处理将值一一对应成对象类型
  7. Object.getOwnPropertyDescriptors()

    1. 对 ES5 中 Object.getOwnPropertyDescriptor()的拓展,返回对象中所有属性(非继承)的值
  8. 原型操作方法替代__proto__

    1. Object.setPrototypeOf()(写操作)、
    2. Object.getPrototypeOf()(读操作)、
    3. Object.create()(生成操作
  9. 对象键值对获取

    1. Object.keys(),
    2. Object.values(),
    3. Object.entries()
    4. Object.fromEntries() // Object.fromEntries()方法是 Object.entries()的逆操作,用于将一个键值对数组转为对象

遍历规则如下:

  • 首先遍历所有数值键,按照数值升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有 Symbol 键,按照加入时间升序排列。
Super 关键字

指向当前对象的原型对象。

const proto = {
  x: 'hello',
  foo() {console.log(this.x);
  },
};

const obj = {
  x: 'world',
  foo() {super.foo();
  }
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

上面代码中,super.foo 指向原型对象 proto 的 foo 方法,但是绑定的 this 却还是当前对象 obj,因此输出的就是 world。


ES6 进阶篇

进阶篇,我们主要对新增的 Symbol、ArrayBuffer 以及 Set 和 Map 数据结构介绍、Promise 到 async的演变和差异, 异步遍历的思想, 以及Class、Module 的使用

Symbol 类型

ES6 引入的新的数据类型,保证了数据的独一无二的特性,可以用来标识对象的额唯一 key 值

// 新建类型
let a = Symbol()
// 添加描述(用于标识 Symbol 值)let a = Symbol('描述文件')

特性:

  1. 不能 new 操作 注意 Symbol()是一个类似 string 类型的值,不是对象
  2. 不能与其他类型值进行计算
  3. 可以显性的转换成字符类型 / 布尔值
  4. 座位对象的属性的时候需要注意和字符型属性,赋值、取值区别开来

方法:

  1. Symbol.for()、Symbol.keyFor()
    返回同一个标识的 Symbol 类型,keyFor 返回当前 Symbol 类型的唯一标识
  2. Symbol.isConcatSpreadable
    判断数组 concat()操作的时候是否可以会别呗展开
  3. Symbol.match
  4. Symbol.replace
  5. Symbol.search
  6. Symbol.match
  7. Symbol.iterator
  8. Symbol.toPrimitive
  9. Symbol.toStringTag
  10. Symbol.unscopables

Set、Map 数据结构

Set

Set 数据结构。它类似于数组,但是成员的值都是唯一的,没有重复的值。

特性:

  1. 去重
    Set() 去重的判断相等依据和 === 基本一致,但是在判断 NAN 的时候会判断相等​​​,并且两个对象总是不相等的​​​
  2. 属性

    • size Set 实例成员的总数
  3. 方法

    • add()
    • delete()
    • has() bool 值, 是否是 set 成员
    • clear() 清除所有 set 成员

WeakSet

Set 的区别

  • 只能存储对象类型,不能是其他数据类型
  • 不进入垃圾回收机制
  • 没有 size 属性,同时不能遍历

Map

Map 的数据类型类似对象,但是对象的 key 值可以是任意类型的 Map: 值 - 值,不同于传统Object: 字符串 - 值
属性

  1. 属性

    • size 成员总数
  2. 方法

    • set()
    • get()
    • has()
    • delete()
    • clear()

WeakMap

和 Map 的结构类似

Map 的差异

  • 只接收对象作为键名
  • WeakMap 的键名所指向的对象,不计入垃圾回收机制

用途

  • 在 DOM 对象上保存相关数据
  • 数据缓存

    const cache = new WeakMap();function countOwnKeys(obj) {if (cache.has(obj)) {console.log('Cached');        return cache.get(obj);    } else {console.log('Computed');        const count = Object.keys(obj).length;        cache.set(obj, count);        return count;    }}
  • 私有变量

ArrayBuffer

操作二进制数据的一个接口。早就存在,属于独立的规格(2011 年 2 月发布),ES6 将它们纳入了 ECMAScript 规格,并且增加了新的方法。它们都是以数组的语法处理二进制数据,所以统称为 二进制数组

注意:二进制数组并不是真正的数组,而是类似数组的对象。

与 Array 的区别

  • 属性类型

    • Array 可以是基础数据类型,也可以是复杂数据类型
    • ArrayBuffer 只能是 0 和 1 组成的二进制数据
  • 数据存放规则

    • Array 复杂数据类型存放在堆中
    • ArrayBuffer 存放在栈中, 读取数据更快
  • 规格定义区别

    • Array 无需初始定义大小,并且在使用的时候可以缩放大小
    • ArrayBuffer 初始化需要定义大小,并不能再次修改

ArrayBuffer 对象

存储二进制数据的一段内存,不能直接进行读写,需要通过视图进行操作

    new ArrayBuffer(32) // 分配一个 32 位字节的内存
属性
- byteLength 返回分配区域的字节长度
方法
- slice() 操作 ArrayBuffer 对象 生成一个新的内存地址
- isView() bool 值返回是否是 TypedArray 视图实例(是否是一个视图)

TypedArray 视图

构造方法

通过构造方法生成 视图

注:存在溢出问题

  • TypedArray(buffer, byteOffset=0, length?)
  • TypedArray(length) // 直接分配内存
  • TypedArray(typedArray) // 直接复制一个视图的值,生成一个新的视图
  • TypedArray(arrayLikeObject) // 直接生成 TypedArray 实例
实例属性
- buffer // 返回 ArrayBuffer 对象
- byteLength // 占据内存长度(成员长度)- byteOffset // 视图从 ArrayBuffer 中开始位置
- length // 字节长度
实例方法
- set()// 复制数组
- subarray()// 返回新的视图
- slice()// 返回新的视图
- of()// 用于将参数转为一个 TypedArray 实例
- from() // from 接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于这个结构的 TypedArray 实例。​ 可有两个参数,第二个参数是 fun

DataView 视图

实例属性
- buffer // 返回 ArrayBuffer 对象
- byteLength // 占据内存长度(成员长度)- byteOffset // 视图从 ArrayBuffer 中开始位置

应用场景

  1. 网络请求中 blob 数据类型
  2. Canvas 读取二进制像素数据
  3. WebSocket 传输二进制数据
  4. File new FileReader() 读取 ArrayBuffer 对象

Promise/async

Promise 异步编程的解决方案,社区的提案,async 结局了 Promise 的回调地狱

Promise

有 padding(进行中)、fulfilled(成功)、rejected(失败)三种状态,状态已经改变就并不会变动

回调
  1. then() 返回结果
  2. catch() 捕获异常
  3. finally() 异步回调后都会执行,不管成功还是失败
方法
  1. all(<array>)
    多个 promise 回调 等待内部所有 Promise 都返回成功结果 / 凡是一个返回 reject 才执行 then(),多异步方法同时执行,所有都回调了再执行 then() 方法
  2. race(<array>)
    同 all,区别在于, 只返回最先返回结果的异步请求,不管成功还是失败
  3. any()
    和 recede 区别是一组 Promise 中只有所有都 reject 之后才会返回失败
  4. allSettled()
    不同于 all 的点是不管成功还是失败都需要等所有异步都返回接口才会返回,所以他的状态永远是 fulfilled

注意:

  1. 在 reject、resolve 中使用 return,避免后续代码执行问题
  2. Promise.all 如果 子 Promise 已经拥有了 catch() 则 all() 的 catch() 不再触发​​

Async

async 只是对 promise 的写法上的一种语法糖

async: 函数 返回一个 Promise 对象,return 返回 Promise 的结果

await: 后面默认是一个 Promise 对象

try catch(): 捕获错误

顶层 await

解决异步模块加载的问题

class

将传统 实例对象通过构造方法生成的过程放到 class 的语法糖中, 如下例

// 传统方法
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {return '(' + this.x + ',' + this.y + ')';
};

var p = new Point(1, 2);

// class 方法
class Point {constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {return '(' + this.x + ',' + this.y + ')';
  }
}

差异点: 严格模式、不存在变量提升、this 指向

取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为

静态方法 Static

静态方法不会被继承

静态属性

// 现有
class Foo {
}

Foo.prop = 1;
Foo.prop // 1

// 提案
Static prop = 1

私有方法 / 私有属性

私有方法只能通过命名规则或者 Symbol 数据类型定义,私有属性有个提案使用的是#

Module

ES6 提出的 Moduel 是前端发展过程中演变从 CommonJs 递进

Module 语法

  1. 静态化(编译时加载)
  2. 严格模式 遵循 ES5 的严格模式
  3. export 和 export default 暴露模块
  4. import 引入模块
  5. import() 解决了按需加载 / 条件加载 / 动态模块路径的问题
  6. export * from <moduel> 实现模块的继承

Module 的加载实现

  1. defer、async
    defer 是“渲染完再执行”,async 是“下载完就执行”。另外,如果有多个 defer 脚本,会按照它们在页面出现的顺序加载,而多个 async 脚本是不能保证加载顺序的。
  2. ES6 和 Commonjs 差异
    CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  3. 循环加载问题

    1. Commonjs
      CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。
    2. ES6
      使用 函数 提升的优势解决报错
退出移动版