ECMA Script 6

ECMA Script 6(ES2016)学习笔记

TIP

最后更新时间:2019年09月21日

字数:18029

ES6

  • ECMA-262 可以称之为:ES1.0
  • ECMA2015 -> ES6
  • ECMA每年6月份发布一个版本,(不一定是大版本,所以有时候叫ES7也不合适)
  • ESNext:下一代的ES开发语言,或者称之为下一个版本的开发语言

学习资料

Stage-n

提案变成标准需要经过五个阶段

  • stage 0 :展示阶段
  • stage 1 :征求意见阶段
  • stage 2 :草案阶段(bebel配置,一般都是stage2,其实就是意味着可以使用的阶段)
  • stage 3 :候选阶段
  • stage 4 :定案阶段(标准)

TIP

Ecma TC39委员会(任何人都可以提es任何意见和建议哦):快捷连接

ES6之前变量

全局作用域

  • 在任何地方都可以访问的变量,就是全局变量
  • 全局变量生效的作用域,就是全局作用域
var a = "全局";
function fn(){
    console.log(a);
}
fn();//结果:全局
1
2
3
4
5

函数作用域

  • 只能在函数内部访问的变量就是局部变量
  • 对应的作用域就是局部作用域(又称函数作用域)
  • 因为js只有函数可以划分作用域(当前阶段ES5)
function fn(){
    var a = "局部";
}
fn();
console.log(a);// ReferenceError: a is not defined

function fn1() {
    b = '不带var'
}
fn1()
console.log(b); // 不带var
1
2
3
4
5
6
7
8
9
10
11

TIP

函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

ES6作用域

块级作用域

  • ES6引入了块级作用域,明确允许在块级作用域中声明函数
  • 块级作用域中,函数声明语句的行为类似于let,在块级作用域之外,不可以引用
  • 块级作用域用一对花括号来表示
// ES5之前模拟块级作用域:匿名函数自调用,LLFE,立即执行函数
(function(){
  // TODO
})()

{
	// 这里是块级作用域
}

{{ // 块级作用域可以嵌套 }}
1
2
3
4
5
6
7
8
9
10

let

相当于之前的var

const

  • const翻译过来是常量的意思
  • 所谓常量,就是定义好了,就不能改变了
  • const声明的是一个只读常量,在声明时就需要赋值
    • 如果 const 的是一个对象,对象所包含的值是可以被修改的
    • 抽象一点儿说,就是对象所指向的地址不能改变,而变量成员是可以修改的
const a = 12
const a = 14
console.log(a) 
// Identifier 'a' has already been declared
// 不允许重重复定义
1
2
3
4
5
const a = 12
a = 14
console.log(a) 
// Assignment to constant variable
// 就是定义好了,就不能改变了
1
2
3
4
5
const a = {
  name:"zhangsan"
}
a.name = "lisi"
console.log(a) // {name: "lisi"}
// 如果 const 的是一个对象,对象所包含的值是可以被修改的
1
2
3
4
5
6

let和const特点

1. letconst 都是块级作用域,以{}代码块作为作用域范围 只能在代码块里面使用


if(1) {
    var a = 12
    console.log('a===',a) // a=== 12
}
console.log('a===',a) // a=== 12
if(1) {
    let b = 14
    const c = 16
    console.log('b===',b) // b=== 14
    console.log('c===',c) // c=== 16
}
console.log('b===',b) // b is not defined
console.log('c===',b) // c is not defined (这里其实是执行不到的,执行到也会报错)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

TIP

let可以在{},if,for里声明,其用法同var,但是作用域限定在块级,也就是说出了块级作用域,let和const声明的变量是无法使用的,不是全局作用域

2. 不存在变量提升,只能先声明再使用,否则会报错

var a = 12
function fn() { 
  alert(a) // 12
}
fn()
// 这是正常情况下的,没有毛病,a全局作用域
1
2
3
4
5
6
var a = 12
function fn() { 
  alert(a) // undefined
  var a = 20
}
fn()
//undefined 也是没有问题的,打印的a,会使用函数内部的a,但是因为变量提升,但是没有赋值,所以打印undefined
1
2
3
4
5
6
7
var a = 12
function fn() { 
  alert(a) // a is not defined
  let a = 20
}
fn()
// 这里问题来了,他说a没有定义,可以说明:
// a没有使用外部的全局变量a,同时,let声明的a变量没有做变量提升
1
2
3
4
5
6
7
8

TIP

在代码块内,在声明变量之前,该变量都是不可用的

这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)

3. 在同一个代码块内,不允许重复声明

{
  let a = 11
  let a = 12 // Identifier 'a' has already been declared
  {
    let a = 13 // 可以,因为不在同一个块级作用域里面(大括号)
  }
}
1
2
3
4
5
6
7

let的for循环问题

var a = []
for (let i =0; i<10; i++) {
  a[i] = function () {
    console.log(i)
  }
}
a[6]() // 6
1
2
3
4
5
6
7
  • 变量i是let声明的 当前i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量
  • Javascript之所以知道上一轮循环的值,是因为javascript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算,而不是let声明的i自加
var arr = []
for (var i =0; i<10; i++) {
  arr[i] = function () { 
    console.log(i)
  }
}
arr[4]() // 10

for (let i =0; i<5; i++) {
  let i = "xiaofengge"
  console.log(i) // 打印五次小峰哥,而不是数字i,因为他们其实在不同的作用域
}
1
2
3
4
5
6
7
8
9
10
11
12

TIP

for循环还有一个特别之处,就是设置循环变量那部分是一个父级作用域,而循环体内部是一个单独的子作用域

Object.freeze()冻结一个对象

  • Object.freeze()是ES5新增的特性,可以冻结一个对象,防止对象被修改
  • 冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。防止对象被修改
  • 如果你有一个巨大的数组或Object,并且确信数据不会修改,使用Object.freeze()可以让性能大幅提升
  • Vuejs中可以使用提升性能,同时:Object.freeze()冻结的是值,你仍然可以将变量的引用替换掉,也就是重新赋值(一个不是Object.freeze()的值)

解构赋值

结构示例

let arr = ['11','22']
console.log(arr) //  ["11", "22"]

let [a, b, c] =  ['aaa','bbb','ccc']
console.log(a,b,c) // aaa bbb ccc  这就是最简单的解构赋值

let json = {
  name: "zhangsan",
  age: 18,
  job: "CEO"
}
let { name, age, job } = json

console.log(name, age, job) // zhangsan 18 CEO
1
2
3
4
5
6
7
8
9
10
11
12
13
14

规定:解构赋值要求:左右两边,结构和格式要保持一致,否则有些数据会出问题

解构别名(冒号)

let json = {
  name: "zhangsan",
  age: 18,
  job: "CEO"
}
let { name:n, age:a, job:j } = json
// 解构别名
console.log(n, a, j) // zhangsan 18 CEO
1
2
3
4
5
6
7
8

解构默认值

let [a1, b1, c1="暂无数据"] = ['aaa', 'bbb']
console.log(a1, b1, c1) // aaa bbb 暂无数据
let [a2, b2, c2="暂无数据"] = ['aaa', 'bbb', undefined]
console.log(a2, b2, c2) // aaa bbb 暂无数据
let [a3, b3, c3="暂无数据"] = ['aaa', 'bbb', null]
console.log(a3, b3,c3) // aaa bbb null

let json = {
  name: "zhangsan"
}
let {name, age=18} = json
console.log(name, age) // zhangsan 18
1
2
3
4
5
6
7
8
9
10
11
12

TIP

  • 解构赋值是可以设置默认值的
  • 注意后台传回来的是null的时候,是有值,但是值是null,而不可以像undefined去取到默认值

解构应用

  • import {a ,b , c } from 'xxx'
  • 函数传参解构
  • 数组交换顺序解构(比如交换2个变量的值: [a,b] = [b,a])

字符串模板

ES6新增字符串模板``

let name = "zhangsan"
let age = 18

// 我是zhangsan,我的年龄是18岁
console.log('我是'+name+',我的年龄是'+age+'岁')

// 我是zhangsan,我的年龄是18岁
console.log(`我是${name},我的年龄是${age}岁`)
1
2
3
4
5
6
7
8

includes

字符串或数组查找

  • Str.includes() 和 Array.includes()
  • indexOf()返回的是数值,而includes()返回的是布尔值
  • indexOf() 不能判断NaN,返回为-1 ,includes()则可以判断

startWith

判断以xxx开头

  • str.startWith()

endsWith

判断以xxx结尾

  • str.endsWith()
let str = 'https://www.baidu.com'

console.log(str.startsWith('https')) // true
console.log(str.endsWith('.com')) // false
1
2
3
4

repeat

字符串重复次数

  • str.repeat(次数)
  • 参数次数必须大于等于0的整数,写小数会被默认取整的,不会报错
let str = '你好!'
// 重复三次(后面参数是次数,必须大于等于0)
console.log(str.repeat(3)) // 你好!你好!你好!
1
2
3

padStart和padEnd

  • str.padStart(长度,需要填充的参数):往前填充
  • str.padEnd(长度,需要填充的参数):在后面填充

函数默认参数

ES6中函数的默认参数是可以传递默认值的

function fn(a='你好',b="小峰哥") {  
  console.log(a,b)
}
fn() // 你好 小峰哥
fn('') //  小峰哥 (前面传递的空字符串)


// 解构联合函数默认参数
// 如果都不传,默认参数是:{}
function fn({ a = 0, b = 0 } = {}) {  
  console.log(a,b)
}
fn({a:1}) // 1 0
fn({b:2}) // 0 2
fn({c:3}) // 0 0
fn()      // 0 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

TIP

函数的参数默认是已经定义好了,不能再用let和const去定义

function fn(a,b) {  
  let a = 13 // 报错: Identifier 'a' has already been declared
  console.log(a,b)
}
fn()
1
2
3
4
5

扩展运算符(rest运算符)

写法:三个点   ...
1
  • 扩展和重置
let arr = ['aaa','bbb','ccc']
console.log(arr) // 数组: ["aaa", "bbb", "ccc"]
console.log(...arr) // 数组被展开了:  aaa bbb ccc

// ... 既可以展开,也可以重置
function fn(...a) {  
  console.log(a) // [1, 2, 3, 4, 5, 6] 数组
}
fn(1,2,3,4,5,6)
1
2
3
4
5
6
7
8
9
  • 剩余
// ...c 就是剩余的
function fn(a, b, ...c) {  
  console.log(a,b) // 1 2
  console.log(c) // 数组:  [3, 4, 5, 6]  
}
fn(1,2,3,4,5,6)
1
2
3
4
5
6

TIP

剩余必须放在最后,不允许放在中间,必须是最后一个参数

箭头函数

let fn = () => {
  console.log('我是箭头函数')
}

fn() // 我是箭头函数
1
2
3
4
5

箭头函数如果有返回值,可以直接写在后面

let fn = () => '我是箭头函数!'

function fn1() {
  return '我是箭头函数!!'
}

// 上下2个函数是等价的
console.log(fn()) // 我是箭头函数!
console.log(fn1()) // 我是箭头函数!!
1
2
3
4
5
6
7
8
9

TIP

箭头函数的this永远指向定义函数所在的对象,而不是运行时所在的对象

箭头函数的this永远指向其上下文的 this,任何方法都改变不了其指向,如call(), bind(), apply()

 var name = "lisi"
 let obj = {
   name: 'zhangsan',
   say: function () { 
     setTimeout( function () {
       console.log(this.name) // lisi
     }, 2000);
   }
 }
 obj.say()
1
2
3
4
5
6
7
8
9
10
var name = "lisi"
let obj = {
  name: 'zhangsan',
  say: function () { 
    setTimeout(() => {
      console.log(this.name) // zhangsan
    }, 2000);
  }
}
obj.say()
1
2
3
4
5
6
7
8
9
10

箭头函数不绑定arguments,取而代之用rest(...)参数

function fn() {
  console.log(arguments)
}
fn(1,2,3)

let fn1 = () => {
  console.log(arguments)
}
fn(1,2,3)
fn1(1,2,3) // 报错 arguments is not defined

let fn2 = (...c) => {
  console.log(c)
}
fn2(1,2,3) // [1, 2, 3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

箭头函数作为匿名函数,是不能作为构造函数的,不能使用new

var B = ()=>{
  value:1;
}
var b = new B(); //TypeError: B is not a constructor
1
2
3
4

ES6数组扩展

新增运算符

幂运算符:****** (2个星号)

console.log(2 ** 3) // 8
1

ES6之前

循环

  • for

  • while

  • arr.forEach()

    • 代替普通for
  • arr.map()

    • 非常有用,做数据交互 "映射",重新整理数据结构
    • 正常情况下,需要配合return,返回是一个新的数组
    • 若是没有return,相当于forEach
    • 注意:平时只要用map,一定是要有return
  • arr.filter()

    • 过滤,过滤一些不合格“元素”, 如果回调函数返回true,就留下来
  • arr.some()

    • 类似查找, 数组里面某一个元素符合条件,返回true
  • arr.every()

    • 数组里面所有的元素都要符合条件,才返回true
  • arr.reduce()

    • 从左往右
  • arr.reduceRight()

    • 从右往左

ES6新增循环for...of...

  • 遍历所有数据结构的统一的方法
  • 使用范围:
    • 数组
    • Set
    • Map
    • 类数组
      • 字符串
      • arguments
      • DOM Nodelist对象
      • Generator

对象简洁语法

对象新增

Promise

简单来说,Promise 就是用同步的方式写异步的代码,用来解决回调(异步)问题

js单线程

  • Javascript语言的执行环境是"单线程"(single thread),就是指一次只能完成一件任务
  • 如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推

TIP

注意:

  • 单线程和多线程的区别
  • 线程和进程的区别
  • js是单线程的,浏览器是多进程的

Promise之前

三种解决异步问题的方案:

  • 回调函数(callback)
  • 事件监听
  • 发布/订阅模式(又称:观察者模式)

回调函数(callback)

  • 调是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行
function fn2(arg) {
    console.log(arr)
}
function fn1(callback){
  setTimeout(function () {
    // f1的任务代码
    let arr = [1,2,3]
    callback(arr);
  }, 1000);
}
// 执行
fn1(fn2) // 1秒后打印 arr
1
2
3
4
5
6
7
8
9
10
11
12

回调并不一定就是异步,他们没有直接关系,我们只是用回调来解决异步的问题

  • 同步回调
    • 很简单,就是链式同步执行,一条线同步走下去
  • 异步回调
    • 其实就是耗时的我们都扔给异步去做,做好了再通知下我们做完了,我们拿到数据继续往下走
    • 例如ajax,nodejs事件循环机制
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);   //第三个参数决定是否采用异步的方式
xhr.send(data);
xhr.onreadystatechange = function(){
    if(xhr.readystate === 4 && xhr.status === 200){
       ///do something
    }
}
1
2
3
4
5
6
7
8
var fs=require('fs');
function getData(callback){
    fs.readFile('a.txt',function(err,data){
        callback && callback(data);
    })
}
getData(function(res){
    console.log(res);
})
1
2
3
4
5
6
7
8
9

