8.模块化

8.1 什么是模块?

模块(modules)就是使用不同的方式加载的js文件.模块代码和脚本(script)有不同的语义.

  1. 模块代码自动运行在严格模式下,并且没有任何办法跳出严格模式
  2. 在模块代码的顶级作用域创建的变量,不会被自动添加到共享的全局作用域,只会在模块顶级作用域的内部存在
  3. 模块的顶级作用域的this值为undefined,
  4. 模块不允许在代码中使用HTML风格注释
  5. 对于需要让模块外部代码访问的内容,必须导出它们
  6. 允许模块从其它模块导入绑定._

模块的主要能力是按需导出和导入代码的能力.,不用把所有的内容放在同一个文件内

基本的导出

可以使用export 关键字把已经写好的功能代码,拆分之后给其它的模块.最直接的方法就是export放置在任意的变量,函数或类声明之前.

export var user = "ggb";
export let age =  11;
export const a = 1;

export function num(){
    return 1;
}

export class users {
    constructor(){
        this.len = 1;
        this.str = '2'
    }
}

function sub(){
    return 2;
}

export default sub
export {
   //按需导出
}

基本的导入

导入的前提是有导出的模块.可以在其它的模块代码中,使用 import 关键字来访问已经被导出的功能.

import有两个部分.一个是需要导入的标识符,一个是来源模块

基本语法

import {sub,a} from 'app.js'

单个的绑定

<script type="module">
    import {sub} from './app.js'
    console.log(sub())
    sub = 1;  //报错
</script>

我们app.js,导出了两个.但是在上面这个案例中.仅仅只导出了一个函数,如果说要给这个sub赋值,是不允许,不能导入的绑定重新赋值.

在导入的文件名前要使"/","../","./"

如果想入多个

import {sub,a,user} from './app.js'
console.log(sub(1,4))
// sub = 1;
console.log(user)

想要所有的导入

import * as obj from './app.js'
console.log(obj.sub(1,4))
// sub = 1;
console.log(obj.user)

无论对同一个模块使用了多少次的导入import 但是这个模块都会被执行一次

import {sub} from './app.js'
import {a} from './app.js'
import {user} from './app.js'

这里导出三次….但是app.js都只会被执行一次.

7.Promise

背景

Promise 主要的功能就是为了更好的解决异步编程问题.工作方式就是用于延迟代码执行并在未来执行当前这个作业.

一个Promise指定一些稍后要执行的代码,这个工作机制就和我jquery AJAX 中回调函数一样.明确标示作业代码是..否执行成功.以成功处理或失败处理为准.把promise串联在一起.

异步编程的背景

JS引擎建立在单线程事件循环的概念上,什么是单线程:单线程意味着同一时刻只能执行一段代码.这和其它语言比C++,JAVA,可以同时执行多段不同代码的多线程语言不一样.

多段代码可以同时访问或修改状态.但是维护很难.

JS引擎因为同一时刻只能执行一段代码.所以不用管另外的可能会运行的代码.js代码在执行中会把代码放置在作业队列中(job qeeue) ,在我们js代码中每一段准备被执行,就会被添加进作业队列.当结束当前代码执行后,就会进入下一个代码作业.这就是所谓的事件循环.是一个内部处理线程,可以监视代码执行并管理作业队列,因为是队伍.所以一定是依次进行.直到最后一个.

7.1 事件模型

当用户点击一个按扭或在键盘上按下一个健时.一个事件(event),比如onclick被触发.这个事件可能会对此交互进行响应,从而把一个新的代码作业添加到我当前的作业的队列尾部.这就是异步编程的最基本的形式.事件事处理程序代码一直到事件发生后才会被执行.比如:

let oBtn = document.getElementById('btn');
oBtn.onclick=function(){
    alert('中国')
}

以上代码,alert(中国) 直到oBtn点击之后才会执行.当被点击之后.赋值给onclick的匿名函数就被添加到了作业队列的尾部,并在队列前所有的任务结束之后再执行

7.2 回调模式

回调函数模式类似于事件模型.因为也会在后面一个时间点执行.不同的地方是回调是以参数的形式传入.

//下课后补全

$.ajax({
    url:'1.txt',
    success:function (res) {
         $.ajax({
             url:'1.txt',
             success:function(){
                 $.ajax({
                         url:'1.txt',
                         success:function(){
                             $.ajax({
                                     url:'1.txt',
                                     success:function(){
                                         $.ajax({
                                                 url:'1.txt',
                                                 success:function(){

                                                 }
                                             }
                                         )
                                     }
                                 }
                             )
                         }
                     }
                 )
             }
         }
    )
    }
})

7.3 Promise 基础

个人认为Promise是ES6,夜空中最亮的星.是为异步操作的结果所准备的占位符,可以用函数返回一个Promise,不需要事件或回调函数.比如:

//readFile函数会在某个时间点完成作业并赋值给promise
let promise = readFile('1.txt')

在这行代码中.我们可以理解为 readFile(‘1.txt’) 并没有马上读取1.txt文件,可能会在稍后发生.这个函数会返回一个promise的对象以表示异步读取操作.可以在未来特定时间再来操作它,对结果进行操 作的时间.取决promise的生命周期是如何进行的;

7.4 Promise 的生命周期

有一个短暂的生命周期,初始为挂起,表示异步操作尚未结束,一个被挂起的promise,会被认为是未决的.在上面的一个例子中,promise会在readFile()返回它的时候就处于一个挂起状态.一旦异步操作结束,promise就会被认为是已决的.并进入两种可能的状态之一:

  1. 已完成:异步操作已成功结束
  2. 已拒绝:异步操作未成功结束,可能是一个错误,也可能是其它原因导致的.

相差词汇解释:

  1. pending:挂起,表示未结束的promise状态,相关专业词汇"挂起态".
  2. fulilled:已完成,表示已经成功结束的promise状态,可以理解为成功完成,相关专业词汇:"完成","被完成","完成态".
  3. rejected:已拒绝,表示已结束但失败的promise状态,相关词汇,"拒绝","被拒绝","拒绝态".
  4. unsettled:未决,未解决,表示promise尚未被完成或拒绝,与挂起是近义词.
  5. seteled:已决,已经解决,表示promise已经被完成或拒绝,注意这个与"已完成"或"已决议"不同,"已决的状态可能是,拒绝态(失败)
  6. resolve:决议,表示把promise推向成功态,,可以理解为决义通过,在promise概念中与完成是近义词.相关词汇"决义态",’已决议’,"被决议".
  7. fulfillment handler: 完成处理函数,表示promise为完成态时会被调用的函数
  8. rejection handler:拒绝处理函数,表示promise为拒绝态时会被调用的的函数.

在promise内部有 一个promiseState属性会被设置为,"pending(挂起)","fulilled(已完成,)","rejected(已拒绝)",用来反应promise的当前状态.注意这属性并没有在promise对象中暴露出来.所以我们无法以编程的方式判断当前promise处理什么状态.但是可以使用 then() 方法 在promise的状态改变时执行一些操作.

then()方法在所有的promise上都存在.接受两个参数,第一个参数是被完成时要调用的函数,与异步操作关联的任何所有数据都会被传入这个函数.第二个参数是被拒绝时要调用的函数.和完成函数相似.拒绝函数也会被入与拒绝相关的数据.

传递给then()方法的两个参数都是可选的.比如:

let promise = new Promise((resolve,reject)=>{
    resolve('success')
    // reject()
});

console.log(promise)
promise
.then((res)=>{
    console.log(res)
},(err)=>{
    console.log(err)
})
.then(()=>{
    console.log('fdsfs')
})
.then(()=>{
    console.log('可以无限调用')
})
.catch(function(){
    console.log('这里是重大的错误')
})
// 这个方法的行为拒绝处理给then(),catch()和then()方用是等效的
promise.catch(function(err){
    console.log(err.message)
})

promise
.then(null,(err)=>{
    console.log(err)
})

就算是完成或是拒绝处理的函数在已经被解决之后添加到队列作业,还是被执行.允许你随时添加新的完成或拒绝处理函数.比如:

let promise = new Promise((resolve,reject)=>{
    resolve('success')
    // reject()
});

// 原始的完成处理
promise.then(function(res){
    console.log(res)
    // 添加另一个
    promise.then(function(res){
        console.log(res)

    })
})

以上代码中then()又为同一个promise添加了一另一个完成处理函数.这个promisen已经完成了.因此新的处理程序.就被加入到了任务队列中.在前面的执行完毕之后…再以同样的方式执行

调用 resolve() 触发了一个异步操作,在每次调用then()或catch()都会创建一个新的作业,会在promise已经决议时被执行.并且把它他们添加给了作业队列再执行:比如:

let promise = new Promise(function(resolve,reject){
    console.log('promise')
    resolve()   
});

promise.then(function(){
    console.log('resolved')
})

console.log('hello')

//结果:
promise
hello
resolved

