ECMAScript新特性

let与块级作用域

全局污染

for (var i = 0; i < 3; i++) {
  for (var i = 0; i < 3; i++) {
    console.log(i)
  }
}

输出结果
0 1 2

以上代码只会输出3次,因为内层循环执行完毕之后,i的值已经是3了,因为使用var声明的变量是全局作用域,所以外层循环不满足条件,不会执行。

使用let关键可以产生块级作用域

for (let i = 0; i < 3; i++) {
  for (let i = 0; i < 3; i++) {
    console.log(i)
  }
}

以上代码可以按预期输出9次

var全局污染demo2

var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
  const element = elements[i];
  element.onClick = function () {
    console.log(i)
  }
}
elements[0].onClick()
elements[1].onClick()
elements[2].onClick()

以上代码无论调用哪一个元素的onClick方法,都会打印3,原因跟上面一样,使用var声明的i变量为全局变量,循环执行完成之后,i的值已经是3了。
可以使用闭包解决这个问题

var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
  const element = elements[i];
  element.onClick = (function (i) {
    return function() {
      console.log(i)
    }
  })(i)
}
elements[0].onClick()
elements[1].onClick()
elements[2].onClick()

使用let解决此问题

var elements = [{}, {}, {}]
for (let i = 0; i < elements.length; i++) {
  const element = elements[i];
  element.onClick = function () {
    console.log(i)
  }
}
elements[0].onClick()
elements[1].onClick()
elements[2].onClick()

for循环两层嵌套作用域

for (let i = 0; i < 3; i++) {
  let i = 'foo'
  console.log(i)
}

以上代码正常输出三次"foo"
代码拆解

let i = 0
if (i < 3) {
  let i = 'foo'
  console.log(i)
}
i++
if (i < 3) {
  let i = 'foo'
  console.log(i)
}
i++
if (i < 3) {
  let i = 'foo'
  console.log(i)
}
i++
if (i < 3) {
  let i = 'foo'
  console.log(i)
}
i++

变量声明提升

console.log(foo)
var foo = 'aa'

以上代码能正常运行,并输出undefined
使用let修改

console.log(foo)
let foo = 'aa'

以上代码会报出异常

const 恒量/常量

变量一旦声明就不能被修改

const name = 'a'
name = 'b'

const name
name = 'a'

以上代码都会报错
但是使用const声明的对象内的属性可以被重新赋值

const obj = {}
obj.name = 'a'

const变量不能被修改意思是不能被重新分配内存地址

数组的解构

const arr = [100, 200, 300]
const [foo, bar, baz] = arr
console.log(foo, bar, baz)
// 解构部分值
const [, , baz] = arr
console.log(baz)
// ...rest剩余值
const [foo, ...rest] = arr
console.log(foo, ...rest)
// 多出来的值会输出undefined
const [foo, bar, baz, more] = arr
console.log(foo, bar, baz, more)
// 设置默认值
const [foo, bar, baz, more = 'default value'] = arr
console.log(foo, bar, baz, more)
//示例
const path = '/foo/bar/baz'
const [, rootDir] = path.split('/')
console.log(rootDir)

对象的解构 Destructuring

const obj = { name: 'zce', age: 18 }
const { name, age } = obj
console.log(name)
// 重命名+默认值
const { name: objName = 'jack' } = obj

模板字符串

const str = `hello es 2015, this is a \`string\``
const name = 'tome'
const msg = `hey, ${name}`

带标签的模板字符串

