ES6片段
那些可能会忘记或不知所以然的点:
1、
2、
3、
4、
5、
6、
/* 函数调用会在内存形成一个“调用记录”,又称调用帧,保存调用位置和内存变量等信息。 如果在函数 A 的内部调用函数 B,那么在 A 的调用帧上方,会形成一个 B 的 调用帧,等到 B 运行结束,将结果返回到 A,B 的调用帧才会消失。 */ function add(n) { if (n === 1) { return 1; } return n * add(n - 1); } add(3); // 6 /* 第一个入栈 add(3), return 3 * add(3 - 1) = 3 * add(2) 第二个入栈 add(2), return 2 * add(2 - 1) = 2 * add(1) 第三个入栈 add(1), return 1 第三个出栈 add(1), return 1,将返回结果返回到第二个栈中, 第二个出栈 add(2), return 2 * 1, 将返回结果返回到第一个栈中, 第一个出栈 add(3), return 3 * 2 * 1 */
7、
8、
9、
10、
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
function strMaptoObj (map) { let obj = Object.create(null); for (let [k, v] of map) { obj[k] = v; } return obj } let map = new Map([ [2, "e"], [{f: 'bar'}, 323], [{g: 'foo'}, "后来的对象会把前面的覆盖"] ]); console.log(strMaptoObj(map)); // {2: 'e', [object Object]: '后来的对象会把前面的覆盖'} console.log(strMaptoObj(map)["[object Object]"]); // 后来的对象会把前面的覆盖
11、
proxy 章节延伸 prototype
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> /* isPropertyOf:允许检查一个对象是否存在于另一个对象原型链上。 Foo.prototype.isPrototypeOf(baz):表示检查 baz 对象是否继承自 Foo 或者说 Foo.prototype 在 baz 的原型链上。 */ // Baz.prototype / Bar.prototype / Foo.prototype / Object.prototype 在 baz 对象的原型链上: function Foo() {} function Bar() {} function Baz() {} Bar.prototype = Object.create(Foo.prototype); Baz.prototype = Object.create(Bar.prototype); var baz = new Baz(); console.log(Baz.prototype.isPrototypeOf(baz)); // true console.log(Bar.prototype.isPrototypeOf(baz)); // true console.log(Foo.prototype.isPrototypeOf(baz)); // true console.log(Object.prototype.isPrototypeOf(baz)); // true </script>
12、promise 章节延伸执行机制
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
const promise = new Promise(function (resolve, reject) { resolve(2); }); // 备注为 A promise .then(value => {console.log(value); return value + s2; }) .catch(error => console.log("error1:", error)); // 备注为 B promise .then(value => { console.log(value); return value + 2 }) .then(val => { console.log(val); return val + y; }) .catch(error => console.log("error2: ", error)); /* 2 2 error1: ReferenceError: s2 is not defined 4 error2: ReferenceError: y is not defined 说明: catch 捕捉第一个报错的 promise 实例, A 中的 promise 实例链式里,出错的是第一个 then B 中的 promise 实例链式里,出错的是第一个 then */ /* 第一轮循环:promise 的第一个 then 方法,then 方法后的 then 或 catch 同样是异步,整体的执行顺序是: 第一轮微任务 A: promise.then // 打印 2 B: promise.then // 打印 2 第二轮微任务 A: promise.then.catch // 打印 error1: ReferenceError: s2 is not defined B: promise.then.then // 打印 4 第三轮微任务 B: promise.then.then.catch // 打印 error2: ReferenceError: y is not defined */
13、allSettled —— 不设置 await 写法
14、try —— 匿名函数执行,其实第二个写法直接写成 new Promise(resolve => resolve(f()) 不成了吗~
15、const 声明数字类型不能再重新修改赋值。
16、
17、
18、
typeof str -->“string” 返回的是字符串类型。
19、Iterator 和 Generator 、Array 对象的 for ... of 间的一些理解
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 具有 Iterator 接口的对象可以进行遍历,该对象具有 Symbol.iterator 属性,调用这个属性方法会生成一个遍历器对象。 使用遍历器对象调用 next() 方法可以返回含 value、done 属性对象。 字符串、数组、set、map、new Set()、new Map()、NodeList、Arguments、Array.from、Generator 生成的对象具有 Iterator 接口。 for ... of 对具有 iterator 接口才可遍历,对于数组来说,相当于数组对象调用 Symbol.iterator 方法,返回一个遍历器对象,该对象再调用 next() 方法,返回一个由数组成员作为 value 属性值的对象。 Generator 生成器函数返回的是一个遍历器对象,该遍历器对象也会有 Symbol.iterator 属性。 所以说 Generator 函数执行后,会返回一个遍历器对象。 该对象本身具有 Symbol.iterator 属性,执行后返回本身。 对于不具有 Iterator 接口的数据结构,可以为其添加 Symbol.iterator 属性方法,再在该方法中设置 next 方法返回对象,最后可以由 for of 直接比遍历输出。 */ var arr = [1,2]; var it = arr[Symbol.iterator](); var nextObj1 = it.next() var nextObj2 = it.next() console.log(nextObj1); // {value: 1, done: false} console.log(nextObj2); // {value: 2, done: false} console.log(nextObj1.value); // 1 console.log(nextObj2.value); // 2 for (v of arr) { console.log(v); // 1 // 2 }
20、yield
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 关于 yield 它也是一个表达式,但是这个表达式返回值,也就是它表示的值为 undefined。所以: var a = yield 3 中,a 为 undefined。 当遍历生成器对象调用 next 方法,指针指向这个内部状态(yield 3)时,返回的对象的 value 为 yield 后面的表达式值,也就是 3 ([value: 3, done: false])。 要使得 yield 表达式值不为 undefined,如上变量 a 也会因此不为 undefined。 a 的值是要在计算完 yield 3 后才返回的,也就是当遍历生成器对象再次调用 next 方法时才会对变量 a 求值。 这时在调用 next 方法,使得指针要指向下一个内部状态的 next 方法中传入参数,这个参数就作为上一个内部指针状态(也就是 yield 3) 的值,注意别和 next 返回的 {value: 3, done: false} 混淆。 */ function* gen(x) { var a = 2 + (yield (x + 3)); // 注,当 yield 作为表达式里面的一项表达式时,要用圆括号括起。 yield a; } var it = gen(3); // 调用生成器函数,生成遍历器对象 console.log(it.next()); // 指针指向第一个内部状态:yield (x + 3) = yield 6 —> {value: 6, done: false} console.log(it.next(3)); // 指针指向第二个内部状态: yield a,因为传了参数,所以上一个内部状态【yield (x + 3)】应该为 3,那么 a 则为 2 + 3;这里返回对象即 {value: 5, done: false} console.log(it.next()); // 函数没有设置返回值,故这里返回对象是 {value: undefined, done: true};如果设置 return 9, 这里返回对象就是 {value: 5, done: true}
21、Generator 抛出错误案例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> function* gen() { try { yield; } catch(e) { if (e != 'a') { console.log('内部 throw'); throw e; } console.log('内部抛出', e); } } var i = gen(); i.next(); // i.throw('a'); // 遍历器对象内部抛出的错误有在 catch 捕获,所以下面的第一行代码还能继续进行。 // i.throw(new Error('agl')); // 遍历器内部再抛出 throw e;时,没有捕获,从而后面的抛出不执行。 // throw(new Error('throw 命令抛出')); // 如果把这句放在前面两句前,那直接外部抛出错误,因为没有捕获,所以后面的两句不执行,除非在这句里添加 try ... catch /* Error: throw 命令抛出 内部抛出 a * */ try { throw (new Error('throw 命令抛出')); // 不放在try ... catch 中是不能正确捕获的。 } catch (e) { console.log(e); } i.throw('a'); i.throw(new Error('遍历器对象内部抛出')); /* Error: throw 命令抛出 内部抛出 a Uncaught Error: 遍历器对象内部抛出 * */ </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> /* 内部处理 throw 会自动执行下一次 next 方法(throw 后面的),因为执行的是 throw 后面的 next,所以 throw 前如果有多个内部状态(yield),在 throw 前只进行部分 next,那剩下的内部状态不会有指针所指向,如 b、c 不会被打印。 * */ function* gen() { try { yield console.log('a'); yield console.log('b'); yield console.log('c'); } catch(e) { console.log('throw 前后各一次 next'); } yield console.log('d'); yield console.log('e'); yield console.log('f'); } var i = gen(); i.next(); i.throw(); i.next(); /* a throw 前后个一次 next d e */ </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> function* gen() { yield 1; console.log("throwing an error"); throw new Error('generator broke'); yield 2; yield 3; } function log(generator) { console.log('try 1 ----------------'); try { var v = generator.next(); // 调用 next 完后执行下面的打印(yield 1 后面的在下一个 next 调用才执行) console.log('第一次调用 next 方法后遍历器对象为: ', v); } catch (err) { console.log('第一个 catch:', err); } console.log('try 2 ----------------'); try { var v2 = generator.next(); // 第二次 next 后执行第一个 next(yield 1)后面的内容,由于这个抛出错误,没有接住,所以直接 catch,故 v2 是未赋值成功(undefined) console.log('第二次调用 next 方法后遍历器对象为: ', v2); } catch (err) { console.log("v2", v2); console.log("v", v); // 这里是在第一个 try 声明的 v, 如果用 let 声明 v,会因作用域访问不到(index.html?_ijt=ntsbisjc0pgv9nplj3k5m4lq4l:149 Uncaught ReferenceError: v is not defined) console.log('第二个 catch:', err) } console.log('try 3 ----------------'); try { var v3 = generator.next(); // 抛出错误没有被内部捕获,不会再执行,返回 {value: undefined, done: true},表结束。 console.log('第三次调用 next 方法后遍历器对象为: ', v3); } catch (err) { console.log('第三个 catch: ', err); } } log(gen()); </script>
22、返回多层匿名函数 | call | apply
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> function fun(args) { console.log(args); } function arr() { return "3"; } var Thunk = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); return function (callback) { args.push(callback); console.log("args 参数", args); // args 参数 (2)['e', '3'] fn.call(this, args); // (2)['e', '3'] —— call 中, args 是以一个数组参数传入 fun 函数的第一个参数去的。 fn.apply(this, args); // e —— apply 中,args 是一个数组参数,其中,数组各个元素各自作为 fun 函数参数,因为 fun 只有一个参数,所以该参数为数组参数的第一个元素。 } } }; var first = Thunk(fun); // 返回第一个 return 函数 var second = first('e'); // 执行第一个 return 返回的匿名函数,返回第二个匿名函数 second(arr()); // 执行第二个匿名函数 /* 一个函数有两 return 时,运行该函数会返回第一个匿名函数; 第一个匿名函数执行时返回第二个匿名函数; 第二个匿名函数执行表示整一个函数执行完毕。 */ </script>
23、async generator 函数作为参数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> function f(a, cb) { cb(a); } var Thunk = function (f) { return function (a) { return function (cb) { f(a, cb); // 1 } } }; var ft = Thunk(f); // 调用函数作为参数(这个参数是个函数,该函数参数一是下面的第一个匿名函数的参数,参数二是下面的第二个匿名函数的参数[回调函数]),返回一个匿名函数 ft(1)(console.log); // 执行第一个匿名函数,返回第二个匿名函数, console.log 是个浏览器内置函数。 </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> var thunkify = function (f) { return function () { var args = new Array(arguments.length); var ctx = this; for (var i = 0; i < args.length; i++) { args[i] = arguments[i]; } return function (done) { var called; args.push(function () { // 这里是 f 的最后一个参数,当执行 f ,里面的 callback 就是这个匿名函数,sum 即参数。 if (called) return; called = true; done.apply(null, arguments); // 函数声明可以不带参数,调用时可传入,这里的 arguments 即 callback(sum) 中的 sum。 }); try { f.apply(ctx, args); } catch(err) { console.log(err); } } } }; function f(a, b, callback) { var sum = a + b; callback(sum); } var ft = thunkify(f); ft(1, 2)(console.log); /* 有一个共同点是: ft 返回的是一个函数,在这里函数里面有两个匿名函数。 第一个匿名函数是给参数函数 f 添加两个参数,第二个匿名函数是给参数函数 f 添加匿名函数。 * */ </script>
24、
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> function aaa() { return new Promise((resolve) => { // 返回一个 Promise 对象 console.log('1'); // setTimeout(function () { // 这里使用计时器模拟异步 // resolve(123); // }, 2000); resolve(123); // 这里是为了说明 new Promise 对象里面的 resolve(...) 方法才是异步 console.log('2'); }); // return Promise.resolve(123);等同于上面 return new Promise(...) } async function getaaa() { let a = await aaa(); // 会得到异步返回结果。 console.log(a); } getaaa(); // 1 - 2 - 123 </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> var sleep = function (time) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve('异步'); }, time); }); }; var start = async function() { // 在这里使用起来就像同步代码那样直观, console.log('start'); // 立即打印 start console.log(await sleep(3000)); console.log('end'); } start(); </script>
25、
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> var sleep = function (time) { return new Promise(function(resolve, reject) { setTimeout(function () { resolve(123); }, time); }); }; var start = async function() { console.log('start'); console.log(await sleep(2000)); // await 相当于 resolve 的 then sleep(2000).then(() => { // 这里不接收 resolve 传入的参数 console.log(456); }); console.log('end'); }; start(); // start // 123 两秒后输出 // end 紧接着上面输出而输出 // 456 两秒后输出 </script>
26、
关于执行顺序的注意事项:
代码执行顺序,从上到下,从左到右,同步>>异步,微任务>>宏任务
then的回调属于微任务
计时器等异步属于宏任务
await下边的代码都是异步执行的
return await 123 等价 return 123 ,下边的代码不执行
promise的状态不改变,then中的回调不执行
注:在下面的第二个例子中【await async2(); console.log('async1 end');】,await 只是影响它下面的语句(console.log(‘async1 end’))的运行,而不是阻塞原来同步语句的运行,也就是不影响 promise 1、end 的输出。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); async1(); new Promise(function (resolve) { console.log('promise1'); resolve(); }).then(() => { console.log('promise2'); }); console.log('end'); /* script start async1 start async2 promise1 end async1 end promise2 setTimeout * */ </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> var sleep = function () { return new Promise(function (resolve, reject) { resolve(123); }); } var start = async function() { console.log('start'); console.log(await sleep()); console.log('await 后面代码都是异步执行,await 只会存在 async 函数里面喔~') console.log('end'); }; start(); console.log('finished'); /* start finished 123 await 后面代码都是异步执行,await 只会存在 async 函数里面喔~ end */ </script>
27、class
类的数据结构是函数,类本身指向构造函数。
类的所有方法都定义在类的 prototype 属性上面。
调用类的实例方法,其实就是调用原型上的方法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> class Point { constructor() {} toString() {} toValue() {} } // 等同于(可以参看下面 Person 和 Animate 例子)。 Point.prototype = { constructor() {}, toString() {}, toValue() {} }; </script> <script> /* 构造函数写法 */ // 人的构造函数写法 function Person (name, age) { this.name = name; this.age = age; } // 获取人的信息方法,方法也可以在构造器里边些 Person.prototype.showInfo = function() { return `${this.name} is ${this.age} age`; }; // 通过构造函数创建人实例 var p = new Person("Leonie", "18"); console.log(p.showInfo()); // Leonie is 18 age /* 扩展说明: 在构造器中,如果要使得实例具有同样的属性和方法,除了在创建构造器时添加之外,后续可以通过 prototype 添加,这时实例通过原型可继承,如项目的 weight 属性。 */ Person.height = "168mm"; Person.prototype.weight = "50KG"; console.log(p.height); // undefined console.log(p.weight); // 50KG p.hairColor = 'black'; console.log(p); // Person {name: 'Leonie', age: '18', hairColor: 'black'} /* 类写法 */ class Animate { constructor(type, langue) { this.type = type; this.langue = langue; } say() { return `${this.type} are ${this.langue}`; } } var lion = new Animate("lion", "吼~~"); // lion are 吼~~ console.log(lion.say()); </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> class Point {} console.log(new Point() instanceof Point); // true /* constructor 方法默认返回实例对象,即 this,完全可以指定返回另一个对象 */ class Foo { constructor () { return Object.create({}); } } console.log(new Foo() instanceof Foo); // false console.log(new Foo() instanceof Object); // true class Bar { constructor () { return Object.create(null); // 创建对象为 null 时, 对象实例的 instanceof 没有 } } console.log(new Bar() instanceof Bar); // false console.log(new Bar() instanceof Object); // false </script>
28、class 扩展
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> /* 子类无法继承父类私有属性,或者说,私有属性只能在定义的 Class, 子类就可以通过非私有方法,如【useData】,读写私有属性。 */ class Foo { #p = 1; #m() { console.log('hello'); } useData() { console.log( this.#p ); this.#m(); } } class Bar extends Foo { constructor() { super(); this.useData(); } } new Bar(); // 打印 1 和 hello class Bar2 extends Foo { constructor () { super(); /* 下面这两行如果没有注销,会直接注销,最上面的 “1、hello”不会打印,是不是可以理解为在很多类继承的时候,先执行的是继承,然后再到实例【new Bar()】 Uncaught SyntaxError: Private field '#p' must be declared in an enclosing class */ this.#p; this.#m(); } } </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> class A { constructor() { this.x = 1; } print() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 2; super.y = 4; super.print(); } m() { super.x = 5; super.z = 7; super.print(); } } var b = new B(); // 执行构造函数中的 super.print() b.m(); // 5 因为 super.x = 5 console.log(b.y); // 4 console.log(A.prototype); // {constructor: ƒ, print: ƒ} A.prototype.d = 9; console.log(A.prototype); // {d: 9, constructor: ƒ, print: ƒ} /* * 打印出 2 * 如果 B 的构造函数不执行 this.x = 2; 那打印出来的就是 1,因为子类 B 的 x 属性会从 A 继承。 * 同理。普通方法中,super 可以表示父类的原型,如 A.prototype,当在子类中使用 super.y 时,相当于: * A.prototype.y,即子类 B 也会继承该属性,这时,子类中的 super 相当于 this 的作用。 * 但是需要注意的是,在子类的方法里,super 只相当于子类 B 实例的 this 对象,而不能因为普通方法里 super 看做是 A.prototype 而为 A 添加属性,如 y 、z。 * 只有明确添加才能是,如 A.prototype.d = 9; * */ </script>
29、
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> class A { func() { console.log('非静态继承方法'); } static func() { console.log('静态继承方法'); } } class B {} var b = new B(); // B 的实例继承 A 的实例 Object.setPrototypeOf(B.prototype, A.prototype); b.func(); // 非静态继承方法 // B 继承 A 的静态属性 Object.setPrototypeOf(B, A); B.func(); // 静态继承方法 class C { func() { console.log('extends 方式非静态继承'); } static func() { console.log('extends 方式静态继承'); } } class D extends C { } var d = new D(); // d.func(); // extends 方式非静态继承 D.func(); // extends 方式静态继承 console.log(D.__proto__ == C); // true console.log(D.prototype.__proto__ == C.prototype); // true console.log(C.prototype.constructor == C); // true console.log(C); // 注意类 “C” 其实就是构造函数的语法糖 console.log(C.prototype); // {constructor: ƒ, func: ƒ} 这里的 func 指的是非静态的。 </script> Object.setPrototypeOf 的实现方式: Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> /* 第一种,子类继承 Object 类。 A 其实就是构造函数 Object 的复制,A 的实例就是 Object 的实例。 * */ class A extends Object {} console.log(A.prototype.__proto__ == Object.prototype); // true console.log(A.__proto__ == Object); // true /* 第二种,不存在继承关系 * 这种情况下,B 作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承 Function.prototype。但是,B 调用后返回一个空对象(即 Object 实例),所以 B.prototype.__proto__ 指向构造函数(Object)的 prototype 属性。 */ class B {} console.log(B.prototype.__proto__ == Object.prototype); // true console.log(B.__proto__ == Function.prototype); // true </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> function mix(...mixins) { class Mix { constructor() { for (let mixin of mixins) { copyProperties(this, new mixin()); // 拷贝实例属性 -- constructor 方法里 } } } for (let mixin of mixins) { copyProperties(Mix, mixin); // 拷贝静态属性 -- static 方法里 copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性 -- 普通方法里 } return Mix; } function copyProperties(target, source) { for (key of Reflect.ownKeys(source)) { if ( key !== "constructor" && key !== "prototype" && key !== "name" ) { // 返回指定对象上一个自由属性对应的属性描述符,自有属性指的是直接赋予该对象属性,不需要从原型链上进行查找的属性。 var desc = Object.getOwnPropertyDescriptor(source, key); // 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 Object.defineProperty(target, key, desc); } } } class A { constructor () { this.A = '123'; } static ASFunc() { console.log('A-static-func'); } AFunc() { console.log('A-func'); } } class B { constructor() { this.B = '456'; } static BSFunc() { console.log('B-static-func'); } BFunc() { console.log('B-func'); } } class D extends mix(A, B) {} var d = new D(); console.log(d.A); D.ASFunc(); d.AFunc(); console.log(d.B); D.BSFunc(); d.BFunc(); // 方法理解 var testObj = { a: '123' }; var obj = Object.getOwnPropertyDescriptor(testObj, 'a'); console.log(obj); // {value: '123', writable: true, enumerable: true, configurable: true} Object.defineProperty(testObj, "b", { value: "456" }); console.log(testObj); //{a: '123', b: '456'} // for in 、for of var str = 'str'; for (item in str) { console.log(item); // 0、1、2 } for (item of str) { console.log(item); // s、t、r } </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script> 'use strict' /* 在 ES5 中,方法调用会看调用的对象,如果是在全局的情况下,this 默认为 window。 在 ES6 中,方法如果没有指定调用对象,则方法内顶层的 this 为 undefined。 * */ function test () { console.log(this); } test(); // undefined var obj = { fun: test }; obj.fun(); // {fun: f} 这个对象只有一个 fun 属性,值是一个函数 </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* export | import 命令可以出现在模块任何位置,只要处于模块顶层就可以。如果处于块级作用域,就会报错。 这是因为处于代码块之中,没法做静态优化,违背了 ES6 模块设计初衷。 为啥记录这点咧,因为对“可以出现在模块任何位置,只要处于模块顶层就可以”理解思路不对。 我是以平面的角度看待,而模块指的是整一个位置,可以理解为全局作用域,即变量如果不声明在其它作用域块中,它就是全局变量。 */ export var foo = 'bar'; // 属于模块顶层位置 function func() { export defult 'bar'; // 不属于模块顶层位置,要这是在块级作用域里边了 } export var name = "Leonie"; // 属于模块顶层位置 export var name2 = "Leonie"; // 属于模块顶层位置 export var name2 = "Leonie"; // 属于模块顶层位置
30、module
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script type="module"> yearFun(); import {yearFun} from './module/profile.js'; /* 上面代码不会报错,因为 import 的执行早于 foo 的调用。 这种行为的本质是:import 命令是编译阶段执行的,在代码运行之前。 */ import { 'f' + 'foo'} from 'my_module'; let module = 'my_module'; import { foo } from module; if(x = 1) { import { foo } from 'module1'; } else { import { foo } from 'module2'; } /* 由于 import 是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。 上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。 */ </script>
31、循环模块