事件监听

也就是事件驱动模式,任务的执行不取决代码的顺序,而取决于某一个事件是否发生

  • 常见事件监听函数:

    • on
    • bind
    • listen
    • addEventListener
    • observe
  • 以jQuery写法为例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="box">你好</div>
    <script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
    <script>
        function fn (){
            console.log('div被点击了')
        }
       // 当div被点击了,就会调用fn函数,打印输出
       $('#box').on('click',fn)
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 当同一个element元素绑定多个事件时,只有最后一个事件会被添加
el.onclick = function(){
   //处理函数
}

// IE:attachEvent
el.attachEvent("onclick",fn);

// 标准addEventListener
el.addEvenListener("click",fn,false);
1
2
3
4
5
6
7
8
9
10

发布/订阅

  • 发布订阅模式,又称观察者模式

ES6和ES6之后

  • Promise
  • async/await

TIP

Promise的出现让我们告别回调函数,写出更优雅的异步代码

Async/Await提供了一种使得异步代码看起来像同步代码的替代方法

Promise

  • 三种状态:
    • pending-进行中:Pending既不是Resolve也不是Rejected状态;可以理解为Promise对象实例创建时候的初始状态
    • resolved-已完成:Resolve 可以理解为成功的状态
    • rejected-已失败:Rejected 可以理解为失败的状态