const name = 'tom'
const gender = true
function myTagFunc(strings, name, gender) {
  // console.log(strings, name, gender)
  // return '123'
  const sex = gender ? 'man' : 'woman'
  return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTagFunc`hey, ${name} is a ${gender}`
console.log(result)

可以用来处理模板字符串中的参数

字符串的扩展方法

startWith
endWith
includes

const messge = 'Error: foo is not defined.'
message.startWith('Error')
message.endWith('.')
message.includes('foo')

参数默认值

function foo (bar, enable = true) {
  console.log(enable)
}
foo(false)

带有默认值的参数需要放到最后

剩余参数

function foo() {
  console.log(arguments) // 伪数组
}
foo(1, 2, 3, 4)
function bar(...args) {
  console.log(...args)
}
bar(1, 2, 3, 4)

展开数组Spread

const arr = ['foo', 'bar', 'baz']

console.log.apply(console, arr)
console.log(...arr)

箭头函数

const fn = (a) => a + 1

箭头函数和this

const person = {
  name: 'tom',
  sayHi: function () {
    console.log(`${this.name}`)
  },
  sayHi2: () => {
    console.log(`${this.name}`) // 输出undefined
  },
  sayHiAsync: function () {
    setTimeout(function (){
      console.log(this.name) // 输出undefined
    }, 200);
  },
  sayHiAsync: function () {
    const _this = this
    setTimeout(function (){
      console.log(_this.name) // 输出tom
    }, 200);
  },
  sayHiAsync2: function () {
    setTimeout(() => {
      console.log(this.name) // 输出tom
    }, 200);
  }
}

对象字面量增强

const bar = '1'
const obj = {
  foo: 123,
  // bar: bar,
  bar,
  method1: function () {
    console.log('method1')
  },
  method2() {
    console.log('method2')
    console.log(this) // this指向当前字面量
  },
  [Math.random()]: 123 // 计算属性名
}
console.log(obj.method2())

对象扩展方法 Object.assign

将多个原对象中的属性复制到一个目标对象中

const source1 = {
  a: 123,
  b: 123
}

const target = {
  a: 456,
  c: 456
}

const result = Object.assign(target, source1)
console.log(target) // { a: 123, c: 456, b: 123 }
console.log(target === result) // true

function func (obj) {
  obj.name = 'func obj' // 形参obj与全局变量obj指向同一块内存地址,所以这样修改会影响到全局变量
  console.log(obj)
  // 使用Object.assign生成一个新的对象
  const funcObj = Object.assign({}, obj)
  funcObj.name = 'func obj'
  console.log(funcObj)
}

const obj = { name: ' global obj'}
func(obj)
console.log(obj)

对象扩展方法 is

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

Proxy 代理对象

ES5中可以使用Object.defineProperty来为对象添加属性,这样可以捕获到属性的读写过程

const personProxy = new Proxy(person, {
  get (target, property) {
    console.log(target, property)
    return property in target ? target[property] : 'default'
    // return 100
  },
  set (target, property, value) {
    console.log(target, property, value)
    if (property === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError(`${value} is not an int`)
      }
    }
    target[property] = value
  }
})

personProxy.gender = true
personProxy.age = '11' // 报错

console.log(personProxy.name)
console.log(personProxy.xxx)

Proxy和Object.defineProperty区别

  • Object.defineProperty只能监视到属性的读写,Proxy能够监视到更多对象操作,如delete,对对象中方法的调用等。
const personProxy2 = new Proxy(person, {
  deleteProperty(target, property) {
    console.log(target, property)
    delete target[property]
  }
})

delete personProxy2.name
console.log(person)

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调用一个函数

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)

Proxy不需要侵入对象

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

const person = {}
// 使用Object.defineProperty
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)

const person2 = {
  name: 'zce',
  age: 20
}
// 使用Proxy更加合理
const personProxy = new Proxy(person2, {
  get(target, property) {
    return target[property]
  },
  set (target, property, value) {
    target[property] = value
  }
})

Reflect

统一提供一套用于操作对象的API

示例

const objA = {
  foo: '123',
  bar: '456'
}

const proxy = new Proxy(objA, {
  get(target, property) {
    console.log('watch logic')
    Reflect.get(target, property)
  }
})

const personA = {
  name: 'zce',
  age: 20
}
console.log('name' in personA)
console.log(delete personA['age'])
console.log(Object.keys(personA))

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

Promise

解决了传统异步编程中回调函数嵌套过深的问题

class类

function Student(name) {
  this.name = name
}

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

const stu = new Student('andy')
stu.say()

class StudentClass {
  constructor(name) {
    this.name = name
  }
  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}
const sc = new StudentClass('jack')
sc.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.number = number
  }

  hello () {
    this.say()
    console.log(`my number is ${this.number}`)
  }
}

const s = new Student('andy', 10)
s.hello()

Set

const set = new Set()
set.add(1).add(2).add(3).add(4).add(2)
console.log(set)
set.forEach(element => {
  console.log(element)
});

for (const i of set) {
  console.log(i)
}

Map

传统对象只能使用字符串作为key

