目录
以往ES6文章
ES6标准---【一】【学习ES6看这一篇就够了!!】_es6学习-CSDN博客
ES6标准---【二】【学习ES6看这一篇就够了!!】_es6中的includes-CSDN博客
ES6标准---【三】【学习ES6看这一篇就够了!!!】-CSDN博客
ES6标准---【四】【学习ES6标准看这一篇就够了!!!】_es6 有arguments 吗-CSDN博客
ES6标准---【五】【看这一篇就够了!!!】-CSDN博客
ES6标准---【六】【学习ES6标准看这一篇就够了!!!】-CSDN博客
ES6标准---【七】【学习ES6标准看这一篇就够了!!!】-CSDN博客
ES6标准---【八】【学习ES6看这一篇就够了!!!】-CSDN博客
JavaScript在浏览器中的加载
传统方法
HTML网页中,浏览器通过<scipt>标签加载JavaScript脚本
<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
// module code
</script>
<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>
一般情况下,浏览器是同步加载JavaScript脚本
渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染
这就造成如果脚本体积很大,渲染时间会很长,造成浏览器堵塞,所以浏览器允许脚本异步加载,下面是两种异步加载方法:
- defer:等待整个页面在内存中正常渲染结束(DOM结构完全生成,其它脚本执行完成),才会执行
- async:一旦下载完,渲染引擎就会中断渲染,优先执行这个脚本,待执行完这个脚本之后,再继续渲染
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
加载规则
浏览器加载ES6模块,也使用<script>标签,但是要加入【type="module"】属性
浏览器对于带有type="module"的<script>,都是异步加载,不会造成浏览器堵塞,等同于使用了<scipt>标签的defer属性
<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>
注意
对于外部的模块脚本,有下面几点需要注意:
- 代码是在模块作用域之中运行,而不是在全局作用域中运行,模块内部的顶部变量,外部不可用
- 模块脚本自动采用严格模式,不管有没有声明use strict
- 模块之中,顶层的this关键字返回undefined,而不是指向window,也就是说在模块顶层使用this关键字,是无意义的
顶部变量外部不可用
<body>
<script type="module">
var x = 10;
</script>
<script>
var y = 10;
</script>
<script>
console.log(y); // 10
console.log(x); // 报错,x未定义
</script>
</body>
效果:
this关键字返回undefined
<body>
<script type="module">
console.log("这是module模块:",this);
</script>
<script>
console.log("这是普通模块:",this);
</script>
</body>
效果:
JavaScript的循环加载
循环加载指的是:
“a脚本的执行依赖b脚本,而b脚本的执行有依赖a脚本”
这就造成了“强耦合”,还可能导致递归加载(在其它编程语言如Python中,会报错)
但实际上,循环加载是不可避免的,为此ES6标准作出如下规定:
ES6模块的循环加载
ES6模块是动态引用,如果使用import从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';
结果:
b.mjs
ReferenceError: foo is not defined
下面我们来分析一下执行过程:
首先执行a.mjs以后,引擎发现它加载了b.mjs,因此会优先执行b.mjs,然后再执行a.mjs
接着,执行b.mjs的时候,已知它从a.mjs输入了foo接口,这时不会去执行a.mjs,而是认为这个接口已经存在了,继续往下执行
执行到console.log(foo)的时候,才发现这个接口根本没定义,因此报错
解决办法:
使用函数代替变量表达式(函数具有提升效果)
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
function foo() { return 'foo' }
export {foo};
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo());
function bar() { return 'bar' }
export {bar};
- 但是如果把函数改写成函数表达式,仍然会报错(函数表达式也是表达式,没有提升效果)
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
const foo = () => 'foo';
export {foo};
块级作用域
let取代var
ES6提出的let和const,其中let可以完全取代var,因为两者语义完全相同,而且let没有副作用(var命令存在变量提升效果,let命令没有这个问题)
'use strict';
if (true) {
let x = 'hello';
}
for (let i = 0; i < 10; i++) {
console.log(i);
}
上面代码如果用var代替let,实际上就声明了两个全局变量
变量应该只在声明的代码块内有效
全局变量和线程安全
在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量
const优于let的原因:
- 提醒代码阅读者,这个变量不能修改
- 复合函数时编程思维,运算不改变值,只是新建值
- JavaScript编译器会对const进行优化
// bad
var a = 1, b = 2, c = 3;
// good
const a = 1;
const b = 2;
const c = 3;
// best
const [a, b, c] = [1, 2, 3];