尽管then()方法出现在hello之前.实际上会被稍后执行.和执行器中的console.log(‘promise’)不同.完成处理函数和拒绝函数总是会在执行操作结束之后添加到作业队列的尾部.

基于Promise构造器行为的动态本质.是创建未决的最好的方式.如果想要一个promise代表一个已知的值.传值给resolve()函数并没有多大的意义.有两种方法可以指定值来创建已决的promise

  1. Promise.resolve()

接受一个参.返回一个完成状态.比如

let promise = Promise.resolve('hello')

promise.then(function(val){
    console.log(val)
})
  1. Promise.reject()

创建一个已拒绝的promise

let promise = Promise.reject('hello')
promise.catch(function(val){
    console.log(val)
})

7.5 AXIOS基于一个promise封装的一个ajax框架

7.Set与Map

7.1 Set

是一个无重复值的有序列表.允许对它包含的数据进行快速的访问.增加了一个追踪离散值的方式.

7.1.1 创建set并添加项目.

let set = new Set();
set.add(1)
set.add('1')
console.log(set.size)

以上使用了add()方法向set中添加了项目.可以通过size属性能查看有多少项.

Set不会使用强制类型的转换来判断当前值是否重复.允许存在不同类型的相同值,但是有一个例外 0

-0 和 +0 被认为是一样的

let set = new Set();
set.add(1)
set.add(-1)
set.add(0)
set.add(-0)
set.add(+0)
set.add('1')
console.log(set.size)  //4

也可以添加多个对象,但是多个对象并不会认为是一同一项

let set = new Set(),
    a = {},
    b = {};

    set.add(a)
    set.add(b)
    console.log(set.size)  //2

如果add()方法对相同的值进行多次调用,会在第一次之后被忽略;

let set = new Set();
set.add(1)
set.add('1')
set.add(1)
set.add(1)
console.log(set.size)  //2

当然也可以用数组来初始化Set,且Set会确保不重复的使用这些值.比如:

let set = new Set([1,2,3,4,3,4,3,1,4,6,3]);
console.log(set.size) // 5

在以上这个例子中,数组中的重复值被去除了.虽然当中的数字很多都出现多次.但是只会存一个.

可以使has()方法来判断一个值是否存于Set中,比如:

console.log(set.has(19))  //false

7.1.2 移除值

想要把Set中的值移除.可以使用 delete() 方法来实现,或着也可以用调用clear()方法来把所有的值从Set中移除

比如:

let set = new Set([1,2,3,4,3,4,3,1,4,6,3]);
console.log(set.size) // 5
console.log(set.has(6))  //false
console.log(set.has(19))  //false
set.delete(6)  //删除
console.log(set.has(6))  //false
set.clear() //清除所有
console.log(set.has(1))  //false
console.log(set.size) // 0

所有的这些方法都都有提供一个机制追踪唯一值,不过,在给Set添加之后,如果想要对每个项执行 一些操作,需要用到forEach()方法.

7.1.3 set.forEach()方法

这个的是属于Set上的forEach()方法,在之前的forEach方法,可以更容易处理数组中的数组.不用建立循环.于是在Set类型也加了相同的方法.工作方式是一样的.

接受三个参数,

  1. Set中下个位置的值
  2. 和第一个参数相同的值
  3. Set本身

set中forEach和数组中的版本有一点点差别.set版本传给回调函数的第一个和第二个是相同的…Set没有所谓的健.具体的使用方法.

let set = new Set([1,2]);

set.forEach(function(value,key,selfSet){
     console.log(value,key)
     console.log(selfSet === set)
})
//结果
1 1
true
2 2
true

以上代码在每一项中进行了迭代,并对传递给forEach(),回调函数的值进行了输出.回调函数每次执特时.key和value总是相同的,同时selfSet和set 也是相同的.

 7.1.4 把Set转换为数组

可以使用剩余参数把set赋值给一个新的数组变量.

let set = new Set([1,2,4,5,3,3,5,6,3])
let arr = [...set];

console.log(arr)  //[1, 2, 4, 5, 3, 6]

以上代码中,set在初始化的时候包含了一个有重复值的数组.在清除重复值之后.又使用了扩展运算符把自己的项放到了一个新的数组中.这一步只是把这数组中的每一个项复制给了新数组.本身依旧存在.

7.2 Map

Map类型是键值对的有序列表.健和值可以是任意的类型.

可以使用map.set()方法传递一个健和一个值,来添加项.

可以用键名来调用map.get()方法来取得对应的值

let map = new Map();
map.set('name','朱鹏富');
console.log(map.get('name'))

以上案例存用name为键,朱鹏富为值.接着使用get()方法取得对应的值.如果不存在.就是undefined;

也可以使用对象来创建键.在这之前是不可能做到的.

let map = new Map();
let key1 = {},key2 = {};
map.set(key1,100);
map.set(key2,200)
console.log(map.get(key1))
console.log(map.get(key2))

以上用了对象作为map的键.并了两个值,因为这些健不会被转换为其它形式,每个对象又是唯一所以就允许我们关联额外数据.不用去修改对象自己本身.

7.2.1 Map的方法

Map和Set共享了几个方法.允许我们使用相似的方法来进行map和set的交互.

  1. has() 判断指定的键是否存在
  2. delete() 删除map中的健以及对应的值
  3. clear() 删除所有的map中的健与值
  4. 同样有一个size属性.用于指定有多少个的键值对.

示例:

let map = new Map();
map.set('name','朱鹏富');
map.set('age','18');

console.log(map.size)  //2

console.log(map.has('age'))  //true
console.log(map.get('age'))  //18

map.delete('name');
console.log(map.has('name'))  //false
console.log(map.get('name'))  //undefiend
console.log(map.size)  //1

map.clear();
console.log(map.has('name'))  //false
console.log(map.get('name'))  //undefiend
console.log(map.has('age'))  //false
console.log(map.get('age'))  //undefiend
console.log(map.size)  //0

size属性包含了map中的键值对的数量.分别演示各方法的结果.

 7.2.2 Map的初始化

和Set一样,可以把数组给Map,比如:

let map = new Map([['name','朱澳'],['age','18']]);

console.log(map.has('name'))   //true
console.log(map.get('name'))   //朱澳
console.log(map.has('age'))    //朱澳
console.log(map.get('age'))    //18
console.log(map.size)   //2

以上代码通过Map构造器进行了初始化.name.age添加到了map的变 量中.由数组的形式构成.允许键是任意类型.使用数组是确保它们在添加到map之前不会被强制转为其它类型的唯一方法

7.2.3 Map.forEach()方法

方法类似于set和数组中的同名方法,接受一个回调函数.回调函数接受三个参数:

  1. map下位置的值
  2. 这个值对应的健
  3. Map本身自己

回调函数的这些参数和数组中的forEach方法的行为基本上契合的.也就是第一个在数是值,第二个参数是健

在数组中是索引值.比如:

let map = new Map([['name','朱棣'],['age','18']]);
map.forEach(function(value,key,self){
    console.log(value,key,self)
})

forEach回调函数传递了具体的每个参数的详细信息.

6.符号 Symbol

新的基本类型:符号 (Symbol),刚刚设计出来的时候的被用于创建对象的私有成员.也是javascript开发者一直期待的一个功能特性.这个私有名称最后就成了ES6 中的符号.

6.1 创建symbol

Symbol没有字面量的形式.可以用全局的symbol函数来创建一个符号值:

let first = Symbol('frist');
let obj = {};

obj[first] = '大明王朝1566';
console.log(obj[first])

以上代码创建了一个symbol类型 frist 变量.并把它作为obj对象的一个属性.每次访问属性都要使用这个symbol符号值.

因为symbol基本类型值.因为不能使 new 操作符,如果使用会报错.但是可以通过 new Object(Symbol()) 来创建一个符号实例,在经验中和 Symbol() 没有任何的区别.

Symbol还可以接受一个参数用于描述符号值.可用开发调试.比如:

let first = Symbol('frist');
let obj = {};

obj[first] = '大明王朝1566';
console.log(obj[first])

console.log(first)  //Symbol(frist)

符号的描述信息存在内部属性[[desription]]中.当Symbol的toString()方法被调用(隐式或显示).这个信息就会被读取.

因为是基本类型,所以可以使用typeof 操作符来判断一个变量是否为Symbol类型

let first = Symbol('frist');
console.log(typeof first)  //symbol

6.2 使用Symbol

以上的例子已经展示了符号的方括号的用法,还能在对象的"需计算字面量属性名"中去使用Symbol.也可以在Object.defineProperty() 或 Object.defineProperties() 调用中使用它,比如:

let first = Symbol('frist');
// 使用一个需计算的字面量属性
let obj = {
    [first]:"朱高炽"
}
// 让这个属性变为只读
Object.defineProperty(obj,first,{
    writable:false
});

let last = Symbol('last');
Object.defineProperties(obj,{
    [last]:{
        value:"朱厚照",
        writable:false
    }
})

console.log(obj[first])
console.log(obj[last])

