Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

谈谈ES6语法(汇总中篇) #37

Open
reng99 opened this issue Jul 13, 2019 · 0 comments
Open

谈谈ES6语法(汇总中篇) #37

reng99 opened this issue Jul 13, 2019 · 0 comments
Labels
blog a single blog javascript javascript tag

Comments

@reng99
Copy link
Owner

reng99 commented Jul 13, 2019

本次的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]

顺便添加下es5的数组去重的方法吧:

ES5实现: 
[1,2,3,1,'a',1,'a'].filter(function(ele,index,array){ return index===array.indexOf(ele)})

需要留意的Set属性和方法有以下:

  • size: 返回实例成员的总数
  • add(value): 添加某个值,返回Set结构本身
  • delete(value): 删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value): 返回一个布尔值,表示该值是否为Set的成员
  • clear(): 清除所有成员,没有返回值。
  • key():返回键名的遍历器。
  • values(): 返回键值的遍历器。
  • entries(): 返回键值对的遍历器。
  • forEach(): 使用回调函数遍历每个成员

WeakSet

WeakSet结构与Set类似,也是有不重复元素的集合。但是它和Set有两个区别:

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

  2. 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(已失败)。

  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。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

参考和后话

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

更多的内容,请戳我的博客进行了解,能留个star就更好了💨

@reng99 reng99 added javascript javascript tag blog a single blog labels Jul 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blog a single blog javascript javascript tag
Projects
None yet
Development

No branches or pull requests

1 participant