ES6简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了,也叫ECMAScript 2015
块级作用域
ES5作用域缺陷
function fn() { |
ES6之前 只有全局作用域和函数作用域,没有块级作用域,带来很多不合理的场景
第一种场景
内层变量可能会覆盖外层变量
var a = 'aaa'; |
相当于
var a = 'aaa'; |
第二种场景
用来计数的循环变量泄露为全局变量
for (var i = 0; i < 5; i++) { |
解决办法
for (let i = 0; i < 5; i++) { |
ES6块级作用域
- 花括号 {} 和其中代码生成一个块
- 在块中,let和const声明的变量和常量对外都是不可见的,称之为块级作用域
- 只有使用let和const声明的变量或者常量在块中对外不可见,var声明的变量对外依然可见
function fn() { |
上面的函数有两个代码块,都声明了变量n,运行后输出 5,这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10
function fn() { |
let 和 const
let
// 用来声明变量 |
示例
var a = 10; |
const
// 用来声明常量(不可改,只读) |
复合类型的数据(主要是对象和数组),可以这样子变动(它们在栈中的引用地址没变)
{ |
{ |
区别
// 相同点: |
推荐
// 对于 数值、字符串、布尔值 经常会变的,用 let 声明 |
解构赋值
数组解构赋值
一次性声明多个变量
{ |
结合扩展运算符
{ |
允许指定默认值
{ |
{ |
{ |
应用场景
// 变量交换 |
对象解构赋值
数组中,变量的取值由它 排列的位置 决定;而对象中,变量必须与 属性 同名,才能取到正确的值
变量与属性同名
{ |
{ |
允许指定默认值
{ |
允许重命名
{ |
函数参数解构赋值
函数参数的默认值
function fn(a = 1, b = 2){ |
函数参数的默认值(对象参数)
function fn({a = 1, b = 2} = {}) { |
字符串解构
字符串也可以解构赋值,这是因为此时,字符串被转换成了一个类似数组的对象
字符串解构
{ |
把字符串转为数组
{ |
字符串扩展
模板字符串
模板字符串 用反引号(``)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
字符串中嵌套变量
原生JS写法
var a = 1; |
var name = 'A'; |
ES6 写法
// 字符串中嵌入变量 模板字符串中嵌入变量,需要将变量名写在 ${ } 之中,可放入表达式 |
var name = 'A'; |
var obj = { |
var fn = (name) => `Hello ${name}!`; |
可调用函数
// 可以调用函数 |
注意:如果在模板字符串中需要使用反引号,则前面要用反斜杠转义
let a = `\`Hello\` World!`; // `Hello` World! |
字符串函数
| 函数 | 含义 |
|---|---|
| includes() | 返回布尔值,表示是否找到了参数字符串 |
| startsWith() | 返回布尔值,表示参数字符串是否在原字符串的头部 |
| endsWith() | 返回布尔值,表示参数字符串是否在原字符串的尾部 |
{ |
这三个方法都支持第二个参数,表示开始搜索的位置
{ |
| 函数 | 含义 |
|---|---|
| repeat() | 字符串重复 |
| padStart() | |
| padEnd() |
{ |
字符串填补(多用于日期:2019-09-01)
{ |
数组的扩展
扩展运算符(spread)是三个点(…),它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列
扩展运算符
展开数组
console.log(...[1, 2, 3]); // 1 2 3 |
注意,只有函数用时,扩展运算符才可以放在圆括号中
(...[1, 2]); // 报错 |
数组合并
const a = [1, 2, 3]; |
函数调用
function add(a, b) { |
*替代 apply *
由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了
function fn(a, b, c) { |
// ES5 的写法 |
将一个数组添加到另一个数组的尾部
// ES5的 写法 |
与解构赋值结合
const [a, ...b] = [1, 2, 3]; |
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
const [...a, b] = [1, 2, 3]; // 报错 |
复制数组
const a = [1, 2]; |
以下两种写法,不会修改原来的数组
const a = [1, 2]; |
不过,是浅拷贝,使用的时候需要注意
将字符串转为真正的数组
[...'hello'] |
数组函数
| 函数 | 含义 |
|---|---|
| includes() | 方法返回一个布尔值,判断数组是否包含给定的值,与字符串的 includes 方法类似 |
| find() | 返回符合传入测试(函数)条件的数组元素 |
| findIndex() | 返回符合传入测试(函数)条件的数组元素索引 |
| for…of | 数组遍历 |
| Array.of() | 方法用于将一组值,转换为数组 |
includes()
[1, 2, 3].includes(2); // true |
该方法的第二个参数表示搜索的起始位置,默认为 0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为 -4,但数组长度为 3 ),则会重置为从 0 开始
[1, 2, 3].includes(3, 3); // false |
find() 和 findIndex()
[1, 2, 3, 4, 5].find((item) => item > 3); // 4 -可以看到只返回一个值 |
[1, 2, 3, 4, 5].find((item, index, arr) => item > 9); // -1 -所有成员都不符合条件,则返回-1 |
两个方法都可以发现NaN,弥补了数组的indexOf方法的不足
[NaN].indexOf(NaN); // -1 |
for … of数组遍历
直接得到数组的值
var arr = [10, 20, 30]; |
var arr = [{a: 10}, {b: 20}, {c: 30}]; |
keys() 是对键名的遍历 values()是对值的遍历 entries() 是对键值对的遍历
for (let index of [1, 2].keys()) { |
Array.of()
Array.of(1, 2, 3); // [1, 2, 3] |
与Array()的区别
Array(); // [] |
对象的扩展
属性简写
const foo = 'bar'; |
上面代码中,变量foo直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值
let a = 1; |
function fn(a, b) { |
方法简写
const es6 = { |
属性表达式
{ |
扩展运算符
let { a, b, ...c } = { a: 1, b: 2, c: 3, d: 4 }; |
由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象
let { ...a } = null; // 报错 |
解构赋值必须是最后一个参数,否则会报错
let { ...a, b, c } = obj; // 报错 |
注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本
let obj = { a: { b: 1 } }; |
对象API
判断两个字符串是否相等
'abc' === 'abc'; // true |
判断两个数组是否相等
[] === []; // false |
拷贝对象
Object.assign({a: 1}, {b: 2}); // {a: 1, b: 2} 浅拷贝 |
遍历对象
{ |
Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
const target = { a: 1 }; |
Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target = { a: 1, b: 1 }; |
Object.assign 方法实行的是浅拷贝,而不是深拷贝
const obj1 = {a: {b: 1}}; |
上面代码中,源对象 obj1 的 a 属性的值是一个对象,Object.assign 拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面
数值的扩展
指数运算符
ES2016 新增了一个指数运算符(**)
2 ** 2; // 4 |
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的
指数运算符可以与等号结合,形成一个新的赋值运算符(**=)
let a = 1.5; |
函数的扩展
默认参数
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法
function fn(a, b) { |
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面
function fn(a, b = 2) { |
function Point(a = 0, b = 0) { |
参数变量是默认声明的,所以不能用let或const再次声明
function fn(a = 3) { |
与解构赋值默认值结合使用
function fn({a, b = 5}) { |
function fn({a, b = 5} = {}) { |
参数默认值位置
通常情况下,定义了默认值的参数,应该是函数的尾参数
function fn(a = 1, b) { |
上面代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined
如果传入undefined,将触发该参数等于默认值,null则没有这个效果
function fn(a = 5, b = 6) { |
上面代码中,x参数对应undefined,结果触发了默认值,y参数等于null,就没有触发默认值
rest 参数
ES6 引入 rest参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
function add(...values) { |
function add(a, ...values) { |
上面代码的 add 函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错
// 报错 |
箭头函数(ES6)
箭头函数语法
总结:
箭头函数相当于匿名函数,所以需要变量接收
如果参数只有一个,不需要括号,参数没有或者多个则需要括号
如果代码块部分多于一条语句,需要大括号{}
如果需要返回一个对象,必须在对象外面加上括号
var f = () => 'hello' |
// ES5 |
当箭头函数只有一个参数时,可以省略括号,直接写参数名
var f = a => a; |
当箭头函数没有参数或者多个参数是,不能省略括号
var f = () => 'hello'; |
var sum = (a, b) => a + b; |
当箭头函数的代码块部分多于一条语句,就要使用 {} 将它们括起来,并且使用 return 语句返回
var sum = (a, b) => { |
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
let getObj = id => ({ id: id, name: 'aaa' }); |
箭头函数示例
箭头函数可以与变量解构结合使用
const getName = ({ first, last }) => first + ' ' + last; |
箭头函数使得表达更加简洁。
const iseven = n => n % 2 === 0 |
箭头函数的一个用处是简化回调函数。
// 正常函数写法 |
另一个例子是
// 正常函数写法 |
下面是 rest 参数与箭头函数结合的例子
const f = (...a) => a; |
注意: 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象
this 对象的指向是可变的,但是在箭头函数中,它是固定的
function foo() { |
上面代码中,setTimeout 的参数是一个箭头函数,这个箭头函数的定义生效是在 foo 函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时 this 应该指向全局对象window,这时应该输出 21。但是,箭头函数导致 this 总是指向函数定义生效时所在的对象(本例是{ id: 42}),所以输出的是 42。
箭头函数特性
* 没有 this super arguments |
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数
箭头函数没有this,只能从上下文获取this
箭头函数的this值,取决于函数外部非箭头函数的this值,如果上一层还是箭头函数,那就继续往上找,如果找不到那么this就是Window对象
var person = { |
箭头函数没有arguments对象
箭头函数也没有arguments对象,但是如果它外层还有一层非箭头函数的话,就会去找外层的非箭头函数的arguments对象,注意:箭头函数找arguments对象只会找外层非箭头函数的函数,如果外层是一个非箭头函数的函数如果它也没有arguments对象也会中断返回,就不会在往外层去找了,如下
var f1 = () => console.log(arguments); // 执行该函数会抛出错误 |
可以清楚的看到当前的箭头函数没有arguments对象,然而就去它的外层去找非箭头函数的函数。``
function fn(a) { |
上面示例中可以看到,里面的箭头函数往外层找非箭头函数的函数,然后不管外层这个函数有没有arguments对象都会返回。只要它是非箭头函数就可以,如果外层是箭头函数还会继续往外层找
var f = (...a) => console.log(a); |
箭头函数不能用new关键字声明
var Fn = () => {}; |
箭头函数没有原型prototype
箭头函数没有原型,有可能面试官会问,JavaScript中所有的函数都有prototype属性吗
var fn = () => {}; |
箭头函数不能改变this指向
var person = {}; |
箭头函数和普通函数的区别
* 相比普通函数, 箭头函数有更简洁的语法 |
Set
ES6 提供了新的数据结构 Set,它类似于数组,但是成员的值都是唯一的,没有重复的值
Set 本身是一个构造函数,用来生成 Set 数据结构(既然是构造函数,肯定需要new)
const s = new Set(); |
去除数组的重复成员
const set = new Set([1, 2, 3, 4, 4]); |
或者
const arr = [1, 1, 2, 3, 4, 4]; |
或者
const arr = [1, 1, 2, 3, 4, 4]; |
Map
Map 数据结构类似于对象,是键值对的集合,传统的键只能用字符串,Map 的键不限于字符串,各种类型的值(包括对象)都可以当作键。
属性和操作方法
size 属性,返回 Map 结构的成员总数 |
Map 遍历
keys() // 返回键名的遍历器 |
Promise 教程
Promise 实例对象
Promise 是异步编程的一种解决方案 ,比传统的解决方案(回调函数和事件)更合理和更强大
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
Promise对象特点
(1)Promise对象可以保存异步操作的结果
(2)Promise异步操作具有三种状态,Pending、Resolved 和 Rejected
(3)Promise对象状态的改变只存在两种情况,Pending 到 Resolved 或者 Pending 到 Rejected
(4)Promise对象的状态一旦确定,那么就无法改变,要么是 Resolved,要么是 Rejected特别说明:Pending表示等待状态,Resolved表示处于完成状态,Rejected处于未完成状态
既然Promise创建的实例对象,是一个异步操作,那么异步操作的结果,只能有两种状态:
状态1:异步执行成功,需要在内部调用成功的回调函数resolve把结果返回给调用者
状态2:异步操作失败,需要在内部调用失败的回调函数reject 把结果返回给调用者
基本用法
let p = new Promise((resolve, reject) => { |
简单分析:
(1)通过构造函数new Promise()可以创建一个Promise对象实例,构造函数的参数是一个回调函数
(2)构造函数的回调函数具有两个函数参数,由引擎提供,也就是不需要我们提供
(3)执行resolve函数,那么状态变为Resolved,执行reject函数,状态变为Rejected
(4)构造函数调用后,回调函数会立马执行,然后根据调用的是resolve还是reject函数,确定状态
(5)状态确定后,再利用then方法执行对应的操作
p.then((value) => { |
简单分析:
(1).then方法的参数是两个回调函数
(2)如果p处于Resolved完成状态,那么执行第一个回调函数,如果处于Rejected状态,那么执行第二个回调函数
(3)回调函数中的参数value,分别是传递给resolve和reject函数的参数
(4) 第二回调函数是可以省略的
Promise示例
const p = new Promise(function(resolve, reject) { |
function getHello(ms) { |
const p = new Promise((resolve, reject) => { |
上面代码中, Promise 新建后立即执行 ,首先输出1,然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
resolve回调函数的参数除了正常的值以外,还可以返回另外一个Promise实例对象
const p1 = new Promise((res, rej) => { |
上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。
注意点
new Promise((resolve, reject) => { |
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外
new Promise((resolve, reject) => { |
.then() 方法
只要是Promise实例对象就能调用then方法
p.then(onResolved, onRejected) |
Promise 实例对象具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的
调用then方法,预先为这个Promise异步操作,指定成功(resolve)和失败(reject)回调函数
let p = new Promise((resolve, reject) => { |
分析:
调用Promise构造函数后,其回调函数会立马被调用
通过if语句判断之后,resolve()会被调用,于是Promise对象状态变为Resolved
于是就会执行then的第一个回调函数,打印结果是”resolve被调用”
then()方法返回一个promise对象,所以可以使用链式调用方式
let p = new Promise((resolve, reject) => { |
分析:
上述代码中,第二个then()方法的回调函数的参数是上一个then回调函数的返回值
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
new Promise((res, rej) => { |
上面代码,可以看出,第二then的成功回调函数参的数,其实就是第一个then成功回调函数return返回的值,也就是说第一个then返回的值当做第二then的参数
.catch() 方法
p.catch(onRejected); |
Promise.prototype.catch()方法是 |
下面代码中,
如果该对象状态变为resolved,则会调用then()方法指定的回调函数
如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误
另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
p |
上面代码等同于:
p |
示例
const p = new Promise((res, rej) => { |
上面代码中,promise抛出一个错误,就被catch()方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。
// 写法一 |
比较上面两种写法,可以发现reject()方法的作用,等同于抛出错误。
如果 Promise 状态已经变成resolved,再抛出错误是无效的。
const p = new Promise((resolve, reject) => { |
上面代码中,Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。
let p = new Promise(function(resolve, reject) { |
或者
let p = new Promise(function(resolve, reject) { |
或者
let p = new Promise(function(resolve, reject) { |
catch()可以捕获它之前Rejected状态变化,不必紧邻
let p = new Promise(function(resolve, reject) { |
.all() 方法
等待所有都完成(或第一个失败)
const p1 = Promise.resolve(3); |
Promise.all 可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一
个结果数组,而失败的时候则返回最先被reject失败状态的值
let p1 = new Promise((resolve, reject) => { |
let wake = (time) => { |
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。
.race() 方法
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管
结果本身是成功状态还是失败状态
let p1 = new Promise((resolve, reject) => { |
解决回调地狱
function getFileByPath(path) { |
解决回调地狱
getFileByPath('./files/01.txt') |
如果前面的Promise执行失败,我们不想让后续的Promise被终止,可以为每个Promise指定失败回调
getFileByPath('./files/01.txt') |
有时候,我们有这样的需求,如果后续Promise执行,依赖于前面Promise执行的结果,如果前面失败了, 则后面的就没有执行下去的意义了,此时我们想要实现,一旦报错,则立即终止所有Promise的执行
getFileByPath('./files/01.txt') |
const someAsyncThing = function(flag) { |
上面代码中,someAsyncThing 函数成功返回 ‘OK’, 失败返回 ‘error’, 只有失败时才会被 catch 捕捉到。
async/await异步操作
含义
ES2017 标准引入了 async 函数,使得异步操作变得更加方便
async 函数是什么?一句话,它就是 Generator 函数的语法糖
async/await从字面意思上很好理解,async是异步的意思,await有等待的意思,而两者的用法上也是如此。async用于申明一个function是异步的,而await 用于等待一个异步方法执行完成。
async 函数的使用方式,直接在普通函数前面加上 async,表示这是一个异步函数,在要异步执行的语句前面加上 await,表示后面的表达式需要等待
基本用法
async关键词
async的语法很简单,就是在函数开头加一个async关键字,示例如下:
function f1() { |
凡是在前面添加了async的函数在执行后都会自动返回一个Promise对象
async函数会返回一个promise对象,如果function中返回的是一个值,async直接会用Promise.resolve()包裹一下返回:
async function f2() { |
await关键词
关键词await是等待的意思,其后面是一个表达式,这个表达式可以是常量、变量、Promise以及函数等
await必须在async函数里使用,不能单独使用
await后面需要跟Promise对象,不然就没有意义,而且await后面的Promise对象不必写then,因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数
await操作符等的是一个返回的结果,那么如果是同步的情况,那就直接返回了
异步的情况下,await会阻塞整一个流程,直到结果返回之后,才会继续下面的代码
function f1() { |
阻塞代码是一个很可怕的事情,而async函数,会被包在一个promise中,异步去执行。所以await只能在async函数中使用,如果在正常程序中使用,会造成整个程序阻塞,得不偿失。
基本示例
当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
function timeout(ms) { |
正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值
async function f() { |
async function f() { |
注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。
如果await后面的Promise变为reject状态,则reject的参数会被catch回调函数接收
如果是变为reject状态,前面不加return,reject的参数也会被catch回调函数接收
async function f() { |
错误处理
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function f() { |
上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject。
有时候,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
async function f() { |
另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
async function f() { |
promise并不是只有一种resolve,还有一种reject的情况。而await只会等待一个结果,发生错误了有以下方式捕捉:
用try-catch来做错误捕捉
async function f() { |
async function f() { |
async function f() { |
async function func() { |
继发并发
如果具有多个await语句,且它们之间不是继发关系,建议让它们同时触发,以达到最大性能
getA和getB是独立的异步操作,没必要是继发关系,也就是执行完a再去执行b,那么可以采用以下方式
let [a, b] = await Promise.all([getA(), getB()]); |
或者
let aPromise = getA(); |
优点
在我们处理异步的时候,比起回调函数,Promise的then方法会显得较为简洁和清晰,但是在处理多个彼此之间相互依赖的请求的时候,就会显的有些累赘。这时候,用async和await更加优雅
Module 的语法
// CommonJS模块 |
严格模式
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
严格模式主要有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop,会报错,只能删除属性delete global[prop] eval不会在它的外层作用域引入变量eval和arguments不能被重新赋值arguments不会自动反映函数参数的变化- 不能使用
arguments.callee - 不能使用
arguments.caller - 禁止
this指向全局对象 - 不能使用
fn.caller和fn.arguments获取函数调用的堆栈 - 增加了保留字(比如
protected、static和interface)
import 和 export
导出 export
// moudle.js |
或者(推荐)
// moudle.js |
导入 import
// main.js |
别名
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名
var a1 = 10 |
import { a1 as a } from './moudle.js'; |
注意写法
变量
export 1; // 报错 |
// 写法一 |
方法
// 报错 |
export 导出命令
一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。ES6 将moudle.js其视为一个模块,里面用export命令对外部输出了三个变量。
另外,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
export var foo = 'bar'; |
上面代码输出变量foo,值为bar,500 毫秒之后变成baz。
最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。
function fn() { |
上面代码中,export语句放在函数之中,结果报错
import导入命令
import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(moudle.js)对外接口的名称相同。
由于
import是静态执行,所以不能使用表达式和变量。import命令具有提升效果,会提升到整个模块的头部,首先执行
fn(); |
上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面
// moudle.js |
普通加载
// main.js |
整体加载
// main.js |
export default
export default命令,为模块指定默认输出
// moudle.js |
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字
// main.js |
需要注意的是,这时import命令后面,不使用大括号
export default命令用在非匿名函数前,也是可以的
export default function sayHello() { |
上面代码中,sayHello函数的函数名sayHello,在模块外部是无效的,加载的时候,视同匿名函数加载
// moudle.js |
正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句
// ok |
同样地,因为export default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在export default之后
// ok |
上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定对外接口为default
总结
1. 当用 export default 导出时,就用 import 导入(不带大括号) |
class 基本语法
基本用法
ES5
function Person(name, age) { |
ES6
class Person { |
每个类中都有一个构造器,如果我们手动指定构造器,那么默认类内部有个隐形的、看不见的空构造器,类似于constructor() {}
构造器的作用:constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,那么会默认添加一个空的constructor方法
也就是说,ES5 的构造函数Person,对应 ES6 的Person类的构造方法
function Person {} |
实例属性和静态属性
实例方法和静态方法
通过new出来的实例能访问到的属性,叫做实例属性
通过构造函数,直接访问到的属性,叫做静态属性
ES5
function Person(name, age) { |
ES6
class Person { |
类的继承
在class中,使用extends关键字实现子类继承父类
父类
class Person { |
子类
- 美国人
class American extends Person { |
- 中国人(有身份证号独有)
class Chinese extends Person { |
1.为什么一定要在constructor中调用super?
答:因为如果一个子类通过extends关键字继承父类,那么在子类的constructor构造函数中必须优先调用super()
2.super是什么东西?
答: super是一个函数,他是父类的构造器,子类中的super其实就是父类中constructor构造器的一个引用
3.如果不调用super方法,子类就得不到 this 对象
class Point {} |
class注意问题
- 在class内部,只能写构造器,实例方法,静态属性,静态方法
class Person { |
- class关键字内部,还是用原来的ES5实现,我们把class关键字称作语法糖