文章首发javascript
本文已经默认你已经知道generator
是什么以及for...of
和数据类型map
怎么用了。html
先来一道思考题:java
经过下面的变量ios
const students = {
xiaohong: {
age: '22',
fav: ['sleep', 'basketball'],
teachers: {
english: 'daming',
chinense: 'helios',
math: ['helios2', 'helios3']
}
},
xiaoming: {
age: '22',
fav: ['sleep', 'basketball', 'football'],
teachers: {
english: 'daming',
chinense: 'helios',
math: ['helios2', 'helios3']
}
},
}
复制代码
对于第一个问题来讲,咱们可使用各类循环语句: for/whilegit
for (let i =0 ;i < students[xiaoming].fav.length; i++) {
if (students[xiaoming].fav[i] === 'basketball') console.log(true)
}
let i = 0;
while(i++ < students[xiaoming].fav.length) { if (students[xiaoming].fav[i] === 'basketball') console.log(true) } 复制代码
for...ofes6
for (let item of students[xiaoming].fav ) {
if (item === 'basketball') console.log(true)
}
复制代码
那么对于第二个问题来讲,由于for/while
是不能遍历对象的,因此行不通,可是对象有一个专属的遍历方法 for...in
github
咱们来看一下怎么经过for...in来遍历:数组
for (let stu in students) {
console.log(stu)
}
复制代码
你可能会想了,经过for...in
去遍历数组会怎样呢? 咱们看一下经过for...in
去遍历:网络
for (let item in students[xiaoming].fav) {
console.log(item)
// 或者去判断
}
复制代码
哎呀,经过for...in
不也照样能实现数组的遍历么,那为何不归结到数组的遍历里面去呢! 这里面还有一些细节须要去了解(这也是上面的“对象有一个专属的遍历方法”为何加粗),咱们经过一段代码去解释:数据结构
const num = [5, 6, 7]
for (let i in num) {console.log(i + 1)}
// 01
// 11
// 21
复制代码
这是由于for-in
是为普通对象({key: value})设计的,因此只能遍历到字符串类型的键。
还有下面这个虽然不经常使用,可是也是不得不说的:
const arr = [5, 6, 7]
arr.foo = function() {}
for (let i in arr) {
console.log(i)
}
// 5
// 6
// 7
// foo !!!
复制代码
foo
属于arr上面的方法,被遍历出来是说的过去的。
那么用for...of
咱们来看看会怎么样
for (let stu of students){}
// Uncaught TypeError: students is not iterable
复制代码
is not iterable,这个iterable
是神马东西,咱们接下来下面一步步的看。
iterable是ES6对iteration(迭代/遍历)引入的接口。
若是一个对象被视为iterable(可迭代)那么它必定有一个Symbol.iterator
属性,这个属性返回一个iterator(迭代器)方法,这个方法返回一个规定的对象(这个后面会说)。也就是说iterable
是iterator
的工厂,iterable
可以建立iterator
。iterator
是用于遍历数据结构中元素的指针。
Axel Rauschmaye大神的图简直不能再清晰了。
数据消费者: javascript自己提供的消费语句结构,例如for...of循环和spread operator (...) 数据源: 数据消费者可以经过不一样的源(Array,string)获得供数据消费者消费的值;
让数据消费者支持全部的数据源这是不能够行的,由于还可能增长新的数据消费者和数据源。所以ES6引入了Iterable
接口数据源去实现,数据消费者去使用
可迭代协议(iterable protocol) 是容许js对象可以自定义本身的迭代行为。
简单的说只要对象有Symbol.iterator
这个属性就是可迭代的,咱们能够经过重写(一些对象实现了iterable,好比Array,string)/添加(对于没有实现iterable的对象好比object,能够添加这个属性,使之变为可迭代的)该熟悉使之变为可迭代的。
当一个对象须要被迭代(for...of 或者 spread operator )的时候,他的Symbol.iterator
函数被调用而且无参数,而后返回一个迭代器。
迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值。
当一个对象被认为是一个迭代器的时候,它会至少实现next()
方法,next()
方法返回两个属性value
(d迭代器返回的值)和done
(迭代时候已经结束)。
还有几个可选的方法能够被实现,具体请看:sec-iterator-interface
再文章的最开始咱们已经说了再前ES6的时候,如何去遍历。 如今咱们说说ES6新增的for...of
的做用。
在前面也已经说了,在ES6以前遍历object的时候用for...in
循环,for...in
会遍历对象上全部可枚举的值(包括原型(prototype)上的属性),好比下面这样:
function foo() {
this.name = 'helios'
}
foo.prototype.sayName = function() {
return this.name;
}
var o = new foo();
for (var i in o) {
console.log(i)
}
// name
// sayName
复制代码
若是咱们只想遍历对象自身的属性,可使用hasOwnProperty
,以下:
function foo() {
this.name = 'helios'
}
foo.prototype.sayName = function() {
return this.name;
}
var o = new foo();
for (var i in o) {
if (!o.hasOwnProperty(i)) continue;
console.log(i)
}
复制代码
若是咱们不想让一个对象的属性,在for...in
中不被遍历出来,但是使用Object.defineProperty
来定义对象上的属性是否可别枚举(更多的属性请看:Object.defineProperty()),具体以下面代码:
var obj = {name: 'helios'}
Object.defineProperty(obj, 'age', {
enumerable: false
})
for (var i in obj) {
console.log(i)
}
复制代码
在这一小节的最后咱们来讲说for...in
中的in操做符的含义:
prop in obj
:
咱们来看一组例子:
var o = {
name: 'heliso'
}
console.log('name' in o) // true
console.log(Symbol.iterator in o) // false
console.log('toString' in o) // true
复制代码
这个操做符虽然也适用于数组,可是尽可能仍是不要用在数组中,由于会比较奇怪,以下代码:
var arr = [6, 7,8]
console.log(7 in arr) // false
console.log(1 in arr) // true
console.log('length' in arr) // true
复制代码
主要是前两个比较奇怪对不对,由于对于数组prop
表明的是数组的索引而为其存在的值。 按照这样的思路,正在看的读者你能思考一下in
操做符在字符串中是怎么的模式么?
只要是实现了Interable
接口的数据类型都能被遍历。
javascript内部实现的有:
并非全部的iterable内容都来源于数据结构,也能经过在运行中计算出来,例如全部ES6的主要数据结构有三个方法可以返回iterable对象。
那就数据结构(数据源)去实现iterable就能够了。
用通俗的话说就是,你若是要遍历一个对象的话,有一下几个步骤:
Symbol.iterator
那就去实现Symbol.iterator
函数要返回一个iterator
iterator
返回一个对象,对象中至少要包含一个next方法来获取value
和done
上面咱们已经铺垫了这么多了,咱们说了javascript中object是不能被迭代了,由于没有实现iterable
,如今让咱们来实践一下让object变的可迭代。
下面这样写确定是不行的
const obj = {
name: 'helios',
age: 23
}
for (let it of obj) {
console.log(it)
}
// TypeError: obj is not iterable
复制代码
const obj = {
name: 'helios',
age: 23,
[Symbol.iterator]: function() {
let age = 23;
const iterator = {
next() {
return {
value: age,
done: age++ > 24
}
}
}
return iterator
}
}
复制代码
若是iterable
和iterable
是一个对象的话,上面的代码能够简化为:
function iterOver() {
let age = 23;
const iterable = {
[Symbol.iterator]() {return this},
next() {
return {
value: age,
done: age++ > 24
}
}
}
return iterable
}
for (let i of iterOver()) {
console.log(i)
}
复制代码
咱们若是每次想把一个不能迭代的对象变为可迭代的对象,在实现Symbol.iterator
的时候,每次都要写返回一个对象,对象里面有对应的next方法,next方法必须返回valua和done两个值。
这样写的话每次都会很繁,好在ES6提供了generator(生成器)能生成迭代器,咱们来看简化后的代码:
const obj = {
name: 'helios',
age: 23,
[Symbol.iterator]: function* () {
while (this.age <= 24) yield this.age++
}
}
for (let it of obj) {
console.log(it)
}
复制代码
知乎的这个回答是颇有水平的了:为何es6里的object不可迭代?
在stackoverflow中也有很高质量的回答:Why are Objects not Iterable in JavaScript?
在上面的回答中从技术方面说了为何Object不能迭代(没有实现iterable),还说了以什么样的方式去遍历Object是个难题,因此把如何迭代的方式去留给了开发者。
可是仍是要思考的一个问题就是:咱们真有必要去迭代对象字面量么?
想一下咱们要迭代对象字面量的什么呢?是keys
仍是values
亦或者是entries
,这三种方式在ES6提供的新的数据类型map里面都有呀,彻底是能够代替object的。在这里不说object
和map
的区别,只是说说在ES6之后咱们想把两个事物关联起来的时候,不必定要非得是用对象字面量
了,map
支持的更好一下。
对于何时用对象字面量(object)何时使用map咱们能够作一下总结:
对象字面量(object)应该是静态的,也就是说咱们应该已经知道了里面有多少个,和对象的属性有什么
使用对象字面量(object)的通常场景有:
JSON.stringify
和JSON.parse
时候其余的状况用map,其余的状况包括:
也并不说是map
就确定比对象字面量(object)好,map
也有以下的缺点:
JSON.stringify
/JSON.parse