前言
继续ES6的学习,但是不想按照目录学习,因为内心会觉得很枯燥,学习得很抗拒。所以挑一些感兴趣的章节学习,其余章节以后有机会再补充。
正文
函数的扩展
ES6是对ES5的一次大的升级,对于ES5中一些数据类型等,进行了一些扩展。包括字符串扩展,数值扩展,函数扩展和对象扩展等。
- 函数参数的默认值 在ES5的时候,函数是不能设置默认值的,一般通过取巧的办法达到默认值的效果。
function ex1(x, y) {
y = y || 'world'
console.log(x, y)
}
如上面代码所示,当调用函数ex1时,如果没有给y赋值,那么y将取‘world’值。这里也会出现一个错误,那就是如果y被赋值了,但是赋值的是空字符串""或null等布尔值为false的话,那么赋值会失败。这里的原因,回忆一下JS的逻辑或||运算规则。 ES6改进了这一点,支持对函数参数进行默认赋值。语法很简单,直接在令参数等于默认值即可。
function ex1(x, y='world') {
console.log(x, y)
}
- rest参数
我们知道函数中
arguments是一个类数组的对象,里面包括了传入函数的所有参数。所以我们可以在函数内部,使用arguments来获取、使用函数参数。
// arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
注意上面第一段代码,因为arguments不是数组,而是类似数组的对象,所以我们需要将它转换为数组,才能使用数组的方法。ES6引入了rest参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。所以可以直接使用数组的方法。
unction add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
我们还要注意两点:1、rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。 2、函数的length属性,不包括 rest 参数。
// 报错
function f(a, ...b, c) {
// ...
}
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
- 严格模式 从 ES5 开始,函数内部可以设定为严格模式。
function doSomething(a, b) {
'use strict';
// code
}
- name属性 函数的name属性,返回该函数的函数名。
function foo() {}
foo.name // "foo"
- 箭头函数 此处省略,算是基本技能。
- 双冒号运算符
- 尾调用优化
- 函数参数的尾逗号 ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。 此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
Module的语法
- 为什么要模块化 随着程序的编写,代码越来越多的积累在同一个文件中,变得愈加臃肿。这让程序很难维护的同时,也给复用代码增加了难度。自然而然地,聪明的程序员们想到了分而治之的思想,即将大的程序变为多个小的文件组装在一起的复合体。每一个单独的小文件被称作模块,这样一来,我们可以复用模块,不必要每一个东西都需要自己写。许多编程语言也都支持模块化的方式写程序,但是这其中不包括JavaScript,为了解决这个问题,社区诞生了自己的方案。那就是CommonJs和AMD,加上后来的CMD与ES6的export/import。
CommonJS => AMD => CMD => export/import
CommonJS用于服务器,AMD用于浏览器。ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。**ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。**比如,CommonJS 模块就是对象,输入时必须查找对象属性。 JS代码先进行编译,再运行。所以说不能讲export/import用在js代码中,例如下面:
// 会报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
以上代码报错是因为import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。 2. export 命令 模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
// 等价于
var year = 1958;
export {year}
以上代码是使用export规定单条对外模块的接口,其中定义的接口与内部的变量时一一对应的。 如果按照下面代码,是报错的
// 报错,因为year是一个数,不是变量
var year = 1958;
export year;
除此之外,还可以一次规定多条对外模块的接口。
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName,lastName,year};
- import 命令 使用import命令,加载模块的功能,也就是其他模块对外的接口。
import {firstName,lastName,year} from "./profile.js"
console.log(firstName);
from 后面跟的是导入模块的地址,绝对路径和相对路径皆可,同时「.js」的后缀可以省略。 import导入的接口名称和export导出的接口名称必须一致,这点需要格外注意。但是对于想要偷懒的人,不想记住导入模块的方法名,就可以使用export default。 4. export default 默认导出变量,函数或类,import时不需要定义特定的变量名,直接使用自定义的名字即可引入。
Module的加载
上面我们知道了JS的模块化规范是逐渐发展的,顺序如下:
CommonJs -> CMD -> AMD -> import/export
一开始社区诞生的方案CommonJs是用于服务器上面的,最终被Node采纳。它的特点是同步加载模块,因为服务器上面模块文件都存在本地硬盘里,读取速度很快,同步加载模块不会发生堵塞。但是在浏览器环境上就不一样了,默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到
ES6模块CommonJS模块的差异
它们有两个重大差异。
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
我们再看看第一个差异,CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
还是举上面那个例子。
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
上面代码说明,ES6 模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。