以上使用了字面量属性方式创建了一个符号类型的属性first,可以使用Object.getOwnPropertyDescriptor()查看属性enumerable:true.显示是可以允许遍历,但无法用for-in遍历,也不会显示在Object.keys()结果中。

6.3 共享

在不同的代码中使用相同的符号值,比如,在一个应用中需要在两个不同的对象类型中使用同一个符号属性。用来表示一个唯一的标识符。为此ES6提供了一个“全局符号注册表”,允许我们在任意时间点上去访问。

如果想要创建一个共享符号,需要使用symbol.for()方法,比如:

let userid = Symbol.for("userid")
let obj = {};
obj[userid] = '4324234243';
console.log(obj[userid]);
console.log(userid); //Symbol(userid)

Symbol.for()方法首先第一步会搜索全局的符号注册表。查询是否存在一个名为“userid”的Symbol的值。如果存在,这个方法会返回已经存在的值,否则就会创建一个新的值。并且使用这个值把它记录到全局符号注册表中。再返回新值。

这就是说之后使用同一个值去调用 Symbol.for()方法。都会返回同一个符号值。比如:

let u = Symbol.for('u');
let obj = {
    [u]:'1566'
}

console.log(obj[u]);
console.log(u)

let u2 = Symbol.for('u');

console.log(u2 === u)  //true
console.log(obj[u2]);
console.log(u2) //Symbol(u)

在以上这个案例中。u和u2包含同一个symbol值。因此可以互换使用,第一次调用Symbol.for()方法。创建了这个符号值,而第二次调用全局符号注册表中把它搜索出来。

另外还有一个方法Symbol.keyFor()方法可以在全局符号注册表中根据符号值找出对应的键值。比如:

let name = Symbol.for('name');
console.log(Symbol.keyFor(name))  //name

let name2 = Symbol.for('name');
console.log(Symbol.keyFor(name2))  //name

let name3 = Symbol('name');
console.log(Symbol.keyFor(name3))  //undefined

在以上这个案例中.name 和 name2 都返回了键值 "name".但是name3.在全局符号注册表中不存在.所以并没有关联的健值.所以只能是undefined.

全局符号注册表,可以简单的理解为全局作用域.一个共享的环境.

5.解构 – 更方便的数据访问

对象和数组的字面量是我们最常用的两种方式,已经是我们这门语言很重要的组成部分.在写代码的时候,基本上任何时候都会在定义对象和数组.定义之后再从这些结构中取出数据.

ES6增加了解构功能.是一个把数组结构分解到更小更精更细的部分的过程 .今天的课程以此展开.

5.1 解构有什么用

在ES6之前,从对象或是数组中获取数据,或是信息.需要写很多相似的代码

比如:

let options = {
    x:true,
    y:false
}

let x = options.x,
    y = options.y;

以上的代码显示了从对象中取出数据,赋值给了同名的本地变量上.如果只有这样两个.其实也还好.但是如果想象一下有100个.我们是不是需要一个个拿出来赋值,并且如果有一个嵌套的数据结构还要遍历寻找信息,在这种情况下.可能我们只是想要拿出其中一个小节的数据.去深挖整个数据结构。

基于这种情况。ES6增加了个解构赋值的功能。

5.2 对象解构

对象解构在赋值语法的左侧用了对象字面量,比如:

let user = {
    name:"赵中",
    age:20
}

let {name,age} = user;

console.log(name)
console.log(age)

以上代码中.user.name的值被赋值给了本地的变量 name ,user.age给了本地变量age.以上这种写法,属于简写的属性初始化器.name,age标识符代表了本地变量.也读取了相应的属性值

当使用解构来配合var,let,const 来声明变量时.必须提供初始化(也就是等号右边的值) 比如:

var {name,age};       //语法错误
let {name,age};       //语法错误
const {name,age}      //语法错误

const 本身的要求就是必须要有初始化.就算是没有使用解构,也是必须要有初始化的值,

let,var则仅仅在使用解构的时候才有此要求;

5.2.1 解构赋值

以上案例用于变量声明,但是,也可以在赋值的时候使用解构.比如,我想在变量声明之后改变它们的值:比如

let user = {
    name:"赵中",
    age:20
};
let name = '颜色';
let age = 22;

// 使用解构来分配不同的值
({name,age} = user)

console.log(name)
console.log(age)

在以上这个案例中,name和age属性在声明时被初始化,而同时有两个同名变量也被声明且初始化为不同的值,接下来有一行使用解构表达式,能过读取user对象来更改这两个 变量的值.

这里需要注意的是必须用圆括号包裹解构赋值语句,这是因为暴露的花括号会被解析为代码块语句,而块语句不允许在赋值操作符左侧出现.圆括号标示的的意思是花括不是块语句,而被解释为表达式.从而允许完成赋值操 作.

解构赋值表达式的值为右侧赋值操作符之后的值,就是说在任何期望有值的位置都可以使用解构赋值表达式.比如:

let user = {
    name:"赵中",
    age:20
};
let name = '颜色';
let age = 22;

function out(value){
   console.log(value===user); //true
}

out({name,age}=user)

console.log(name)
console.log(age)

以上这个函数在被使用一个解构赋值表达式进行了调用,这个表达式的计算结果为user,因为表达式右侧的值.对name,age的赋值正常进行.同时user也被传入out函数

在使用解构赋值表达式的时候右侧的计算结果如果是null或是undefiend,会出现错误.因为任何读取null或undefined的行为都导致运行时错误.(runtime error)

5.2.2 解构默认值

在使用解构赋值语句时,如果我们所指定的本地变量在对象中没有找到同名属性,哪么这个变量的值赋值为undefined

let user = {
    name:"赵中",
    age:20
};

let {name,age,job} = user;

console.log(name)
console.log(age)
console.log(job)

以上代码定义了一个额外变job,并且试图希望对其赋值.但是user中不存在同名属性,因此job理所当然赋值为undefined.

当然可以为他定义一个默认值,在指定属性不存在的时候使用这个默认值。如果想要实现这样的效果,比如:

 let {name,age,job = true} = user;

以上的变量job被指定了一个默认值,这个默认值只有在user对应的属性缺失的情况下,或对应的属性值为undefined的情况下。才会被启用。比如:

let user = {
    name:"赵中",
    age:20,
    job:'前端工程师'   //如果这里有值,并不为undefined 就使用这里的值,否则就用true
};

let {name,age,job = true} = user;

console.log(name)
console.log(age)
console.log(job)

5.2.3 赋值给不同的本地变量

以上的每个案例,我们都使用了对象中的属性名作为本地变量的名称,如果使用相同的名称,这样做是没有任何的问题的,但是如果你不想使用属性中的同名属性作为本地变量?

ES6有一个扩展语法。允许你在给本地变量赋值的时候使用一个不同的名称。比如:

let user = {
    name:"赵中",
    age:20,
    job:'前端工程师'
};

let {
    name:n,
    age:a,
    job:j
} = user

console.log(n)
console.log(a)
console.log(j)

在以上为个案例中,我们使用了解构赋值来声明了三个变量。n,a,j, 并且成功的获取了user.name;user.age;user.job属性的值。name:n 这种语法表示读取user.name的属性。并且把它的值存在n变量之上。

这种语法和传统的对象字面量的语法相反。传统的是左侧为名,右侧为值,但是在当前这个案例中。左侧为值,右侧为名。

注意:依然可以给变量别名添加默认值,依然是在本地变量名称后添加等于默认值。比如:

let user = {
    name:undefined,
    age:20,
    job:'前端工程师'
};

let {
    name:n = '颜色',
    age:a,
    job:j
} = user

console.log(n)   //颜色
console.log(a)
console.log(j)

在以上这个案例中 n 变量有一个默认值 “颜色” 这个变量值。会在对应的对象属性不存或为undefiend时启用。

5.2.4 嵌套的对象解构

类似于对象字面量的语法,可以使到嵌套的对象结构中的数据,比如:

let user = {
    name:"颜苗",
    love:{
        s:{
            sex:"男",
            number:5
        },
        e:{
            sex:"女",
            number:7
        }
    }
}

let {
    love:{s}
} = user

console.log(s)   // {sex: "男", number: 5}

以上案例在解构中使用了花括号,表示应当下行到user对象中的love属性内部对找s属性,这里需要记住。每当有一个冒号(:)在解构中模式中出现,则表示冒号(:)之前的标识符代表需要检查的位置,而右则是赋值的目标。当右侧存在花括时,表示目标被嵌套对象更深的一层当中。

如果需要更进一步,在对的嵌套解构名同样能为本地变量使用不同的名称,比如:

let { love:{s:php}} = user

以上user.love.s的值被赋值给了一个新的变量php。

5.3 数组解构

数组解构和对象解构在语法上面非常的相似,只不过就是把对象字面量换成了数组字面量。

数组解构时,解构作用在数组的内部位置上,而不是作用在对象的具名属性上。比如:

