复习回顾 ECMAScript 标准

复习回顾 ECMAScript 标准

ECMAScript 概述

内容比较长,可能有纰漏,以下是正文

通常情况下,我们会有ECMAScript看做是JavaScript的标准化规范,但是实际上 JavaScript 是 ECMAScript 的扩展语言
ECMAScript 只提供了最基本的语法,它只是停留在语言层面,规范如果定义变量,如何去写循环等等。并不能直接用来去完成实际应用中的功能开发。
JavaScript 语言本身指的就是 ECMAScript。
JavaScript 实现了ECMAScript语言的标准, 并且在这之上做了其他扩展。使得 js 可以在浏览器平台下 可以操作dom ;在node 平台下可以读写文件 等等
从 2015年开始,ECMAScript 保持每年一个大版本的更新。

ECMAScript 2015

概述

其实就是常听到的ES6,之前的版本是ES5.1,ES2015 改变了命名方式但是很多人习惯性称呼ES2015为ES6。
也可以把 ES6 去泛指所有的 ESCMAScript 2015之后的新标准
比如经常听到这种说法

“使用ES6的async/await” 而实际上,这是ES2017的内容

ES2015 的新标准主要体现在以下四个方面:

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

新标准

let 与块级作用域

作用域: 指某个成员能够起作用的范围
在ES2015之前 js 只有两种作用域:全局作用域和函数作用域
在ES2015中,新增了块级作用域

if (true) {
	console.log('hello')
}

for (var i = 0; i<10; i++) {
	console.log('hello')
}

在if 和 for 的{ } 中都会产生
块 指的就是一对花括号包裹起来的范围
通过 let 声明的成员只会在块级作用域中生效
根据 let 的特点,我们可以在在循环绑定事件的时候 使用let,这样可以在事件处理函数中获得正确的索引

// 伪代码
 var elements = [{元素1}, {元素2}, {元素3}]
 for (let i = 0; i < elements.length; i++) {
   elements[i].onclick = function () {
     console.log(i)
   }
 }
 elements[0].onclick()

let 修复了变量声明提升现象。
关于let的提升问题存在一些争议

let x = 'global'
{
  console.log(x) // Uncaught ReferenceError: x is not defined
  let x = 1
}

从上面代码看 似乎是没有提升。

MDN 关于 let 是否存在提升的章节,被编辑了两次,第一次说存在提升,第二次说不存在提升(参考 2017 年 3 月 10 号的变更记录)。也就是说 MDN 的维护者都在这个问题上产生过分歧,更何况我们了。
ES 文档里出现了「var/let hoisting」字样。
鉴于此,我认为应该尊重 ES 文档,认为 let 确实存在提升。只不过由于暂时死区的限制,你不能在 let x 之前使用 let

我个人的理解是,js中变量的创建过程可以分为三个阶段: 创建create、初始化initialize 、赋值assign

var val = 123
// 定义一个变量 分成三个步骤
// 1. 创建val变量
// 2. 初始化val的值为 undefined
// 3. 对val进行赋值 val = 123

在var 中 对创建 和 初始化进行了提升,而let 只对创建做了提升。

const

常量是块级范围的,非常类似用 let 语句定义的变量。

  • 常量声明过后不允许重新赋值
  • 常量在声明时就必须同时进行赋值
  • 常量对于复杂类型只是要求内层指向不能被修改,如果是内部成员修改是没有问题的
const obj = {}
obj.name = 'zs' // no problem
obj = {} // no !

数组和对象的解构

解构 (destructuring)

数组解构赋值在绑定值时根据属性下标和进行匹配的。赋值操作符(等号)两边的解构要一致才能正确赋值,如果匹配不成功则取值为undefined。

  • 基本用法
 const arr = [100, 200, 300]

 const foo = arr[0]
 const bar = arr[1]
 const baz = arr[2]
 console.log(foo, bar, baz) // 100 200 300
  • 跳跃式的赋值,即获取某一个具体位置的值
 const [, , baz] = arr
 console.log(baz) // 300 
  • 获取数组剩下所有的元素, 通过…来获取,但是只能写在最后一个元素的位置上
const [foo, ...rest] = arr
console.log(rest) // [200,300]
  • 设置默认值,对于没有匹配到 或者匹配到的属性值为undefined的变量可以指定默认值
const [foo, bar, baz = 123, more = 'default value'] = arr
console.log(bar, more) // 200 default value

对象解构是根据属性名来进行提取,而不是位置

  • 基本用法
const obj = { name: 'zs', age: 18 }

const { name } = obj // 提取name属性值 放到一个name变量中
console.log(name) // 'zs'
  • 解构属性重命名
const { name: objName } = obj
console.log(objName)
  • 添加默认值
 const { name: objName = 'jack' } = obj
 console.log(objName)

模板字符串

ES2015中提出可以使用反引号来创建模板字符串

`string`

如果直接使用跟普通字符串没有什么区别,但是它比普通字符串多了很多新功能

  • 支持换行,多行字符串
  • 插入表达式
var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
  • 标签模板
const name = 'tom'
const gender = false

function myTagFunc (strings, name, gender) {
	// 这里的strings 就是模板字符串中静态的字符串组成的数组
	console.log(strings) // ['hey',' is a']
	const sex = gender ? 'man' : 'woman'
	// 标签函数的返回值就是模板字符串的返回值
	return strings[0] + name + strings[1] + sex + strings[2]
}
}
const result = myTagFunc`hey,${name} is a ${gender}`

字符串扩展方法

includes()

判断字符串中是否包含参数

startsWith()

判断字符串是否以参数开头

endsWith()

判断字符串是否以参数结尾

const message = 'Error: foo is not defined.'

console.log(
  message.startsWith('Error')
  message.endsWith('.')
  message.includes('foo')
)

参数默认值,剩余参数

参数默认值 (parmeter-defaults)

函数默认参数允许在没有值或undefined被传入时使用默认形参,带默认值的参数必须写在最后一个

// 默认参数一定是在形参列表的最后
function foo (enable = true) {
  console.log('foo invoked - enable: ')
  console.log(enable)
}

剩余参数(Rest Parameters)

剩余参数语法允许我们将一个不定数量的参数表示为一个数组

function foo (first, ...args) {
  console.log(args) // [1,2,3,4]
}

foo(1, 2, 3, 4)

展开数组(Spread)

… 修饰符 可以用来展开数组

const arr = ['foo', 'bar', 'baz']
console.log(...arr) // foo,bar,baz

箭头函数 (Arrow Functions)

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数

  • 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
  • 当箭头函数只有一个参数时,可以省略参数的圆括号
  • 当箭头函数的函数体只有一个 return 语句时,可以省略 return 关键字和方法体的花括号

对象字面量增强 (Enhanced object literals)

  • 属性名与变量名相同,可只写属性名
  • 成员是方法,可以省略function
  • 通过[] 让表达式结果作为属性名
// 对象字面量

const bar = '345'

const obj = {
  foo: 123,
  // bar: bar
  // 属性名与变量名相同,可以省略 : bar
  bar,
  // method1: function () {
  //   console.log('method111')
  // }
  // 方法可以省略 : function
  method1 () {
    console.log('method111')
    // 这种方法就是普通的函数,同样影响 this 指向。
    console.log(this)
  },
  // Math.random(): 123 // 不允许
  // 通过 [] 让表达式的结果作为属性名
  [bar]: 123
}

// obj[Math.random()] = 123

console.log(obj)
obj.method1()

Object.assign

Object.assign(target, …sources) 方法,可以将多个源对象中的属性复制到一个目标对象中,返回目标对象。
Object.assign是浅拷贝

function func (obj) {
  // obj.name = 'func obj'
  // console.log(obj)

  const funcObj = Object.assign({}, obj)
  funcObj.name = 'func obj'
  console.log(funcObj)
}

const obj = { name: 'global obj' }

func(obj)
console.log(obj)

Object.is

Object.is() 方法判断两个值是否为同一个值。

