数组扩展
数组扩展运算符
数组扩展运算符(spread)是三个点(...
)。它好比rest参数的逆运算,将一个数组转为用空格分隔的参数序列。
console.log(...[1, 2, 3]); // 1 2 3 console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5 复制代码
⚠️rest参数是运用在函数参数上的,将函数参数转换为数组的形式,如下:
function fn(...values) { console.log(values); // ['jia', 'ming'] } fn('jia', 'ming'); 复制代码
下面我们结合数组扩展运算符和rest参数来实现一个类似call
的方法call2操作:
Function.prototype.call2 = function(context, ...args){ // 这里使用到rest参数 context = context || window; // 因为传递过来的context有可能是null context.fn = this; // 让fn的上下文为context const result = context.fn(...args); // 这里使用了数组扩展运算符 delete context.fn; return result; // 因为有可能this函数会有返回值return } var job = 'outter teacher'; var obj = { job: 'inner teacher' }; function showJob() { console.log(this.job); } showJob(); // outter teacher showJob.call2(obj); // inner teacher 复制代码
复习一下,我们把var job = 'outter teacher'
改为let job = 'outter teacher'
后,showJob()
会输出什么?
答案是undefined
。在前一篇中也提到过,ES6语法声明的变量是不会挂载在全局对象上的~
Array.from()
Array.from
方法用于将两类对象
转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(对象包括ES6新增的数据结构Set和Map)。
// 类数组转化成数组 let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 } // ES5的写法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] // ES6的写法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'] 复制代码
Array.of()
Array.of()
方法用于将一组值,转换为数组。
let arr = Array.of(2, 3, 'reng'); console.log(arr); // [2, 3, 'reng'] console.log(arr.pop()); // reng 复制代码
Array.of
基本上可以弥补Array()或new Array()
带来的因为参数个数导致的不同行为。Array.of
基本上可以替代它们两个了。
Array.of(); // [] Array.of('reng'); // ['reng'] Array.of(2, 'reng'); // [2, 'reng'] 复制代码
数组中还有其它有用的方法:
- copyWithin(target, start = 0, end = this.length): 拷贝指定数组的范围值
- find(fn): 用于查找第一个符合条件的数组成员,没有返回undefined
- findIndex(fn): 用于查找第一个符合条件的数组成员的位置,没有返回-1
- entries(): 对键值对的遍历
- keys(): 对键的遍历
- values(): 对值的遍历
- includes(el): 返回一个布尔值,表示某个数组是否包含给定的值,与字符串的
include(el)
方法相似 - flat(num): 将嵌套的数组拉平,num是遍历的深度
[1, [2, [3]]].flat(Infinity); // [1, 2, 3] 复制代码
有这么一个需求:将数组[[2, 8], [2], [[4, 6], 7, 6]]转成一维且元素不重复的数组。
我们的实现方案如下:
let arr = [[2, 8], [2], [[4, 6], 7, 6]]; console.log([...new Set(arr.flat(Infinity))]); // [2, 8, 4, 6, 7] 复制代码
对象扩展
属性名表达式
ES6允许字面量定义对象时,把表达式放在方括号内:
let lastWord = 'last word'; const a = { 'first word': 'hello', [lastWord]: 'world', ['end'+'symbol']: '!' }; a['first word'] // 'hello' a[lastWord] // 'world' a['last word'] // 'world' a['endsymbol'] // '!' 复制代码
对象的扩展运算符
上面整理数组扩展内容的时候,提到了数组的扩展运算符。ES2018
将这个运算符引入了对象~
let z = { a: 3, b: 4 }; let n = { ...z }; // 关键点 n // { a: 3, b: 4 } 复制代码
对象中某些新增的方法
- Object.is(arg1, arg2): 比较两个值是否严格相等,与
===
行为基本一致 - Object.assign(target, source1, ...): 用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。属于浅拷贝
- Object.keys(obj): 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名
- Object.values(obj): 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
- Object.entries(obj): 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 }; Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ] 复制代码
Set和Map数据结构
Set
Set
翻译出来就是集合,有元素唯一性的特点。
在数组去重的场景上很有用处:
// 去除数组的重复成员 [...new Set(array)] // 如 console.log([...new Set([2, 2, 3, 2])]); // [2, 3] 复制代码
需要留意的Set属性和方法有以下:
- size: 返回实例成员的总数
- add(value): 添加某个值,返回Set结构本身
- delete(value): 删除某个值,返回一个布尔值,表示删除是否成功。
- has(value): 返回一个布尔值,表示该值是否为Set的成员
- clear(): 清除所有成员,没有返回值。
- key():返回键名的遍历器。
- values(): 返回键值的遍历器。
- entries(): 返回键值对的遍历器。
- forEach(): 使用回调函数遍历每个成员
WeakSet
WeakSet
结构与Set
类似,也是有不重复元素的集合。但是它和Set有两个区别:
WeakSet
对象中只能存放对象引用, 不能存放值, 而Set
对象都可以.
WeakSet中
对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样, WeakSet 对象是无法被枚举的, 没有办法拿到它包含的所有元素。
var ws = new WeakSet(); var obj = {}; var foo = {}; ws.add(window); ws.add(obj); ws.has(window); // true ws.has(foo); // false, 对象 foo 并没有被添加进 ws 中 ws.delete(window); // 从集合中删除 window 对象 ws.has(window); // false, window 对象已经被删除了 ws.clear(); // 清空整个 WeakSet 对象 复制代码
WeakSet 没有size属性,没有办法遍历它的成员。
Map
Map
对象保持键值对。任何值(对象或者原始值)都可以作为一个键或一个值。
Object和Map的比较:
- 一个
Object
的键只能是字符串或者Symbols
,但一个Map
的键可以是任意值,包括函数、对象、基本类型。 Map
中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map
对象是按插入的顺序返回键值。Map
在涉及频繁增删键值对的场景下会有些性能优势`。- ...
如果你需要“键值对”的数据结构,Map
比Object
更合适。
const set = new Set([ // 数组转换为map ['foo', 1], ['bar', 2] ]); const m1 = new Map(set); m1.get('foo') // 1 const m2 = new Map([['baz', 3]]); const m3 = new Map(m2); m3.get('baz') // 3 复制代码
Map拥有的属性和方法和Set相似,多出了些:
- set(key, value):set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
- get(key):get方法读取key对应的键值,如果找不到key,返回undefined
WeakMap
WeakMap
结构与Map
结构类似,也是用于生成键值对的集合。但是有两点区别:
WeakMap
只接受对象作为键名(null除外),不接受其他类型的值作为键名。WeakMap
的键名所指向的对象,不计入垃圾回收机制。和WeakSet
相似啦。
属性方法啥的跟Map
差不多,就是没有了size
和forEach
,因为其是不可枚举的。
Promise对象
Promise
是异步编程的一种解决方案,比传统的解决方案“回调函数和事件”更合理和更强大。
Promise
对象有以下两个特点:
- 对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种情况:从pending
变成fulfilled(fulfilled也称resolved)
和从pending
变成rejected
。
用法
const promise = new Promise(function(resolve, reject) { // ...some code if(/* 异步操作成功 */) { resolve(value); } else { reject(error); } }) 复制代码
参数resolve
和reject
是两个函数,由JavaScript引擎提供,不用自己部署。
Promise
实例生成之后,可以使用then
方法分别指定resolved
状态和rejected
状态的回调函数。
promise.then(function(value) { // success }, function(error) { // failure }); 复制代码
我们来粘贴个简单例子:
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); }); } timeout(100).then((value) => { console.log(value); // 100ms后输出'done' }); 复制代码
嗯~我们顺道来复习下setTimeout
的第三个参数。哦😯,不,是第三个,第四个...
var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]); 复制代码
function
是你想要在到期时间(delay
毫秒)之后执行的函数。delay
是可选语法,表示延迟的毫秒数。param1, ..., paramN
是可选的附加参数,一旦定时器到期,它们会作为参数传递给function
那么,到这里你理解了上面的例子为什么在100ms
后输出done
了嘛💨
详细的setTimeout
信息,请戳MDN的setTimeout。
简单的例子看完了,看下我们在工作中使用得比较多的请求接口的例子:
const getJSON = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if(this.readyState !== 4) { return; } if(this.status === 200) { resolve(this.response); // this.response作为参数传给then中的json } else { reject(new Error(this.statusText)); } }; const client = new XMLHttpRequest(); client.open('GET', url); client.onreadystatechange = handler; client.responseType = 'json'; client.setRequestHeader('Accept', 'application.json'); client.send(); }); return promise; }; getJSON('/post.json').then(function(json) { console.log('Contents: '+ json); }, function(error) { console.log('error happen ', error); }); 复制代码
catch方法
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
p.then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); // promise中任何一个抛出错误,都会被最后一个catch捕获 // 等同于 p.then((val) => console.log('fulfilled:', val)) .then(null, (err) => console.log('rejected:', err)); 复制代码
finally方法
Promise.prototype.finally()
方法(其不接受任何参数)用于指定不管Promise
对象最后状态如何,都会执行的操作。
该方法是 ES2018 引入标准的。
语法:
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···}); 复制代码
Promise.all
构造函数方法Promise.all
方法用于将多个Promise
实例,包装成一个新的Promise
实例。
const p = Promise.all([p1, p2, p3]); 复制代码
上面代码中,Promise.all
方法接受一个数组作为参数,p1, p2, p3
都是Promise
实例。如果不是会调用Promise.resolve
方法,具体看文档。
// 生成一个Promise对象的数组 const promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON('/post/' + id + ".json"); }); Promise.all(promises).then(function (posts) { // ... }).catch(function(reason){ // ... }); 复制代码
上面代码中,promises
是包含 6 个 Promise
实例的数组,只有这6个实例的状态都变成fulfilled
,或者其中有一个变为rejected
,才会调用Promise.all
方法后面的回调函数。
⚠️注意,如果作为参数的Promise
实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。所以使用Promise.all()
别手痒在每个实例promise内添加错误捕获。
一道练手题
需求:使用promise改写下面的代码,使得输出的期望结果是每隔一秒输出0, 1, 2, 3, 4, 5
,其中i < 5
条件不能变
for(var i = 0 ; i < 5; i++){ setTimeout(function(){ console.log(i); },1000) } console.log(i); 复制代码
我们直接上使用promise改写的代码吧~
const tasks = []; // 存放promise对象 for(let i = 0; i < 5; i++){ tasks.push(new Promise((resolve) => { setTimeout(() => { console.log(i); resolve(); }, 1000 * i); })); } Promise.all(tasks).then(() => { setTimeout(() => { console.log(tasks.length); }, 1000); }); // 每隔一秒输出 0, 1, 2, 3, 4, 5