ECMAScript简介


ECMASciprt是JavaScript的语言本身
通常看作JavaScript的标准化规范
实际上JavaScript是ECMAScript的扩展语言
ECMAScipt只提供了最基本的语法
JavaScript=ECMAScript + Web APIs(BOM + DOM)
JavaScipt实际上是由ECMAScript、BOM和DOM三部分组成
Node.js = ECMAScript + Node APIs (fs + net + etc)
Node.js是由ECMAScript、fs、net、etc组成

ES6变化简介


官方网址 http://www.ecma-international.org/ecma-262/6.0/
ES6变化简单归纳为4类:

  • 解决原有语法上的一些问题或者不足
  • 解决原有语法上的一些问题或者不足
  • 新增全新的对象、全新的方法、全新的功能
  • 新增全新的数据类型和数据结构

    新的声明方式


作用域

作用域就是某个成员能够起到作用的范围
在ES6之前,只有两种作用域

  • 全局作用域

变量在函数或者代码块 {} 外定义,即为全局作用域。不过,在函数或者代码块 {} 内未定义的变量也是拥有全局作用域的(不推荐)。

  • 函数作用域

在函数内部定义的变量,就是局部作用域。函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!

ES6新增作用域

  • 块级作用域

在其他编程语言中,块状作用域是很熟悉的概念,但是在JavaScript中不被支持,就像上述知识一样,除了全局作用域就是函数作用域,一直没有自己的块状作用域。在 ES6 中已经改变了这个现象,块状作用域得到普及。关于什么是块,只要认识 {} 就可以了。

Let

ES6新增了let命令,用来声明变量,有以下几个特性:

  1. let声明的全局变量不是全局对象window属性
  2. 用let定义变量不允许重复声明
  3. let声明的变量不存在变量提升
  4. let声明的变量具有暂时性死区
  5. let声明的变量具有块级作用域

常见场景
给DOM循环添加事件

  1. var elements = [{}, {}, {}]
  2. for (var I = 0 ; i < elemrntd.length; i++){
  3. elements[i].onclick = function () {
  4. console.log(i)
  5. }
  6. }
  7. elements[0].onclick()

当点击DOM元素的时候,无论点击哪一个,打印出来的是3,因为 for 循环里面是同步操作,点击事件是异步操作。当执行点击事件后,for 以及执行完毕,此时console.log打印出来的i是全局作用域里面的 i ,全局作用域的 i 为3

如果希望的值是0、1、2,也就是每次保存循环时候 i 的值,应该如何做呢?

ES5的解决方案——闭包

  1. var elements = [{}, {}, {}]
  2. for (var i=0;i<elements.length;i++){
  3. elements[i].onclick = (function (i) {
  4. return function() {
  5. console.log(i)
  6. }
  7. })()
  8. }
  9. elements[0].onclick()

ES6解决方案——let

  1. var elements = [{}, {}, {}]
  2. for (let i = 0 ; i < elemrntd.length; i++){
  3. elements[i].onclick = function () {
  4. console.log(i)
  5. }
  6. }
  7. elements[0].onclick()

事实上,上述代码内部也是一个闭包机制,使用 babel 转化成 ES5 语法,会把代码转化成闭包形式 。

补充:
for循环有两层作用域
循环层内层是一个独立的作用域,
外层是它本身的作用域

  1. for(let i= 0;i<3;I++){
  2. i = 'foo'
  3. }

所以执行上述代码不会报错。

Const

const用来声明一个只读的常量,它的特性在let基础上多个一个只读特性

使用const注意事项

  • const定义的时候必须要赋一个初始值
  • const实际上不允许修改变量的地址,但地址里面的内容是可以修改的

例如:

  1. const obj = {}
  2. obj.name = 'zce' //允许
  3. obj = {} //不允许

声明变量最佳实践:不用var,主用const,配合let。

解构赋值


解构赋值就是允许按照一定模式,从数组和对象中提取值,对变量进行赋值