console.log(
  // 0 == false              // => true
  // 0 === false             // => false
  // +0 === -0               // => true
  // NaN === NaN             // => false
  // Object.is(+0, -0)       // => false
  // Object.is(NaN, NaN)     // => true
)

正常来讲……用不到 === 足矣

Proxy,对比defineProperty

Object.defineProperty 方法可以用来监听对象属性的读写,在Vue3之前的版本中 就是通过它来实现数据响应,
在ES2015中新增了Proxy 专门用来为对象设置访问代理器。
什么叫代理,简单来讲就是对象的访问交给一个中间人去完成 不用亲自去操作
相比于defineProperty,Proxy功能更加强大也更方便。

  • Proxy基本用法
const person = {
  name: 'zce',
  age: 20
}

const personProxy = new Proxy(person, {
  // 监视属性读取
  // target 目标对象 这里指的就是person
  // property 要访问的目标对象的属性
  get (target, property) {
    return property in target ? target[property] : 'default'
    // console.log(target, property)
    // return 100
  },
  // 监视属性设置
  // target 目标对象 这里指的就是person
  // property 要访问的目标对象的属性
  // value是新值
  set (target, property, value) {
    if (property === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError(`${value} is not an int`)
      }
    }

    target[property] = value
    // console.log(target, property, value)
  }
})

personProxy.age = 100

personProxy.gender = true

console.log(personProxy.name)
  • 对比Proxy与defineProperty
    1. Proxy 可以监视读写以外的操作
      		const person = {
      		  name: 'zce',
      		  age: 20
      		}
      		
      		const personProxy = new Proxy(person, {
      		  deleteProperty (target, property) {
      		    console.log('delete', property)
      		    delete target[property]
      		  }
      		})
      		
      		delete personProxy.age
      		console.log(person)
      
    2. Proxy 可以很方便的监视数组操作
      	const list = []
      
      	const listProxy = new Proxy(list, {
      	  set (target, property, value) {
      	    console.log('set', property, value)
      	    target[property] = value
      	    return true // 表示设置成功
      	  }
      	})
      	
      	listProxy.push(100)
      	listProxy.push(100)
      
    3. Proxy 不需要侵入对象,方式更为合理
      	const person = {}
      	// 数据劫持需要设置一个内部属性_name来转存数据
      	Object.defineProperty(person, 'name', {
      	  get () {
      	    console.log('name 被访问')
      	    return person._name
      	  },
      	  set (value) {
      	    console.log('name 被设置')
      	    person._name = value
      	  }
      	})
      	Object.defineProperty(person, 'age', {
      	  get () {
      	    console.log('age 被访问')
      	    return person._age
      	  },
      	  set (value) {
      	    console.log('age 被设置')
      	    person._age = value
      	  }
      	})
      	
      	person.name = 'jack'
      	
      	console.log(person.name)
      	
      	// Proxy 方式更为合理
      	const person2 = {
      	  name: 'zce',
      	  age: 20
      	}
      	
      	const personProxy = new Proxy(person2, {
      	  get (target, property) {
      	    console.log('get', property)
      	    return target[property]
      	  },
      	  set (target, property, value) {
      	    console.log('set', property, value)
      	    target[property] = value
      	  }
      	})
      	
      	personProxy.name = 'jack'
      	
      	console.log(personProxy.name)
      

Reflect

Reflect 是一个内置的对象,内部封装了一系列对对象的底层操作。Reflect不是一个函数对象,因此它是不可构造的。
Reflect 成员方法就是Proxy处理对象的默认实现 (意思就是如果Proxy没有实现get 相当于默认调用了 Reflect.get)

  • Reflect.apply(target, thisArgument, argumentsList) 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。
  • Reflect.construct(target, argumentsList[, newTarget]) 对构造函数进行 new 操作,相当于执行 new target(…args)。
  • Reflect.defineProperty(target, propertyKey, attributes) 和 Object.defineProperty() 类似。如果设置成功就会返回 true
  • Reflect.deleteProperty(target, propertyKey) 作为函数的delete操作符,相当于执行 delete target[name]。
  • Reflect.get(target, propertyKey[, receiver]) 获取对象身上某个属性的值,类似于 target[name]。
  • Reflect.getOwnPropertyDescriptor(target, propertyKey) 类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符, 否则返回
    undefined.
  • Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf()。
  • Reflect.has(target, propertyKey) 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
  • Reflect.isExtensible(target) 类似于 Object.isExtensible().
  • Reflect.ownKeys(target) 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable影响).
  • Reflect.preventExtensions(target) 类似于 Object.preventExtensions()。返回一个Boolean。
  • Reflect.set(target, propertyKey, value[, receiver]) 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
  • Reflect.setPrototypeOf(target, prototype) 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。

Reflect 我觉得它的意义在于统一提供了一套用于操作对象的API

const obj = {
  name: 'zce',
  age: 18
}

// console.log('name' in obj)
// console.log(delete obj['age'])
// console.log(Object.keys(obj))

console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))

Promise

这里不再赘述Promise了,可以参考我的另一篇博客 JavaScript 异步编程

class 类

替代之前的构造函数创建类的方式

  • 基本用法
class Person {
  // 构造器 
  constructor (name) {
  // 实例成员
    this.name = name
  }
  // 原型方法
  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}

const p = new Person('tom')
p.say()
  • extends 继承

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}

class Student extends Person {
  constructor (name, number) {
    super(name) // 父类构造函数 , 要在this之前使用
    this.number = number
  }

  hello () {
    super.say() // 调用父类成员
    console.log(`my school number is ${this.number}`)
  }
}

const s = new Student('jack', '100')
s.hello()

static 静态方法

只能通过类名访问

// static 方法

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }

  static create (name) {
    return new Person(name)
  }
}

const tom = Person.create('tom')
tom.say()

set

set 是ES2015提供的一种全新的数据结构,可以理解为集合,与数组非常类似。
与数组不同的是,set 中的值是不允许重复的,每个值都是唯一的

// Set 数据结构
// 创建集合实例
const s = new Set()
// 通过add方法新增成员,并且add返回集合实例 所以可以链式调用
s.add(1).add(2).add(3).add(4).add(2)

  • 数组去重
// 数组去重
const arr = [1, 2, 1, 3, 4, 1]

// const result = Array.from(new Set(arr))
const result = [...new Set(arr)]

console.log(result)
  • 循环遍历
s.forEach(i => console.log(i))
// 也可以使用ES2015中的 for…of 来遍历
for (let i of s) {
  console.log(i)
}
  • 常用的实例方法

Set.prototype.add(value)
在Set对象尾部添加一个元素。返回该Set对象
Set.prototype.clear()
移除Set对象内的所有元素。
Set.prototype.delete(value)
移除Set中与这个值相等的元素,返回Set.prototype.has(value)在这个操作前会返回的值(即如果该元素存在,返回true,否则返回false)。Set.prototype.has(value)在此后会返回false。
Set.prototype.has(value)
返回一个布尔值,表示该值在Set中存在与否

console.log(s.has(100))
console.log(s.delete(3))
console.log(s)
s.clear()

Map

Map数据结构与对象非常类似,本质上它们都是键值对集合,但是对象结构中的key只能是字符串类型
虽然我们平时在编码时,可能会把布尔值,对象作为一个对象的键,虽然它们本身不是字符串 但是对象内部会把这些属性toString,比如

const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'

console.log(Object.keys(obj))  // ['123','true','[object Object]']
console.log(obj['[object Object]']) // value 
  • map 可以用来映射两个任意类型数据之间的对应关系,算是真正的集合
const m = new Map()

const tom = { name: 'tom' }

m.set(tom, 90)

console.log(m)  // Map { { name: 'tom' } => 90 }  这里key就是tom这个对象  value就是90

console.log(m.get(tom)) // 90
  • 常见的方法

