9 月抽空重新回顾了下 ES6 所有知识点,整个回顾过程既惊喜又感慨,感慨开发这么久好像真的没有好好的静下心去读一本好的书,大多情况下只是在使用的时候用到了,不熟悉或者感兴趣再去走马观花一通,感慨之余也发现了一些自身的的问题,知识体系还是不够丰富、扎实,一句话:有空多读书总没错,好了不闲扯了,下面我们步入正题
关于 JavaScript
如今前端知识日新月异,越是晚入门小伙伴,很多基础层面的东西,接触的真的是少之又少,各种前端框架层粗不穷
Vue
、React
、Ng
这三大带有里程碑意义的框架Api
我想看的这么文字的盆友应该都有接触,基础的Api
使用应该是没有什么难度,但是在遇到问题,解决问题、以及在原有组件以及自我封装组件,搭建框架过程总会遇到一些莫名其妙的问题,解决这个问题,看源码
是大众都知道的一些方法,但个人认为这是一种进阶的方法,新手入门建议可以先了解咱们JavaScript 的发展史
可以比较轻松的认识我们整个技术体系的发展和未来方向,避免一些认知错误
发展史
-
互联网早期
- 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 的官方标准。
- BS 架构:1990 年的 12 月 25 日,西方的圣诞节,Tim Berners-Lee 在他的 NeXT 电脑上部署了第一套
-
互联网发展期
- 动态页面伊始:由于
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.0
、Web 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
包管理工具为整个前端社区提供了一个规范良好的平台 - 动态页面伊始:由于
-
互联网进击
- 移动 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 技术。
- 移动 App、Hybrid App:移动互联网的到来,飞速发展,原生 app 迭代远远满足不了,大家找到折中的方法,损耗部分性能,提高产品产出;
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
特性如下
- 区别于
var
的块级作用域 - 不存在变量提升问题
- 具有暂时性死区
- 不能重复命名
// 下面这块很好的体现了 ` 块级作用域 `
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()代替
遍历方法
- for in
-
Object.getOwnPropertyNames()
- 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
-
Object.getOwnPropertySymbols()
- 返回一个数组,包含对象自身的所有 Symbol 属性的键名。
-
Reflect.ownKeys()
- 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
-
Object.is()
- 传统判断值是否相等使用
==
或者===
, 前者会强制类型转换,后者NAN 不等于自身,并 + 0 等于 -0
, 此方法就解决了上述问题
- 传统判断值是否相等使用
-
Object.assign()
- Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),
第一个参数就是目标对象
, 只会处理可枚举的数据类型,并 Symbol 值属性也会被拷贝 - 数组处理将值一一对应成对象类型
- Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),
-
Object.getOwnPropertyDescriptors()
- 对 ES5 中 Object.getOwnPropertyDescriptor()的拓展,返回对象中所有属性(非继承)的值
-
原型操作方法替代
__proto__
- Object.setPrototypeOf()(写操作)、
- Object.getPrototypeOf()(读操作)、
- Object.create()(生成操作
-
对象键值对获取
- Object.keys(),
- Object.values(),
- Object.entries()
-
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('描述文件')
特性:
- 不能 new 操作 注意 Symbol()是一个类似 string 类型的值,不是对象
- 不能与其他类型值进行计算
- 可以显性的转换成字符类型 / 布尔值
- 座位对象的属性的时候需要注意和字符型属性,赋值、取值区别开来
方法:
- Symbol.for()、Symbol.keyFor()
返回同一个标识的 Symbol 类型,keyFor 返回当前 Symbol 类型的唯一标识 - Symbol.isConcatSpreadable
判断数组 concat()操作的时候是否可以会别呗展开 - Symbol.match
- Symbol.replace
- Symbol.search
- Symbol.match
- Symbol.iterator
- Symbol.toPrimitive
- Symbol.toStringTag
- Symbol.unscopables
Set、Map 数据结构
Set
Set 数据结构。它类似于数组,但是成员的值都是唯一的,没有重复的值。
特性:
-
去重
Set() 去重的判断相等依据和 === 基本一致,但是在判断 NAN 的时候会判断相等,并且两个对象总是不相等的 -
属性
- size Set 实例成员的总数
-
方法
- add()
- delete()
- has() bool 值, 是否是 set 成员
- clear() 清除所有 set 成员
WeakSet
和 Set
的区别
- 只能存储对象类型,不能是其他数据类型
不进入垃圾回收机制
- 没有 size 属性,同时不能遍历
Map
Map 的数据类型类似对象,但是对象的 key 值可以是任意类型的
Map: 值 - 值
,不同于传统Object: 字符串 - 值
属性
-
属性
- size 成员总数
-
方法
- 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 中开始位置
应用场景
- 网络请求中 blob 数据类型
-
Canvas
读取二进制像素数据 - WebSocket 传输二进制数据
-
File
new FileReader() 读取 ArrayBuffer 对象
Promise/async
Promise 异步编程的解决方案,社区的提案,async 结局了 Promise 的回调地狱
Promise
有 padding(进行中)、fulfilled(成功)、rejected(失败)三种状态,状态已经改变就并不会变动
回调
- then() 返回结果
- catch() 捕获异常
- finally() 异步回调后都会执行,不管成功还是失败
方法
- all(<array>)
多个 promise 回调 等待内部所有 Promise 都返回成功结果 / 凡是一个返回 reject 才执行 then(),多异步方法同时执行,所有都回调了再执行 then() 方法 - race(<array>)
同 all,区别在于, 只返回最先返回结果的异步请求,不管成功还是失败 -
any()
和 recede 区别是一组 Promise 中只有所有都 reject 之后才会返回失败 -
allSettled()
不同于 all 的点是不管成功还是失败都需要等所有异步都返回接口才会返回,所以他的状态永远是 fulfilled
注意:
- 在 reject、resolve 中使用 return,避免后续代码执行问题
- 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 语法
- 静态化(编译时加载)
- 严格模式 遵循 ES5 的严格模式
- export 和 export default 暴露模块
- import 引入模块
- import() 解决了按需加载 / 条件加载 / 动态模块路径的问题
- export * from <moduel> 实现模块的继承
Module 的加载实现
- defer、async
defer 是“渲染完再执行”,async 是“下载完就执行”。另外,如果有多个 defer 脚本,会按照它们在页面出现的顺序加载,而多个 async 脚本是不能保证加载顺序的。 - ES6 和 Commonjs 差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 -
循环加载问题
- Commonjs
CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。 - ES6
使用 函数 提升的优势解决报错
- Commonjs