let users = ["赵中","颜苗","周斯佳"];
let a = users[0],b=users[1]  //es5
let [ z,y ] = users;
console.log(z,y)  // 赵中 颜苗 

以上从数组解构中取出了赵中和颜苗,并且赋值给了z和y变量。这些值被选择。取决于他们在数组中的位置。实际的变量名称是任意的。(和位置无关)。任何没有在解构模式明确指定的值都会被忽略。要注意的是,数组本身没有变任何方式方法改变。

let [,,j] = users;
console.log(j)  //周斯佳

以上案例中加的逗号是为了数组前面的项提供的占位符。使用这种方法可以取出数组任意位的值。不用提供其它的变量名。

需要注意的是。在使用var,let,const进行数组解构时,必须提供初始化值。

我们还可以在赋值表达式中使用数组解构。但是和对象解构不同,不用把表达式包含在圆括号内,比如:

let users = ["赵中","颜苗","周斯佳"],
    z = "张也",
    y = "叶彬";

[z,y] = users;

console.log(z,y)  // 赵中 颜苗

在ES5如果想要互相两个变量的值,需要使用第三个变量作训临时变量,比如:

var a = 10,
    b = 20;

var c = a;
a = b;
b = c;

console.log(a,b)

以上这个C变量对于互相是非常重要的。但是如果使用了es6的数组解构。就不用这个临时变量了。比如:

var a = 10,
    b = 20;
[a,b] = [b,a]
console.log(a,b) // 20 , 10

以上这个案例,赋值语句左侧的解构其它的数组解构的例子是一样的。右侧则是为了互换而临时创建的数组字面量。b和a的值分别被复制给了临时数的第一个和第二个位置。并且进了解约。结果就是两个变量的值进行了互相

和对象解构赋值相同的是,若等号右则的计算结果,为null或undefined,数组解构赋值表达式会发生错误。

5.3.1 数组解构默认值

数组同样允许在数组任意位置指定默认值,如果指定位不存在,或着值是undefined,这个默认值就会被使用.比如:

let users = ["颜苗"];
let [a,b = '赵中',c = '大鹏'] = users
console.log(a,b,c) //颜苗 赵中 大鹏

以上代码中users数组只有一个项,因此并没有为b,c提供匹配的项.然后我们又给他们加了默认值.所以就是对就的名字

5.3.2 嵌套

和对象相似,可以用对象的方式来解构嵌套的数组.也就是可以整个解构中插入另一个数组.就以下这样

let users = ["颜苗",['orange','yellow'],'赵中'];
let [first,[color]] = users
console.log(first,color)

以上first表示users数组中的颜苗,而内层数组orange,包含在里面的一个数组中.因此我们想要拿到它就必须也要加一个方括号[].可以使用任意深度的嵌套.

5.3.3 剩余项

数组解构剩余项,可以使用 … 语法来把剩余项的项目赋值给一个变量,比如:

let users = ["颜苗",['orange','yellow'],'赵中'];
let [first,...arr] = users
console.log(first,arr)  //颜苗 (2) [Array(2), "赵中"]
console.log(arr.length) //2
console.log(arr[0][0]) //orange

以上这个案例,第一个项给了first,剩余项给了一个新的变量arr

剩余项的另一个功能克隆功能。比如:

首先不淡这个剩余项,我们用es5的方法( concat() )来实现克隆

var arr = ['a','b','c'];
var newArr = arr.concat();
newArr.push('d')
console.log(newArr,arr)

尽管concat方法的本意是合并两个数组。但是如果不使用任何的参数来调用这个方法。就会得到个新的数组。但是如果在es6中,我们可以这样做。比如:

let arr = ['a','b','c'];
let [...newArr]  = arr;
newArr.push('d')
console.log(newArr,arr)

以上这个方法,并没有比concat好多少,但是在使用上面也算是一个技巧。

剩余项必须是解构模式中最后的部分,之后不能再有任何的东西。否则就会报语法错误。

5.3.4 混合解构

对象和数组解构被用在一起,用来创建更加复杂的数据形式。大多数时候可以用于ajax数据效果的时候,体现特别的明显。比如:

const  obj = {
    isCheck:true,
    name:"obj",
    local:{
        access:{
           x:100,
           y:300
        }
    },
    news:['尽管concat方法的本意是合并两个数组。但是如','混合解构']
};

let {
    local:{
        access
    },
    news:[firstNews]
} = obj;

console.log(access,firstNews)

以上obj.local.access和obj.news[0]被取出来。并别存给了access和firstnews中。这里需要注意和记住的是解构模式中 local:和 news:只是对应obj 这个对象中属性的位置。混合使用。obj对象的任何部分都能被提出来。对于ajax中返回给我们的json数据来说。 这种方法尤其高效,因为不需要去搜索整个结构.

5.4 参数解构

一个不能被忽略的场景就是,传递函数参数时。当我们的函数接收大量的可选参数时的一个常用的模是创建一个options对象。会在其中包含符加的参数,比如:

// options上的属性表示符加参数
function ajax(url,data,options){
    options = options || {}
    let time = options.time,
        success = options.success;
}

ajax('1.php','张志翔',{
    time:3000,
    success:function(){
        console.log(success)
    }
})

以上这样的案例大家如果去看一些js库的源码,你会看到很多类似这样的代码。在这个ajax函数中,url和data是必须的。而options对象中的time,success则不是必须的。这种方法是有效的,但是无法通过查看函数的定义就能知道我想要输入什么样的代码。必须去阅读开发文档或是源码。

但是用了参数解构之后,可以在第一时间更清楚明白的去标明函数期望输入的参数值。使用对象或数组模式替代具名参数。比如:

function ajax(url,data,{time,success}){
    //其它代码
}

ajax('1.php','张志翔',{
    time:3000,
    success:function(){
        console.log(success)
    }
})

以上这个案例,第三个参数使用了解构来抽取必要的数据。那么现在对于开发者来说,解构之外参数是必须的,而可选的项目放在参数组中。同时如果使用了第三个参数其中应该需要的值,是极期明确。如果没有传值。他们的值就是undefined.

但是如果我们没有给解构传参。在默认情况下,调用函数时未给参数解构传值会报错:比如:

ajax('1.php','张志翔')

在实际调用时,第三个参数的缺失。就相当于undefiend,会导致一个错误,因为参数解构实际上只是解构了声明的简写、当我们的ajax()函数被调用时。js的引擎会修改我们的代码为如下效果

function ajax(url,data,options){
     let {time,success} = options
    //  其它代码
}

因为赋值过程中。右侧的值为null 或是 undefined时。解构会抛出错误,那么也就是未向ajax()函数传递第三个参数时同样也会出错。但是如果我们的要求是可先的。我们可以这样做:

function ajax(url,data,{time,success} = {}){
   console.log('其它代码')
}

ajax('1.php','张志翔')

以上这个案例第三个参数给了一个空对象作为默认值.也就是说没有向函数传递第三个参数.则里面的time,success的值都是undefiend.这时就不会报错.

基于以上的原理,可以为参数解构提供默认值.就和解构赋值时的操作一样.那么也就可以在其中每个参数的后面添加默认值.比如:

function ajax(url,data,{time = 5000,success = true} = {}){
   console.log('其它代码')
}

ajax('1.php','张志翔')

以上代码中参数解构给每个属性都提供了默认值.所以我们可以避免检查指定的属性是否有被传入 .以确保在没有被传入数据的时候,可以使用正确的类型值.而整个解构的参数同样有一个默认值,就是一个空对象,可以让这个参数成为一个可选参数,这个案例稍微要复杂一点.但是可以确保每个参数都有可用的值,而不必加必要检查,所以这样的付出是值得的.

总结

  1. 解构会让我们操作对象和数组时变的更加容易,可以把数据分离并且只拿到你想要的信息.

  2. 对象解构模式可以让你从对象中进行取值,而数组模用于数组.

  3. 对象和数组解构都可以提供默认值.

  4. 特加需要注意的是,如果赋值操作符也就是等号右侧的值为null或undefined的时候,两种模都会报错.

  5. 可以在深层的嵌套中使用解构.可以是任意深度.

  6. 在使用var,let,const,的解构声明创建的变量.必须要提供初始化值.允许我们把值解构到对象属性或已经存在的变量上

  7. 参数解构,可以让选项对象变的更加的清楚透明.

对象的扩展

在ES6当中把对象这块内容做了提升的效用.因为js中基本上所有的值,都是某些类型的对象.

 4.1 对象字面量语法的扩展