数组解构赋值

  • 基本用法 ```javascript const arr = [100, 200, 300] //获取数组里面的成员

//以前的方法 const foo = arr[0] const bar = arr[1] const bae = arr[2] console.log(foo, bar, bae)

//新方法 const [foo, bar ,bae] =arr console.log(foo, bar, bae)

  1. - **rest参数**
  2. ```javascript
  3. let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]

我们可以使用 rest 来接收赋值数组的剩余元素,不过要确保这个 rest 参数是放在被赋值变量的最后一位。

  • 默认值

如果数组的内容少于变量的个数,并不会报错,没有分配到内容的变量会是undefined。

  1. let [firstName, surname] = []
  2. console.log(firstName) // undefined
  3. console.log(surname) // undefined

可以给变量赋予默认值,防止undefined的情况出现:

  1. // default values
  2. let [name = "Guest", surname = "Anonymous"] = ["Julius"]
  3. console.log(name) // Julius (from array)
  4. console.log(surname) // Anonymous (default used)

对象解构赋值

对象解构赋值需要根据属性名进行解构。

  • 基本用法 ```javascript let options = { title: “Menu”, width: 100, height: 200 }

let {title, width, height} = options

console.log(title) // Menu console.log(width) // 100 console.log(height) // 200

  1. > 在这个解构赋值的过程中,左侧的“模板”结构要与右侧的 Object 一致,但是属性的顺序无需一致。
  2. - **默认值**
  3. 赋值过程中可以指定默认值。
  4. ```javascript
  5. let options = {
  6. title: "Menu"
  7. }
  8. let {width = 100, height = 200, title} = options
  9. console.log(title) // Menu
  10. console.log(width) // 100
  11. console.log(height) // 200
  • rest运算符

如果我们象相操作数组一样,只关心指定的属性,其他可以暂存到一个变量下,这就要用到 rest 运算符了。

  1. let options = {
  2. title: "Menu",
  3. height: 200,
  4. width: 100
  5. }
  6. let {title, ...rest} = options
  7. // now title="Menu", rest={height: 200, width: 100}
  8. console.log(rest.height) // 200
  9. console.log(rest.width) // 100
  • 注意事项 ```javascript const obj = {name: ‘zce’, age: 18}

const { name } = obj

const name = ‘tom’ const { name } = obj console.log(name)

  1. 当解构的变量名与当前作用域的变量名相同时,就会产生冲突,这个情况就会报错。<br />使用重命名方法解决:
  2. > const { name: objName } = obj
  3. <a name="VunND"></a>
  4. ### 字符串解构赋值
  5. 可以当做是数组的解构
  6. ```javascript
  7. let str = 'imooc'
  8. let [a, b, c, d, e] = str
  9. console.log(a, b, c, d, e)

String


模板字符串

  • 使用一对” ` “进行标识
  • 支持多行字符串
  • 支持插值表达式
    1. const name = 'tom'
    2. const msg = `hey, ${name}`
    3. console.log(msg)

ES2015带标签的模板字符串

  1. const name = 'tom'
  2. const gender = ture
  3. function myTagFunc (strings) {
  4. console.log(strings)
  5. }
  6. //返回正常内容
  7. function myTagFunc(strings, name, gender) {
  8. //console.log(strings, name, gender)
  9. return string[0] + name + strings[1] + gender + strings[2]
  10. }
  11. const result = maTagFunc `hey, ${name} is a ${gender}.`

扩展方法

String.prototype.includes()
ES6提供了includes方法来判断一个字符串是否包含在另一个字符串中,返回boolean类型的值。

Sting.prototype.stratsWith()
判断参数字符串是否在原字符串的头部, 返回boolean类型的值。

Sting.prototype.endsWith()
判断参数字符串是否在原字符串的尾部, 返回boolean类型的值。

  1. const message = 'Error: foo is not defined'
  2. console.log{
  3. message.startsWith('Error')//true
  4. message.endsWith('.')//true
  5. message.includes('foo')//true
  6. }

Function


默认参数

对于函数而言,经常会用到参数,关于参数的默认值通常都是写在函数体中,如在 ES5 的时候大家都会这么写:

  1. function foo (enable) {
  2. enable = enable === undefined || true
  3. console.log('foo invoked - enable: ')
  4. console.log(enable)
  5. }
  6. foo()

在 ES6 中改变了对这种知识的写法:

  1. function foo (enable = true) {
  2. console.log('foo invoked - enable: ')
  3. console.log(enable)
  4. }
  5. foo()

需要注意的是传递多个参数是,带有默认值的参数一定要放在最后。

剩余参数

  1. function foo (…args){
  2. // 执行代码
  3. }
  4. foo(1, 2, 3, 4)

形参会以数组的形式,去接收从当前参数位置开始往后所有的形参,只能出现在参数的最后一位,而且只能使用一次。

扩展运算符

把固定数组内容“打散”到对应的参数。

  1. const arr = ['foo', 'bar', 'baz']
  2. //以前的方法
  3. console.log.apply(console,arr)
  4. //新方法
  5. console.log(…arr)

箭头函数

const inc = n => n + 1

箭头的左边是参数列表,右边是函数体。

箭头函数与this

  1. const person = {
  2. name = 'zce'
  3. //普通函数 this 指向调用时的对象
  4. sayHi: function (){
  5. console.log(`hi, my name is ${this.anme}`)//正常打印
  6. }
  7. //因为箭头函数中对this的处理是定义时,this的指向也就是person外层所指向的window
  8. //而window没有name属性,所以结果是 undefined
  9. sayHi () => {
  10. console.log(`hi, my name is ${this.name}`)//undefined
  11. },
  12. //setTimeout在全局作用域执行,this会丢失,指向window
  13. //需要用一个变量保存this
  14. sayHiAsync: function () {
  15. const _this = this
  16. setTimeout(function(){
  17. console.log(_this.name)
  18. },1000)
  19. //箭头函数不会丢失this
  20. sayHiAsync: function () {
  21. setTimeout(()=>{
  22. console.log(this.name)
  23. },1000)
  24. }
  25. }

总结

  1. 箭头函数中this指向定义是所在的对象,而不是调用时所在的对象
  2. 箭头函数不可以当作构造函数
  3. 箭头函数不可以使用arguments对象

Object


对象字面量增强

在 ES6 之前 Object 的属性必须是 key-value 形式,在 ES6 之后是可以用简写的形式来表达:

  1. const bar = '345'
  2. const obj = {
  3. foo: 123
  4. bar: bar
  5. methods: function(){
  6. console.log('1')
  7. }
  8. }
  9. 新方法
  10. const bar = '345'
  11. const obj = {
  12. foo: 123,
  13. bar,
  14. method1(){
  15. console.log('1')
  16. }
  17. }

对象扩展方法

Object.assign()
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,返回目标对象

  1. const source1 = {
  2. a: 123,
  3. b:123
  4. }
  5. const target = {
  6. a: 456,
  7. c: 456
  8. }
  9. const result = Object.assign(taeget, source1)
  10. console.log(target)//{a:123,c:456,b:123}

Object.is()
判断两个对象是否相等

  1. let obj1 = { // new Object()
  2. name: 'zhangsan',
  3. age: 34
  4. }
  5. let obj2 = { // new Object()
  6. name: 'zhangsan',
  7. age: 34
  8. }
  9. console.log(obj1 == obj2) // false
  10. console.log(Object.is(obj1, obj2)) // false
  11. let obj2 = obj1
  12. console.log(Object.is(obj1, obj2)) // true

Proxy


Object.defineProperty监视对象的读写
Proxy为对象设置访问代理器

Proxy基本语法

  1. let p = new Proxy(target, handler)

解释

参数 含义 必选
target 用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) Y
handler 一个对象,其属性是当执行一个操作时定义代理的行为和函数 Y

第一个参数target就是用来代理的“对象”,被代理之后它是不能直接被访问的,而handler就是实现代理的过程。

  1. const person = {
  2. name: 'zce',
  3. age: 20
  4. }
  5. const personProxy = new Proxy(person,{
  6. get (target, property){
  7. return property in target ? target[property] : 'default'
  8. },
  9. set(target, property, value){
  10. if(property === 'age'){
  11. if(!Number.isInteger(value)){
  12. throw new TypeError(`${value} is not an int`)
  13. }
  14. }
  15. }
  16. })

Proxy vs. Object.defineProperty()

defineProperty 只能监视属性的读写,Proxy能够监视到更多对象操作。

  1. const person = {
  2. name: 'zce',
  3. age: 20
  4. }
  5. const personProxy = new Proxy(person, {
  6. deleteProperty (target, property) {
  7. console.log('delete', property)
  8. delete target[property]
  9. }
  10. })
  11. delete personProxy.age


Proxy更好的支持数组对象的监视,重写数组的操作方法(通过自定义的方法去覆盖掉数组原生对象上的方法,以此来劫持对应事件方法调用过程)。**

使用Proxy对象监视数组

  1. const list = []
  2. const listProxy = new Proxy(list, {
  3. ser(target, property, value) {
  4. console.log('set', property, value)
  5. target[property] = value
  6. return true //表示设置成功
  7. }
  8. })
  9. listProxy.push(100)

Proxy是以非侵入的方式监管了对象的读写

补充:

handler方法 触发方式
get 读取某个属性
set 写入某个属性
has in操作符
deleteProperty delete 操作符
getPrototypeOf Object.getPrototypeOf()
setPrototypeOf Object.setPrototypeOf()
isExtensible Object.isExtensible()
preventExtensions Object.preventExtensions()
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor()
defineProperty Object.defineProperty()
ownKeys Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()
apply 调用一个函数
construct 用 new 调用一个函数

Reflect


Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。

设置目的

  • 将Object属于语言内部的方法放到Reflect上
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。
  • 统一提供一套用于操作对象的API

例如:

  1. const obj = {
  2. name: 'zce'
  3. age: 18
  4. }
  5. console.log('name' in obj)
  6. console.log(delete obj['age'])
  7. console.log(Object.keys(obj))
  8. // 判断是否包含一个值,删除元素和获取键值这些操作看起来不统一,新手难以学习
  9. console.log(Reflect.has(obj, 'name'))
  10. console.log(Reflect.deleteProperty(obj, 'age'))
  11. console.log(Reflect.ownKeys(obj))
  12. // Reflect方法就统一许多

注意事项

  • Reflect是一个静态类,不能通过new Reflect()去构建一个实例化对象
  • Reflect内部封装了一些列对对象的底层操作 13个
  • Reflect成员方法就是Proxy处理对象的默认实现 ```javascript const obj = { // … }

const proxy = new Proxy(obj, { get() })

  1. proxy里面没有定义get方法,但是能使用get方法,这是因为proxy默认调用Reflect成员方法。
  2. <a name="l8QZm"></a>
  3. ## Promise
  4. ---
  5. 一种更优的异步编程解决方案,解决了传统异步编程中回调函数嵌套过深的问题。<br />在JavaScript异步编程中有详细介绍。
  6. <a name="vriVG"></a>
  7. ## Class
  8. ---
  9. <a name="p9Ror"></a>
  10. ### 声明类
  11. ```javascript
  12. class Animal {
  13. constructor(type) {
  14. this.type = type
  15. }
  16. walk() {
  17. console.log( `I am walking` )
  18. }
  19. }
  20. let dog = new Animal('dog')
  21. let monkey = new Animal('monkey')

静态成员

实例方法 vs. 静态方法
实例方法:通过这个类型构造出来的实例对象去调用
静态方法:直接通过这个类本身去调用
需要注意的是,因为静态方法是挂载在类上面的,在静态方法内部它的this就不会指向某个实例对象,而是指向当前这个类

类的继承

  1. class Animal {
  2. constructor(type) {
  3. this.type = type
  4. }
  5. walk() {
  6. console.log( `I am walking` )
  7. }
  8. static eat() {
  9. console.log( `I am eating` )
  10. }
  11. }
  12. class Dog extends Animal {
  13. constructor () {
  14. super('dog')
  15. }
  16. run () {
  17. console.log('I can run')
  18. }
  19. }

Set


set内部成员不允许重复

基本语法

生成Set实例

  1. let s =new Set()

可以定义一个空的 Set 实例,也可以在实例化的同时传入默认的数据。

  1. let s = new Set([1, 2, 3, 4])

初始化的参数必须是可遍历的,可以是数组或者自定义遍历的数据结构。

添加数据

  1. s.add('hello')
  2. s.add('goodbye')
  3. //或者
  4. s.add('hello').add('goodbye')

Set数据结构不允许数据重复,所以添加重复的数据是无效的。

删除数据

  1. // 删除指定数据
  2. s.delete('hello') // true
  3. // 删除全部数据
  4. s.clear()

统计数据
Set可以快速进行统计数据,如数据是否存在、数据的总数

  1. // 判断是否包含数据项,返回 true 或 false
  2. s.has('hello') // true
  3. // 计算数据项总数
  4. s.size // 2

数组去重

  1. let arr = [1, 2, 3, 4, 2, 3]
  2. let s = new Set(arr)
  3. console.log(s)

合并去重

  1. let arr1 = [1, 2, 3, 4]
  2. let arr2 = [2, 3, 4, 5, 6]
  3. let s = new Set([...arr1, ...arr2])
  4. console.log(s)
  5. console.log([...s])
  6. console.log(Array.from(s))

交集

  1. let s1 = new Set(arr1)
  2. let s2 = new Set(arr2)
  3. let result = new Set(arr1.filter(item => s2.has(item)))
  4. console.log(Array.from(result))

差集

  1. let arr3 = new Set(arr1.filter(item => !s2.has(item)))
  2. let arr4 = new Set(arr2.filter(item => !s1.has(item)))
  3. console.log(arr3)
  4. console.log(arr4)
  5. console.log([...arr3, ...arr4])

遍历方式

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员
  • for…of:可以直接遍历每个成员 ```javascript console.log(s.keys()) // SetIterator {“hello”, “goodbye”} console.log(s.values()) // SetIterator {“hello”, “goodbye”} console.log(s.entries()) // SetIterator {“hello” => “hello”, “goodbye” => “goodbye”} s.forEach(item => { console.log(item) // hello // goodbye })

for (let item of s) { console.log(item) }

for (let item of s.keys()) { console.log(item) }

for (let item of s.values()) { console.log(item) }

for (let item of s.entries()) { console.log(item[0], item[1]) }

  1. <a name="MScVN"></a>
  2. ## Map
  3. ---
  4. ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
  5. > 语法上可以把键值设置位对象,布尔值,数字。但如果我们的键值不是字符串,对象内部会使用toString()结构作为键,如果使用对象作为键,不管对象如何不同,toString后结果都为'[object Object]'。
  6. <a name="9Vym5"></a>
  7. ### 基本语法
  8. **实例化**
  9. ```javascript
  10. let map = new Map([iterable])

Iterable 可以是一个数组或者其他 iterable 对象,其元素为键值对(两个元素的数组,例如: [[ 1, ‘one’ ], [ 2, ‘two’ ]])。 每个键值对都会添加到新的 Map。null 会被当做 undefined。

添加数据

  1. let keyObj = {}
  2. let keyFunc = function() {}
  3. let keyString = 'a string'
  4. // 添加键
  5. map.set(keyString, "和键'a string'关联的值")
  6. map.set(keyObj, '和键keyObj关联的值')
  7. map.set(keyFunc, '和键keyFunc关联的值')

删除数据

  1. // 删除指定的数据
  2. map.delete(keyObj)
  3. // 删除所有数据
  4. map.clear()

统计数据

  1. // 统计所有 key-value 的总数
  2. console.log(map.size) //2
  3. // 判断是否有 key-value
  4. console.log(map.has(keyObj)) // true

查询数据

  • ge()方法返回某个Map对象中的一个指定元素
    1. console.log(map.get(keyObj)) // 和键keyObj关联的值

遍历方式

  • keys() 返回一个新的 Iterator 对象。它包含按照顺序插入 Map 对象中每个元素的 key 值
  • values() 方法返回一个新的 Iterator 对象。它包含按顺序插入Map对象中每个元素的 value 值
  • entries() 方法返回一个新的包含 [key, value] 对的 Iterator ? 对象,返回的迭代器的迭代顺序与 Map 对象的插入顺序相同
  • forEach() 方法将会以插入顺序对 Map 对象中的每一个键值对执行一次参数中提供的回调函数
  • for…of 可以直接遍历每个成员 ```javascript map.forEach((value, key) => console.log(value, key))

for (let [key, value] of map) { console.log(key, value) }

for (let key of map.keys()) { console.log(key) }

for (let value of map.values()) { console.log(value) }

for (let [key, value] of map.entries()) { console.log(key, value) }

  1. <a name="KB1Ql"></a>
  2. ## Symbol
  3. ---
  4. 一种全新的原始数据类型,表示一个独一无二的值。可以传入字符串作为描述文本,使用Symbol()作为属性名。
  5. <a name="hXAKi"></a>
  6. ### 声明方式
  7. ```javascript
  8. let s = Symbol()
  9. typeof s
  10. // "symbol"

变量s就是一个独一无二的值。typeof的结果说明s是 Symbol 数据类型。
既然是独一无二的,那么两个Symbol()就一定是不相等的:

  1. let s1 = Symbol()
  2. let s2 = Symbol()
  3. console.log(s1)
  4. console.log(s2)
  5. console.log(s1 === s2) // false

Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始数据类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类型于字符串的数据类型。

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

  1. let s1 = Symbol('foo')
  2. let s2 = Symbol('foo')
  3. console.log(s1)
  4. console.log(s2)
  5. console.log(s1 === s2) // false

Symbol.for()

Symbol.for() 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

  1. let s1 = Symbol.for('foo')
  2. let s2 = Symbol.for('foo')
  3. console.log(s1 === s2) // true

Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。

这个方法维护的是字符串和Symbol的关系,如果传入的不是字符串,这个方法会自动把它转化成字符串。

Symbol.keyFor()

Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。

  1. const s1 = Symbol('foo')
  2. console.log(Symbol.keyFor(s1)) // undefined
  3. const s2 = Symbol.for('foo')
  4. console.log(Symbol.keyFor(s2)) // foo

作为属性名

由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

可以使用Symbol模拟实现私有成员。

  1. const name = Symbol()
  2. const person = {
  3. [name]:'zce',
  4. say() {
  5. console.log(this[name])
  6. }
  7. }

由于外部无法创建一个完全相同的Symbol,所以无法使用person[Symbol()]来拿到这个值,就实现了私有成员

自定义字符串字符标签

  1. const obj = {
  2. [Symbol.toStringTag]: 'XObject'
  3. }
  4. console.log(obj.toString())
  5. //[object XObject]

为对象实现迭代器时会经常用到

遍历属性

使用Symbol作为属性名时,无法使用for in循环访问到Symbol属性值,也不能使用Object.keys()访问到
使用JSON.stringify时,Symbol也会被忽略掉,所以Symbol适合作为对象的私有属性。

可以使用Object.getOwnPropertySymbols()获取到Symbol类型的属性名,不过只能获取到Symbol类型的属性名。

for…of循环


for 适合遍历普通数组,for in 适合遍历键值对。ES5一些对象的遍历方法,这些遍历方式都有一定的局限性,引入了全新的遍历方式for…of循环,作为遍历所有数据结构的统一方式。

  1. const arr = [100, 200, 300, 400]
  2. for(const item of arr) {
  3. console.log(item)
  4. }
  • 取代forEach循环

    1. arr.forEach(item => {
    2. console.log(item)
    3. })

    相比于forEach方法,for…of可以使用break跳出循环。

    1. for(const item of arr){
    2. console.log(item)
    3. if(item > 100){
    4. break
    5. }
    6. }

    arr.forEach()不能跳出循环。以前为了解决这个问题,使用arr.some()和arr.every()方法,arr.some()返回true,arr.every()返回false,用来中止遍历。

  • 伪数组也可以使用for…of遍历

  • for…of可以遍历set,与普通数组没有区别

**

  • 遍历Map得到包含键值对字符串的数组,可以配合数组的结构语法,直接拿到数组的键和值

    1. for(const [key, value] of m) {
    2. console.log(key, value)
    3. }
  • for…of遍历对象

    1. const obj = {foo: 123, bar: 456}
    2. for(const item of obj){
    3. console.log(item)
    4. }

    打印出:TypeError: obj is not iterable。Iterator章节解答这个疑惑。

Iterator


for…of循环是一种数据结构统一遍历方式,ES中能够表示有结构的数据类型越来越多,为了个各种各样的数据结构提供统一遍历方式,ES2015提供了Iterable接口,实现Iterable接口就是for…of的前提。

所有可以直接被for…of遍历的数据类型都要实现Iterable这个接口,它的内部都需要挂载一个Symblo.Iterator的方法,这个方法会返回next()方法,我们不断遍历这个方法就可以实现遍历。

  1. cosnt set = new Set(['foo','bar','baz'])
  2. const iterator = set[Symbol.iterator]()
  3. console.log(iterator.next())
  4. console.log(iterator.next())
  5. console.log(iterator.next())
  6. console.log(iterator.next())
  7. //set数据就会被遍历

实现可迭代接口

之所以说for…of循环可以作为遍历所有数据结构的统一方式,因为它内部就是调用被遍历对象的iterator方法得到一个迭代器,从而去遍历内部所有的数组,换句话说,只要我们的对象实现的Iterable接口,就可以使用for…of遍历。

  1. const obj = {
  2. store:['foo','bar','baz']
  3. [Symbol.iterator]:function(){
  4. let index = 0
  5. const self = this
  6. return {
  7. next: function(){
  8. const result = return{
  9. value:self.store[index]
  10. done:index>=self.store.length
  11. }
  12. index++
  13. return result
  14. }
  15. }
  16. }
  17. }
  18. //Iterable可迭代接口-->Iterator迭代器接口-->IterationResult迭代结果接口

迭代器模式

场景:你我协同开发一个任务清单应用

  1. //我的代码------------------
  2. const todos = {
  3. life:['吃饭','睡觉','打豆豆']
  4. learn:['语文','数学','外语']
  5. work:['喝茶']
  6. each: function(callback){
  7. cosnt all = [].concat(this.lif,this.learn,this.work)
  8. for(const item of all){
  9. callback(item)
  10. }
  11. }
  12. [Symbol.iterator]: function(){
  13. const all = […this.lif, this.learn, this.work]
  14. let index = 0
  15. return {
  16. next: funtion () {
  17. return {
  18. value: all[index].
  19. done: index++ >= all.lenth
  20. }
  21. }
  22. }
  23. }
  24. }
  25. //你的代码------------------
  26. todos.each(function (item) {
  27. console.log(item)
  28. })
  29. for(const item of todos){
  30. console.log(item)
  31. }

对外提供统一遍历接口,让外部不再关心数据内部结构是什么样的。

Generator


避免异步编程中回调嵌套过深,提供更好的异步编程解决方案。

  1. functioon * foo () {
  2. console.log('zce')
  3. return 100
  4. }
  5. const result = foo()
  6. console.log(result) //打印一个Generator对象
  7. function * foo () {
  8. console.log('111')
  9. yield 100
  10. console.log('222')
  11. yield 200
  12. console.log('333')
  13. yield 300
  14. }

调用生成器函数会一个返回Generator对象,调用这个对象的next()方法才会执行,在执行过程中。遇到yield关键字就会停下来,yield的值会作为next的结果返回回来,继续调用next会继续执行

生成器应用

  1. //发号器
  2. function * createIDMaker () {
  3. let id = 1
  4. while (true) {
  5. yield id++
  6. }
  7. }
  8. const idMaker = createIDMaker()
  9. idMaker.next()
  10. //使用Generator函数实现iterator方法
  11. const todos = {
  12. life:['吃饭','睡觉','打豆豆'],
  13. learn:['语文','数学','外语'],
  14. work:['喝茶'],
  15. [Symbol.iterator]:function * () {
  16. const all = […this.life, this.learn, this.work]
  17. for(const item of all){
  18. yield item
  19. }
  20. }
  21. }
  22. for (const item of todos) {
  23. console.log(item)
  24. }

ES Modules


语法层面的模块化标准。

ECMAScript 2016


Array.prototype.includes

  1. const arr = ['foo', 1, NaN, false]
  2. //之前的方法
  3. console.log(aee.indexOf('foo'))
  4. //会返回数组的下标
  5. //缺陷:不能查找数组里面的NaN
  6. console.log(aee.includes('foo'))
  7. //返回布尔值
  8. //可以查找NaN

指数运算符

  1. //以前的方法
  2. console.log(Math.pow(2,10))
  3. //现在的方法
  4. console.log(2 ** 10)

ECMAScript 2017


object.values

返回对象所有的值的数组,Object.keys返回对象所有的键的数组。

Object.entries

  1. //返回对象中所有的键值对的数组
  2. for (const [key, value] of Object.entries(obj)){
  3. console.log(key, value)
  4. }
  5. //可以使用for…of遍历Object.entires返回的数组
  6. console.log(new Map(Object.entries(obj)))
  7. //Map就需要这种格式的数组,使用这种方法就可以将对象转换成Map类型的对象

Object.getOwnPropertyDescriptors

ES5之后,就可以给对象添加get() set()属性。

  1. const p1 = {
  2. firstName: 'lei',
  3. lastName:'Wang',
  4. get fullName(){
  5. return this.firstName + '' + this.lastName
  6. }
  7. }
  8. console p2 = Object.assign({}, p1)
  9. p2.firstNmae = 'zce'
  10. console.log(p2)
  11. //拿到的结果是p1的firztName,这是因为assign在复制时,他只是把fullNma当作一个普通的属性去复制了
  12. //解决方法
  13. const descript = Object.getOwnPropertyDescriptors(p1)
  14. const p2 = Object.defineProperties({}, descriptors)
  15. p2.firstName = 'zce'
  16. console.log(p2.fullName)

String.prototype.padStart / String.prototype.padEnd

字符串的填充方法

  1. const books = {
  2. html: 5,
  3. css: 15,
  4. javascript: 128
  5. }
  6. for(const [name, count] of Object.entries(books)) {
  7. console.log(`${name.padEnd(16, '-')}|${count.tostring().padStrat(3, '0')}`)
  8. }
  9. //html------------|005
  10. //css--------------|016
  11. //javascript-----|128
  12. //去填充目标字符串的开始或结束位置

尾逗号Trailing commas

ES8 允许函数的最后一个参数有尾逗号(Trailing comma)。
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。

async/await

async 和 await 是一种更加优雅的异步编程解决方案,是Promise 的拓展。
javascript异步编程中有详细介绍。