Map.prototype.clear()
移除Map对象的所有键/值对 。
Map.prototype.delete(key)
如果 Map 对象中存在该元素,则移除它并返回 true;否则如果该元素不存在则返回 false。随后调用 Map.prototype.has(key) 将返回 false 。
Map.prototype.get(key)
返回键对应的值,如果不存在,则返回undefined。
Map.prototype.has(key)
返回一个布尔值,表示Map实例是否包含键对应的值。

  • 循环遍历
m.forEach((value, key) => {
  console.log(value, key)
})
  • Map类型的构造函数可以将一个二维键值对数组转换成一个Map对象
console.log(new Map([['uname':'zs'],['age':12]]))

Symbol

ES2015 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // symbol

// 两个 Symbol 永远不会相等

console.log(
  Symbol() === Symbol() // false
)

为了区分到底是哪一个symbol 我们可以个它传入一个描述字符串

// Symbol 描述文本

console.log(Symbol('foo'))
console.log(Symbol('bar'))
console.log(Symbol('baz'))

如果要获取一个添加了描述字符串的symbol我们需要通过 symbol的for方法
Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境*搜索
只接受string类型参数 如果传入的不是string会自动转为string

 
 const s1 = Symbol.for('foo')
 const s2 = Symbol.for('foo')
 console.log(s1 === s2) // true
 const yellow = Symbol("Yellow");
 const yellow1 = Symbol.for("Yellow");
 yellow === yellow1;      // false

从 ES2015开始,对象可以开始使用symbol作为属性key

const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) // { [Symbol()]: '123', [Symbol()]: '456' }

可以使用Symbol为对象添加私有成员

// Symbol 模拟实现私有成员

// a.js ======================================

const static_name = Symbol()
const person = {
  [static_name]: 'zce',
  say () {
    console.log(this[static_name])
  }
}
// 只对外暴露 person

// b.js =======================================

// 由于无法创建出一样的 Symbol 值,
// 所以无法直接访问到 person 中的「私有」成员
// person[Symbol()]
person.say()

对象中的Symbol属性名 无法被for…in、Object.key()、JSON.stringify 这些手段获取到
如果要获取对象中的Symbol属性 可以使用 Object.getOwnPropertySymbols() 方法来获取

除了自己创建的symbol,JavaScript还内建了一些在ECMAScript 5 之前没有暴露给开发者的symbol,它们代表了内部语言行为,作为内部方法的标识,这些标识符可以让自定义对象去实现一些js内置的接口。
比如: Symbol.iterator Symbol.hasInstance 等等

for…of循环

可以作为遍历所有数据结构的统一方式,只要明白它的工作原理( 迭代器 )就可以使用它去遍历任何一种自定义的数据结构

  • 基本使用
const arr = [100, 200, 300, 400]
// item 是数组中的每一个成员
for (const item of arr) {
  console.log(item)
}
  • for…of 循环可以替代 数组对象的 forEach 方法
arr.forEach(item => {
  console.log(item)
})
// ==================================
for (const item of arr) {
  console.log(item)
  // 使用for of 可以配合break终止循环
  if (item > 100) {
    break
  }
}
  • for…of 遍历set 和 map
//遍历 Set 与遍历数组相同

const s = new Set(['foo', 'bar'])

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

//遍历 Map 可以配合数组结构语法,直接获取键值

const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
// 遍历map 得到的是[key, value]结构的数组
for (const [key, value] of m) {
  console.log(key, value)
}
  • 普通对象不能被 for…of 遍历
const obj = { foo: 123, bar: 456 }
for (const item of obj) {
  console.log(item)
}

可迭代接口

因为for…of 不能直接遍历普通对象,所以我们需要明白它遍历的原理,也就是可迭代的。
ES2015 提供了 Iterable 接口,只要数据结构实现了Iterable接口 就可以被 for of 进行遍历

  • 所有能够被for…of遍历数据类型都要实现 Iterable 这个接口 (内部需要挂载一个Iterable方法) 即 Symbol.Iterable
  • 这个方法需要返回一个带有next() 方法的对象,不断调用next就可以实现对内部数据的不断遍历

