重点介绍ES5.1基础之上的变化,这些变化可归纳为4类:
//块级作用域--if语句
if (true) {
console.log('hello, baby');
}
//块级作用域--for循环
for (let i = 0; i < 8; i++) {
console.log(i);
}
if (true) {
var bar = 'hello'
}
console.log(bar); //hello
if (true) {
let bar = 'hello'
}
console.log(bar); //ReferenceError: bar is not defined
//for循环内部有两层作用域
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i);
}
console.log('内存结束 i = ' + i);
}
// 内存结束 i = 0
// 0
// 1
// 2
// 内存结束 i = 1
// 0
// 1
// 2
// 内存结束 i = 2
console.log(foo); //ReferenceError: Cannot access 'foo' before initialization
let foo = 'aa'
const name = 'aaa'
name = 'bbb' //TypeError: Assignment to constant variable.
const obj = {}
obj.name = 'eeee'
console.log(obj); //{ name: 'eeee' }
obj = {} //TypeError: Assignment to constant variable.
最佳实践:不用var,主用const,配合let 。
通过数组解构的方式,可以从数组当中去快速提取数组中的成员。(与元素的顺序有关,需通过元素的位置去匹配)
//数组解构
const arr = [2, 4, 5]
//传统做法
const foo = arr[1]
console.log(foo); //4
//数组解构方式
const [, baz] = arr
console.log(baz); //4
//剩余参数
const [f, ...rest] = arr
console.log(f); //2
console.log(rest); //[ 4, 5 ]
//超过
const [one, two, three, more] = arr
console.log(more); //undefined
//默认值
const [a, b, c, d = 'ok'] = arr
console.log(d); //ok
//适用场景-如拆分字符串
const path = '/foo/bar/baz'
// const tmp = path.split('/')
// const dir = tmp[1]
const [, dir] = path.split('/')
console.log(dir); //foo
通过对象解构的方式,可以从对象当中去快速提取对象中的属性。(与属性的顺序无关,只需通过属性名去匹配)
//对象解构
const obj = { age: 18, class: '科技一班' }
// const obj = { age: 18 }
const { age } = obj
console.log(age); //18
//属性重命名
const { age: myAge } = obj
console.log(myAge); //18
//默认值
// const { class: myClass = '太空二班' } = obj
// console.log(myClass); //太空二班
//适用场景-简化console.log方法
const { log } = console
log('foo')
log('aaa')
log('bbb')
// foo
// aaa
// bbb
模板字符串需要使用`来表示。模板字符串的新特性:
//支持换行
const str = `hello, 2020,
we are here`
console.log(str);
// hello, 2020,
// we are here
//支持插入JS表达式
const name = 'jack'
const msg = `hey, ${name}, welcome to ${new Date().getFullYear()}`
console.log(msg);
//hey, jack, welcome to 2020
//带标签的模板字符串
const str = console.log`hello` //[ 'hello' ]
const name = 'rock'
const gender = true
function myTagFunc(strings, name, gender) {
// console.log(strings, name, gender); //[ 'hey,', ' is a ', '' ] rock male
// return 123
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
const res = myTagFunc`hey,${name} is a ${gender}`
// console.log(res); //123
console.log(res); //hey,rock is a man
//字符串的扩展方法
const msg = 'Error: a is not defined.'
console.log(msg.startsWith('Error')); //true
console.log(msg.endsWith('ok')); //false
console.log(msg.includes('a')); //true
//函数参数的默认值
function foo(bar, enable = true) {
console.log(bar, enable);
}
foo(3) //3 true
foo(3, false) //3 false
ES2015新增了个…操作符,有两个作用:
//以前的做法
function foo() {
console.log(arguments);
}
foo(1, 2, 3) //[Arguments] { '0': 1, '1': 2, '2': 3 },伪数组
//剩余参数做法
function bar(first, ...args) {
console.log(first, args);
}
bar(3, 4, 5) //3 [ 4, 5 ]
//展开数组参数
const arr = ['foo', 'bar', 'baz']
console.log(
arr[0],
arr[1],
arr[2]
);
//foo bar baz
//以前做法
console.log.apply(console, arr); //foo bar baz
//ES6
console.log(...arr); //foo bar baz
//以前定义函数
function func(num) {
return num + 2
}
//箭头函数
const func1 = n => n + 2
console.log(func(3)); //5
console.log(func1(3)); //5
//使用箭头函数大大简化了回调函数的编写
const arr = [3, 2, 4, 6, 7]
const even = arr.filter(function (item) {
return item % 2
})
console.log(even); //[ 3, 7 ]
const even1 = arr.filter(item => item % 2)
console.log(even1); //[ 3, 7 ]
箭头函数会让我们的代码变的更简短而且更易读,并且,箭头函数不会改变this的指向。
const person = {
name: 'Rock',
sayHi1: function () {
console.log(`Hi, this is ${this.name}`);
},
sayHi2: () => {
console.log(`Hi, this is ${this.name}`);
},
sayHiAsync1: function () {
setTimeout(function () {
console.log(this.name);
}, 1000)
},
sayHiAsync2: function () {
setTimeout(() => {
console.log(this.name);
}, 1000)
}
}
person.sayHi1() //Hi, this is Rock
person.sayHi2() //Hi, this is undefined
person.sayHiAsync1() //undefined
person.sayHiAsync2() //Rock
ECMAScript 2015升级了对象字面量的语法。
//对象字面量
const bar = 123
const obj = {
foo: 34,
bar,
func: function () {
console.log(this.foo);
},
func2() {
console.log(this);
},
[bar]: 888
}
//可以使用表达式返回值作为对象的属性名
obj[Math.random()] = 123
console.log(obj); //{ '123':888, foo: 34, bar: 123, func: [Function: func], func2: [Function: func2], '0.5719176856362458': 123}
obj.func2() //{ '123':888, foo: 34, bar: 123, func: [Function: func], func2: [Function: func2], '0.5719176856362458': 123}
const source = {
a: 2,
b: 3
}
const source1 = {
c: 222,
d: 21
}
const target = {
a: 888,
c: 999
}
const res = Object.assign(target, source, source1)
console.log(res); //{ a: 2, c: 222, b: 3, d: 21 }
console.log(res === target); //true
//可以用来复制对象
function func(obj) {
obj.name = 'func obj'
console.log(obj);
}
function func2(obj) {
const funcObj = Object.assign({}, obj)
funcObj.name = 'func obj'
console.log(funcObj);
}
const obj = { name: 'global obj' }
// func(obj) //{ name: 'func obj' }
// console.log(obj); //{ name: 'func obj' }
func2(obj) //{ name: 'func obj' }
console.log(obj); //{ name: 'global obj' }
console.log(
0 == false, //true
0 === false, //false
+0 === -0, //true
NaN === NaN, //false
Object.is(+0, -0), //false
Object.is(NaN, NaN) //true
);
const person = {
name: 'Rock',
age: 5
}
const personProxy = new Proxy(person, {
get(target, prop) {
return prop in target ? target[prop] : 'default'
},
set(target, prop, value) {
if (prop === 'age') {
//校验
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not int`)
}
}
target[prop] = value
}
})
console.log(personProxy.name); //Rock
console.log(personProxy.xxx); //default
// personProxy.age = 'ss' //TypeError: ss is not int
personProxy.age = 20
console.log(personProxy); //{ name: 'Rock', age: 20 }
//delete操作
const personProxy2 = new Proxy(person, {
defineProperty(target, prop) {
delete target[prop]
}
})
delete personProxy.age
console.log(person); //{ name: 'Rock' }
Proxy的其他对象操作 : //数组操作
const list = []
const listProxy = new Proxy(list, {
set(target, prop, value) {
console.log('set', prop, value);
target[prop] = value
return true
}
})
listProxy.push(100)
// set 0 100
// set length 1
const obj = {
a: 123,
b: '346'
}
const proxy = new Proxy(obj, {
get(target, prop) {
return Reflect.get(target, prop)
}
})
console.log(proxy.a); //123
const obj2 = {
name: 'Rock',
age: 20
}
//传统做法
console.log('name' in obj2); //true
console.log(delete obj2['age']); //true
console.log(Object.keys(obj2)); //[ 'name' ]
//Reflect方式
console.log(Reflect.has(obj2, 'name')); //true
console.log(Reflect.deleteProperty(obj2, 'age')); //true
console.log(Reflect.ownKeys(obj2)); //[ 'name' ]
Reflect的对象操作: 提供了一种更优的异步编程解决方案,通过链式编程方式解决了传统异步编程中回调函数嵌套过深的问题。
在此之前,ECMAScript中都是通过定义函数以及函数的原型对象来去实现的类。
//传统实现类--通过函数及函数的原型对象
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
console.log(`hi, my name is ${this.name}`);
}
const person = new Person('Jack')
person.say() //hi, my name is Jack
//ES6实现
class Person1 {
constructor(name) {
this.name = name
}
say() {
console.log(`hi, my name is ${this.name}`);
}
}
const per = new Person1('Rock')
per.say() //hi, my name is Rock
静态方法和实例方法
实例方法:需要通过这个类构造的实例对象去调用。
静态方法:直接通过这个类本身去调用。
ES2015中新增添加静态成员与静态方法的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() //hi, my name is Tom
类的继承
继承是面向对象中一个非常重要的特性。通过继承能够抽象出来相似类型之间重复的地方。
ES2015之前,大多数情况都是使用原型实现继承,但在Es2015中新增关键字extends关键字实现继承。
class Person {
constructor(name) {
this.name = name
}
//实例方法
say() {
console.log(`hi, my name is ${this.name}.`);
}
}
class Student extends Person {
constructor(name, age) {
super(name)
this.age = age
}
hello() {
super.say()
console.log(`my age is ${this.age}.`);
}
}
const student = new Student('Jerry', 6)
student.hello()
// hi, my name is Jerry.
// my age is 6.
可以理解为集合,与传统的数组比较类似,但是Set中的元素不允许重复。每一个值在Set当中都是唯一的。
//Set数据结构
const set = new Set()
set.add(1).add(2).add(3).add(4)
console.log(set); //Set(4) { 1, 2, 3, 4 }
//遍历
set.forEach(i => console.log(i))
// 1
// 2
// 3
// 4
//for...of遍历
for (let item of set) {
console.log(item);
}
// 1
// 2
// 3
// 4
//获取set的长度
console.log(set.size); //4
//判断是否包含某个元素
console.log(set.has(19)); //false
//删除元素
console.log(set.delete(3)); //true
//清空元素
set.clear()
console.log(set); //Set(0) {}
常见应用场景:为数组去重
//数组去重
const arr = [1, 2, 3, 2, 4, 1, 2, 5]
const result = new Set(arr)
console.log(result); //Set(5) { 1, 2, 3, 4, 5 }
//set转换为数组
const resArr1 = Array.from(new Set(arr))
const resArr2 = [...new Set(arr)]
console.log(resArr1, resArr2); //[ 1, 2, 3, 4, 5 ] [ 1, 2, 3, 4, 5 ]
与ECMAScript中对象非常类似,都是键值对集合,ECMAScript中键只能为字符串类型,不方便存放复杂数据结构。
//ES6以前
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用来去映射两个任意类型数据之间的映射关系。与对象最大的区别就是,它可以用任意类型的数据作为键,而对象只能用字符串作为键。
//ES6
const map = new Map()
const jack = { name: 'jack' }
map.set(jack, 90)
map.set('age', 19)
console.log(map); //Map(1) { { name: 'jack' } => 90, 'age' => 19 }
console.log(map.get(jack)); //90
console.log(map.has(jack)); //true
// console.log(map.delete('age')); //true
// map.clear()
// console.log(map); //Map(0) {}
//遍历
map.forEach((value, key) => {
console.log(value, key);
})
// 90 { name: 'jack' }
// 19 age
一种全新的原始数据类型
在ES2015之前,对象的属性名都是字符串,而字符串是有可能重复的,容易引起冲突。
ES2015为了解决这个问题,提供了一种全新的数据类型,叫Symbol(符号)。作用是表示一个独一无二的值。
最主要的作用就是为对象添加独一无二的属性名。
//ES2015以前
//store.js
const cache = {}
//a.js
cache['a_foo'] = Math.random()
//b.js
cache['b_foo'] = '123'
console.log(cache); //{ a_foo: 0.8887530628553206, b_foo: '123' }
//ES2015
const s = Symbol()
console.log(s); //Symbol()
console.log(typeof s); //symbol
console.log(Symbol() === Symbol()); //false
截止到ES2019,一共定义了7种原始数据类型,未来还会新增BigInt原始数据类型,用于去存放更长的数字。目前处于stage-4阶段,预计下个版本被标准化,标准化过后就是8种数据类型了。
每次通过Symbol去创建的值一定是唯一的,如果想全局复用一个相同的Symbol值,可以使用全局变量方式或使用Symbol提供的静态方法:
console.log(Symbol('foo')); //Symbol(foo)
console.log(Symbol('bar')); //Symbol(bar)
console.log(Symbol('car')); //Symbol(car)
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj); //{ [Symbol()]: '123', [Symbol()]: '456' }
const obj1 = {
[Symbol()]: 888
}
console.log(obj1); //{ [Symbol()]: 888 }
//a.js
const name = Symbol()
const person = {
[name]: 'abc',
say() {
console.log(this[name]);
}
}
//b.js
person.say() //abc
const obj2 = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
//不能遍历普通对象
// for (let i of obj2) {
// console.log(i); //TypeError: obj is not iterable
// }
console.log(Object.keys(obj2)); //[ 'foo' ]
console.log(JSON.stringify(obj2)); //{"foo":"normal value"}
console.log(Object.getOwnPropertySymbols(obj2));
// {"foo":"normal value"}
// [ Symbol() ]
提供了一些内置的Symbol常量:
在ECMAScript中遍历数据的方法:
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
//不能遍历普通对象
// for (let i of obj) {
// console.log(i); //TypeError: obj is not iterable
// }
const arr = [23, 45, 67, 87]
// for (const item of arr) {
// console.log(item);
// }
// 23
// 45
// 67
// 87
//forEach不能跳出循环
// arr.forEach(item => console.log(item))
//for...of能跳出循环
for (const item of arr) {
console.log(item);
if (item > 50) {
break
}
}
// 23
// 45
// 67
const s = new Set(['aaa', 'bbb'])
for (let item of s) {
console.log(item);
}
// aaa
// bbb
const m = new Map()
m.set('foo', 123)
m.set('bar', 345)
for (let [key, value] of m) {
console.log(key, value);
}
// foo 123
// bar 345
ES中能够表示有结构的数据类型越来越多,为了给各种各样的数据结构提供统一的遍历方式,ES2015提供了Iterable接口,即可迭代的。实现Iterable接口就是for…of的前提。
//迭代器(iterator)
const set = new Set(['aa', 'bb', 'cc'])
const iterator = set[Symbol.iterator]()
console.log(iterator.next()); //{ value: 'aa', done: false }
console.log(iterator.next()); //{ value: 'bb', done: false }
console.log(iterator.next()); //{ value: 'cc', done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
只要对象实现了Iterator,那对象也可以使用for…of方法。
//实现可迭代接口
const obj = {
//iterable
[Symbol.iterator]: function () {
//iterator
return {
next: function () {
//IterationResult
return {
value: 'aaa',
done: true
}
}
}
}
}
const obj2 = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: function () {
let index = 0
const self = this
return {
next: function () {
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++
return result
}
}
}
}
for (const item of obj2) {
console.log('循环体', item)
}
// 循环体 foo
// 循环体 bar
// 循环体 baz
for…of可以取代forEach,而且for…of内可以使用break。
迭代器模式
迭代器的核心就是对外提供统一遍历接口,让外部不用再去关心内部这个数据结构是啥样的。
// 场景:你我协同开发一个任务清单应用
//实现代码
const todos = {
life: ['阅读', '运动', '音乐'],
learn: ['JS', '算法', 'Vue'],
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)
}
// 阅读
// 运动
// 音乐
// JS
// 算法
// Vue
// 编码
function* foo() {
console.log('qqq') //qqq
return 100
}
const result = foo()
console.log(result.next()) //{ value: 100, done: true }
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
// 1111
// { value: 100, done: false }
// 2222
// { value: 200, done: false }
// 3333
// { value: 300, done: false }
// { value: undefined, done: true }
// 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)
// 1
// 2
// 3
// 4
// 案例2:使用 Generator 函数实现 iterator 方法
const todos = {
life: ['阅读', '运动', '音乐'],
learn: ['JS', '算法', 'Vue'],
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)
}
// 阅读
// 运动
// 音乐
// JS
// 算法
// Vue
// 编码
惰性执行。
发布于2016/6,是个小版本,仅实现了两个特性:
const arr = [2,3,'aa',4]
console.log(arr.indexOf(2)); //0
console.log(arr.includes(1)); //-1
console.log(arr.includes(2)); //true
console.log(Math.pow(2,10)); //1024
console.log(Math.pow(2,10)); //1024
发布于2017/6,也是小版本,带来了新功能: