ES6快速入门(二)数据结构
ES6快速入门
一、解构
1. 对象解构
let person = { name: 'Tang', age: 28 }; //必须同名,必须初始化 let {name, age} = person; console.log(`Name: ${name} Age: ${age}`); //Name: Tang Age: 28
let person = { name: 'Tang', age: 28 }, name = 'Mao', age = '22'; //let {name, age} = person; 此时报错Identifier 'name' has already been declared //必须在圆括号内使用解构表达式,因为暴露的花括号会被解析为块声明语句。 ({name, age} = person); console.log(`Name: ${name} Age: ${age}`); //Name: Tang Age: 28 //可以在任何期望传值的位置使用解构表达式 function see(value) { for (let key in value) { console.log(value[key]); } } see({name, age} = person);//Tang 28
默认值:
let person = { name: 'Tang', age: 28 }; //默认值 /* let {name, age, phone} = person; console.log(phone); //undefined*/ //设置默认值 let {name, age, phone = 110} = person; console.log(phone); //110
赋值给不同名字的变量:
let person = { name: 'Tang', age: 28 }; //赋值给不同名的变量 let {name: realName, age: realAge, phone: realPhone = 119} = person; //寻找name属性并赋值给realName console.log(`Name: ${realName} Age: ${realAge} Phone: ${realPhone}`); //Name: Tang Age: 28 Phone: 119
2.数组解构
let colors = ['white','yellow','blue', 'red']; //值的选择和它们在数组中的位置有关,实际变量名称可以是任意的 let [ , ,first, second, third = 'green'] = colors; //可以忽略一些项 console.log(`First: ${first} Second: ${second} Third: ${third}`); //First: blue Second: red Third: green //和解构对象不同,解构赋值表达式只需要 let fruit = ['apple', 'orange', 'banana'], a = 'pear', b = 'grape'; [a, b] = fruit; console.log(`${a} ${b}`); //apple orange
// 在 ECMAScript 6 中交换变量的值 let a = 1, b = 2; [ a, b ] = [ b, a ]; console.log(a); // 2 console.log(b); // 1
嵌套:
let num = [1, 2, [3, 4, 5], 6, 7]; let [a, b, [c, d], e] = num; console.log(`${c} ${d} ${e}`);//3 4 6
剩余项:
let colors = [ "red", "green", "blue" ]; //剩余项必须是解构语句中的最后项并且不能在后面添加逗号 let [ firstColor, ...restColors ] = colors; console.log(firstColor); // "red" console.log(restColors.length); // 2 console.log(restColors[0]); // "green" console.log(restColors[1]); // "blue" //使用剩余项clone数组 let [...cloneColors] = colors; console.log(cloneColors); //[ 'red', 'green', 'blue' ]
3.混合解构
混合使用数组和对象解构。
let node = { type: "Identifier", name: "foo", loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 } }, range: [0, 3] }; let { loc: { start }, range: [ startIndex ] } = node; console.log(start.line); // 1 console.log(start.column); // 1 console.log(startIndex); // 0
4.参数解构
使用对象或数组解构的使用形式取代了命名参数
function setCookie(name, value, { secure, path, domain, expires }) { // code to set the cookie } setCookie("type", "js", { secure: true, expires: 60000 });
必选的参数解构:
调用函数时未给参数解构传值会抛出错误。例如:
setCookie("type", "js");//出错
js引擎的实际做法
function setCookie(name, value, options) { let { secure, path, domain, expires } = options; //=的右侧为undefined肯定会抛出错误 }
//可以通过设置默认值解决该问题 function setCookie(name, value, {secure, path, domain, expires} = {}) {}
//当然也可以对每个解构参数提供默认值 function setCookie3(name, value, { secure = false, path = '/tang', domain = 'jia.com', expires = new Date() } = {}) { }
二、符号与符号属性
在JS已有的基本类型上,ES6又新增了一种新增了一种基本类型:符号(Symbol)。
1.创建符号值
符号没有字面量,可以使用Symbol函数创建符号值。
三、Set与Map
1.ES6中的Set
一种无重复值的有序列表,Set允许对它包含的数据进行快速访问。
let set = new Set(); //不会使用强制类型转换来判断值是否重复 set.add(8); set.add('8'); //添加多个对象也不会被合并为一项,因为key1,key2不会被 //转换为字符串,所以不会被认为为一项 let key1 = {}; let key2 = {}; set.add(key1); set.add(key2); console.log(set.size);//4 //set构造器可以接收任意可迭代对象作为参数,包括数组 //并会自动除去数组中的重复值 let set2 = new Set([1, 1, 2, 3, 'tang', 'tang']); console.log(set2.size); //4 //使用has()方法测试值是否存在set中 console.log(set2.has(3)); //true //移除值 set2.delete(3); console.log(set2.has(3));//false //移除所以值 set.clear(); console.log(set.size); //0 //set上的forEach()方法 //为了与数组的forEach()方法中回调函数参数个数 //保持一致该方法也有三个参数,但前两参数都是当前set值 set2.forEach(function (key, value, thisSet) { console.log(`Key = ${key} Value = ${value} ${thisSet === set2}`); }); /*Key = 1 Value = 1 true Key = 2 Value = 2 true Key = tang Value = tang true*/ let p = { output(value) { console.log(value); }, //如果想在回调函数中使用this,可以给forEach第二个参数传入this doForEach(data) { data.forEach(function (value) { this.output(value); }, this) }, //或者使用箭头函数 doForEach2(data) { data.forEach((v) => this.output(v)); } };
Set的不能使用索引访问值,所以有时候我们需要将它转换为数组
let set = new Set([1, 2, 2, 3, 4, 5]), //使用扩展运算符将set转换为数组 arry = [...set]; console.log(arry);//[ 1, 2, 3, 4, 5 ]
WeakSet
set 类型根据它存储对象的方式,也被称为 strong set。一个对象存储在 set 内部或存储于一
个变量在效果上是等同的。只要对该 Set 实例的引用存在,那么存储的对象在垃圾回收以释
放内存的时候无法被销毁 。
let set = new Set(), key = {}; set.add(key); console.log(set.size); // 1 // 销毁引用 key = null; console.log(set.size); // 1 // 重新获得了引用 key = [...set][0];
ES6中同时引进了weakset,该类型不允许存储原始值(非对象的值都不允许)而专门存储弱对象引用。
let set = new WeakSet(), key = {name: 'Tang', age: 22}; set.add(key); // console.log(set.size); undefined console.log(set.has(key)); //true key = null; console.log(set.has(key)); //false
另外,不是可迭代类型且没有size属性。
2. ES6中的map
ES6中的 map 类型包含一组有序的键值对,其中键和值可以是任何类型。键的比
较结果由 Object.is() 来决定,所以你可以同时使用 5 和 "5" 做为键来存储,因为它们是不同
的类型。这和使用对象属性做为值的方法大相径庭,因为对象的属性会被强制转换为字符串类型。
let map = new Map(), key1 = {}, //可以使用对象作为键 key2 = {}; //因为这些对象不会被强制转换为其他类型,所以是唯一的 map.set(key1, 'Guns'); map.set(key2, 'Flowers'); map.set('name', 'tang'); console.log(`key1 = ${map.get(key1)}; key2 = ${map.get(key2)}; name = ${map.get('name')}`); //key1 = Guns; key2 = Flowers; name = tang
map的方法有:has(), delete(), clear()
和 set 类似,你可以将数据存入数组并传给 Map 构造函数来初始化它。数组中的每一项必须
也是数组,后者包含的两项中前者作为键,后者为对应值。因此整个 map 被带有两个项的数组所填充。
let map = new Map([['name', 'Tom'], ['age', 2]]); console.log(`It's name is ${map.get('name')} and age is ${map.get('age')}`);// It's name is Tom and age is 2
//map中的forEach方法,同样接收带有三个参数的回调函数 //1.map中的下一位置的值 2.该值对应的键 3,map本身 map.forEach((v, k, m) => { console.log(`key:${k} value:${v} size:${m.size}`); }); /*key:name value:Tom size:2 key:age value:2 size:2*/
WeakMap
无序键值对的集合,其中键必须是非 null 的对象,值可以是任意类型,其他与weakset类似。
无clear()方法。
四、迭代器与生成器
1. ES6中迭代器的定义
迭代器只是带有特殊接口的对象。所有迭代器对象都带有 next() 方法并返回一个包含两个属
性的结果对象。这些属性分别是 value 和 done,前者代表下一个位置的值,后者在没有更多
值可供迭代的时候为 true 。迭代器带有一个内部指针,来指向集合中某个值的位置。
2. ES6中的生成器
生成器是返回迭代器的函数。生成器函数由 function 关键字和之后的星号(*) 标识,同时还
能使用新的 yield 关键字。星号的位置不能论是放在 function 关键字的后面还是在它们插入空
格都是随意的 。
// 生成器 function *createIterator() { //ECMAScript 6 新引入的 yield 关键字指 //定迭代器调用 next() 时按顺序返回的值 yield 1; yield 2; yield 3; } // 调用生成器类似于调用函数,但是前者返回一个迭代器 let iterator = createIterator(); console.log(iterator.next().value); // 1 console.log(iterator.next().value); // 2 console.log(iterator.next().value); // 3 /* 或许生成器函数中最有意思的部分是,当执行流遇到 yield 语句时,该生成器就停止运转了。 例如,当 yield 1 执行之后,该生成器函数就不会执行其它任何部分的代码直到迭代器再次调 用 next() 。在那时,yield 2 会被执行。生成器函数在运行时能被中断执行的能力非常强大而 且引出了很多有意思用法*/
function* mkIterator(item = [1, 2, 3]) { for (let i = 0, len = item.length; i < len; i++) { yield item[i]; } } let iterator = mkIterator(); let nextObj = iterator.next(); while (!nextObj.done) { console.log(`The current value is ${nextObj.value}`); nextObj = iterator.next(); } /*The current value is 1 The current value is 2 The current value is 3*/
注意:yield关键字的使用范围
function *createIterator(items) { items.forEach(function(item) { // 语法错误:Unexpected identifier yield item + 1; }); }
对象中的生成器:
var o = { /*createIterator: function *(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } }*/ //简写: *createIterator(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } } }; let iterator = o.createIterator([1, 2, 3]);
3. 可迭代类型与for-of
可迭代类型是指那些包含 Symbol.iterator 属性的对象。该知名的symbol 类型
定义了返回迭代器的函数。在 ECMAScript 6 中,所有的集合对象(数组,set 和
map) 与字符串都是可迭代类型,因此它们都有默认的迭代器。可迭代类型是为了
ECMAScript 新添加的 for-of 循环而设计的。
for-lof 循环会在可迭代类型每次迭代执行后调用 next() 并将结果对象存储在变量中。
循环会持续进行直到结果对象的 done 属性为 true。
let map = new Map(); map.set('name', 'Jimmy'); map.set('age', 22); map.set('phone', 123); for(let v of map) { console.log(v); } /* * [ 'name', 'Jimmy' ] * [ 'age', 22 ] * [ 'phone', 123 ] * */
可以使用 Symbol.iterator 来访问对象默认的迭代器:
let iterator = map[Symbol.iterator](); console.log(iterator.next().value); //[ 'name', 'Jimmy' ]
确认一个对象是否可以迭代:
function isIterable(object) { return typeof object[Symbol.iterator] === "function"; }
创建可迭代类型:
let collection = { items: [], *[Symbol.iterator]() { for (let item of this.items) { yield item; } } }; collection.items.push(1); collection.items.push(2); collection.items.push(3); for (let x of collection) { console.log(x); }
4.内置的迭代器
1.集合迭代器
数组,map,set三种类型都有如下迭代器:
1)entries():每次调用时返回一个双项数组,对于数组来说该数组第一项为
数组索引值,对于set来说第一项是值,map第一项是键。
let arry = [2, 3, 5, 8]; let set = new Set([1, 6, 9]); let map = new Map(); map.set("one", 1); map.set("tow", 2); for (let entry of arry.entries()) { console.log(entry); } /*[ 0, 2 ] [ 1, 3 ] [ 2, 5 ] [ 3, 8 ]*/ for (let v of arry) { console.log(v); } /*2 3 5 8*/ for (let entry of set.entries()) { console.log(entry); } /*[ 1, 1 ] [ 6, 6 ] [ 9, 9 ]*/ for (let v of set) { console.log(v); } /*1 6 9*/ for (let entry of map.entries()) { console.log(entry); } /*[ 'one', 1 ] [ 'tow', 2 ]*/ for (let v of map) { console.log(v); } /*[ 'one', 1 ] [ 'tow', 2 ]*/
2)values迭代器:仅能返回集合内的值
for (let value of map.values()) { console.log(value); // 1 2 }
3)keys()迭代器:返回集合中的键
2.NodeList的迭代器
var divs = document.getElementsByTagName("div"); for (let div of divs) { console.log(div.id); }
5.扩展运算符与非数组可迭代类型
let map = new Map([ ["name", "Nicholas"], ["age", 25]]), array = [...map]; console.log(array); // [ ["name", "Nicholas"], ["age", 25]]
扩展运算符可以和任意的可迭代类型搭配并使用默认的迭代器来决定包含哪些值。
迭代器会按顺序返回数据集中所有的项并依次插入到数组当中。
6.迭代器高级用法
1.向迭代器传入参数
传递给next()的参数会作为已返回yield语句的值(因此首次调用next()传给它的任何参数都会被忽略)。
function *createIterator() { let first = yield 1; let second = yield first + 2; // 4 + 2 yield second + 3; // 5 + 3 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.next(5)); // "{ value: 8, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
2.在迭代器中抛出错误
通过使用 throw() 方法,迭代器可以选择在某一次迭代抛出错误。
function *createIterator() { let first = yield 1; let second = yield first + 2; // yield 4 + 2, 之后抛出错误 yield second + 3; // 不会执行 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.throw(new Error("Boom"))); // 由生成器抛出错误
3.包含return的生成器
既然生成器本质上是函数,你可以使用 return 语句来让它提前执行完毕并针对 next() 的调用
来指定一个返回值。在本章的大部分实例中,最后一次在迭代器上调用 next() 会返回
undefined,不过你可以像在普通函数中那样使用 return 语句来指定另外的返回值。生成器会
将 return 语句的出现判断为所有的任务已处理完毕,所以 done 属性会被赋值为 true,如果
指定了返回值那么它会被赋给 value 属性。
function *createIterator() { yield 1; return 42; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 42, done: true }" console.log(iterator.next()); // "{ value: undefined, done: true }"
function *createNumberIterator() { yield 1; yield 2; } function *createColorIterator() { yield "red"; yield "green"; } function *createCombinedIterator() { yield *createNumberIterator(); yield *createColorIterator(); yield true; } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "red", done: false }" console.log(iterator.next()); // "{ value: "green", done: false }" console.log(iterator.next()); // "{ value: true, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
7.异步任务
异步操作的传统做法是在它结束之后调用回调函数
let fs = require("fs"); //当该操作完成后,回调函数开始执行 fs.readFile("config.json", function(err, contents) { if (err) { throw err; } doSomethingWith(contents); console.log("Done"); });
一旦需要嵌套多个回调函数或者处理一大批异步任务时,代码会变得极其复杂。
1.任务运行器
原理:因为 yield 可以中断执行,并在继续运行之前等待 next() 方法的调用,
你可以不使用回调函数来实现异步调用,首先,你需要一个函数来调用生成器以便让迭代器开始运行:
function run(taskDef) { // 创建迭代器,使它们可以在别处使用 let task = taskDef(); // 任务开始执行 let result = task.next(); // 递归函数持续调用 next() function step() { // 如果任务未完成 if (!result.done) { if (typeof result.value === "function") { result.value(function(err, data) { if (err) { result = task.throw(err); return; } result = task.next(data); step(); }); } else { result = task.next(result.value); step(); } } } // 开始递归 step(); }