我们可以利用现有的可以直接被for of 遍历的数据结构 来证明这一点

const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()

console.log(iterator.next()) //{ value: 'foo', done: false } 
console.log(iterator.next()) //{ value: 'bar', done: false }
console.log(iterator.next()) //{ value: 'baz', done: false }
console.log(iterator.next()) //{ value: undefined, done: true }
console.log(iterator.next()) //{ value: undefined, done: true }

我们可以在set的原型上找到 Symbol.iterator
复习回顾 ECMAScript 标准
迭代器设计模式

const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],

  // 提供统一遍历访问接口
  each: function (callback) {
    const all = [].concat(this.life, this.learn, this.work)
    for (const item of all) {
      callback(item)
    }
  },

  // 提供迭代器(ES2015 统一遍历访问接口)
  [Symbol.iterator]: function () {
    const all = [...this.life, ...this.learn, ...this.work]
    let index = 0
    return {
      next: function () {
        return {
          value: all[index],
          done: index++ >= all.length
        }
      }
    }
  }
}

todos.each(function (item) {
  console.log(item)
})

console.log('-------------------------------')

for (const item of todos) {
  console.log(item)
}

生成器(Generator)

  • 生成器函数会返回一个生成器对象,
  • 当我们调用这个生成器对象的next方法 才会开始直接函数体,
  • 在执行的过程中一旦遇到了yiel关键词 函数的执行就会被暂停下来,
  • yield 后面的值会作为 next方法的返回值的结果 { value:yield后面的值, done:false/true },
  • 继续调用next就会从暂停的位置 继续执行
function * foo () {
  console.log('1111')
  yield 100
  console.log('2222')
  yield 200
  console.log('3333')
  yield 300
}

const generator = foo()

console.log(generator.next()) // 第一次调用,函数体开始执行,遇到第一个 yield 暂停
console.log(generator.next()) // 第二次调用,从暂停位置继续,直到遇到下一个 yield 再次暂停
console.log(generator.next()) // 。。。
console.log(generator.next()) // 第四次调用,已经没有需要执行的内容了,所以直接得到 undefined

ES2016

Array.prototype.includes

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

const arr = ['foo', 1, NaN, false]

// 直接返回是否存在指定元素
console.log(arr.includes('foo'))
// 能够查找 NaN
console.log(arr.includes(NaN))

指数运算符

console.log(Math.pow(2, 10))

console.log(2 ** 10)

ES2017

Object.values

Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同

Object.entries

Object.entries()方法 可以以数组的形式返回对象中所有的键值对

const obj = {
  foo: 'value1',
  bar: 'value2'
}
console.log(Object.entries(obj))
// 可以配合for of 来遍历对象 
for (const [key, value] of Object.entries(obj)) {
  console.log(key, value)
}
// 因为Map类型的构造函数可以将一个二维键值对数组转换成一个Map对象 所以可以使用entries把对象转化为Map对象
console.log(new Map(Object.entries(obj)))

Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符(获取一个对象中属性的完整描述信息)
主要是为了解决 Object.assign() 无法正确拷贝 get 属性和 set 属性的问题

const p1 = {
  firstName: 'Jim',
  lastName: 'Green',
  get fullName () {
    return this.firstName + ' ' + this.lastName
  }
}

// console.log(p1.fullName) 

// const p2 = Object.assign({}, p1)
// p2.firstName = 'LiLei'
// console.log(p2)

const descriptors = Object.getOwnPropertyDescriptors(p1)
// console.log(descriptors)
const p2 = Object.defineProperties({}, descriptors)
p2.firstName = 'LiLei'
console.log(p2.fullName)

String.prototype.padStart / String.prototype.padEnd

padStart() 方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充
padEnd() 方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充

const books = {
  html: 5,
  css: 16,
  javascript: 128
}


for (const [name, count] of Object.entries(books)) {
  console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
}

html------------|005
css-------------|016
javascript------|128
上一篇:【从头学前端】01-什么是JavaScript


下一篇:JavaScript之ES6快速入门