文章说明:本文章为拉钩大前端训练营所做笔记和心得,若有不当之处,还望各位指出与教导,谢谢 !
一、Symbol
-
Symbol 数据类型 表示一个独一无二的值,可以用来避免对象属性名重复的问题,还可以借助这种特性模拟对象的私有成员
console.log(Symbol() === Symbol()) //false
//================================================
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) //{[Symbol()]:'123',[Symbol()]:'456}//作为对象唯一属性
//============================================================
const obj = {
[Symbol()]:123
}
console.log(obj)//{[Symbol]:'123'}
//===================================================
const name = Symbol()
const person = {
[name]:'zce',
say(){
console.log(this[name])
}
}
person[Symbol()]// 无法访问 外部因为无法创建完全相同的Symbol,所以就无法直接访问到这个成员,这样该对象便实现了私有成员
person.say() //zce 内部可以拿到Symbol属性值的成员
Symbol 补充
// Symbol 补充
console.log(
//Symbol() === Symbol() false
// Symbol('foo') === Symbol('foo') false
)
const s1 = Symbol.for('foo') //该方法提供了字符串和Symbol一一对应的关系, 方法里维护了字符串和Symbol之间的对应关系
const s2 = Symbol.for('foo')
console.log(s1 === s2)// true
console.log(
Symbol.for(true) === Symbol.for('true') //true 如果传入的不是字符串,方法里会转为字符串
)
// Symbol 提供了很多内置的Symbol常量,用来作为内部方法的标识,这些标识符可以让自定义对象实现一些js当中内置的接口
console.log(Symbol.iterator)
console.log(Symbol.hasInstance)
const obj = {
[Symbol.toStringTag]:'XObject'
}
console.log(obj.toString())// [object Object] 把这样的字符串叫做对象的字符串标签,若想要对象的字符串标签
const obj = {
[Symbol()]:'symbol value',
foo:'normal value'
}
for(var key in obj){
console.log(key)
}
console.log(Object.keys(obj))//['foo] 获取不到Symbol这样的一个属性名
console.log(JSON.stringify(obj)) //{"foo":"normal value"} 序列化obj为json字符串,Symbol属性也会被忽略掉
console.log(Object.getOwnPropertySymbols(obj))// 这个方法类似于Object.keys方法,Object.keys方法只能够获取对象中字符串属性名,而这个方法获取到的全是Symbol属性名
更多详情请见:https://developer.mozilla.org/zh-CN/docs/Glossary/Symbol
二、for...of 循环
- for循环遍历普通的数组,for...in循环 适合遍历键值对,foreach 适合遍历数组对象,新出来的for...of循环可以作为遍历所有数据结构的统一方式
const arr = [100,200,300,400]
for(const item of arr){
console.log(item) //100 200 300 400
}
for(const item of arr){// for of 可以用break关键词随时终结循环
console.log(item)
if(item > 100){
break
}
}
arr.forEach() //forEach无法终止遍历
arr.some() //返回true终止遍历
arr.every()// 返回false终止遍历
-
伪数组对象也可用for..of遍历,例如arguments对象、dom操作时元素节点的列表
const s = new Set(['foo','bar'])
for(const item of s){// 可用for of循环set
console.log(item)//foo bar
}
- 遍历Map对象
const m = new Map()
m.set('foo','123')
m.set('bar','456')
for(const item of m){
console.log(item)// ['foo','123] ['bar','456'] 键和值放在数组中
}
for(const [key,value] of m){
console.log(key,value)// foo 123 bar 456
}
- 然而普通的对象无法循环,原因在下文
const obj = {foo:123,bar:456}
for(const item of obj){// obj不可被迭代
console.log(item)// obj is not iterable
}
迭代器iterator
ES中能够表示有结构的数组类型越来越多,现在新增了Set、Map,为了给各种各样的数据结构提供统一遍历方式,ES2015提供了Iterable接口,理解为规格标准,实现了统一接口。实现Iterable接口就能够被for...of遍历。[],Set,Map 三个可以被for...of遍历的对象当中都有这么Symbol(Symbol.iterator)方法,可被循环遍历的对象中必须挂载iterator方法
在这个迭代器当中内部应该是维护了一个数据指针,每调用一次next,指针都会往后移一位,done属性的作用是表示内部所有的属性遍历完了。
要能够被for...of 循环,必须实现iterator的接口,内部必须挂载一个iterator方法,这个方法里面要返回带有next()方法的对象,不断调用这个方法就能实现内部所有的数据的遍历。
const Set = new Set(['foo','bar','baz'])
const iterator = Set[Symbol.iterator]()// 获取Set里面的迭代器
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}
三、实现上述的可迭代接口
-
对象实现的了Iterable接口,则可用for...of循环
const obj = {//外层为自定义对象,实现了可迭代接口,称为Iterable
[Symbol.iterator]:function(){//内部必须有一个用于返回迭代器的iterator方法
return{// iterator 方法返回的这个对象实现了迭代器接口,这个接口约定了必须要有一个用于迭代的next方法
next:function (){
return{// next方法当中返回的对象,这个对象实现的是迭代结果接口,称为IterationResult,这个对象内部必须要有value属性,表示当前被迭代的数据,它的值可以使任意类型,
//除此之外还必须要有个done的布尔值用来表示 迭代是否结束
value:'zce',
done:true
}
}
}
}
}
for(const item of obj){
console.log('循环体') //未报错
}
-
普通对象添加迭代接口并放入数据后可用for...of循环出来
const obj = {
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 obj){
console.log('循环体',item) //循环体 foo 循环体 bar 循环体 baz
}
四、迭代器设计模式
-
迭代器模式核心就是对外提供统一遍历接口,让外部不用再去关心内部的结构是怎样的
// 场景:你我协同开发一个任务清单应用
// 我的代码 =================================
const todos ={
life:['吃饭','睡觉','打豆豆'],
learn:['语文','数学','外语'],
work:['喝茶'],
each:function(callback){ // 对外提供了统一遍历的接口
const all = [].concat(this.life,this.learn,this.work)
for(const item of all){
callback(item)
}
},
/*迭代器模式核心就是对外提供统一遍历接口,让外部不用再去关心内部的结构是怎样的,只不过上面的each方法只适用于当前对象结构.
ES2015中的迭代器,它是语言层面实现的迭代器模式,适用于任何数据结构,只需要通过代码实现iterator方法实现它的迭代逻辑就可以了 */
[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)
}
五、ES2015 生成器
-
ES2015 生成器 引入的目的就是避免异步编程中回调嵌套过深,提供更好的异步编程解决方案
Generator 函数
//Generator 函数
function * foo(){
console.log('zce')
return 100
}
const result = foo()
console.log(result)// Object [Generator] {} 打印了一个生成器对象
console.log(result.next()) // zce {value:100,done:true} 该返回值和迭代器的返回值一样,放到value当中
- yield 语句跟return非常类似,但是不会结束掉方法的执行
-
调用这个对象的next方法才会让这个函数的函数体开始执行,执行过程中遇到yield就会被暂停下来,yield后面的值会作为next的结构返回,继续调用的话,就会暂停的位置继续执行,如此下去,最后done变为true
function * foo(){
console.log('1111')
yield 100 //yield 语句跟return非常类似,但是不会结束掉方法的执行
console.log('2222')
yield 200
console.log('3333')
yield 300
}
const generator = foo()
//调用这个对象的next方法才会让这个函数的函数体开始执行,执行过程中遇到yield就会被暂停下来,yield后面的值会作为next的结构返回,继续调用的话,就会暂停的位置继续执行,如此下去,最后done变为true
console.log(generator.next()) // 1111 {value:100,done:false}
console.log(generator.next()) // 1111 {value:100,done:false} 222 {value:200,done:false}
console.log(generator.next()) // 1111 {value:100,done:false} 222 {value:200,done:false} 333 {value:300,done:false}
console.log(generator.next()) // 1111 {value:100,done:false} 222 {value:200,done:false} 333 {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)//1
console.log(idMaker.next().value)//2
console.log(idMaker.next().value)//3
console.log(idMaker.next().value)//4
// 案例2 使用Generator 函数实现 iterator 方法
const todos = {
life:['吃饭','睡觉','打豆豆'],
learn:['语文','数学','外语'],
life:['喝茶'],
[Symbol.iterator]:function * (){
const all = [...this.life,...this.learn,...this.work]
// let index = 0 //不再需要手动返回一个迭代器对象,直接在iterator方法内部遍历所有的成员,通过yield去返回每一个被遍历到的对象
// return {
// next:function(){
// return{
// value:all[index],
// done:index ++ >= all.length
// }
// }
// }
for(const item of all){
yield item
}
}
}
for(const item of todos){
console.log(item)
}
六、ES2016
//ECMAScript 2016 相对于2015只是一个小版本,仅包含两个小功能
//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))//这种方式判断会存在一个问题,不能够查找数组当中的NaN
console.log(arr.includes('foo')) //这种能直接返回布尔值,存在或者不存在
// 指数运算符 ---------------------------------------
console.log(Math.pow(2,10)) //1024 指数运算借助于Math.pow,2是底数,10是指数
console.log(2 ** 10) //1024
七、ES2017
-
Object.values 详情见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/values
console.log(Object.values(obj)) //['value1','value2'] 将对象的所有值放到数组里
-
Object.entries 详情见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/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
}
console.log(new Map(Object.entries(obj))) //用entries方法转化为Map对象 Map{ 'foo' => 'value1','bar' => 'value2' }
-
Object.getOwnPropertyDescriptors 获取对象当中属性的完整描述信息, 更多详情见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors
const p1 = {
firstName:'Lei',
LastName:'Wang',
get fullName(){
return this.firstName + '' +this.LastName
}
}
console.log(p1.fullName)
const p2 = Object.assign({},p1)
p2.firstName = 'zce'
console.log(p2.fullName) // Lei Wang Object.assign 复制时只是把fullName当作一个普通的属性去复制,才会出现这种情况
const descriptors = Object.getOwnPropertyDescriptors(p1)
// console.log(descriptors)
const p2 = Object.defineProperties({},descriptors)
p2.firstName = 'zce'
console.log(p2.fullName) // zce Wang
-
String.prototype.padStart/ String.prototype.padEnd 用给定的字符串去填充目标字符串的开始或结束位置,直到字符串达到指定位置,详情见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/padStart,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
const books ={
html:5,
css:6,
javascript:128
}
for(const [name,count] of Object.entries(books)){
console.log(name,count) // name 5 //css 16 //javaScript 128
}
for(const [name,count] of Object.entries(books)){
console.log(`${name.pad}`) // html------------|005 //css-------------|016 //javascript------|128
}
-
在函数参数中添加尾逗号
function foo(
bar,
baz,
){
}