谈谈ES6语法(汇总中篇)

简介: 本次的ES6语法的汇总总共分为上、中、下三篇,本篇文章为中篇。

数组扩展


数组扩展运算符


数组扩展运算符(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有两个区别:


  1. WeakSet对象中只能存放对象引用, 不能存放值, 而Set对象都可以.


  1. 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在涉及频繁增删键值对的场景下会有些性能优势`。
  • ...


如果你需要“键值对”的数据结构,MapObject更合适。


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差不多,就是没有了sizeforEach,因为其是不可枚举的。


Promise对象


Promise是异步编程的一种解决方案,比传统的解决方案“回调函数和事件”更合理和更强大。


Promise对象有以下两个特点:


  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。


  1. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种情况:从pending变成fulfilled(fulfilled也称resolved)和从pending变成rejected


用法


const promise = new Promise(function(resolve, reject) {
  // ...some code
  if(/* 异步操作成功 */) {
    resolve(value);
  } else {
    reject(error);
  }
})
复制代码


参数resolvereject是两个函数,由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




相关文章
|
6月前
|
前端开发 JavaScript 网络架构
谈谈你对 ES6 的理解
谈谈你对 ES6 的理解
29 1
|
4月前
ES6 扩展运算符 ...【详解】(含使用场景、实战技巧和范例、实现原理、错误用法)
ES6 扩展运算符 ...【详解】(含使用场景、实战技巧和范例、实现原理、错误用法)
45 5
|
JavaScript 前端开发 Java
|
前端开发 JavaScript 编译器
|
Web App开发 JavaScript 前端开发
|
存储 JavaScript 前端开发
|
前端开发 JavaScript Java
【编程指南】ES2016到ES2023新特性解析一网打尽(二)
【编程指南】ES2016到ES2023新特性解析一网打尽(二)
146 0
|
JSON JavaScript 前端开发
【编程指南】ES2016到ES2023新特性解析一网打尽(一)
【编程指南】ES2016到ES2023新特性解析一网打尽(一)
98 0
|
JavaScript 前端开发 网络架构
每天3分钟,重学ES6-ES12(四)函数的补充 展开语法
每天3分钟,重学ES6-ES12(四)函数的补充 展开语法
81 0
|
监控 前端开发 JavaScript
每天3分钟,重学ES6-ES12(十五)异步代码处理方案
每天3分钟,重学ES6-ES12(十五)异步代码处理方案
98 0