var promise = new Promise((resolve, reject) => {
  // 异步处理
  // 成功调用resolve 往下传递参数 且只接受一个参数
  // 失败调用reject  往下传递参数 且只接受一个参数
  if (操作成功) {
    resolve(value)
  } else {
    reject(error)
  }
})
promise.then(function (value) {
  // success
}, function (value) {
  // failure
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Promise.then

对通过new 生成的promise对象为了设置其值在resolve(成功) / reject(失败) 时调用的回调函数,可以使用promise.then()实例方法

promise.then(onFulfilled, onRejected);
1
  • resolve(成功) 时 调用onFulfilled 方法,reject(失败) 时 调用onRejected方法
  • Promise.then 成功和失败时都可以使用
  • Promise.then返回值还是一个Promise,我们可以链式编程一直点下去

Promise.catch

  • 如果出现异常的情况下可以采用promise.then(undefined,onRejected) 这种方式,只指定onRejected回调函数即可,不过针对这种情况下我们有更好的选择是使用catch这个方法
promise.catch(onRejected);
1
  • Promise.catch()方法是promise.then(undefined,onRejected)方法的一个别名,该方法用来注册当promise对象状态变为Rejected的回调函数

Promise.resolve('')

  • 将现有的东西,转成一个promise对象
  • 并且,这个对象是resolve状态(成功状态)
let p1 = Promise.resolve('小峰哥')
p1.then(res => {
    console.log(res) // 成功打印:小峰哥
})

// 等价于
let p2 = new Promise(resolve => {
    resolve('小峰哥')
})
p2.then(res=> {
    console.log(res) // 成功打印:小峰哥
})
1
2
3
4
5
6
7
8
9
10
11
12

Promise.reject('')

  • 现有的东西,转成一个reject对象
  • 并且,这个对象是reject状态(失败状态)

Promise.all

  • Promise.all可以接受一个元素为Promise对象的数组作为参数
  • 当这个数组里面所有的promise对象都变为resolve时,该方法才会返回
  • 如果有一个失败,是不会执行resolve的
var promise1 = new Promise(function(resolve){
    setTimeout(function(){
        resolve(1);
    },3000);
});
var promise2 = new Promise(function(resolve){
    setTimeout(function(){
        resolve(2);
    },1000);
});
Promise.all([promise1,promise2]).then(function(value){
    console.log(value); // 打印[1,2]
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意:Promise.all方法中会按照数组的原先顺序将结果返回

比如我们需要发2个ajax请求时,不管他们的先后顺序,当这2个ajax请求都同时成功后,我们需要执行某些操作的情况下,这种情况非常适合

Promise.race

  • Promise.all 在接收到的所有对象promise都变为FulFilled或者 Rejected状态之后才会继续后面的处理
  • Promise.race的含义是只要有一个promise对象进入FulFilled或者Rejected状态的话,程序就会停止,且会继续后面的处理逻辑

Deferred

  • Deferred 包含 Promise
  • Deferred具备Promise的状态进行操作的特权方法
  • Promise代表了一个对象,这个对象的状态现在还不确定,但是未来一个时间点它的状态要么变为正常值(FulFilled),要么变为异常值(Rejected)
  • 而Deferred对象表示了一个处理还没有结束的这种事实,在它的处理结束的时候,可以通过Promise来取得处理结果

模块化

ES6之前

  • Commonjs
    • 服务端,require
  • AMD
    • require.js
    • curl.js
  • CMD
    • sea,js
  • UMD

TIP

ES6:统一了客户端和服务端规范

script引入模块

type='moduls

script添加type='moduls

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script type="module" src="./1.js"></script>
    <!-- 
        等同于defer:
        浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器
        即等到整个页面渲染完,再执行模块脚本
        等同于打开了<script>标签的defer属性 
    -->
    <script type="module" src="./1.js" defer></script>
    
    <!-- 也等价于 -->
    <script type="module">
        import {} from './1.js
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

TIP

模块化需要放到服务器环境中,file本地访问时无效的,localhost是可以的

export

  • 导出模块
export let a = 1
export let b = 2

// 或者
export {
	a,
    b
}
1
2
3
4
5
6
7
8

export default

  • export default 导出的不用花括号
export default 1
export let a = 2
export let b = 3

//  导入
import x,{a ,b} from './1.js'
1
2
3
4
5
6

import

  • 导入模块
export let a = 1
export let b = 2

// 或者
export {
	a,
    b
}

// 导入
import {a , b} from './1.js'

// 或者
import * as OneModule from './1.js'
console.log(OneModule.a)
console.log(OneModule.b)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

特点(重要)

  • import可以是相对路径,也可以是绝对路径
  • import只会导入一次,无论你引入了多少次
  • import 必须加上花括号{},除非:使用 export default导出
  • import有提升效果,import会自动提升到顶部,首先执行
  • 到处去的内容,如果包里面的内容变化,那么导出的内容对应的数据也会变化的,不像command规范会缓存!

as 别名

let a = 111
export {
    a as aaa
}

// 这里a无法使用了,要使用别名
import { aaa } from './1.js'
1
2
3
4
5
6
7
let a = 111
export {
    a 
}

// 给导出的a 起别名aaa,后面
import { a as aaa } from './1.js'
1
2
3
4
5
6
7

import()

  • 动态引入,类似nodejs的require()
  • 默认inport语法不能写在if里面的,import()可以
  • 返回值:一个Promise()对象
  • 优点:
    • (动态引入)按需加载
    • 可以写到if中
    • 路径也可以写动态的
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script type="module">
        import('https://cdn.bootcss.com/jquery/3.4.0/jquery.js').then(res => {
            console.log($)
        })
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Promise.all([
    import('./1.js'),
    import('./2.js')
]),then(([mod1,mod2]) =>{
    console.log(mod1)
    console.log(mod2)
})
1
2
3
4
5
6
7

TIP

ES6模块化中默认使用严格模式 'use strict'

类(class)

继承

Symbol

generator

async 和 await

Set 和 WeakSet

Map 和 WeakMap

Proxy

Reflect