对象字面量是js中最常用的模式之一,(json就是基于这种语法.对象字面量的一个普遍性.是因为它的简洁可以少写很多不必要的代码.对 于程序员来说.能少一个字母都是好的.

在我们之前的es5当中,对象字面量必须是名/值对的组合.但是有可能属性值在被始化的时候有些重复.比如

function creFn(name,age){
    return {
        name:name,
        age:age
    }
}

以上属性名和参数名相同,这个结果重复了name和age.在返回的值当中.左边的name是名 右边是值.然后右边的name赋值给了左边的name.

哪么在ES6中.可以更加简洁.比如:

function creFn(name,age){
    return {
        name,
        age
    }
}

当对象中的字面量中的属性只有名称时,js解释器会在周边作用域查找同名的变量.如果找到了,就会把值赋值给同名的属性.

4.2 方法简写

ES6改进了为对象字面量的方法赋值的语法.在早期ES5之前.我们必须指定一个名称并且完整的函数定义来为对象添加方法,比如:

var obj = {
    run:function(){
        return 'hello world'
    }
}

哪么通过es6可以更简洁.比如:

var obj = {
    run(){
        return 'hello world'
    }
}

以上这个案例具有和ES5方法的所有的相同特征和能力,唯一的区别是简写的方法不能使用super,

4.3 需要计算的属性名

需要计算的属性名.其实是在ES5或更加的版本.用于对象实例.只要用方括号表示法来代替小数点表示法就可以.

比如:

var obj = {
    "first name":"虎虎"
}
var lastName = 'last name';
obj["first name"] = '虎';
obj[lastName] = '李';

console.log(obj["first name"])
console.log(obj[lastName])

var lastName = 'lastName';
var obj = {
    "first name":"虎虎",
    [lastName]:"李"
}

console.log(obj["first name"])
console.log(obj[lastName])

// 对象字面量中的方括表示这个属性名需要被计算/其实是一个字符串.也就是说其中可以包含表达式

var su = ' name';
var obj = {
    ["first" + su]:'虎',
    ["last" + su]:'李'
}

console.log(obj["first name"])
console.log(obj['last name'])

4.4 重复的对象字面的属性

ES5的严格模式为重复的字面量引入一个检查.如果重复的属性名.就会报错.

var obj = {
    name:"ddd",
    name:"cccc"   //在es5环境中 strict 模式下不允许一个属性有多个定义 会报错.可以打开IE浏览器进行测试
}

console.log(obj.name)

以上,在ES5严格模式下.第二个name会造成语法错误.,但是在ES6当中移除了重复性的检查.严格和非严格都不在检查.如果存在后面的值就是实际的值.

4.5 正式的方法定义

在ES6之前,方法这个概念在js当中是不存在的.其实叫法是引用了其它语言的叫法,在es6作出了一个正式的定义:方法是一个有homeobject内部属性的函数.此内部属性指向当前方法所属于的对象,比如:

let obj = {
   //这个写法叫方法
   getColor(){
      return 'blue'
   }
}
以下这个写法不是方法.是函数
function getColor(){
    return 'blue'
}

总结

对象是Javascript 编程的中心.做了一些直接的改进.变的更加的强大.主要的改进有,速记法属性定义可以更容易的把作用域内的变量赋值给对象的同名属性;允许被计算,方法的简单写可以省略冒号和function关键字.放弃了同名检查.也就是说我们可以在对象字面量中写两个同名属性值.而不会报错.

函数

3. 函数

函数在任何语言中都是最重要的一部分,没有之一!js函数中.从这门语言诞生到现在…除了遇到了es6之后.发生了一次比较大的化.之前其实都是没有什么变化.但是并不是说完美的…还是有改进的空间.

3.1 在ES5中模拟默认的参数

function ajax(url,type,timeout,callback){
    timeout = timeout || 5000 ;
    type = type || 'get';
    callback = callback || function(){return 1}

    // 其它代码
}

以上这个例子.type和callback 参数是可选的,因为在没有参数的时候,会使用默认值,||或运算符在左侧为假的情况下.返回右侧的内容,因为我们的参数在没有传递的时候返回的是undefined.在逻辑或运算符中用来给缺失的参数提供默认值.但是有一个BUG,因为传进来的参数可能是 0 ,或运算符遇到0为假.所以timeout会被替换成5000

基于以上的情况.我们应该使用一种更加合理更加案例的方法

function ajax(url,type,timeout,callback){
    timeout = (typeof timeout !== 'undefined') ? timeout : 5000

    // 其它代码
}

这种方法,在今天也是比较常见的用法.虽然是安全的..但是代码量过多.而且代码了一个公共的模式.流行的js库中基本上这种写法到处都是..而在ES6当中有更方便的写法,

3.2 es6中的参数默认值

function ajax(url,type = 'get',timeout = 5000,callback = function(){alert(1);}){
    callback()
    // 其它代码
    return {
        url,
        timeout,
        type
    }
}
console.log(ajax('index.html')
console.log(ajax('index.html','post',1000))
console.log(ajax('index.html',undefined,1000))

在以上这个案例中,只有第一个参数需要被传参,其它的都有自己的默认值,这种写法明显比上面的更加的精巧。因为不需要再添加 更多的代码来检查参数值的缺失。

在这个默认参数值的调用中,null是有效的值。而 undefined 则被认为是没有传递参数;

3.3 参数默认值会影响到arguments对象

关于arguments 对象会在使用参数默认值的时候有一些不同的表现,的ES5非严格模式下,在argements 对象会反映出具名参数的变化

function ajax(url,type){
    console.log(url === arguments[0])
    console.log(type === arguments[1])
    url = 'q';
    type = 'w'
    // 其它代码
    console.log(url === arguments[0])
    console.log(type === arguments[1])
}

ajax('url','type')
//以上会得到四个
true
true
true
true

特别需要注意的是,在ES5的严格模式下,这种情况不存在、不会在反映出参数的变化

function ajax(url,type){
    "use strict"
    console.log(url === arguments[0])
    console.log(type === arguments[1])
    url = 'q';
    type = 'w'
    // 其它代码
    console.log(url === arguments[0])
    console.log(type === arguments[1])
}

ajax('url','type')
//以上会得到以下结果
true
true
false
false

在ES6参数默认的函数中。arguments对像的表现总是和ES5的严格模式保持一致。此时无论函数是否运行在严格模式下。参数默认值都会触发arguments对象与具名参数的分离。非常小的一个细节。但是很重要

function ajax(url,type = 'd'){
    console.log(arguments)
    console.log(url === arguments[0])
    console.log(type === arguments[1])
    url = 'q';
    type = 'w'
    // 其它代码
    console.log(url === arguments[0])
    console.log(type === arguments[1])
}

ajax('url')
//以上会得到以下结果
1   //length
true
false
false
false

以上例子中arguments.length的值为1,因为我们只传递了一个参数.也就说是arguments[1]的值是undefined,符合把单个参数传给函数的一个预期值.也就是说url和arguments[0]是相等的.如果去改变url和type 的值不会对arguments对象造成影响.无论是否在严格模式下.都可以始终依据arguments对象来反映初始值调用的状态.

3.4 参数默认值表达式

参数默认值并不要求一定是基本类型值.比如可以接受一个函数产生参数的默认值.比如:

function getValue(){
    return 5;
}
function addValue(number,int = getValue()){
    return number + int
}
console.log(addValue(4))
console.log(addValue(4,87))

以上如果未提供第二个参数…getValue()函数就会被凋用获取表达式的默认值.如果在调用addValue()函数时未提供第二参数时…getValue()才会调用,而在.getValue()的函数声明初次被解析时并不会进行调用.也就意味着.getValue()函数若被写为可变的.则它就有可能返回可变的值.

比如:

let value = 5;
function getValue(){
    return value++;
}

function addValue(number,int = getValue()){
    return number + int
}

console.log(addValue(4))  //9
console.log(addValue(4))  //10
console.log(addValue(4))  //11l

以上就是一个返回可变值的函数的一个体现.因为这样的一种行为.有一个比较有意思的现像,可以把前面参数作为后面参数的默认值

function addValue(number,int = number){
    return number + int
}

console.log(addValue(4,4)) 
console.log(addValue(4)) 

以上案例中number为int提供了默认值.意味只要传入一个参数就会让两个参数获得相同的值.如果是这样的话.哪我们的这个number,是不是可以作为第二个默认参数函数的形参.

function getValue(value){
    return value + 5;
}

function addValue(number,int = getValue(number)){
    return number + int
}

console.log(addValue(4,4))  //8
console.log(addValue(4))    //13

以上案例把number的值设为getValue()函数的返回值.因此addValue(4)会返回(4 + 9),而另一个依然是4+4.

引用其它参数 作为参数进行默认赋值时,只能引用前方的参数,因此前面的参数不能访问后面的参数.比如:

function addValue(number = int,int){
    return number + int
}

console.log(addValue(undefined,1))   //报错

因为int 在number 之后定义,不能把它作为后者的默认值..存在暂时性死区.

3.5 剩余参数

剩余参数 rest parameter由三个点( … )与一个具名参数指定.会包含传递给函数的其余参数的一个数组.名称中的剩余由此而来.比如

function ajax(obj,...keys){
   for(let i=0,length = keys.length;i<length;i++){
       //其它代码
   }
}

keys是一个包含所有在obj之后的参数的剩余参数(这和包含的有的参数的 arguments 不同,因为arguments 会把第一个参数也包含在内).也就是说我可以去对keys进行从头到尾的遍历.

如果你用了剩余参数,就必须把剩余参数放在最后.一个函数中只能有一个剩余参数.

function ajax(obj,...keys,last){
   for(let i=0,length = keys.length;i<length;i++){
       //其它代码
   }
}

以上代码会发生语法错误,不能在剩余参数之合使用具名参数,同样的剩余参数也不能用于对象字面量中的set属性中使用,比如

let obj = {
    // 不能在setter中使用剩余参数
    set abc(...value){

    }
}

3.6 箭头函数

箭头函数算是一个比较明显改变, 使用(=>)来定交箭头函数.在行为上面和传统函数不同.

  1. 没有this,super,arguments,也没有所谓的new.target梆定
  2. 不能被new 调用.也就是如果你要写构造函数不能使用箭头函数.
  3. 没有原型,既然不能对箭头函数使用new 那么也就不需要原型.也就没有prototype属性
  4. 不能改变this.this的值在函数内部不能被修改/在 函数的整个生命周期内其值保持不变
  5. 没有arguments,箭头函数没有和arguments绑定.就必须依赖于具名参数或剩余参数来访问函数的参数
  6. 不允许重复的具名参数,无论是否在严格模式下都不能允许.而对于传统函数来说.只有在严格模式下才禁止这种重复.

 3.6.1 语法

箭头函数的语法有比较多的写法,怎么写取决于你想要完成什么样的目标.所有的写法都以函数参数为开头

后面跟着是箭头,接下来的是函数体.

var res = value => value;
//等价于以下
var res = function(value){
     return value
}

当箭头函数只有单个参数时..这个参数可以直接书写.不需要其它额外的语法.箭头右边的表达式,会被计算并返回结果.

如果要参入多个参数,就要放在一个括号内

var sum = (n,m) => n + m;
// 等价于以下
var sum = function(n,m){
  return n + m;
}

以上这个案例和上面的案例唯一的区别就是参数在括内.相互之间和传统函数一样.用逗号隔开.如果说函数没有任何参数.那必就必须使用一对空括号

var sum = () => '李虎';
// 等价于以下
var sum = function(){
  return '李虎'
}

如果说想要有传统的函数体,也可以包含多个语句的时候,用一对花括号包进来.然后也就以使用return,比如:

var sum = (n,m)  => {
    return n + m;
}
// 等价于以下

var sum = function(n,m){
  return n + m;
}

基本上坷以把花括号内的代码当成传统函数对待就可以,除了不能使用 arguments 如果说想要创建一个空函数;

var sum = ()  => {}
// 等价于以下
var sum = function(){}

花括号表示函数的主体,所有的正常的工作都必须放在花括号当中.如果箭头函数想从函数体内向外返回一个对象字面量.就必须把字面量包括在圆括内;

var getOBJ = id => ({id:id,name:"李虎"})

// 等价于以下

var getOBJ = id => {
    return {
        id:id,
        name:"李虎"
    }
}
var getOBJ = function(id){
    return {
        id:id,
        name:"李虎"
    }
} 

以上把对象字面量包括在括号内.表示了括号内是一个字面量而不是函数体.

3.6.2 创建立即调用函数表达式

我们在使用函数的一种流行的方式 创建立即调用函数表达式 (IIFE: Immediately Invoked Function Expression)

IIFE允许我们自定一个匿名函数并在未保存引用的情况下立即调用它.比如:

let obj = function(name,age){
    return {
        getName:function(){
            return {
                name,
                age
            }
        }
    }
}('李虎',22)

console.log(obj.getName())

在以上案例中IIFE 被用于创建一个包含 getName() 方法的对象.有两个参数作为返回值.可以有效的让为两个返回值(name,age)成为返回对象的一个私有成员. 我们可以使用箭头函数来完成同样的事 情.只要把它包括在括号内就可以.

let obj = ((name,age)=>{
    return {
        getName:function(){
            return {
                name,
                age
            }
        }
    } 
})('李虎',22)

console.log(obj.getName())

这里需要注意的是括号仅仅包含了箭头函数的定义.并没有包括(‘李虎’,22),这区别于传统函数的方式.括号即可以连函数定义与参数一起包含.也可以只包含函数定义.

3.6.3 没有 this 绑定

JavaScript中最常见的错误就是在函数内的this绑定,因为在一个函数内部的this值可以被改变.这个改变的过程.取决于在调用这个函数时的上下文关系,就可能错误的影响一个对象,尽管本来是想要修改另一个对象的..但是结果可能并不是,比如:

var handler = {
    id:"53853",
    init:function(){
        document.addEventListener('click',function(ev){
             this.ido(ev.type) //报错了 this.ido is not a function
        },false) 
    },
    ido:function(type){
        console.log("handler" + type + ' - ' + this.id)
    }
}
handler.init()

handler 对象作用是用于设计处理页面上的交互,在调用init()方法,用于建立交互关系.用于和当前页面进行一次行为对话,并且还添加了一个事件处理函数this.ido().但是在实际调用过程中,this.ido() 发生了错误.告诉我们这不是一个方法.不是一个function,这是因为this是事件目标对象,也就是当前案例中的(docuemnt),而不是被绑定在handler对象上.所以在运行此代码时..会触发一个错误.因为this.ido()这个方法并不存在于document对象上.

在箭头函数之前解决此类问题.可以使用bind()方法把函数的this.绑定到handler对象上.以修正这段代码:示例如下

var handler = {
    id:"53853",
    init:function(){
        document.addEventListener('click',(function(ev){
            this.ido(ev.type) //已经是正确的
       }).bind(this),false) 
    },
    ido:function(type){
        console.log("handler" + type + ' - ' + this.id)
    }
}
handler.init()

以上案例.通过调用bind(this),实际上是创建了一个新函数,它的this绑定到当前的this也就是 handler 对象之上.如果想要避免创建一个函数.最好的方式.不是下面的在之前存一个对象.而是使用箭头函数

也可以在在事件对象之前保存this对象

var handler = {
    id:"53853",
    init:function(){
        var self = this;
        document.addEventListener('click',function(ev){
            self.ido(ev.type) //已经是正确的
       },false) 
    },
    ido:function(type){
        console.log("handler" + type + ' - ' + this.id)
    }
}
handler.init()

在使用箭头函数时.因为没有this绑定,也就意味着.箭头函数内部的this值.只能通过查找作用域链的方式来确定我的当前对象.如果箭头函数被包含在 一个非箭头函数中.哪么this就会和这个函数相等;否则this就会是全局对象也就是window.如果是在node.js当中的是global,下面我们用箭头函数再次修正此代码,示例如下:

//示例1
var handler = {
    id:"53853",
    init:function(){
        document.addEventListener('click',ev=>this.ido(ev.type),false) 
    },
    ido:function(type){
        console.log("handler" + type + ' - ' + this.id)
    }
}
handler.init()
//示例2
//也可以这样写
//效果同上,只是箭头函数的写法有所区别
var handler = {
    id:"53853",
    init:function(){
        document.addEventListener('click',(ev)=>{
            this.ido(ev.type)
        },false) 
    },
    ido:function(type){
        console.log("handler" + type + ' - ' + this.id)
    }
}
handler.init()

以上案例中事件处理函数是一个调用this.ido的箭头函数。。它的this和init()方法相同。所以这个版本代码工作方式类似于我们前面使用bind(this)的案例。尽管this.ido()方法并不返回何值。但依然是函数内体唯 一能被执行的语句。因此无须使用第二个案例用花括号来包含它。但是我依然推荐大家使用第二种,读性更高一点。

箭头函数不用被用于定义新的类型。没有prototype,所以不能使用 new 运算符。如果使用会报错。从这点上可以看出箭头函数。在设计之设就是属于抛弃型函数。

var Fn = ()=>{};
fn = new Fn()  //Uncaught TypeError: Fn is not a constructor

以上代码中报错了,因为是一个箭头函数,它不存在constructor方法,所以箭头函数不能被用于new

同样,由于箭头函数中的this由包含它的函数决定,因此也不能使用call(),apply()或bind()方法来改变其值。

3.6.4 箭头函数和数组

比如如果你想使用自定度的比较器来数组进行排序。我们可以这样写

传统的写法:

 var result = arr.sort(function(a,b){
    return a - b;
 })

以上是传统的写法,如果使用箭头函数,可以有更简洁的书写体验

var result = arr.sort((a,b)=>a-b);

还可以使回调函数的数组方法,比如map() 或reduce().都可以把看着复杂的需求转换成更加简单明了的代码。

3.6.5 没有arguments绑定

箭头函数没有自己的arguments的绑定。但是依然可以访问包含它的函数的arguments对象。因此箭头函数无论在何处被执行。这个对象都是可以用的。比如:

function createFn(){
    return () => arguments[0];
}
var fn = createFn(5)
console.log(fn())  //5

以上案例在createFn()内部,arguments[0]被已经创建的箭头函数所引用。这个引用包含了传传递给createFn()函数的首个参数,因此当箭头函数在执行之后。可以反回5,这是因为传给createFn()的首个参数,尽管箭头不存在arguments。但是但是创建他的函数的作用域内是存在的。由于作用域链的关系。依然可以被访问。

识别箭头函数

尽管箭头函数和传统函数.语法不同.但是依然是函数.这个本质不能搞乱.也能被正常的识别

比如:

var fn = (a,b)=>a-b;
console.log(typeof fn)  //function
console.log(fn instanceof Function)  //true

以上两条console.log()的结果.可以明确的知道,typeof 和 instanceof 在箭头函数中的行为和作用与其它的函数完全一样.所以你依然可以对箭头函数使用call,apply和bind(),虽然函数的this绑定不会受影响.

比如:

var sum = (num1,num2) => num1 + num2;
console.log(sum(1,1))
console.log(sum.call(null,1,2))
console.log(sum.apply(null,[1,2]))
var bindSum = sum.bind(null,1,2);
console.log(bindSum())

以上案例sum()被使用call()和apply()方法.并传了参数,bind()方法用于创建一个新函数bindSum().

总结

可能对于很多有经验的开发者来说.并不会感受到箭头函数有什么革命性的变化.事实上变化也不是我们看到的这么大.只是增加了一些增量改进.让函数更加容易使用而已.

模板字面量

2. 模板字面量

  1. es6之前.想实现多行字符串,要么使用一个原来js,语言级bug来实现一个多行.
  2. 把字符串替换已存在的变量值的能力
  3. HMTL转义

2.1 基本语法

使用一个反引号 “ 用来包含普通字符串,而不是之前的双引号或单号

let msg = `hello ES6`;
console.log(msg)

在以上这 个案例中,并没有感受到和之前有什么不一样.

如果想要输出反引号 “ 可以使用 转义符 \

let msg = `\`hello ES6\``;
console.log(msg)

在模板字符串里面.如果想要输出单引号或双引号.就不需要转义符 \

let msg = `"hello"'ES6'`;
console.log(msg)

2.1 多行字符串

在ES6之前想要实现多行字符串.可以使用一个语法BUG,在换行之前使反斜线 ( \ ),可以用于创建多行字符串.为什么不用.因为是一个语言级的BUG.你不知道什么时候他被修复了.

var msg = "hello \
ES6";
console.log(msg)

这里确实换行了.并且可以被输出.但是打印的时候并不会真正的换行.因为反斜线 ( \ )是延续符号,而不是新行的符号.如果你想要在输出中换行.可以这样写

var msg = "hello \n\
ES6";
console.log(msg)

ES6中推出的多行字符串就不需要使用这种BUG了只需要在你需要的地方包含换行就可以了

let msg = `hello 
es6;
`
console.log(msg)

需要注意提所有的空格都会被认为是内容的一部分,所以需要注意空格的问题.保持合理的缩进

let ouput = `

<h1>
    <div>hello</div>
</h1>

`.trim();
console.log(ouput)

trim()可以清空字符串左右两边的空格.

如果只是单纯想要实现输出结果的换行可以使 \n ;

以上只是可以实现换行…但是真正的意见在于允许你使用js表达式嵌入到模板字面量当中.

比如:

let msg = 'hello';
let res = `${msg},world`;

console.log(res)

替换位 \${msg} 替换位是\$开始,与结束{}来界定.之间可以放入任意的JS表达式.以上是最简单的示例

既然替换位是表达式.哪么可替换的就不可能只是变量.还可以放计算,函数调用.

let n = 5,
    m = 20.452342,
    msg = `${n+m}`;

    msg = `$${(n * m).toFixed(2)}`
console.log(msg)

在上面这个案例中.我可使用了计算.并且调用方法.在替换位之前的美元符号可以被正常输出.因为没{}跟随其后.

另外模板字面量本身也是JS表达式,这也就是可以把模板字面量嵌入到另一个模板字面量的内部

比如:

let msg = "张三",
    res = `hello ${
        `他叫${msg}`
    }`;
    console.log(res)

现在我们都已经理解 了字面量不用做任何的连接…就可以直接使用多行字符串以膝上计算机插值功能.不过这个还不是真正牛逼的地方..真正厉害的是模板标签(template tag),能对模板字面量进行转换并返回最终的字符串值.标签在模板起始处被指定. 也就是在第一个` 之前

比如

let msg = tag`hello 张三`;

在上面这个例子,tag就是会被应用到hello 张三,

一个标签tag仅仅只是一个函数,在被调用 时接收需要处理的模板字面量的数据.标签所接收的数据被划分为独立的片段,并且必须组合来创建结果.第一个参 数是一个数组,包含被js解释过的字面量字符串.随后的参数是每个替换位的解释值

function tag(val,...args){
// 返回一个字符串
}

用一个列子来解释一下传递给标签的是什么参数.

比如:

let conut = 10;
let price = 0.25;
let msg = tag`${conut} items cost $${(conut * price).toFixed}.`;

如果我有一个tag()的函数.这个函数会接受到三个参数,第一个是一个数组 arr.会包含以下的元素

  1. 在首个替换位之的空字符串 ""
  2. 首个替换位与第二个替换位之间的字符串 items cost $
  3. 第二个替换位之后的字符串 "."

第二个参数会是10, 也就是 ${conut} 变量的解释值. 会成为 args 数组的第一个元素.

第三个也就是最后一个,会是 ${(conut * price).toFixed()} 的解释值 2.50,并且是args数组的第二个元素

示例如下:

["","items cost $","."]
[10,2.50]

需要注意的是 第一个参数数组的第一个元素是一个空字符串,以确保arr[0],总是在字符串的起始分,

也就是正如 arr[arr.length – 1] 总是是字符串的结尾部分.同时替换的元素数量也总比字符量元少1

就意味着,args.length === arr.length-1的值是 true;

在当前这种情况下,,可以交替使用arr 和 args 数组来创建一个结果字符串;

用arr中首个元素开始。。。后面紧跟着args的首个元素。。以此反复。直到结果字符串被全部创建完毕

function fn(arr,...args){
    let result = "";
    // 直接使用args的元素数量来进行循环
    for(let i=0;i<args.length;i++){
    result += arr[i];
    result += args[i];
}
    result += arr[arr.length - 1]
    return 1;
}

let conut = 10;
let price = 0.25;
let msg = fn`${conut} items cost $${(conut * price).toFixed()}.`;

console.log(msg)

在以上这个例子中定义了 fn 标签,能够执行与模板字面量的默认行为相同的换操作,唯一的需要注意的是在循环中使用args.length,而不是arr.length用来避免args的数组越界,这是冂es6对 arr 和 …args 一个定义;

…args中包含的值不必是字符串,也可以是计算后的结果.

ES6 块级绑定

1. 块级绑定

基本上在类C语言中,变量的创建总是在它被声明的地方.但javascript并非如此.变量实际的位置是取决于开发着如何声明它..基于这样的一个存在,ES6闪亮登场.提供了一个let,可以让你更容易更直观的控制变量的作用域.

1.1. 所谓的变量提升

如果我们使用var 声明变量.如果在函数内.不管我写在什么地方,都会被视为写在函数顶部.如果不在函数内.则视为全局作用域的顶部.就是所谓的变量提升

function getVal(cond){
    if(cond){
        var color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return null
    }
}

以上代码在js解释器中会为被修改为

function getVal(cond){
    var color
    if(cond){
        color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return null
    }
}

为了解决这个BUG..引入了块级作用域

块级声明也就是让所声明的变量在指定的块的作用域外无法被访问.块级作用域(词法作用域)

  1. 在一个函数内部
  2. 在一个代码块内部 {}

块级作用域是类C语言的工作机制.

1.2. let 声明

使用方法和var语法一致,基本上你可以用let来代替var进行变量声明,但是会把变量的作用域限制在当前的代码块当中.如果想要提升,需要手动操作
以下示例:

function resu(val){
    if(val){
        let color = 'blue';
        return color;
    }else{
        // color 在此处  color is not defined
        return color
    }
}

function resu(val){
    let color 
    if(val){
        color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return color
    }
}

console.log(resu(0))

1.3. 禁止重复声明

如果一个标识符已经被定义,就不能在此代码块中使用同一个标识符进行 let 声明,否则会报错

var number = 0;
let number = 1;   // Uncaught SyntaxError: Identifier 'number' has already been declared

在同一作用域下不能得复声明
但是在嵌套的作用域内使用同名的新变量.就不会所错

var number = 10;
if(true){
    let number = 40;
}

以上案例中.let 并没有报错,因为在if条件语句中创建新变量number.而不是在同一级别再次创建.在if代码块中会屏蔽全局的number变量.从而在局部组止对于后者的访问.

1.4. 常量声明

声明一个常量可以使用const语法进行声明.常量意味声明之后的值不可变.所以所有的常量都要在声明的时候初始化.

// 有效的常量
const MAXNUMBER = 300// 语法错误的常量
const NAME;

以上MAXNUMBER这个常量初始化并且赋值了.因此是正常而有作用的.而NAME常量没有被赋值.会导致语法误法;
const和let声明一样都是块级声明.有声明也不会有提升的情况发生

if(true){
    const NUMBER = 40;
}
// NUMBER 在此处无法被访问

已经被声明过的变量.不可以用常量再次声明

var msg = 0;
let height = 190;

// 以下两者都会报错
const msg = '1';
const height = 170;

常量不可被改变

const height = 170;
height = 180;  //Uncaught TypeError: Assignment to constant variable.

常量确定之后.不可以再次被赋值

如果使用const 声明对象.
const声明是会阻止对于常量绑定与常量自身值的修改,也就是说并不会去阻止对于常量的成员的修改;

const obj = {
    name:"张三"
}  //
obj.name = '李四';
// 报错  Uncaught TypeError: Assignment to constant variable.
obj = {
    name:"赵五"
}
// 数组也是一样的
const arr = [];
arr.push(1)
arr.push(1)
console.log(arr)

此处对象初始化的时候有一个属性,修改属性是可以,但是不能修改对象本身.只需要记住const阻止的是对于常量绑定的修改.而不是对于成员值的修改

1.4. 暂时性死区

使用let , const 声明的变量,在达到声明之前.都是无法访问的.就算是相对安全的操作(比如typeof),也是一样的

console.log(typeof abc)
let abc;

以上abc 变量使用了let 进行定义,但是这条语句.永远不会被执行.因为在声明之前就已经报错了.出现这个问题是因为暂时性死区.但是暂时性死区是 js 社区(比如网上论坛)提出的,这个名称从来没有在ECMAscript规范中被明确命名.但是经常用于描述let或const声明 的变量为何在声明处之前无法被访问

1.5. 循环中的块级绑定

我们最需要使用的变量的块级场景是,也许是for,也就是想让一次性的循环计数只能在内部使用
以下示例应该是最常见的一种行为

for (var index = 0; index < 10; index++) {}
console.log(index)   //10

但是在大多数语言中.以上index.只能在循环内工作访问.这里的index在外部可以被访问.是因为使用var声明导致了变量提升,如果换成let,就不会发生

for (let index = 0; index < 10; index++) {}
console.log(index)   //app.js:8 Uncaught ReferenceError: index is not defined

在以上案例中.index仅在for内部使用. 循环结束..这个变量的任意位置不可被访问

1.6. 循环内的函数

在let没有出现之前,因为var的特点.会使循环变量作用域之外还可以被访问,于是在循环内创建函数会有问题.

var arr = [];
for(var i=0;i<10;i++){
    arr.push(function(){
        console.log(i)
    })
}

arr.forEach(function(fn){
    fn()
})

我们本身的预期是希望输出0-9的数字,但是实际结果是数字10被输出了10次.

我们在修正这个问题的时候可以使用IIFE(立即调用函数表达式),就可以在每次迭代中强制性的创建变量的一个新的副本.

var arr = [];
for(var i=0;i<10;i++){
    arr.push((function(value){
        return function(){
            console.log(value)
        }
    })(i))
}

arr.forEach(function(fn){
    fn()
})

上面在循环内使用IIFE,变量 i 被传递给了IIFE, 去创建了value变量为自身的副本,并在自己当中

1.7. 循环内的let

使用let声明.可以简化IIFE.在每交迭代中.都创建一个新的同名变量,对其进行初始化.

var arr = [];
for(let i=0;i<10;i++){
    arr.push(function(){
        console.log(i)
    })
}

arr.forEach(function(fn){
    fn()
})

以上代码完全实现了相同的效果.但是更加简洁.效果更好,同样的方式在for-in 和 for-of中同样适用.

var arr = [];
var obj = {
    name:"张三",
    sex:"",
    age:22
}

for (let key in obj){
    arr.push(function(){
        console.log(key)
    })
}

// console.log(arr)

arr.forEach(function(fn){
    fn()
})

在以上for-in循环中体现出for相同的行为.

let声明在循环内部的行为是在规范中特别被定义过的.而与不提升变量的特征没有必然的联系.事实上在let刚刚出来的时候是没有这种行为的…在后面不停变更过程中加进来的.

1.8. 循环内的常量

ES6规范没有明确禁止在循环中使用常量.然而它会根据循环方式的不同有不同的行为体现.在常规的for当中.可以使用const初始化.但是如果你去循环了.就等于是想要改变它.就会报错

for (const index = 0; index < 10; index++) {
    console.log(index)
}

代码会执行一次.但是第二循环的时候,试图改变index.这个时候就报错了.

但是如果是在for-in或是for-of.效果和let完全相同.

var arr = [];
var obj = {
    name:"张三",
    sex:"",
    age:22
}

//不会报错
for (const key in obj){
    arr.push(function(){
        console.log(key)
    })
}

arr.forEach(function(fn){
    fn()
})

因为每次迭代创建一个新的变量绑定.而不是试图去修改已经绑定的变量的值.所以就不会报错

1.9. 全局的块级绑定

let和const不同于var的另一个区别就是在全局作用域上的表现.在全局作用域上使用var,会创建一个新的全局变量.并且成为全局对象(window)的一个属性.这也就是竟味使用var可能会无意中覆盖一个已经有的全局属性,

var RegExp = 'h';
console.log(window.RegExp)  //h

RegExp定义在window.但还是被重写了.但是如果使用了let 或 const,虽然在全局中会创建新的绑定,但是不会有任何属性被添加到了全局对象上去.也就意味着不用使用let或const来覆盖一个全局变量.只能把他屏蔽.

let RegExp = 'h';
console.log(RegExp// h
console.log(window.RegExp//ƒ RegExp() { [native code] }
console.log(window.RegExp === RegExp)   //false

如果希望代码能从全局对象中访问.依然需要使用var.在浏览器跨窗口去访问代码时适用.

在拥有了let和const之后..默认情况下应该使用let而不是var 对于90%以上开发者来说let的行为才是正确的行为.是声明变量本来就应该有形式.所以直接使用let更合逻辑.在需要被保护的变量时使用const.

1.10. 总结

  1. let或const有块级作用域
  2. var 只有全局和函数作用域
  3. let 不能重复声明,const也不能重复声明
  4. 如果使let 或 const 必须在使用声明.如果在使用后声明,会产生暂时性死区
  5. 可以解决循环中的闭包的问题
  6. var在全局下声明的变量,会挂载到window下面
  7. let或const会挂载到window下面.

ES6概述

ES6概述

ES6 简介

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

ECMAScript 和 JavaScript 的关系

要讲清 ECMAScript 和 JavaScript 之间的关系,就需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。

该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。

因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。

ES6 与 ECMAScript 2015 之间的关系

ECMAScript 2015(简称 ES2015),在2011年,发布了ECMAScript 5.1 版本,之后就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。

但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等。

但是,标准的制定者不想这样做。他们想让标准的升级成为常规流程:任何人在任何时候,都可以向标准委员会提交新语法的提案,然后标准委员会每个月开一次会,评估这些提案是否可以接受,需要哪些改进。如果经过多次会议以后,一个提案足够成熟了,就可以正式进入标准了。这就是说,标准的版本升级成为了一个不断滚动的流程,每个月都会有变动。

标准委员会最终决定,标准在每年的 6月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了。

ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。

因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。

语法提案的批准流程

任何人都可以向标准委员会(又称 TC39 委员会)提案,要求修改语言标准。

一种新的语法从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由 TC39 委员会批准。

  1. -Stage 0 – Strawman(展示阶段)
  2. -Stage 1 – Proposal(征求意见阶段)
  3. -Stage 2 – Draft(草案阶段)
  4. -Stage 3 – Candidate(候选人阶段)
  5. -Stage 4 – Finished(定案阶段)

一个提案只要能进入 Stage 2,就差不多肯定会包括在以后的正式标准里面。

ECMAScript 的历史

ES6 从开始制定到最后发布,整整用了15 年。

前面提到,ECMAScript 1.0 是 1997 年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998 年 6 月)和 ECMAScript 3.0(1999 年 12 月)。3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学 3.0 版的语法。

2000 年,ECMAScript 4.0 开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是 2000 年。

为什么 ES4 没有通过呢?因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA 的第 39 号技术专家委员会(Technical Committee 39,简称 TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等大公司。

2007 年 10 月,ECMAScript 4.0 版草案发布,本来预计次年 8 月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。

2008 年 7 月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为 ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为 Harmony(和谐)。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。

2009 年 12 月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 委员会的总体考虑是,ES5 与 ES3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是 ES6,第六版发布以后,就指 ES7。TC39 的判断是,ES5 会在 2013 年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。

2011 年 6 月,ECMAScript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。

2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。

2013 年 12 月,ECMAScript 6 草案发布。然后是 12 个月的讨论期,听取各方反馈。

2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。从 2000 年算起,这时已经过去了 15 年。