这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
最近每天学习的时候,发现了一道很有趣的面试题
1 2 3 4 | 1. const [a, b] = { a: 100, b: 200 } 2.console.log(a) 3.console.log(b) 如何在不改变1的情况下,让代码不报错 |
这个时候,我突然想起来,之前看红宝书的时候,有过这么一篇内容
最后我的解题代码为
1 2 3 4 5 6 | Object.prototype[Symbol.iterator] = function(){ return Object.values( this )[Symbol.iterator] } const [a, b] = { a: 100, b: 200 } console.log(a) console.log(b) |
试了下,果然ok
接下来给大家分享下我在红宝书的基础上,在网上找到的几个基础资料,方便大家理解代码
1.什么是symbol
首先symbol是es6新增的类型,它是一个基本类型,symbol英文翻译是符号的意思。
在叙述symbol之前,推出一个概念,“以操作目标为程序本身的行为特性的编程,我们称为元编程。”
我们已知symbol是es6新增类型,按照逻辑推演:作为新增的类型,symbol一定是为了弥补了之前的不足,所以才出现的,是这样的吗?
首先我们说说它的特别之处:
1 2 3 4 | symbol属性值对应的值是唯一的,这解决了命名冲突的问题,类似于id的作用。 symbol值不能与其他数据进行计算,包括与字符串拼接。 for / in , for /of遍历时不会遍布symbol属性 这样看是不是觉得没什么特别的?最多也就弥补了命名冲突的问题。 |
下面推出第二个问题:
2.Iterotor是什么?
Iterator是一个遍历器的接口,是部署在数据结构上的,很多数据结构原生具备Iterator接口,这就意味着,我们不需要任何处理就可以使用for/of了,拥有symbol.Iterator属性返回的对象,都会在使用for/of时被当作Iterator接口当这个对象符合Iterator接口的标准时,for/of就可以完成任务,不符合就会报错!
刨除这个问题,我们知道symbol可以作为属性值存在。并且它具有唯一的特性,举个栗子:
1 2 3 4 5 6 7 | 直接 let s = Symbol();测试s就是Symbol类型了。怎么说他是唯一的呢? let s = Symbol(); let ss = Symbol(); s == ss ; 结果是 false ;或者 let s = Symbol( 'a' ); let ss = Symbol( 'a' ); s == ss ; 结果是 false 有人会好奇Symbol( 'a' )里面的参数a又是怎么回事呢?字符串a表示一种修饰,对你当前创建的Symbol类型的一种修饰,作为区分使用,否则当你创建多个Symbol数据时,容易混淆。 现在我们回过头说Iterotor是symbol的内置符号,而字符串a是自定义的符号。 |
3.symbol的内置符号symbol.Iterator,有什么用?
这个符号可以是任意对象上的一个专门属性,语言机制会自动的在这个属性上寻找一个方法,这个方法会构造一个迭代器来迭代这个对象的值,这个方法就是next方法,...展开和for/of循环会自动使用它,我们可以自定义symbol.iterator属性为任意对象值定义自己的迭代器逻辑,他将覆盖默认的迭代器。相当于是定义了一种元编程行为,提供给Javascript其他部分(也就是运算符和循环结构)在处理定义的对象时使用。
1 2 3 4 | 在Js中迭代器对象实现了可迭代协议,迭代器对象由Symbol.iterator属性的值返回。 Symbol.iterator属性的值是一个函数,它返回一个迭代器对象。 迭代器指的是拥有next方法的对象。 该next方法必须返回一个带有value和done的对象。 |
4.为什么会出现for..of?
为了理解条件语句,我们曾想象JavaScript解释器在源代码中会经过不同路径。而循环语句则是把这些路径弯曲又折回起点,以重复执行代码中的某部分。
es6定义了一个新循环语句:for/of。这种新循环虽然使用for关键字,但它们是完全不一同的两种循环,你说和for/in像不像?为什么?
for/of循环是专门用于可迭代对象的,什么是可迭代对象呢?
我们前文提到具有symbol.iterator属性的对象就是可以迭代的。而这个对象就是可迭代对象。
对象本身默认是不可迭代的。运行时尝试对常规对象使用for/of会抛出TypeError:
1 2 3 4 | let o={x:1,y:2,z:3}; for ( let element of o){ //抛出TypeError,因为o不是迭代的对象 console.log(element); } |
如果你想迭代对象,可以使用for/in循环,或者基于Object.keys()方法的结果使用for/of:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | let o={x:1;y:2,z:3}; let keys= "" ; for ( let k of Object .keys(o)){ keys+=k; } console.log(keys) //x y z /*这是因为Object.keys()返回一个对象属性名的数组,而数组是通过for/of来迭代的。也要注意,这种对象的键的迭代并不是实时的,在循环体内修改o不会影响迭代。如果你不在乎对象的属性,也可以像下面这样迭代每个键对应的值: */ let o={x:1;y:2,z:3}; let sum=0; for ( let v of Object .values(0)){ sum+=v; } console.log(sum) //6 如果你即想要对象的属性还想要属性的值,可以基于Object.entries()和解构赋值来使用 for /of: let pairs= "" ; for ( let [k , v] of Object .entries(o)){ pairs+=k + v; } console.log(pairs) //x 1 y 2 z 3 Object .entries()返回一个数组的数组,其中每个内部数组表示对象的一个属性的键值对。这里使用解构赋值把这些内部数组分开并且分别把它们的元素赋值给两个变量。 |
不知道你懂了没有,如果没有,那么接下来一篇,会加深你的印象
es6中有三类结构生来就具有Iterator接口:数组、类数组对象、Map和Set结构。
1 2 3 4 5 6 7 8 | var arr = [1,2,3,4]; let iterator = arr[Symbol.iterator](); console.log(iterator.next()); //{ value: 1, done: false } console.log(iterator.next()); //{ value: 2, done: false } console.log(iterator.next()); //{ value: 3, done: false } console.log(iterator.next()); //{ value: 4, done: false } console.log(iterator.next()); //{ value: undefined, done: true } |
至于对象没有布置iterator接口的原因,不知道最近大家有没有看根据《你一生的故事》拍成的电影“降临",片中出现的外星语言是一门非线性的语言。而我们说的数组,Map等结构中的成员都是有顺序的,即都是线性的结构,而对象,各成员并没有一个确定的顺序,所以遍历时先遍历谁后遍历谁并不确定。所以,给一个对象部署iterator接口,其实就是对该对象做一种线性转换。如果你有这种需要,就需要手动给你的对象部署iterator接口咯~
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | let obj = { data: [ 'hello' , 'world' ], [Symbol.iterator]() { const self = this ; let index = 0; return { next() { if (index < self.data.length) { return { value: self.data[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } }; |
可以看到,Symbol.iterator会返回一个对象,这就是一个遍历器对象,而作为遍历器对象,其必须具备的特征就是必须具备next()方法。
我们还可以据此实现指针结构的数据结构。具体略~
至于可以使用Array.from转换成数组的类数组对象,部署iterator有一种很简单的方法,即直接使用数组的[Symbol.iterator]接口。
1 | fakeArray.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; |
当然,不知道你们看到next是否想到了es6的一个新玩意儿,即Generator函数。用Generator函数来实现Symbol.iterator接口,事半功倍。
1 2 3 4 5 6 7 8 9 | var yieldIterator = {}; yieldIterator[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...yieldIterator] // [1, 2, 3] 注意,yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。 |
注意,yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
其次,其它调用到遍历器的操作还有解构赋值、扩展操作符、其它任何接受数组作为参数的场合,如:
1 2 3 4 5 | for ...of Array. from () Map(), Set(), WeakMap(), WeakSet()(比如) Promise.all() Promise.race() |
一旦当你给你的结构部署了iterator接口,那么恭喜你,你可以使用for...of来遍历你的结构了!
遍历器对象除了必须布置next方法以外,还有2个可选方法。return()和throw()。当一个解构在遍历的时候异常提前退出(比如break,continue或者出错)的时候,就会调用return方法,其次,return方法必须返回一个对象。
至于throw方法,则是用于抛出错误,Generator.prototype.throw这里不展开讲了,感兴趣的可以搜索一下。
for of循环有很多优点,比如不像for...in一样只遍历键名(甚至包括原型链上的键),而且不像foreach不能跳出循环。并且for...of为各种数据结构提供了一个统一的遍历方法。
foreach无法终止循环,当通过break来终止循环时会报错 Illegal break statement。
好了,相信到这里你应该对整个Symbol有一定的理解了,如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
__EOF__
![](/Files/SrcImg/?url=https://images.cnblogs.com/cnblogs_com/smileZAZ/2043435/o_211011061131user.jpg)