一、 ES2015(es6)
1. let与块级作用域
console.log(a)//undefined
var a = 1
let 声明变量,但不存在变量提升
console.log(a)// a is not defined
let a = 1
js包括全局作用域
、函数作用域
和块级作用域
,let和const只能在块级作用域内部被访问
(内部也是闭包机制)。let的提出要求我们先声明在使用变量!
for (var i = 0; i < 3; i++) {
var i = 'foo' //(1)
let i = 'foo' // (2)
console.log(i) //(1)只打印一个foo 因为i混淆了 (2)打印三个foo
}
在熟悉不过的问题
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)//3 3 3
})
}
解决
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)//0 1 2
})
}
2. const
const name = 'lcj'
name = 'xyz'//error
const name
name = 'xyz'//error
const必须声明和赋值在一起写const name = 'lcj'
const定义的常量一旦别定义就不可修改
const obj = {}
obj = { x: 1 }//error
const obj = {}
obj.name = 'xyz'//可以
const可以修改常量属性,但不能改变内存指向!
合理的声明规范:主用const、配合let、不用var!
3. 数组的解构
const arr = [100, 200, 300]
const [a, b, c] = arr
console.log(a, b, c)//100, 200, 300
//想要取最后一位,前面别忘记,,
const [, , d] = arr
console.log(d)// 300
//...解构只能用在数组最后一个位置
const [e, ...rest] = arr
console.log(rest)//[ 200, 300 ]
//从第一个开始赋值
const [f] = arr
console.log(f)//100
//数组项数多余实际数组项数,找不到返回undefined
const [g, h, i, j] = arr
console.log(j)//undefined
//数组中没有就拿默认值
const [k, l, m, n = '123'] = arr
console.log(n)//123
const path = 'foo/bar/baz'
const [, rootdir] = path.split('/')
console.log(rootdir)//bar
4. 对象的解构
数组的解构是根据下标去提取,对象的解构是根据属性名去提取
const obj = { name: 'lcj', age: 18 }
const { name } = obj
console.log(name)//lcj
产生一个问题,当我们利用对象解构取name和声明一个name冲突时,怎么办
const obj = { name: 'lcj', age: 18 }
const { name } = obj
const name = 'abc'
console.log(name)//error
解决办法:给对象属性重命名
const obj = { name: 'lcj', age: 18 }
const { name: myName = 'daisy' } = obj
const name = 'abc'
console.log(name)//abc
console.log(myName)//lcj
const { name: myName = 'daisy' } = obj
【= ‘daisy’】这里是设置默认值。name对象obj中的变量名,myName是我们重新的命名。打印myName可以拿到结果。
const { log } = console
console.log(log)//[Function: bound consoleCall]
log('123')//123
5. 模版字符串
const str = `hello world`
console.log(str)//hello world
//想要在模版字符串中输出`,使用转义字符\
const str2 = `hello ,this is a \`string`
console.log(str2)//hello ,this is a `string
//可以换行输入,也能正常输出
const str3 = `hello
我换行了`
console.log(str3)//hello (换了行)我换行了
//可以赋值
const name = 'lcj'
const str4 = `hey,${name}`
console.log(str4)//hey,lcj
//可以进行基本计算
const str5 = `hey,${1 + 2}`
console.log(str5)//hey,3
6. 模版字符串标签函数
const str = console.log`hello world`
console.log(str)//[ 'hello world' ]
定义模版字符串之前添加一个标签,如:【const str = console.loghello world
】,此处tag是个标签函数(一个特殊的函数),打了标签就执行这个函数
const name = 'lcj'
const gender = false
function myTag(arr, name, gender) {
console.log(arr)//[ 'hey,', ' is a ', '.' ]
let sex = gender ? 'man' : 'woman'
return arr[0] + name + arr[1] + sex + arr[2]
}
const res = myTag`hey,${name} is a ${gender}.`
console.log(res)//hey,lcj is a woman.
7. 字符串的扩展方法
startsWith:判断是否以开头,endsWith:判断是否以结尾,includes:判断是否包含
const msg = `I am a girl.`
console.log(msg.startsWith('I'))//true
console.log(msg.startsWith('a'))//false
console.log(msg.endsWith('.'))//true
console.log(msg.endsWith('l'))//false
console.log(msg.includes('am'))//true
console.log(msg.includes('am ab'))//false
8. 参数默认值
*** 参数存在那么就正常使用,不存在就默认改成true ***
function foo(enable) {
enable = enable || true //(1)
enable = enable === undefined ? true : enable//(2)
console.log(enable)
}
foo()//true
foo(false)//(1)true (2)false
上面使用短路运算,但是foo(false)依然返回了true,所以不正确,应该改成(2),es6新写法
function foo(enable = true) {
console.log(enable)
}
foo()//true
foo(false)//false
9. 剩余参数
function foo() {
console.log(arguments)//[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }
}
foo(1, 2, 3, 4)
未知参数个数的时候,我们使用arguments获取参数,它其实是个伪数组
function foo(...arg) {
console.log(arg)//[ 1, 2, 3, 4 ]
}
foo(1, 2, 3, 4)
…arg只能出现在形参的最后一位,只能使用一次.以数组形式,从当前位置接收所有参数
function foo(a, ...arg) {
console.log(a)//1
console.log(arg)//[ 2, 3, 4 ]
}
foo(1, 2, 3, 4)
10. 展开数组
const arr = ['foo', 'bar', 'abc']
console.log.apply(console, arr)//foo bar abc【原来写法,利用apply】
console.log(...arr)//foo bar abc
...自动把数组中的成员依次传递到参数列表中
11. 箭头函数
箭头函数代码更简短易读,箭头左边是参数的列表,右边是函数体
const inc = (m,n)=>m+n
12. 箭头函数与this
箭头函数不会改变this指向
const person={
name:'tom',
//*** 原来写法 ***
// sayHi:function(){
// console.log(`hi,my name is ${this.name}`)//hi,my name is tom
// }
// *** es6 ***
sayHi:()=>{
// console.log(this)//{}
console.log(`hi,my name is ${this.name}`)//hi,my name is undefined
},
sayHiAsync:function(){
//*** 原来写法 ***
// const that = this
// console.log(this)//{name: 'tom',sayHi: ...}
// setTimeout(function(){
// console.log(this)//Timeout {...}
// console.log(this.name)//undefined
// console.log(that.name)//tom
// },1000)
// *** es6 ***
setTimeout(()=>{
console.log(this.name)//tom
})
}
}
person.sayHi()
person.sayHiAsync()
箭头函数中,没有this的机制,所以this和方法外面的this一致
13. 对象字面量的增强
const bar = '123'
const obj = {
name:'lcj',
//bar:bar //传统写法
bar, //现在可以省略:和变量名
//method:function(){//传统写法
// console.log(123456)
//}
method(){
console.log(this)//{ name: 'lcj', bar: '123', method: [Function: method] }//this指向当前对象
console.log(123456)
}
//es6动态添加属性名
[Math.random()]:1,
[1+2]:2
[bar]:3
}
console.log(obj)//{ name: 'lcj', bar: '123', method: [Function: method] }
obj.method()//123456
//*** 之前动态添加属性名 ***
obj[Math.random()]=1
14. Object.assign
将多个源对象中的属性复制到一个目标对象中,如果对象对象中有相同的属性,源对象的属性会覆盖目标对象的属性。
const s1 ={
a:123,
b:123
}
const s2 ={
b:567,
d:567
}
const target = {
a:456,
c:456
}
const res = Object.assign(target,s1)
console.log(res)//{ a: 123, c: 456, b: 123 }
const res2 = Object.assign(target,s1,s2)
console.log(res2)//{ a: 123, c: 456, b: 567, d: 567 }
console.log(res === target)//true 说明是一个
后面覆盖前面的!
function func(obj){
obj.name = 'func obj'
console.log(obj)//{ name: 'func obj' }
}
const obj = {name:'global obj'}
func(obj)
console.log(obj)//{ name: 'func obj' }
上面题由于obj引用的是同一个内存地址,所以在内部更改了属性值,外部也跟着更改了,如何只在内部更高呢?内部生成一个全新的属性!
function func(obj){
const funcObj = Object.assign({},obj)
funcObj.name = 'funcObj obj'
console.log(funcObj)//{ name: 'funcObj obj' }
}
const obj = {name:'global obj'}
func(obj)
console.log(obj)//{ name: 'func obj' }
Object.assign参数是对象,如果不是对象会先转为对象,对于无法转成对象的报错。
Object.assign(undefined)//error
Object.assign(null)//error
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
下面代码中,v1、v2、v3分别是字符串、布尔值和数值,结果只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
常见用途
1.为对象添加属性–通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
2.对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
3.克隆对象
function clone(origin) {
return Object.assign({}, origin);
}
采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
4.合并多个对象
const merge = (target, ...sources) => Object.assign(target, ...sources);
5.为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}
DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。
15. object.is
用来判断两个值是否相等
。之前我们判断两个值是否相等使用==(会自动转换数据类型)或者===(三等严格比较)
//==会进行类型转换
console.log(0==false)//true
//===严格,但是无法分辨+0和-0,NAN
console.log(0===false)//false
console.log(+0===-0)//true
// console.log(NAN === NAN)//error
//Object.is可以分辨+0和-0,NAN(但这里认为NaN===NaN,将NAN当做一种特别的数据类型了)
console.log(Object.is(+0,-0))//false
console.log(Object.is(NaN,NaN))//true
16. Proxy
vue3.0之前使用Object.defineProperty为属性添加读写
const person = {
name:'lcj',
age:18
}
const personProxy = new Proxy(person,{//为person创建代理对象,(代理的目标对象,代理的处理对象)
get(target,property){//监视属性读取访问(代理的目标对象,外部访问属性的属性名)
// console.log(target,property)//{ name: 'lcj', age: 18 } name
return property in target?target[property]:'default'//作为外部访问属性得到的结果
},
set(target,property,value){//代理目标对象,我们要写入的名称,我们要写入的值
if(property==='age'){//此处可以判断某些属性,是否符合条件
if(!Number.isInteger(value)){
throw new TypeError(`${value} is not int`)
}
}
target[property] = value
}
})
console.log(personProxy.name)//100 //lcj
console.log(personProxy.hobby)//default
personProxy.gender = true
console.log(personProxy)//{ name: 'lcj', age: 18, gender: true }
17. Proxy与defineProperty
- defineProperty只能监视对象的读写,Proxy更强大!(省略部分与例子16一样)
const personProxy = new Proxy(person,{
//此处省略...
//代理目标对象,所要删除属性名称
deleteproperty(target,property){
console.log('delete ',property)
delete target[property]
}
})
console.log(personProxy.name)//lcj
personProxy.gender = true
console.log(personProxy)//{ name: 'lcj', age: 18, gender: true }
delete personProxy.age
console.log(personProxy)//{ name: 'lcj', gender: true}
- proxy更好的支持数组对象的监听,重写数组的操作方法。思路:通过自定义的方法,覆盖掉数组原型上的push、pop等方法,以此来劫持这个方法调用的过程。
下面使用proxy对象监视数组
const list = []
const listProxy = new Proxy(list,{
set(target,property,value){
console.log('set',property,value)//
target[property] = value
return true //表示设置成功
}
})
// proxy 内部会根据push操作推算出来他所处的下标
listProxy.push(100)//set 0 100(0是数组当中的下标,100是0这个下标所对应的值)
listProxy.push(200)//set 1 200
- proxy以非侵入的方式监管了对象读写
下图为defineProperty写法:proxy写法更合理,proxy写法如上面例子
18. Reflect
Reflect属于一个静态类,不能通过 new Reflect() 的方式去构建一个实例对象,只能去调用这个静态类中的一些方法,如:Reflect.get(),和js中的Math对象一样。Reflect内部封装了一系列对对象的底层操作。原有14个方法,废弃了一个,剩下13个方法名与proxy当中处理对象里面的方法名完全一致,其实Reflect成员方法就是Proxy处理对象方法内部的默认实现。
const obj ={
foo:'123',
bar:'456'
}
const proxy = new Proxy(obj,{
get(target,property){
console.log('watch ')//watch
return Reflect.get(target,property)
}
})
console.log(proxy.foo)//123
统一提供了一套用于操作对象的API
const obj = {
name:'lcj',
age:18,
sex:'woman'
}
// console.log('name' in obj)//true
// console.log(delete obj['age'])//true
// console.log(Object.keys(obj))//[ 'name', 'sex' ]
console.log(Reflect.has(obj,'name'))//true
console.log(Reflect.deleteProperty(obj,'age'))//true
console.log(Reflect.ownKeys(obj))//[ 'name', 'sex' ]
相关链接:MDN(Reflect)
19. Promise
一种更优的异步编程解决方案,通过链式调用解决了传统异步编程回调嵌套过深的问题
20. class类
es6之前是通过定义函数和函数的原型对象来定义类
,想要定义一个Person类型,先定义Person函数作为这个类型的构造函数,构造函数中通过this访问实例对象,如果需要在实例之间共享一些成员,需要借助prototype(原型)去实现
function Person(name) { //
this.name = name
}
Person.prototype.say = function () {
console.log(`hi,my name is ${this.name}`)
}
let me = new Person('lcj')
me.say() //hi,my name is lcj
es6使用class关键词去声明一个类型
class Person {
constructor(name) {//构造器
this.name = name
}
say() {
console.log(`hi,my name is ${this.name}`)
}
}
let me = new Person('lcj')
me.say()//hi,my name is lcj
21. 静态方法
类型当中的方法分为实例方法和静态方法。实例方法需要这个类型构造的实例对象来调用,静态方法之间通过类型本身去调用。以前我们实现静态方法,我们直接在构造函数对象上去挂载方法
(js中函数也是对象,可以挂载属性和方法)。es6中新增添加静态成员的static关键词
。
class Person {
constructor(name) {//构造器
this.name = name
}
say() {
console.log(`hi,my name is ${this.name}`)
}
static create(name) {
return new Person(name)
}
}
let me = new Person('lcj')
me.say()//hi,my name is lcj
const tom = Person.create('tom') //create是静态方法
tom.say()//hi,my name is tom
22. 类的继承
extends实现继承,super始终指向父类,调用他就是调用父类的构造函数
class Student extends Person {
constructor(name, id) {
super(name)
this.id = id
}
hello() {
super.say()
console.log(`my schoolId is ${this.id}`)//my schoolId is 100
}
}
const s = new Student('xiaoming', '100')
s.say()//hi,my name is xiaoming
s.hello()//hi,my name is xiaoming
23. Set
Set数据结构,类似于数组,可以理解为集合,但是Set内部成员不可以重复
const s = new Set()
//add 返回集合对象本身,可以链式调用添加
s.add(1).add(2).add(3).add(4).add(2)
console.log(s)//Set { 1, 2, 3, 4 }【重复的值会被忽略调】
//使用forEach遍历数组
s.forEach(i => console.log(i))// 1 2 3 4
//使用for..of(es6)遍历数组
for (let i of s) {
console.log(i)// 1 2 3 4
}
//打印数组长度
console.log(s.size)//4
//判断是否含有某个值,有的话返回true否则false
console.log(s.has(100))//false
//判断是否删除了某个值,删除成功返回true否则false
console.log(s.delete(3))//true
console.log(s)//Set { 1, 2, 4 }
//清空数组
s.clear()
console.log(s)//Set {}
//常见用法,数组去重
const arr = [1, 2, 3, 1, 4, 3, 4, 5]
const res = new Set(arr)
console.log(res)//Set { 1, 2, 3, 4, 5 }
//Array.from将set结构数组转为真正数组
const res2 = Array.from(new Set(arr))
console.log(res2)//[ 1, 2, 3, 4, 5 ]
//...将set结构数组转为真正数组
const res3 = [...new Set(arr)]
console.log(res3)//[ 1, 2, 3, 4, 5 ]
24. Map
Map数据结构,类似于对象,本质上都是键值对集合。对象结构中的键名只能是字符串形式。
const obj = {}
obj[true] = 'val1'
obj[123] = 'val2'
obj[{ a: 1 }] = 'val3'
console.log(obj)//{ '123': 'val2', true: 'val1', '[object Object]': 'val3' }
console.log(obj[{}])//val3
console.log(obj['[object Object]'])//val3
我们设置的键并不是字符串,但是打印显示已经被转成了字符串(toString())作为键。而且上面可以看到不同的键名,打印出相同结果,这不合理。Map是严格意义上的键值对集合,用来映射两个任意数据之间的对应关系。
const m = new Map()
const tom = { name: 'tom' }
//可以用任意键名存数据
m.set(tom, 90)
console.log(m)//Map { { name: 'tom' } => 90 }
//用键名取数据
console.log(m.get(tom))//90
//判断是否存在
console.log(m.has(tom))//true
//forEach遍历(键值,键名)
m.forEach((value, key) => {
console.log(value, key)//90 { name: 'tom' }
})
//删除
console.log(m.delete(tom))//true
console.log(m)//Map {}
//清空所有键值
console.log(m.clear)//[Function: clear]
console.log(m)//Map {}
与对象最大区别:可以使用任意类型的值作为键名,而对象只能用字符串作为键名
25. Symbol
一种全新的原始数据类型
const cache = {}
//----- a.js ----
cache['foo'] = Math.random()
//----- b.js ----
cache['foo'] = 123
console.log(cache)
上面两个文件改了相同的地方(cache[‘foo’])的值,这时候访问就会出现不正确,以前的解决办法是不同的文件带自己的独名,如a.js文件里面cache[‘a_foo’],a.js文件里面cache[‘b_foo’].这样只是暂时规避了问题,并没有解决。我们可以使用Symbol解决,Symbol表示一个独一无二的值。
const s = Symbol()
//Symbol()函数创建symbol类型
console.log(s)//Symbol()
//symbol是原始数据类型
console.log(typeof s)//symbol
//每次创建都是一个新的
console.log(Symbol() === Symbol())//false
//可以接收一个参数判断是哪个Symbol来区分
console.log(Symbol('foo'))//Symbol(foo)
console.log(Symbol('foo2'))//Symbol(foo2)
console.log(Symbol('foo3'))//Symbol(foo3)
// 对象属性名可以是string和symbol两种类型
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = 'abc'
console.log(obj)//{ [Symbol()]: '123', [Symbol()]: 'abc' }
const obj2 = {
[Symbol()]: '123'
}
console.log(obj2)//{ [Symbol()]: '123' }
因为每次生成Symbol都是一个新的,所以可以避免重复键名问题
// a.js
const name = Symbol()
const person = {
[name]: 'lcj',
say() {
console.log(this[name])
}
}
// b.js
person.say()//lcj
最主要作用:为对象添加独一无二的属性名
,截止es2019共有7种数据类型, 将来会有BigIn类型存放更长的数字。
26. Symbol补充
console.log(Symbol() === Symbol())//false
因为每次生成Symbol都是一个新的,那么怎么复用呢?
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2)//true
Symbol.for
,symbol 内部维护了一个全局注册表,为字符串和Symbol值提供了一个一一对应的关系
,传入的不是字符串,内部会把他转成字符串
console.log(Symbol.for('true') === Symbol.for(true))//true
Symbol类型中提供了许多内置的Symbol常量,用来去作为内部方法的标识,这些标识符可以让自定义对象去实现一些js当中内置的接口
console.log(Symbol.iterator)//Symbol(Symbol.iterator)
console.log(Symbol.hasInstance)//Symbol(Symbol.hasInstance)
const obj = {}
console.log(obj.toString())//[object Object]
const obj1 = {
//避免和内部成员重复
[Symbol.toStringTag]: 'XObject'
}
//obj的toString()标签
console.log(obj1.toString()) //[object XObject]
Symbol作为对象属性名,特别适合作为对象的私有属性,常规访问无法访问到,只能访问到字符串类型属性名属性
const obj2 = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
for (let key in obj2) {
console.log(key)//foo
}
console.log(Object.keys(obj2))//[ 'foo' ]
console.log(JSON.stringify(obj2))//{"foo":"normal value"}
获取Symbol()属性名
console.log(Object.getOwnPropertySymbols(obj2))//[ Symbol() ]
Object.keys获取字符串类型属性名,getOwnPropertySymbols获取symbol类型属性名
27. for…of循环
js中for适合遍历普通数组, for..in 适合遍历键值对, 还有一些对象遍历方法,如 forEach,for ... of 作为遍历所有数据结构的统一方式
const arr = [100, 200, 300, 400]
for (const i of arr) {
console.log(i)//100 200 300 400
}
arr.forEach(i => {
console.log(i)//100 200 300 400
})
for...of 可以随时终止循环,forEach不能终止遍历,some返回true终止遍历,every返回false终止遍历
for (const item of arr) {
console.log(item)//100 200
if (item > 100) {
break;//可以随时终止循环
}
}
// arr.forEach()//不能终止遍历
// arr.some()//返回true终止
// arr.every()//返回false终止
遍历返回-set返回值
const s = new Set(['foo', 'bar'])
for (const i of s) {
console.log(i)//foo bar
}
遍历返回-map返回键值对,const [key, value] of m
拆解开
const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const i of m) {
console.log(i)//[ 'foo', '123' ] [ 'bar', '345' ]
}
for (const [key, value] of m) {
console.log(key, value)//foo 123 ;bar 345
}
出现问题了,下面代码报错,可见只能遍历数组形式,如果是对象会报错,看【28 可迭代接口】
const obj = { foo: 123, bar: 456 }
for (const item of obj) {//Error:obj is not iterable
console.log(item)
}
28. 可迭代接口
上面说到 for...of 数据的统一遍历方式
,es中能够表示有结构的数据类型越来越多,为了给各种各样的数据类型提供统一的遍历方式,es6提出Iterable接口
,实现Iterable(可迭代的)接口就是for…of 的前提
看数组原型对象__proto__,里面有个Symbol(Symbol.iterator)
方法,三个可以被for…of遍历的都有这个方法,这个方法的名字叫iterator。iterator约定我们对象当中必须要挂载iterator方法。
总结:所以可以直接被for…of遍历的数据都需要实现iterator接口,它的内部必须要挂载iterator的方法,这个方法需要返回一个带有next方法对象,不断调用next返回数据
const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()//得到这个对象的迭代器,调用迭代器的next方法得到数据
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 }
29. 实现可迭代接口
基础实现
const obj = {//实现可迭代接口,内部有[Symbol.iterator]
[Symbol.iterator]: function () {
return {//实现迭代器,实现用于迭代的next
next: function () {
return {
value:'lcj',
done:true
}
}
}
}
}
for (const i of obj) {
console.log(i)//循环体没有执行,因为done: true
}
改装
const obj = {//实现可迭代接口,内部有[Symbol.iterator]
store: ['foo', 'bar', 'baz'], //创建一个数组
[Symbol.iterator]: function () {
let index = 0 //记录下标
const self = this //存this
return {//实现迭代器,实现用于迭代的next
next: function () {
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++
return result
}
}
}
}
for (const i of obj) {
console.log(i)//foo bar baz
}
30. 迭代器模式
// 协同开发一个任务清单
const todos = {
life: ['吃饭', '睡觉'],
learn: ['语文', '英文']
}
for (const i of todos.life) {
console.log(i)//吃饭 睡觉
}
todos.life todos.learn高度耦合,todos变化,使用就要变化。解决:
const todos = {
life: ['吃饭', '睡觉'],
learn: ['语文', '英文'],
each: function (callback) {
const all = [].concat(this.life, this.learn)
for (const item of all) {
callback(item)
}
}
}
todos.each(item => console.log(item))//吃饭 睡觉 语文 英文
实现可迭代接口
const todos = {
life: ['吃饭', '睡觉'],
learn: ['语文', '英文'],
each: function (callback) {
const all = [].concat(this.life, this.learn)
for (const item of all) {
callback(item)
}
},
//实现可迭代接口
[Symbol.iterator]: function () {
const all = [...this.life, ...this.learn]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
for (const i of todos) {
console.log(i)////吃饭 睡觉 语文 英文
}
31. 生成器-Generator
避免异步编程中回调嵌套过深,提供更好的异步编程解决方案。
function* foo() {
console.log('lcj')
return 100
}
const result = foo()
console.log(result)//Object [Generator] {},内部也有一个next方法
console.log(result.next())// lcj ;{ value: 100, done: true }
//**** 配合 yield使用 ****
function* foo1() {
console.log('111')
yield 100
console.log('222')
yield 200
console.log('333')
yield 300
}
const generator = foo1()
console.log(generator.next())//111 { value: 100, done: false }
console.log(generator.next())//222 { value: 200, done: false }
console.log(generator.next())//333 { value: 300, done: false }
console.log(generator.next())//{ value: undefined, done: true }
生成器函数会自动帮我们返回一个生成器对象,调用这个函数的next这个函数才会开始执行,执行过程中一旦遇到yield关键词就会暂停下来,而且yield后面的值将会作为next的结果返回,继续调用next函数会从刚才暂停位置继续执行,直到执行完成。
32. 生成器应用
- 发号器
function* createIdMaker() {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)//1
console.log(idMaker.next().value)//2
console.log(idMaker.next().value)//3
console.log(idMaker.next().value)//4
- 使用Generator函数实现iterator方法
原来写法
const todos = {
life: ['吃饭', '睡觉'],
learn: ['语文', '英文'],
[Symbol.iterator]: function () {
const all = [...this.life, ...this.learn]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
改变后
const todos = {
life: ['吃饭', '睡觉'],
learn: ['语文', '英文'],
[Symbol.iterator]: function* () {
const all = [...this.life, ...this.learn]
for (const item of all) {
yield item
}
}
}
for (const i of todos) {
console.log(i)//吃饭 睡觉 语文 英文
}
二、 ES2016概述
相比于ES2015只增加了两处
- 1.数组实例对象的includes方法(Array.prototype.includes),原来
使用indexOf查找,但找不了NaN,includes可以,找到返回true
const arr = ['foo', 1, NaN, false]
//判断是否存在某个元素,不能查找NAN
console.log(arr.indexOf('foo'))
console.log(arr.indexOf(NaN))
console.log(arr.includes('foo'))
console.log(arr.includes(NaN))
- 2.指数运算符 **
console.log(Math.pow(2, 10))//1024
console.log(2 ** 10)//1024
三、 ES2017概述
-
1.Object对象的三个扩展方法
- Object.values
console.log(Object.values(obj)) //[ 'value1', 'value2' ]
Object.values(obj)返回所有值组成的数组,Object.keys(obj)返回所有键组成的数组`
- Object.entries
//以数组形式返回所有键值对 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 } //将对象转成map类型对象 console.log(new Map(Object.entries(obj))) //Map { 'foo' => 'value1', 'bar' => 'value2' }
- Object.getOwnPropertyDescriptors
//获取对象中属性完整信息 const p1 = { firstName: 'cj', lastName: 'l', get fullName() { return this.firstName + ' ' + this.lastName } } console.log(p1.fullName)//cj l const p2 = Object.assign({}, p1) p2.firstName = 'abc' console.log(p2)//{ firstName: 'abc', lastName: 'l', fullName: 'cj l' } //此时把fullName当成一个普通属性去复制了
解决
const p1 = { firstName: 'cj', lastName: 'l', get fullName() { return this.firstName + ' ' + this.lastName } } const des = Object.getOwnPropertyDescriptors(p1) console.log(des) // { firstName: // { value: 'cj', // writable: true, // enumerable: true, // configurable: true }, // lastName: // { value: 'l', // writable: true, // enumerable: true, // configurable: true }, // fullName: // { get: [Function: get fullName], // set: undefined, // enumerable: true, // configurable: true } } const p2 = Object.defineProperties({}, des) p2.firstName = 'abc' console.log(p2.fullName)//abc l
-
2.字符串padStart/padEnd(string.prototype.padStart / string.prototype.padEnd)
const books = {
html: 5,
css: 16,
js: 128
}
for (const [name, count] of Object.entries(books)) {
console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
}
// html------------|005
// css-------------|016
// js--------------|128
- 3.在函数参数中添加尾逗号,让代码管理工具更精确的找到代码变化
function foo(bar, baz, ) { }
const arr = [100, 200, 300,]
- 4.Async/Await 彻底解决了异步编程回调地狱问题,本质promise语法糖
评论列表(4条)
你怎么在忙什么
妈妈在吗最近在看
苦中苦看着看着看看这款裤子
没这么早妈妈在吗怎么这么说