const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
console.log(Object.keys(obj))
// 输出 [ '123', 'true', '[object Object]' ]

使用Map

const m = new Map()
const tom = { name: 'tom'}
m.set(tom, 90)

console.log(m)
console.log(m.get(tom))

m.forEach((value, key) => {
  console.log(value, key)
})

Symbol

最主要的作用就是为对象添加独一无二的属性名

// SharedArrayBuffer.js
const cache = {}
// a.js
cache['foo'] = Math.random()
// b.js
cache['foo'] = '123'
console.log(cache)

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

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

const obj = {
  [Symbol()]: 123
}
console.log(obj)

// 私有成员
const name = Symbol()
const person = {
  [name]: 'zce',
  say () {
    console.log(this[name])
  }
}
console.log(Symbol() === Symbol(), Symbol('foo') === Symbol('foo')) // false
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true

const obj = {
  [Symbol.toStringTag]: 'SObject',
}
console.log(obj.toString()) // [object SObject]

const obj2 = {
  [Symbol()]: 'symbol value',
  foo: 'normal value'
}

for (const key in obj2) {
  console.log(key) // foo
}
console.log(Object.keys(obj2)) //[ 'foo' ]
console.log(JSON.stringify(obj2)) //{"foo":"normal value"}

console.log(Object.getOwnPropertySymbols(obj2)) //[ Symbol() ]

for...of

const arr = [100, 200, 300, 400]

for (const item of arr) {
  console.log(item)
  // 100
  // 200
  // 300
  // 400
}
arr.forEach(item => console.log(item))

for (const item of arr) {
  console.log(item)
  // 随时跳出循环
  if (item > 100) {
    break
  }
}
// arr.forEach() //不能跳出循环
// arr.some()
// arr.every()

const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const [key, value] of m) {
  console.log(key, value)
}

for...of循环是一种数据统一遍历方式,但是不能遍历普通对象

const obj = { name: 'andy'}
for (const item of obj) {
  console.log(item)
}

TypeError: obj is not iterable

可迭代接口

Array,Set,Map都实现了Ieterator接口

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

console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

let isDone = false
while(!isDone) {
  const valueObj = iterator.next()
  console.log(valueObj)
  isDone = valueObj.done
}

迭代器模式

const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '英语'],
  work: ['喝茶'],
  each: function(callback) {
    const all = [].concat(this.life, this.learn, this.work)
    for (const item of all) {
      callback(item)
    }
  },
  [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(item => console.log(item))

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

Generator生成器函数

function * foo2 () {
  console.log(111)
  yield 100
  console.log(222)
  yield 200
  console.log(333)
  yield 300
}

const generator = foo2()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

应用

function * createIdMaker () {
  let id = 1
  while (true) {
    yield id++
  }
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)

const todoList = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '英语'],
  work: ['喝茶'],
  [Symbol.iterator]: function * () {
    const all = [...this.life, ...this.learn, ...this.work]
    for (const item of all) {
      yield item
    }
  }
}

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

ECMAScript 2016

Array.prototype.includes

const arr = ['foo', 1, NaN, false]
console.log(arr.indexOf('foo')) // 0
console.log(arr.indexOf('bar')) // -1 
console.log(arr.indexOf(NaN)) // -1 

console.log(arr.includes('foo')) // true
console.log(arr.includes(NaN)) // true

指数运算符

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

ECMAScript 2017

const obj = {
  foo: 'value1',
  bar: 'value2'
}
// Object.values
console.log(Object.values(obj))
// Object.entries
console.log(Object.entries(obj))

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value)
}
console.log(new Map(Object.entries(obj)))

// Object.getOwnPropertyDescriptors
const p1 = {
  firstName: 'Lei',
  lastName: 'Wang',
  get fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

const p2 = Object.assign({}, p1)
p2.firstName = 'zce'
console.log(p2) // { firstName: 'zce', lastName: 'Wang', fullName: 'Lei Wang' }

const descriptors = Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'zce'
console.log(p3) // { firstName: 'zce', lastName: 'Wang', fullName: [Getter] }

// String.prototype.padStart / String.prototype.padEnd
const books = {
  html: 5,
  css: 16,
  javascript: 128
}

for (const [name, count] of Object.entries(books)) {
  console.log(name, count)
}

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


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

推荐阅读更多精彩内容