let/const
ES2015 前没有块级作用域,用 var 在花括号内声明一个变量,在花括号外也能访问到。
{
let a = 125;
var b = 521;
}
a // Uncaught ReferenceError: a is not defined
b // 521
在 for 循环中,如果使用 var 来控制循环有时候会产生意料之外的结果。
for (var i = 0; i <10; i++) {
setTimeout(function() { // 同步注册回调函数到 异步的 宏任务队列。
console.log(i); // 执行此代码时,同步代码for循环已经执行完成
}, 0);
}
// 输出 10个10
// i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i); // i 是循环体内局部作用域,不受外界影响。
}, 0);
}
// 输出 0-9
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i);
}
}
// 输出 3次 0 1 2
变量提升
var 生命的变量可以先使用再声明,因为 var 声明的变量相当于在作用域的最前面先赋予 undefined 的值,称之为变量提升;let 必须先生命再使用。
console.log(foo) // 输出 undefined,不报错
var foo = '1'
暂时性死区
var tmp=521;
if(true){
tmp='abc';//ReferenceError: tmp is not defined
let tmp;
}
// 代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
重复声明
var i = 1;
var i = 2; // 不报错
let j = 1;
let j = 2; // 报已经变量存在的错误
let 和 const
const 和 let 的特性相同,区别在于 const 声明的是不可改变的常量。但是如果用 const 声明复杂对象,那么可以改变这个对象的属性值,因为对象常量的内存地址并没有改变。
const i = 4;
i = 5; // 报错
const a = { b: 1, c: 2 }
a.b = 3 // 不会报错,因为 a 的内存地址没有改变
a = { d: 6 } // 报错
数组解构
// 数组的解构
const arr = [100, 200, 300]
// const [foo, bar, baz] = arr
// console.log(foo, bar, baz)
// foo = 100, bar = 200, baz = 300
// const [, , baz] = arr
// console.log(baz)
// baz = 300
// const [foo, ...rest] = arr
// console.log(rest)
// rest = [200, 300]
// const [foo, bar, baz, more] = arr
// console.log(more)
// more = undefined
// 设置默认值
// const [foo, bar, baz = 123, more = 'default value'] = arr
// console.log(baz, more)
// baz = 300, more = 'default value'
const path = '/foo/bar/baz'
// const tmp = path.split('/')
// const rootdir = tmp[1]
const [, rootdir] = path.split('/')
console.log(rootdir)
对象解构
// 对象的解构
const obj = { name: 'zce', age: 18 }
// const { name } = obj
// console.log(name)
// 重命名
// const name = 'tom'
// const { name: objName } = obj
// console.log(objName)
// 设置默认值
// const name = 'tom'
// const { name: objName = 'jack' } = obj
// console.log(objName)
const { log } = console
log('foo')
log('bar')
log('123')
模板字符串
// 模板字符串
// 反引号包裹
// const str = `hello es2015, this is a string`
// 允许换行
// const str = `hello es2015,
// this is a \`string\``
// console.log(str)
const name = 'tom'
// 可以通过 ${} 插入表达式,表达式的执行结果将会输出到对应位置
const msg = `hey, ${name} --- ${1 + 2} ---- ${Math.random()}`
console.log(msg)
带标签的模板字符串
const name = 'tom'
const gender = false
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)
// 输出 hey, tom is a woman.
// 字符串会在模板那里分隔, strings 是 ['hey, ', ' is a ', '.', raw: ['hey, ', ' is a ', '.']]
字符串扩展方法
// 字符串的3个扩展方法
const message = 'Error: foo is not defined.'
console.log(
message.startsWith('Error'),
message.endsWith('.'),
message.includes('foo')
)
参数默认值
// 函数参数的默认值
// 第一种
function zce (enable) {
// 短路运算很多情况下是不适合判断默认参数的,例如 0 '' false null
// enable = enable || true
enable = enable === undefined ? true : enable
console.log('foo invoked - enable: ')
console.log(enable)
}
// 第二种
// 默认参数一定是在形参列表的最后
function foo (enable = true) {
console.log('foo invoked - enable: ')
console.log(enable)
}
foo(false)
扩展运算符 … 的作用
接收剩余参数
… 接收剩余的参数,返回一个数组,接收剩余参数必须在最后。
function foo (first, ...args) {
console.log(args) // [2, 3, 4]
}
foo(1, 2, 3, 4)
数组展开
// 展开数组参数
const arr = ['foo', 'bar', 'baz']
console.log(...arr)
// 输出 foo bar baz
数组、对象拷贝
需要注意的是,… 是浅拷贝,如果是基本数据类型的数组和一层的对象则相互不会影响,如果是对象数组和多层对象则会影响。
var arr = [1,2,3,4,5]
var [ ...arr2 ] = arr
arr[2] = 5
console.log(arr) // [1, 2, 5, 4, 5]
console.log(arr2) // [1, 2, 3, 4, 5]
箭头函数
箭头函数不会改变 this,因为它本身没有 this 的机制。
如果箭头右边不写花括号,相当于返回右边的值,如果写了花括号,想要返回值就需要写 return。
const arr = [1, 2, 3, 4, 5, 6, 7]
// 常用场景,回调函数
arr.filter(i => i % 2)
// 或者这么写
arr.filter(i =>{ return i % 2 })
对象
对象字面量的增强
// 对象字面量
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
将多个源对象的属性复制到一个第一个对象。如果有同一个属性名,保留后传入的。
const source1 = {
a: 123,
b: 123
}
const source2 = {
b: 789,
d: 789
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1, source2)
console.log(target)
console.log(result === target)
// 输出 {a: 123, c: 456, b: 789, d: 789} true
Object.assign 是一层的深拷贝,如果是多层的对象,它还是浅拷贝。下面的 obj 的 name 属性没有被改变,但是更深一层的 address.city 被改变了。
function func (obj) {
const funcObj = Object.assign({}, obj)
funcObj.name = 'funcObj'
funcObj.address.city = 'guangzhou'
console.log(funcObj) // 输出 address: {city: 'guangzhou', country: 'china'} name: "funcObj"
}
const obj = { name: 'global obj', address: { city: 'chaozhou', country: 'china' } }
func(obj)
console.log(obj) //输出 address: {city: 'guangzhou', country: 'china'} name: "global 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
Proxy 类可以避免直接去操作一个对象,而通过代理的方式对对象的属性值进行查询和修改。这么做主要有三个作用:
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
// Proxy 对象
const person = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person, {
// 监视属性读取
get (target, property) {
return property in target ? target[property] : 'default'
// console.log(target, property)
// return 100
},
// 监视属性设置
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)
console.log(personProxy.xxx)
对比 Object.defineProperty,Proxy 具有以下优势:
- 可以进行读写以外的操作,比如删除 ```javascript const person = { name: ‘zce’, age: 20 }
const personProxy = new Proxy(person, { deleteProperty (target, property) { console.log(‘delete’, property) delete target[property] } })
delete personProxy.age
2. 可以很方便地监视数组的操作
```javascript
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)
- 不需要侵入对象 ```javascript const person = {}
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)
<a name="PKvx2"></a>
## Reflect
Reflect 内部封装了一系列对对象的底层操作,都是静态方法,Reflect 成员方法就是 Proxy 处理对象的默认实现。
```javascript
// Reflect 对象
// const obj = {
// foo: '123',
// bar: '456'
// }
// const proxy = new Proxy(obj, {
// get (target, property) {
// console.log('watch logic~')
// return Reflect.get(target, property)
// }
// })
// console.log(proxy.foo)
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
class
ES2015 之前,定义一个类是通过函数定义的方式,定义方法是通过原型链。ES2015 引入了 class,定义一个类变得更加简单了。
// class 关键词
// function Person (name) {
// this.name = name
// }
// Person.prototype.say = function () {
// console.log(`hi, my name is ${this.name}`)
// }
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
const p = new Person('tom')
p.say()
静态
引入 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()
继承
extends 关键字使得一个类可以从另一个类继承属性和方法。
// 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 () {
super.say() // 调用父类成员
console.log(`my school number is ${this.number}`)
}
}
const s = new Student('jack', '100')
s.hello()
Set
Set 是 JavaScript 一种全新的数据结构,类似于数组,但是它的成员的值是不可重复的。Set 经常用于数组的去重。
const s = new Set()
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) // [1,2,3,4]
Map
Map 也是一种新的数据结构,往里面放每一个成员,都要赋予一个键。与普通对象赋值方式不同,Map不会把键的类型转成字符串,它的键的类型任意,可以是一个对象,这是对象赋值做不到的。
const m = new Map()
const tom = { name: 'tom' }
m.set(tom, 90)
console.log(m)
console.log(m.get(tom))
// 常用方法
// m.has()
// m.delete()
// m.clear()
m.forEach((value, key) => {
console.log(value, key)
})
Symbol
Symbol 可以创建一个完全独一无二的值,在如今大量使用第三方模块的情况下,使用 Symbol 作为对象的键可以避免键名的冲突,也可以创建私有成员。
// Symbol 数据类型
// 场景1:扩展对象,属性名冲突问题
// // shared.js ====================================
const cache = {}
// a.js =========================================
cache['a_foo'] = Math.random()
// b.js =========================================
cache['b_foo'] = '123'
console.log(cache)
// =========================================================
const s = Symbol()
console.log(s)
console.log(typeof s)
两个 Symbol 永远不会相等
console.log(
Symbol() === Symbol() // false
)
Symbol 描述文本
console.log(Symbol('foo'))
console.log(Symbol('bar'))
console.log(Symbol('baz'))4
// 使用 Symbol 为对象添加用不重复的键
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)
也可以在计算属性名中使用
const obj = {
[Symbol()]: 123
}
console.log(obj)
// 案例2:Symbol 模拟实现私有成员
// a.js ======================================
const name = Symbol()
const person = {
[name]: 'zce',
say () {
console.log(this[name])
}
}
// 只对外暴露 person
// b.js =======================================
// 由于无法创建出一样的 Symbol 值,
// 所以无法直接访问到 person 中的「私有」成员
// person[Symbol()]
person.say()
Symbol 的常用方法:
// Symbol 补充
console.log(
// Symbol() === Symbol()
Symbol('foo') === Symbol('foo') // false
)
// Symbol 全局注册表 ----------------------------------------------------
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
console.log(
Symbol.for(true) === Symbol.for('true')
) // true for 会把传入的参数转成字符串
// 内置 Symbol 常量 ---------------------------------------------------
console.log(Symbol.iterator)
console.log(Symbol.hasInstance)
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString())
// Symbol 属性名获取 ---------------------------------------------------
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
// for (var key in obj) {
// console.log(key)
// }
// console.log(Object.keys(obj))
// console.log(JSON.stringify(obj))
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol()] 可以获取到Symbol键
for of 循环
所有可被迭代的数据类型都可以使用 for..of 遍历,普通对象不能用 for…of 遍历。
// 遍历 Map 可以配合数组结构语法,直接获取键值
const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const [key, value] of m) {
console.log(key, value)
}
迭代
ES2015 提供了 Iterable 接口,一些数据类型实现了这个接口,如Array,Set,Map。for…of 循环的本质就是调用这些数据类型的迭代器方法。
const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]() // set 的迭代器
while (true) {
const current = iterator.next() // 迭代器实现了 next()
if (current.done) {
break // 迭代已经结束了,没必要继续了
}
console.log(current.value)
}
想要 for…of 可以遍历一个普通对象,那么就实现它的迭代器。
// 迭代器设计模式
// 场景:你我协同开发一个任务清单应用
// 我的代码 ===============================
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
}
}
}
}
}
// 你的代码 ===============================
// for (const item of todos.life) {
// console.log(item)
// }
// for (const item of todos.learn) {
// console.log(item)
// }
// for (const item of todos.work) {
// console.log(item)
// }
todos.each(function (item) {
console.log(item)
})
console.log('-------------------------------')
for (const item of todos) {
console.log(item)
}
生成器 Generator
一种新的异步编程方案。
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()) // 第四次调用,已经没有需要执行的内容了,所以直接得到
// Generator 应用
// 案例1:发号器
function * createIdMaker () {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
// 案例2:使用 Generator 函数实现 iterator 方法
const todos = {
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 todos) {
console.log(item)
}
ES Modules
ES2016
Array.includes()
用于判断数组是否包含指定元素。可以找到 NaN 元素。
const arr = ['foo', 1, NaN, false]
// 直接返回是否存在指定元素
console.log(arr.includes('foo'))
// 能够查找 NaN
console.log(arr.includes(NaN))
ES2017
Object.values()
Object.entries()
遍历对象的属性名和值的对。
const obj = {
foo: 'value1',
bar: 'value2'
}
console.log(Object.values(obj)) // ['value1', 'value2']
console.log(Object.entries(obj)) // [ ['foo', 'value1'], ['bar', 'value2'] ]
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
} // foo value1 bar value2