深入理解 ES6

let && const

let与var的声明用法相同,但是多了一个临时死区(Temporal Distonrtion Zone)的概念。

console.log(a)// -> undefinedvara =1console.log(b)// -> Uncaught ReferenceError: b is not definedletb =1

可以发现在声明前使用let声明的变量会导致报错,这解决了 JS 很多奇怪的问题。并且使用let会生成一个块级作用域,作用域外不能访问该变量。

{leta =1;varb =1;}console.log(b);// -> 1console.log(a);//  -> Uncaught ReferenceError: b is not defined

在 JS 中,声明变量都会提升,不论用什么关键字声明。当使用let时变量也会被提升至块级作用域的顶部,但是只提升声明,不提升初始化。并且会产生临时死区,该区域会存放变量,直到执行过声明语句后,方可使用该变量。

在循环中let会与前面有些不同,每次迭代都会产生一个新的变量,并用之前的值初始化,如何理解这句话呢,请看以下代码。

for(leti =0; i <10; i++) {console.log(i)// -> 输入 0 - 9}// 上面的循环代码可以这样看{// 形成块级作用域leti =0{letii = iconsole.log(ii)    }    i++    {letii = iconsole.log(ii)    }    i++    {letii = iconsole.log(ii)    }    ...}

const与let基本类似,只是用const声明必须赋值,并且不得修改绑定,什么意思呢,请看代码。

consta =1;a =2// -> Uncaught TypeError: Assignment to constant variable// butconstb = {a:1};b.a =2// 起效

当然了,有办法让这个不能改变

constb =Object.freeze({a:1})b.a =2// 没有报错,但是 b.a 没有被改变

但是Object.freeze只能在这里有效,对于数组这些可以看看这个提案

这两个新的声明方式在全局作用域下不会自动加上window

字符串相关

部分新增的字符串函数

letstring ='startend'string.includes('a')// -> true 是否包含string.endsWith('end')// -> true 是否由 end 结尾string.startsWith('start')// -> true 是否由 start 开头

模板字面量

很棒的新功能,解决了之前很多麻烦的写法。

// 语法就是 `` 代替之前的引号,在 `` 中使用引号不需要转义lets =`it's string`

多行字符串

// 这样在语法中就可以换行了lets =`start \

end`// 注意在模板字面量中的任何空白符都是起效的lets =`start \

    end`// ->  start    end

占位符和标签模板

lets ='string'letmessage =`start${s}`// -> startstring// ${} 就是占位符语法,可以更简便的实现字符串插入// 定义一个 tag 函数,然后直接在 `` 前使用就可以letm = tag`s${s}e${message}`// strings 是一个数组,value 是模板字面量中所有的占位符的值functiontag(strings, ...value){// -> ['s', 'e', '']console.log(strings)// -> ['string', 'startstring']console.log(value)}// 上面的 ...value 也是 ES6新出的扩展语句,在这里代表不定参数的写法,用于替换 arguments// 不定参数使用上也是有限制的,必须放在所有参数的末尾,并且在每个函数中只能声明一次// 扩展语句和 arguments 区别就是代表了 strings 参数后面的所有参数// 除了上面的写法,还可以用于展开可以迭代(有Symbol.iterator属性)的对象letarray = [1,2,3]console.log(...array)// 该语法可以解决之前很多地方只能传入单个参数,只能使用 apply 解决的问题Array.prototype.unshift.apply([4,5], array)// -> [1, 2, 3, 4, 5]// 现在可以直接这样写[4,5].unshift(...array)// 展开运算不受不定参数的条件限制,可以一起用

函数

默认参数

ES6 允许给函数增加默认参数

functionfn(a =1, b =2){}// 默认值也可以通过调用函数获得,注意必须调用函数functionfn1(a =1, b = fn()){}

新增函数内部方法

在 JS 中,函数有多种用法,可以直接调用,也可以通过new构造函数。

在 ES6中,函数内部新增了 [[Call]] 和 [[Construct]] 两个方法。后者会在使用new构造函数时执行,其他情况会执行前者方法。

当一个函数必须使用new构造时,你可以使用这个新属性new.target判断

// new.target 只能在函数中使用functionFn(){if(typeofnew.target ==='underfined') {throw....... }}

箭头函数

这个特性真的很棒,先介绍下他的几种语法

// 最简单的写法,只有一个参数,单行表达式value => value// 多个参数需要使用小括号包裹(v1, v2) => v2 + v1// 没有参数需要使用小括号包裹() =>"balabala"// 多行表达式需要大括号包裹(v1, v2) => {returnv1 + v2}// 返回一个对象,需要用小括号包裹() => ({a:1})// 立即执行函数,注意普通的立即执行函数的小括号包裹在最外面,箭头函数不需要((value) =>value)("balabala")

箭头函数和普通函数区别还是蛮大的,说几个常用的

没有this,不能改变this绑定

不能通过new调用,当然也没有原型

没有arguments对象,不能有相同命名参数

箭头函数虽然没有this,但是还是可以在内部使用this的

this的绑定取决于定义函数时的上下文环境

一旦函数调用,任何改变this的方法都无效

// let 有个细节letx =11111leta = {x:1,    init() {// 箭头函数的 this 取决于 init,所以可以打印出 1document.addEventListener('click', () =>console.log(this.x))    },allowInit:()=>{// allowInit 直接是个箭头函数,所以这时的 this 变成了 window// 但是并不会打印出 11111,忘了 let 的一个细节的可以回到上面看看console.log(this.x))    }    otherInit() {// 普通函数的 this 取决于调用函数的位置,this 指向 document// 如果想打印出 x,可以使用 binddocument.addEventListener('click',function(){console.log(this.x)        })    }}a.init()// -> 1a.allowInit()// -> undefineda.otherInit()// -> undefined

对象相关

leta =1// 当 key 和 value 名字相同时可以简写letb = { a }// 对象中的方法也可以简写leta = {    init() {}}// 对象属性名也可以计算 letname ='name'b[name +'1'] =2// === b['name1'] = 2

ES6 也新增了几个对象方法

Object.is(NaN,NaN)// ->true// 结果基本于 === 相似,除了 NaN 和 +0 -0Object.is(+0,-0)// -> falseleto = {a:1}leta =Object.assign({}, o)// -> {a: 1}// 第一个参数为目标参数,后面的参数是不定的,参数属性名如果有重复,后面的会覆盖之前的

原型相关

ES6 之前改变对象原型很麻烦

letobj = {a:1}letobj1 = {a:2}// 已 obj 为原型leta =Object.create(obj)// 改变 a 的原型为 obj1Object.setPrototypeOf(a, obj1)// a.a === 2

访问原型

Object.getPrototypeOf(a)// 访问原型// ES6 中可以直接通过 super 代表原型leta = {    init() {return'Hello'}}letb = {    init() {// 不能在 super 之前访问 thisreturnsuper.init() +'World'}}Object.setPrototypeOf(b, a)b.init()// -> 'HelloWorld'

但是 super 不是每个函数都可以使用的,只有在函数的简写语法中方可使用。因为在 ES6中新增了一个函数内部属性 [[HomeObject]],这个属性决定了是否可以访问到super。首先在该属性上调用Object.getPrototypeOf(绑定的对象),然后找到原型中的同名函数,在设置this绑定并且调用函数,其实就是一个新增的语法糖。

解构赋值

该特性可以用于对象,数组和传参。

letobj = {a:1,b:2}// 对象解构使用 {},数组解构使用 [],因为这里是对象解构,c 不是 obj 的属性,所以 underfined// 数组解构中,如果需要解构的变量大于数组索引,多出来的变量也是 undefined// 解构必须赋值,否则报错。不能 let {a, b, c};// 赋值不能为 null 或者 undefined,会报错let{a, b, c} = obj// 等于 let a = obj.a,可以看做之前介绍的对象属性简写console.log(a, b, c)// -> 1, 2, underfined// 如果已经声明了变量并且想使用解构,必须最外面是小括号({a, b} = obj)// 如果不想使用 obj 中的对象名,又想使用解构赋值let{x: a} = obj// 如果想使用默认值let{a =2, c=3} = obj// -> 1, 3// 因为 a 是 obj 中的对象,所以默认值被覆盖// 解构也可以嵌套letobj = {data: {code:1},message: [1,2]}// 这个写法在 json 中很好用// 注意在这个写法中,data 和 message 都是指代了 obj 的属性,并没有被声明变量let{data: {code},message: [a] } = objconsole.log(code, a)// 数组解构和对象解构基本相似,并且简单多了letmessage = [1,2,3,4]// 因为数组取值只能索引取,所以想跳过某几个索引,就用逗号代替// 同样,数组解构也可以使用默认值和嵌套解构,和对象解构一模一样就不赘述了let[a, , b] = message// -> 1, 3// 在上面章节介绍了扩展语法,同样也可以使用在数组解构中// 可以看到 b 变成了一个数组let[a, ...b] = message// -> 1, [2, 3, 4]// 传参使用解构可以让要传的参数更加清晰functionfn(name, {key, value}){console.log(name, key, value)}// 使用,注意:传参解构必须起码传入一个值,否则报错fn(1, {key:2,value:3})// 因为传参解构类似以下写法functionfn(name, {key, value}){let{key, value} =null// 这个上面讲过不能这样写}

Symbol

ES6 新出的第六个原始类型。多用于避免代码冲突,作为一个私有属性使用,不会被属性遍历出来。可以使用Object.getOwnPropertySymbols()检索 Symbol 属性。

创建和使用

// 创建leta =Symbol()// 更推荐这种写法,可以更加明确这个Symbol的用途// 并且有函数可以通过这个字符串取到相应的Symbolletb =Symbol('is b')// 使用,一般作为可计算属性使用leta = {}letb =Symbol('is b') a[b] =1// 可以在全局注册表中共享同一个 Symbol,但不推荐使用// 不存在 is a 会自动创建leta =Symbol.for('is a')

暴露内部操作

Symbol 中预定义了一些 well-know Symbol,这些 Symbol 定义了一些语言的内部实现

Symbol.hasinstance,用于执行instanceof时检测对象的继承信息

Symbol.isConcatSpreadable,布尔值,用于判断当使用concat函数时是否将数组展开

Symbol.iterator,迭代器,后面会讲到

Symbol.match,Symbol.replace,Symbol.search,Symbol.split,字符串相关方法的对应内部实现

Symbol.toPrimitive,返回对象原始值

Symbol.toStringTag,调用toString

Set 和 Map

Set

Set 是新增的无重复的有序集合,多用于集合去重或者判断集合中是否含有某个元素。

// 创建letset =newSet()// 添加元素set.add(1)set.add('1')// 重复的元素不会被添加set.add(1)// 判断是否包含元素set.has(1)// -> true// 判断长度set.size()// -> 2// 删除某个元素set.delete()// 移除所有元素set.clear()

Map

Map 是新增的有序键值对列表,键值可以是任何类型。

// 创建letmap =newMap()// 设置键值对map.set('year',"2017")map.set({},'obj')// 取值map.get('year')// -> '2017'// 判断是否有该键值map.has('year')// -> true// 获得长度map.size()// -> 2// 删除某个键值map.delete('year')// 移除所有键值map.clear()

迭代器和 Generator 函数

迭代器

顾名思义,用来迭代的。之前介绍过Symbol.iterator,可以迭代的对象都有这个属性,包括数组,Set,Map,字符串和 NodeList。ES6新增的for-of就用到了迭代器的功能,但是默认只有上面这些对象能使用。

leta = [1,2]for(letvalueofa) {console.log(value)// -> 1, 2}// 上面的代码其实就是调用了数组的默认迭代器letiterator = a[Symbol.iterator]()// 当调用 next 时会输出这次迭代的 value 和是否迭代完成console.log(iterator.next())// {value: 1, done: false}console.log(iterator.next())// {value: 2, done: false}// 已经没元素可以迭代了console.log(iterator.next())// {value: undefined, done: true}// 数组的默认迭代器只会输出 value,如果想同时输出索引的话// 这里可以使用新特性数组解构 let [index, value]for(letvalueofa.entries()) {console.log(value)// -> [0, 1]  [1, 2]}

对于自己创建的对象都是不可迭代的,当然我们也可以让他变成迭代的

leta = {array: [],// 这是一个 Generator 函数,马上就会讲到*[Symbol.iterator]() {for(letiteminthis.array) {yielditem        }    }}a.array.push(...[1,2,3])for(letitemofa) {console.log(item)}

Generator 函数

用于异步编程。该函数可以暂停和恢复执行,和同步写法很像。

// 星号表示这是一个 Generator 函数function*gen(){// 第一次 next 只执行到等号右边letfirst =yield1// 第二次 next 执行 let first = 和 yield 2letsecond =yield2// 不执行接下来的 next 就卡在上一步了letthrid =yield3}letg = gen()g.next()// -> {value: 1, done: false}g.next()// -> {value: 2, done: false

接下来看下 Generator 函数如何用于异步

functiongetFirstName(){    setTimeout(function(){        gen.next('alex')    },1000);}functiongetSecondName(){    setTimeout(function(){        gen.next('perry')    },2000);}function*sayHello(){vara =yieldgetFirstName();varb =yieldgetSecondName();// settimeout 本来是异步的,通过 Generator 函数写成了同步写法console.log(a, b);// ->alex perry}vargen = sayHello();gen.next();

JS 中的类不是其他语言中的类,只是个语法糖,写法如下。

classPerson{// 构造函数constructor() {this.name = name    }    sayName() {console.log(this.name)    }}letp =newPerson('name')p.sayName()// -> 'name'// class 就是以下代码的语法糖// 对应 constructorfunctionPerson(name){this.name = name}// 对应 sayNamePerson.prototype.sayName =function(){console.log(this.name)}

类声明相比之前的写法有以下几点优点

类声明和 let 声明一样,有临时死区

类声明中的代码全部运行在严格模式下

必须使用 new 调用

继承

在 ES6 之前写继承很麻烦,既然有个类,那么必然也可以继承类了

classPerson{// 构造函数constructor() {this.name = name    }    sayName() {console.log(this.name)    }}// extends 代表继承自PersonclassStudentextendsPerson{constructor(name, age) {// super 的注意事项之前有说过super(name)// 必须在 super 之后调用 thisthis.age = age    }    sayName() {// 如果像使用父类的方法就使用这个方法使用// 不像使用的话就不写 super,会覆盖掉父类的方法super.sayName(this.name)console.log(this.age)    }}

Promise

概念

用于异步编程。

// 你可以使用 new 创建一个 Promise 对象letpromise =newPromise(function(resolve, reject)){}resole()// 代表成功reject()// 代表失败promise.then(onFulfilled, onRejected)// 当调用 resole 或者 reject ,then 可以监听到promise.catch()// reject 或者 throw时可以监听到

Promise 有三个状态

pending,等待状态,也就是既不是 resolve 也不是 reject 状态

fulfilled,resolve 以后进入这个状态

reject,reject 以后进入这个状态

一般使用情况

functiondelay(){// 创建一个 promisereturnnewPromise((resolve, reject) =>{// 当调用 promise 时,里面的内容会立即执行console.log('in delay')        setTimeout(()=>{          resolve(1)        },1000)    });}functionotherDelay(){returnnewPromise((resolve, reject) =>{        setTimeout(()=>{          reject(1)        },1000)    });}// 这里会先输出 delay 函数中的 log,然后再输出 outer,接下来1秒以后输出3个1delay()// then 可以捕获 resolve 和 reject.then((value) =>{console.log(value)    })console.log('outer')otherDelay()// 捕获 reject时,如果不需要捕获 resolve 时可以这样写.then(null, (value) => {console.log(value)    })// 捕获 reject 或者 throw 时推荐使用这个写法,原因后面会说otherDelay()    .catch((value) =>{console.log(value);    })

以上是最常用的 Promise 写法,现在介绍 Promise 链

delay()// then 会返回一个新的 promise 对象.then((value) =>{// 这样就可以传参了returnvalue +1}).then((value) =>{console.log(value)// -> 2// then 里面可以也可以传入一个函数名,会自动调用// 如果传入的函数有参数会自动传入}).then(delay).then((value) =>{console.log(value)// -> 1// 如果在then 中抛出错误,只有 catch 才能监听到,所以推荐使用 catch 监听错误thrownewError('error')    }).then((value) =>{console.log(value)// 这个then 不会执行}).catch((error) =>{console.log('catch'+ error)// -> catch Error})

Promise 高级用法

开发中可能会有需求,需要一次上传几张图片,全部上传成功以后有个提示,这时候就可以用到Promise.all()

functionupdateOne(){returnnewPromise((resolve, reject) =>{        setTimeout(()=>{            resolve('one')            },1000)    });}functionupdateTwo(){returnnewPromise((resolve, reject) =>{        setTimeout(()=>{            resolve('two')            },2000)    });}// all 函数接收一个可迭代对象,注意这里传入函数必须调用letpromise =Promise.all([updateOne(), updateTwo()])// 只有当 all 中的异步全部完成了才会调用 thenpromise    .then((value) =>{// value 是个函数,顺序按照 all 里的迭代对象的顺序console.log(value)// -> ["one", "two"]})

如果一个异步任务超时了,你想直接取消,可以通过Promise.race()

// 假设该任务执行时间超过1秒就算超时,应该 cancelfunctiondelay(){returnnewPromise((resolve, reject) =>{        setTimeout(function(){            resolve('finish')        },1500);    });}functioncancel(){returnnewPromise((resolve, reject) =>{        setTimeout(function(){            resolve('cancel')        },1000);    });}// 接收的参数和 all 相同letpromise =Promise.race([delay(), cancel()])// race 中只要有一个任务完成,then 就会被调用,这样就可以 cancel 掉所有超时任务promise    .then((value) =>{console.log(value)// -> cancel})

Proxy

Proxy 可以创建一个代替目标对象的代理,拦截语言内部的操作。

lethandle = {}lettarget = {}// 这样就创建了target 对象的代理,但是这个代理其实没有任何用处letp =newProxy(target, handle)

上面的代码中可以看到传入了一个handle的对象,只有当这个对象中包含一些代理行为的函数时,这个代理才有用。具有的代理行为函数可以去MDN查看,这里举例几个用法。

lethandle = {// 改变 set 的内部操作set(target, key, value) {// 当给 age 属性赋值小于19时报错console.log(value)if(key ==='age') {if(value <19) {thrownewError('未成年')                }            }        }    }lettarget = {}letp =newProxy(target, handle)p.age =1// -> 报错p.age =19// -> 没问题

模块化

ES6 引入了原生的模块化,这样就可以抛弃之前的 AMD 或者 CMD 规范了,如果对模块化还没什么了解,可以看下我之前的文章明白 JS 模块化

// example.js 文件下// export 可以导出任何变量,函数或者类exportvarage =14exportfunctionsum(n1, n2){returnn1 + n2}exportclassPerson{constructor(age) {this.age = age    }}// 别的 JS 文件中导入// 如果想导入整个模块并且自己命名,就可以这样使用// import 后面代表模块名,from 后面代表要导入的文件地址import*asExamplefrom'./example'console.log(Example.age)// -> 14// 当然你也可以只使用模块中的一个功能// 这里使用了对象解构的方法拿到需要的功能,注意这里名字必须相同,否则使用会报错import{ age, sum }from'./example'console.log(age)// -> 14console.log(sum(1,2))// -> 3// 现在我只想导出一个功能,并且外部可以随便命名该如何做呢?// default 一个文件中只能使用一次exportdefaultvarage =14// MyAge 可以随便自己喜欢命名importMyAgefrom'./example'console.log(MyAge)// -> 14

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 第一章:块级作用域绑定 块级声明 1.var声明及变量提升机制:在函数作用域或者全局作用域中通过关键字var声明的...
    BeADre_wang阅读 815评论 0 0
  • *node下用express框架,实现一个简单的mvc *构建工具:gulp / babel / webpack ...
    韩娜爱吃辣_前端程序媛阅读 1,081评论 0 1
  • 1、新的声明方式 以前我们在声明时只有一种方法,就是使用var来进行声明,ES6对声明的进行了扩展,现在可以有三种...
    令武阅读 992评论 0 7
  • 一、let 和 constlet:变量声明, const:只读常量声明(声明的时候赋值)。 let 与 var 的...
    dadage456阅读 757评论 0 0
  • 原创文章&经验总结&从校招到A厂一路阳光一路沧桑 详情请戳www.codercc.com 主要知识点有:var变量...
    你听___阅读 633评论 0 1