注:由于ES6之后的标准变化不大,因此在此将ES6及之后标准发生的变化在此一起总结
三点运算符可以作为“扩展运算符”或“其余运算符”或“Object.assign简写形式”
其余运算符。例如:函数定义式参数带有扩展运算符,表示传入实参时可以传递多个,被收集成一个数组。
扩展运算符。作为扩展运算符时,对可迭代对象使用三点运算符都会调用迭代器方法。例如:解构声明变量时配合扩展运算符可以声明数组;console.log中使用扩展运算符可以直接遍历可迭代对象;在中括号中对可迭代对象使用扩展运算符可以进行浅拷贝。
Object.assign简写形式。例如:用于对象浅拷贝,在{}中使用三点展开对象,效果同Object.assign且不会调用迭代器方法。
**操作符代替Math.pow()进行指数运算
?.操作符是用于对象属性访问。短路操作,如果对象通过?.访问属性为null或undefined,那么不论属性访问式有多长访问结果立即为undefined。
let a = {
b: null
}
console.log(a.b?.c.d) // undefined
// ?.左边的a.b为null,那么这个属性访问表达式结果立即返回undefined
console.log(a.b?.().c.d()) // undefined
// 当然在调用方法时也是一样,在调用b()前发现a.b为null,那么访问表达式立即返回undefined
??操作符是用于求值。如果??操作符前的值为null或undefined,那么求值表达式的结果将会为??操作符后的值,反之求值表达式的结果将会为??操作符前的值。短路操作,如果操作符前不是null或undefined,操作符后就不管了。
;(function test(arg) {
arg = arg ?? 1
console.log(arg) // 1
// 由于执行时未给arg传值,arg为undefined,所以函数体第一行arg为1
})();
&&操作符可用于求值。如果&&操作符前后都是真值,那么返回&&操作符后的真值。否则返回假值。短路操作,如果操作符前是假值,操作符后就不管了。
console.log(0 && 12) // 0
console.log("dog" && null) // null
// 只有都是真值才会返回操作符后的结果
console.log("Danny" && 27) // 27
||操作符可用于求值。如果||操作符前后都是假值,那么返回||操作符后的假值。否则返回真值。短路操作,如果操作符前是真值,操作符后就不管了。
console.log(undefined || "Danny") // Danny
console.log("Danny" || undefined) // Danny
// 只有都是假值才会返回操作符后的结果
console.log(undefined || null) // null
解构提供了一种更好地提取数据的方式
let [a, b, c] = [1, 2, 3] // a = 1, b = 2, c = 3
数组解构是按照数组下标来提取值
let obj = {
name: "Danny",
gender: "man"
}
// ES6增强型对象写法
let { name, gender } = obj
// 对象解构普通写法,属性值用于存储提取出来的数据,属性名要和原对象属性名相同
let { name: name2, gender: gender2 } = obj
对象解构是按照属性名来提取值
let obj = {
event: {
target: {
result: "Danny"
}
}
}
let { event: { target: { result } } } = obj
console.log(result)
// 线性回归的核心迭代函数,参数比较多,可能记不清顺序
function linearRegression({theta, Y, X, alpha = 0.5, iter =10000}) {
// 略
}
let theta = []
let X = []
let Y = []
// 调用时只需要直到有哪些参数即可,不需要考虑它们的顺序
linearRegression({theta: theta, X: X, Y: Y})
(数据类型) BigInt也是JavaScript数据类型,至此共有8种基础数据类型。
(数值范围) IEEE754 64位标准表示的最大精度整数是16位,使用BigInt可以表示更大的数,在chrome测试最大能到10** 10 ** 9(10的10亿次方),理论上是无限大的。
(使用) 可以使用BigInt工厂函数或者数值后面加上一个n来表示BigInt类型
增加的Math方法一般用不到
模板字符串允许在字符串中插入动态的值,可以是JavaScript语句。这属于模板语法,模板语法不在此过多介绍,面试题总结基本从未涉及。
let s = "123Danny"
console.log(s.includes("Danny")) // true
let s = "123Danny"
console.log(s.startsWith("Danny")) // false
let s = "123Danny"
console.log(s.endsWith("Danny")) // true
let s = "123Danny"
console.log(s.padStart(20, " ")) // 123Danny
let s = "123Danny"
console.log(s.padEnd(20, " ") + 1) // 123Danny 1
let s = "123Danny"
console.log(s = s.padStart(20, " ")) // 123Danny
console.log(s = s.trimStart()) // 123Danny
let s = "123Danny"
console.log(s = s.padEnd(20, " "), s.length) // 20
console.log(s = s.trimEnd(), s.length) // 8
使用Symbol()工厂函数创建 (Symbol不像其它类型,Symbol只能作为工厂函数,不能作为构造函数),传入字符串作为参数,返回一个符号值,即使每次传入相同字符串,也会返回不同的符号。
使用Symbol.for()方法创建,传入字符串作为参数,如果传入相同字符串,那么返回相同的符号值。
[Symbol.iterator]方法和[Symbol.asyncIterator]方法用于实现迭代器和异步迭代器
let stu = {
name: "Danny"
}
console.log(stu.toString()) // [object Object]
class Test {
constructor() {}
get [Symbol.toStringTag]() {
return "Test"
}
}
let test = new Test()
console.log(test.toString()) // [object Test]
// 不要给函数添加这个方法,只适用于非函数对象
function Student(name, gender) {
let object = new Object()
object.name = name
object.gender = gender
return object
}
Student[Symbol.hasInstance] = function(obj) {
console.log(obj)
return "name" in obj && "gender" in obj ? true : false
}
let student = Student("Danny", "man")
console.log(student instanceof Student) // false,这个在工厂函数中使用不起作用
// 正确示范,在对象中使用
let grade = {
[Symbol.hasInstance](grade) {
return grade >= 0 && grade <= 100 ? true : false
}
}
console.log(98 instanceof grade) //true
详情见“面向对象编程”中继承中关于类的介绍
let stu = {
name: "Danny",
gender: "man",
grade: 100,
changeName(name) {
this.name = name
}
}
// 枚举出所有属性(上述属性都是可枚举的),枚举有特殊顺序
for(let el in stu)
console.log(el)
// 该对象不是可迭代对象,枚举会报错
for(let el of stu)
console.log(el)
function* generator(n) {
let a = 0, b = 1
while(a < n) {
yield a;
[a, b] = [b, a + b]
}
}
let fib = generator(100)
for(let el of fib)
console.log(el)
// 实现一个异步迭代器
function asyncIterator(ar) {
let index = 0, length = ar.length
return {
[Symbol.asyncIterator]() {
return this
},
next() {
if(index === length)
return new Promise(resolve => {
resolve({done: true, value: undefined})
})
else {
if (ar[index] instanceof Promise)
return new Promise((resolve, reject) => {
ar[index ++].then(res => {
resolve({done: false, value: res})
}, error => {
reject(error)
})
})
else
return new Promise(resolve => {
resolve({done: false, value: ar[index ++]})
})
}
}
}
}
// 创建测试数据
let p1 = new Promise(res => {
setTimeout(() => {
res("p1")
}, 3000)
}), p2 = new Promise(res => {
setTimeout(() => {
res("p2")
}, 2000)
});
(async function () {
// 使用for await迭代异步迭代器
for await(let p of asyncIterator([p1, p2]))
console.log(p);
// 手动迭代异步迭代器
(function autoMaker(asyncIter) {
let result = asyncIter.next();
(function next(result) {
result.then(res => {
if(res.done !== true) {
console.log(res.value)
result = asyncIter.next()
next(result)
}
})
})(result);
})(asyncIterator([p1, p2]));
})();
只在ES6的文档中就扩展了不少新的对象方法,这里列举一下常用的。
注:虽然像Object.freeze和Object.seal这些方法也很有用,但是这里先不总结。Object.freeze可以配合Vue使用提高性能,等用到的时候再做总结。
判断规则:
- 类型转换不同于=和,不会对value1和value2做强制类型转换。
- null和undefined不同于=和,都为null或都为undefined时为true
- 字符串判断同=和,字面相同即为true
- 数值判断不同于=和,NaN和NaN为true,+0和-0为true
- 对象判断不同于=和,指向同一个堆内存的对象为true
合并规则:
- target是目标对象,args都是来源对象,将来源对象的可枚举属性都复制到target上面,如果出现覆盖现象,那么按顺序覆盖。
- "Object.assign"和"扩展操作符+对象字面量"来合并对象,都是浅复制,不会复制堆内存。
Array.from(iterator) 工厂方法,可以将一个可迭代对象展开,转换成一个数组
// 斐波那契数列迭代器
function generator(n) {
let a = 0, b = 1
return {
[Symbol.iterator]() {
return this
},
next() {
let temp = a
if(temp <= n ) {
[a, b] = [b, a + b]
return {done: false, value: temp}
}
else
return { done: true, value: undefined }
}
}
}
// 得到一个1000以内的斐波那契数列数组
console.log(Array.from(generator(1000)))
Array.isArray(obj) 工厂方法,判断传入的参数是否是数组
Array.of(…args) 工厂方法,传入任意多个元素,成为数组的成员。在Array构造函数中,如果只传入一个元素,那么会设定为数组长度,Array.of()解决了这个问题。
array.findIndex() 新增了数组元素搜索方法,搜不到返回-1,否则返回满足条件的元素的位置。此方法比indexOf方法更加灵活,这种方法可以说是高阶函数,允许自己编程。
// 制造100个位于-50~+50之间的随机数
let ar = []
for(let i = 0; i < 100; i ++)
ar[i] = (Math.random() - 0.5) * 100
// 寻找第一个大于60的数的下标
let index = ar.findIndex((el, index, array) => {
return el > 30
},this)
console.log(index)
array.find() 新增了数组元素搜索方法,搜不到返回undefined,否则返回满足条件的元素的值。
// 制造100个位于-50~+50之间的随机数
let ar = []
for(let i = 0; i < 100; i ++)
ar[i] = (Math.random() - 0.5) * 100
// 寻找第一个大于60的数的值
let el = ar.find((el, index, array) => {
return el > 30
},this)
console.log(el)
array.flat(depth) 新增了数组扁平化方法。可以设置扁平的层数。这个方法返回一个新数组,不修改原数组。
let ar = [1, [2, [3, [4, [5]]]]]
console.log(ar.flat(1), ar) // [ 1, 2, [ 3, [ 4, [Array] ] ] ] [ 1, [ 2, [ 3, [Array] ] ] ]
console.log(ar.flat(2), ar) // [ 1, 2, 3, [ 4, [ 5 ] ] ] [ 1, [ 2, [ 3, [Array] ] ] ]
console.log(ar.flat(3), ar) // [ 1, 2, 3, 4, [ 5 ] ] [ 1, [ 2, [ 3, [Array] ] ] ]
console.log(ar.flat(4), ar) // [ 1, 2, 3, 4, 5 ] [ 1, [ 2, [ 3, [Array] ] ] ]
array.flatMap() 新增了数组扁平化映射方法。默认扁平化一层。这个方法会先进行map映射,最后再进行数组扁平化。这个方法返回一个新数组,不修改原数组。
// 使用flatMap方法比map方法更高效,除了能扁平化,还能实现filter的效果。
let ar = [1, , , 2, , 3, [], 4, , [], , , 5]
console.log(ar.flatMap((el, index, array) => {
return el ? el : []
}, this)) // [ 1, 2, 3, 4, 5 ]
array.fill(value, start, end) 新增了数组填充方法。从起始位置到终点位置填充value值(左闭右开的区间)。这个方法不返回一个新数组,会修改原数组。
let ar = new Array(10)
ar.fill(12, 0, 9)
console.log(ar) // [ 12, 12, 12, 12, 12, 12, 12, 12, 12, <1 empty item> ]
array.includes(el) 新增了数组的元素存在方法。这个方法相比下面“12”中介绍的集合的测试元素是否存在方法低效很多。
let ar = new Array(10)
ar.fill(12, 0, 9)
console.log(ar.includes(11), ar.includes(12)) // false true
详情见“JavaScript解释器和编译器”中JavaScript运行时执行上下文创建时关于this绑定的问题
注意一下形参赋默认值实际是在执行上下文的词法环境中赋的值,不是在变量环境中赋值。详情见“JavaScript解释器和编译器”中JavaScript运行时执行上下文创建时的词法环境和语法环境,以及提供的例题。
详情见“函数式编程”中尾调用
详情见“JavaScript异步编程”中的Promise
详情见“JavaScript异步编程”中的迭代器和生成器
详情见“JavaScript异步编程”中的async和await
反射对象是Reflect,它类似于Map定义了一些映射关系。Reflect的方法直接映射到操作对象的方法上。举例如下:
let p = new Proxy(target, handler)
对象类型 | 意义 | 作用 |
---|---|---|
代理对象 | Proxy构造函数的实例化对象,指的是p | 目标对象的代理人,用户发起的对目标对象的操作都交给代理对象处理。代理对象接收到操作请求后会交给处理器对象。 |
目标对象 | 一个普通对象,指的是target | 目标对象不希望暴露出去,指派代理对象作为代理人。 |
处理器对象 | 一个普通对象,指的是handler | 目标对象的操作人,处理器对象有自己的处理函数命名规范,用来处理对象操作,例如set用于设置对象属性,get用于获取对象属性,也可以调用反射对象方法将操作返回给目标对象执行。 |
对象代理中可以创建一个可撤销代理,当需要把对象交给第三方库时,可以给对象创建一个透明包装(处理器对象为空对象),并且这个包装是可撤销的,一使用完第三方库就立即撤销代理,第三方库无法访问目标对象。
// 一个涉及到学生成绩隐私的对象
let stu = {
name: "Danny",
grade: [98, 96, 96, 91, 91, 97, 94]
}
// 一个不受信赖的第三方函数
function unSafeFunc(stuInfo) {
console.log(stuInfo)
}
// 创建可撤销代理
let { proxy, revoke } = Proxy.revocable(stu, {})
// 第三方函数操作,此时第三方函数中可以操作该对象
unSafeFunc(proxy)
// 撤销代理,proxy代理对象不能再代理目标对象stu,就是proxy不能再操作stu
revoke()
// 第三方函数再次尝试操作,发现不能操作该对象,直接报错
unSafeFunc(proxy)
在3.5的示例中如果想保护学生隐私,不想让成绩被别发现,那么可以在处理器对象中设置拦截,在has函数和get函数中判断如果出现grade属性相关操作,那么一律拦截。
has(target, name) {
if(name === "grade")
return false
return Reflect.has(target, name)
}
get(target, name, receiver) {
if(name === "grade")
return undefined
return Reflect.get(target, name, receiver)
}
对代理对象的操作会先交付给处理器对象,如果在处理器对象中设置监听操作,那么就可以记录这些针对目标对象的操作。
let stu = {
name: "Danny",
grade: [98, 96, 96, 91, 91, 97, 94]
}
let handler = {
get(target, name, receiver) {
console.log(`监听到访问对象${name}属性的操作`)
return Reflect.get(target, name, receiver)
},
set(target, name, value, receiver) {
console.log(`监听到修改对象${name}属性值为${value}的操作`)
return Reflect.set(target, name, value, receiver)
},
has(target, name) {
console.log(`监听到查询对象是否存在${name}属性的操作`)
return Reflect.has(target, name)
}
}
let proxy = new Proxy(stu, handler)
proxy.name = "Lucy"
console.log(proxy.name)
console.log("grade" in proxy)
可以利用3.4中提到的拦截机制来提供JavaScript中不具备的自动类型检查功能。类型检查不用写在类中,提高了代码的简洁性。
class Student {
constructor(name, gender) {
this.name = name
this.gender = gender
}
}
let StudentProxy = new Proxy(Student, {
construct(target, args, newTarget) {
if(typeof args[0] !== "string")
throw new TypeError("姓名必须是字符串")
if(typeof args[1] !== "string" || (args[1] !== "男" && args[1] !== "女"))
throw new TypeError("性别必须是男或女")
return Reflect.construct(target, args, newTarget)
}
})
// 由于man不符合处理器对象第二条规范,所以会抛出错误
let stu = new StudentProxy("Danny", "man")
可以利用3.4中提到的拦截机制来进行数据绑定,把两个本来不相关的数据绑定在一起。下面实现了每创建一个学生就会自动被加入学生列表的操作。
let stuList = []
class Student {
constructor(name, gender) {
this.name = name
this.gender = gender
}
}
let StudentProxy = new Proxy(Student, {
construct(target, argArray, newTarget) {
let stu = Reflect.construct(target, argArray, newTarget)
stuList.push(stu)
return stu
}
})
let stu = new StudentProxy("Danny", "man")
console.log(stuList)
运用方向 | 应用场景 |
---|---|
保护目标对象 | 3.3 保护引用 |
保护目标对象 | 3.4 隐藏属性 |
提高代码封装性 | 3.5 记录操作 |
提高代码封装性 | 3.6 类型验证 |
提高代码封装性 | 3.7 数据绑定 |
情况 | Map | Object |
---|---|---|
意外的键 | Map默认情况下不包含键 | 默认情况下Object包含原型链上的键,可能会被自身的键覆盖 |
键的顺序 | 枚举时Map的键的顺序是插入顺序 | 枚举时Object的键有特殊顺序 |
键的类型 | Map的键可以是任意类型 | Object的键只能是Symbol或者String |
迭代性 | Map可迭代 | Object只有写迭代器才可以迭代 |
性能优化 | 1.可以快速通过size属性获取键值数量 2.对频繁增删查改键值做出优化 | 未作出优化 |
根据“12.1”中提到的Map和Object的对比,可以考虑用Object来模拟一个Map。实际上就是指定一个特殊的映射,可以使用hash算法,也可以借助Symbol.for()来完成,将键进行序列化后传入Symbol.for()得到一个符号,一定和传入其它字符串产生的Symbol不同。
基础方法:
迭代器方法:
情况 | Map | WeakMap |
---|---|---|
键的要求 | 原始类型或者引用类型 | 必须是引用类型 |
垃圾回收 | Map中的值如果是引用类型,相当于对该堆内存又添加了一个引用,不管是标记清理还是引用计数都无法释放该堆内存 | WeakMap中的值是引用类型,但是不会影响该堆内存被垃圾回收 |
迭代性 | 可迭代 | 由于值随时会被垃圾回收,所以不可迭代 |
情况 | Set | Array |
---|---|---|
顺序性 | 元素插入Set的顺序 | 有数值索引顺序 |
重复性 | 不允许元素重复 | 允许元素重复 |
迭代性 | 可迭代 | 可迭代 |
索引 | 无索引 | 有索引 |
性能优化 | 1.对检查是否存在某一个元素做了优化 | 相比Set没什么优化 |
上述提到的Array来模拟,实际上可以对Array增加一些检查机制,可以通过对象代理来实现,来实现Set对于元素的严格要求。之后对元素检查做一个O(lgn)的优化。
基础方法:
迭代器方法:
1. 模块加载器机制: 为了实现CommonJS,NodeJS或webpack会实现一个模块加载器,用于实现下列两种机制。模块加载器的部分代码如下举例。
2. 包裹机制: 实现CommonJS模块化需要在模块被创建时在外面包裹一个闭包
// 下面是模拟实现,Module类控制模块,调用Module.wrap方法,传入你的代码后就会自动对你的代码进行包装
Module.wrapper = [
// 可以看到CommonJS要求为每个模块额外提供5个全局变量
'(function (exports, require, module, __dirname, __filename) {', '})'
]
Module.wrap = script => {
return `${Module.wrapper[0]}${script}${Module.wrapper[1]}`
}
3. 同步加载机制: 实现CommonJS模块化需要实现重要的模块加载机制。加载机制可以简述为以下几个步骤:
(1) 解析路径
(2) 调用底层的C++提供的IO模块同步读取文件
(3) 创建模块对象,创建时会实现上面的包裹机制
(4) 将模块对象加入自定义的缓存对象存储,提高多次调用时的效率
(5) 返回该模块暴露出来的module.exports对象
// 模拟实现的核心代码
// 实现模块加载的函数
function req (filename) {
//1.我们需要一个绝对路径来,缓存是根据绝对路径的来的
filename = Module.resolvePathName(filename)
if(!filename) return new Error('not find file')
//判断是否有缓存,有的话返回缓存对象
let cacheModule = Module._cache[filename]
if(cacheModule) return cacheModule
// 2,没有模块 创建模块
let module = new Module(filename) //创建模块
//3.加载这个模块{filename: 'c:xx', exports: 'hello world'}
module.load(filename) // 这里不过多列举load方法,感兴趣可以再到网上了解
//4.把加载好的模块加入缓存
Module._cache[filename] = module
return module.exports
}
// 实现路径解析的函数
Module.resolvePathName = filename => {
// 1.拿到路径,进行解析绝对路径
let p = path.resolve(__dirname, filename)
//2.判断路径里是路径还是文件名,如果是文件名的话查找文件
if(!path.extname(p)) {
//4.如果有文件名,则确定是一个文件,开始
for(var i =0, arr = Module._extentions, len = arr.length; i < len; i++) {
let newPath = `${p}${arr[i]}`
//如果访问的文件不存在, 就会发生异常
try {
fs.accessSync(newPath)
return newPath
} catch(e) {}
}
} else {
// 3.如果没有文件名,则进行模块化加载:查找同名文件夹下的package.json || index.js
// 这里没有做处理,只是做了防止报错
try {
fs.accessSync(p)
return p
} catch(e) {}
}
}
require(filename) 表示加载某一个模块,加载规则如下
exports 表示一个对module.exports的引用,即module.exports === exports为true。模块在导出时导出的是module.exports的栈内存地址,而不是exports的栈内存地址。这就让人想到了在“原型链”中总结到的原型的动态性,实例化对象的__proto__指针和构造函数的prototype指针与这里的module.exports指针和exports指针的情况有些相似的地方。 这里也可以举出很多例子来。
举例一: 修改module.exports的堆内存
// A.js
console.log(require("./B.js")) // msg,name,gender都会输出
// B.js
module.exports.msg = "hello"
exports.name = "Danny"
exports.gender = "man"
举例二:重写module.exports的堆内存
// A.js
console.log(require("./B.js")) // 只会输出msg
// B.js
// module.exports的堆内存被重写了,即新开辟了堆内存。下面exports仍然在旧的堆内存上修改,这些修改不会作用到最后的结果上去。
module.exports = {
msg: "hello"
}
exports.name = "Danny"
exports.gender = "man"
module 该对象主要使用module.exports进行模块内容导出,使用module.exports导出的内容会被其它引用它的模块接收(可以参考13.1.1中提到的模块加载机制)
__dirname 表示当前文件所在的文件夹的绝对路径
__filename 表示当前文件所在的绝对路径,包含文件名
NodeJS实现了CommonJS规范,可以直接使用上述提到的所有机制。在客户端中不能直接使用CommonJS规范,需要借助webpack等打包工具实现上述机制。
AMD的实现机制和CommonJS的实现机制大同小异,也分为以下三点。
1. 模块加载器机制: 与CommonJS类似,可以在github中找到对应的实现(require.js)
2. 包裹机制: moduleName表示当前模块的名称,不写moduleName会成为匿名模块,文件名为模块名。requireModules表示要异步引入的模块的名称,这些模块的工厂函数返回值将会作为当前模块的全局变量。function是一个工厂函数,工厂函数是需要返回内容,这个返回的内容将会被引用该模块的模块接收到,在下面例子中提到工厂函数也可以模仿CommonJS的动态加载机制。
// requireModules也可以是require和modules等等,这样就可以在AMD规范中实现一个CommonJS规范的风格
define(moduleName, [...requireModules], function(...requireModules) {
return {
msg: "hello"
}
})
3. 异步加载机制: 与CommonJS类似,只不过换成了异步加载
1. 不同点: AMD是“异步加载机制”更关注于客户端JavaScript,CommonJS在上面提到是“同步加载机制”更关注于服务端JavaScript。
2. 相同点: CommonJS是NodeJS的默认模块机制,如果想在NodeJS使用AMD需要通过npm或yarn安装。CommonJS和AMD都是使用了“模块加载器”机制。
CMD的实现机制和AMD和CommonJS实现大同小异,也分为以下三点。
1. 模块加载器机制: 与CommonJS类似,可以在github中找到对应的实现(sea.js)
2. 包裹机制: 包裹机制和AMD太相似了,但是CMD默认为你提供了一个CommonJS风格的模块。CMD虽然可以像AMD一样在模块创建时在第二个参数写依赖模块,但是提供的CommonJS风格的模块还是希望你使用动态模块加载机制,使用require在用到模块时再加载。
define(moduleName, [...requireModules], function(require, exports, module) {
return {
msg: "hello"
}
})
3. 加载机制: 可以在第二个参数中写依赖的模块,就像模仿AMD规范一样,也可以使用CommonJS风格,动态加载模块。
UMD规范主要是为了整合上述规范。实现思路是,模块创建时会自动检测define,module的类型情况,以此来判断该使用哪一种模块。
注:起始ES6的模块机制的export和import有很多种有意思的用法,这里仅列举出常用的,不涉及一些组合用法。
导出export:
导入import:
CommonJS模块输出测试
// test.js let {a} = require("./server.js") console.log(a) // 1 setTimeout(() => { console.log(a) // 1 }, 3000) // server.js let a = 1 setTimeout(() => { a = 3 }, 2000) exports.a = a
ES6模块测试
// test.js import {a} from "./server.js" console.log(a) setTimeout(() => { console.log(a) }, 3000) // server.js let a = 1 setTimeout(() => { a = 3 }, 2000) export { a }
CommonJS: CommonJS模块是对象。在遇到require时,会加载整个模块。模块导出值集中到module.exports暴露出去。
ES6: ES6模块不是对象。在遇到import时会去找导出的变量,而不是加载整个模块。export+{}表示显示输出代码,而不是将变量集中到某个变量中去。