问渠那得清如许,为有源头活水来。——朱熹
来自《观书有感二首·其一》,作者为宋代的朱熹。原文如下,
“半亩方塘一鉴开,天光云影共徘徊。
问渠那得清如许?为有源头活水来。”
意思是,
半亩大的方形池塘像一面镜子一样打开,清澈明净,天光、云影在水面上闪耀浮动。
要问池塘里的水为何这样清澈呢?是因为有永不枯竭的源头源源不断地为它输送活水。
这是一首抒发读书体会的哲理诗,“半亩方塘一鉴开,天光云影共徘徊”,半亩的“方塘”不算大,只有半亩地的一个方方的池塘,但它像一面镜子那样地澄澈明净,“一鉴”的“鉴”,就是“镜”,照人的镜子,“镜”和“鉴”是一个意思。
“半亩方塘”像一面镜子那样打开了。“半亩方塘”虽然不算大,但它却像一面镜子那样地澄澈明净,“天光云影”都被它反映出来了。闪耀浮动,情态毕见。作为一种景物的描写,这也可以说是写得十分生动的。
这两句展现的形象本身就能给人以美感,能使人心情澄净,心胸开阔。这一种感性的形象本身,它还蕴涵着一种理性的东西。很明显的一点是,“半亩方塘”里边的水很深、很清,所以它能够反映“天光云影”;反之,如果很浅、很污浊,它就不能反映,或者是不能准确地反映。
内容较多,所以分成了两篇文章来发送,以下接着第1部分的内容。
11、模板语法和分隔符
ES6 中有一种十分简洁的方法组装一堆字符串和变量。
${ ... } 可以用来渲染一个变量,其中的` 作为分隔符。例如
1letuser ='Jerry';
2console.log(`Hi$!`);// Hi Jerry!
12、import 和 export
import指的是导入模块、export导出模块,import有点类似Java上的引入文件或包的意思。
例如,
1//全部导入
2importpeoplefrom'./example'
3//有一种特殊情况,即允许你将整个模块当作单一对象进行导入
4//该模块将对象所有属性导出
5import*asexamplefrom"./example.js"
6console.log(example.name)
7console.log(example.age)
8console.log(example.getName())
9//导入对象的部分属性
10importfrom'./example'
11//导出默认, 有且只有一个默认
12exportdefaultApp
13// 部分导出14exportclassAppextendComponent{};
以下是收集到的一些经验总结,
1.当用export default people导出时,就用 import people 导入(不带大括号)。
2.当用export name 时,就用import { name }导入(记得带上大括号)。
3.一个文件里,有且只能有一个export default。但可以有多个export。
4.一个文件里,既有一个export default people, 又有多个export name 或者 export age时,导入就用 import people, { name, age } 。
5.当一个文件里出现n多个 export 导出很多模块,导入时除了一个一个导入,也可以用import * as example进行全部导入。
13、 for...of VS for...in
for...of 用于遍历一个迭代器,如数组:
1letnicknames = ['di','boo','punkeye'];
2nicknames.size =3;
3for(letnicknameofnicknames) {
4console.log(nickname);
5}
//Result di, boo, punkeye
1for...in用来遍历对象中的属性:
2letnicknames = ['di','boo','punkeye'];
3nicknames.size =3;
4for(letnicknameinnicknames) {
5console.log(nickname);
6}
//Result: 0, 1, 2
简单点说就是for in是遍历键名,for of是遍历键值。
由于for of的这个特性,所以它可以实现对iterator对象的遍历,而for in就是简单的遍历了。
14、Promise
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
Promise对象有以下2个特点:
1.对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
2.一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved;从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象田静回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以把异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供了统一的接口,使得控制异步操作更加容易。
在promise之前代码过多的回调或者嵌套,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。
说白了就是用同步的方式去写异步代码。
发起异步请求
1fetch('/api/todos')
2.then(res=>res.json())
3.then(data=>({ data }))
4.catch(err=>({ err }));
另外,ES6 对 Promise 有了原生的支持,一个 Promise 是一个等待被异步执行的对象,当它执行完成后,其状态会变成 resolved 或者rejected。
1varp =newPromise(function(resolve, reject){
2if(/* condition */) {
3// fulfilled successfully
4resolve(/* value */);
5}else{
6// error, rejected
7reject(/* reason */);
8}
9});
每一个 Promise 都有一个 .then 方法,这个方法接受两个参数,第一个是处理 resolved 状态的回调,一个是处理 rejected 状态的回调:
1p.then((val)=>console.log("Promise Resolved", val),
2(err)=>console.log("Promise Rejected", err));
15、Symbol
Symbol 是一种新的数据类型,它的值是唯一的,不可变的。ES6 中提出 symbol 的目的是为了生成一个唯一的标识符,不过你访问不到这个标识符:
1varsym =Symbol("some optional description");
2console.log(typeofsym);// symbol
注意,这里 Symbol 前面不能使用 new 操作符。
如果它被用作一个对象的属性,那么这个属性会是不可枚举的:
1varo = {
2val:10,
3[Symbol("random") ]:"I'm a symbol",
4};
5console.log(Object.getOwnPropertyNames(o));// val
如果要获取对象 symbol 属性,需要使用Object.getOwnPropertySymbols(o)。
16、迭代器(Iterators)
迭代器允许每次访问数据集合的一个元素,当指针指向数据集合最后一个元素是,迭代器便会退出。它提供了 next() 函数来遍历一个序列,这个方法返回一个包含 done 和 value 属性的对象。
ES6 中可以通过 Symbol.iterator 给对象设置默认的遍历器,无论什么时候对象需要被遍历,执行它的 @@iterator 方法便可以返回一个用于获取值的迭代器。
数组默认就是一个迭代器:
1var arr = [11,12,13];
2var itr = arr[Symbol.iterator]();
3itr.next();//{value:11,done:false}
4itr.next();//{value:12,done:false}
5itr.next();//{value:13,done:false}
6itr.next();//{value:undefined,done:true}
你可以通过 [Symbol.iterator]() 自定义一个对象的迭代器。
17、Generators
生成器( generator)是能返回一个迭代器的函数。生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。
这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。
说说迭代器。当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。
1//生成器
2function*createIterator(){
3yield1;
4yield2;
5yield3;
6}
// 生成器能像正规函数那样被调用,但会返回一个迭代器
那生成器和迭代器又有什么用处呢?
围绕着生成器的许多兴奋点都与异步编程直接相关。异步调用对于我们来说是很困难的事,我们的函数并不会等待异步调用完再执行,你可能会想到用回调函数,(当然还有其他方案比如Promise比如Async/await)。
生成器可以让我们的代码进行等待。就不用嵌套的回调函数。使用generator可以确保当异步调用在我们的generator函数运行下行代码之前完成时暂停函数的执行。
那么问题来了,咱们也不能手动一直调用next()方法,你需要一个能够调用生成器并启动迭代器的方法。就像这样子的
1functionrun(taskDef){//taskDef即一个生成器函数
2//创建迭代器,让它在别处可用
3lettask = taskDef();
4//启动任务
5letresult = task.next();
6
7//递归使用函数来保持对 next() 的调用
8functionstep(){
9
10//如果还有更多要做的
11if(!result.done) {
12result = task.next();
13step();
14}
15}
16
17//开始处理过程
18step();
19}
生成器与迭代器最有趣、最令人激动的方面,或许就是可创建外观清晰的异步操作代码。你不必到处使用回调函数,而是可以建立貌似同步的代码,但实际上却使用 yield 来等待异步操作结束。
18、Map 和 WeakMap
ES6 中两种新的数据结构集:Map 和 WeakMap。事实上每个对象都可以看作是一个 Map。
一个对象由多个 key-val 对构成,在 Map 中,任何类型都可以作为对象的 key,如:
1varmyMap =newMap();
2varkeyString ="a string",
3keyObj = {},
4keyFunc = function () {};
5//设置值
6myMap.set(keyString,"value 与 'a string' 关联");
7myMap.set(keyObj,"value 与 keyObj 关联");
8myMap.set(keyFunc,"value 与 keyFunc 关联");
9myMap.size;// 3
10//获取值
11myMap.get(keyString);// "value 与 'a string' 关联"
12myMap.get(keyObj);// "value 与 keyObj 关联"
13myMap.get(keyFunc);// "value 与 keyFunc 关联"
WeakMap
WeakMap 就是一个 Map,只不过它的所有 key 都是弱引用,意思就是 WeakMap 中的东西垃圾回收时不考虑,使用它不用担心内存泄漏问题。
另一个需要注意的点是,WeakMap 的所有 key 必须是对象。它只有四个方法
1delete(key),has(key),get(key) 锟斤拷set(key, val)锟斤拷
2letw =newWeakMap();
3w.set('a','b');
4// Uncaught TypeError: Invalid value used as weak map key
5varo1 = {},
6o2 = function(){},
7o3 = window;
8w.set(o1,37);
9w.set(o2,"azerty");
10w.set(o3, undefined);
11w.get(o3);// undefined, because that is the set value
12w.has(o1);// true
13w.delete(o1);
14w.has(o1);// false
19、 Set 和 WeakSet
Set 对象是一组不重复的值,重复的值将被忽略,值类型可以是原始类型和引用类型:
1letmySet =newSet([1,1,2,2,3,3]);
2mySet.size;// 3
3mySet.has(1);// true
4mySet.add('strings');
5mySet.add({ a:1, b:2});
可以通过 forEach 和 for...of 来遍历 Set 对象:
1mySet.forEach((item) =>{
2console.log(item);
3// 1
4// 2
5// 3
6// 'strings'
7// Object { a: 1, b: 2 }
8});
9for(letvalueofmySet) {
10console.log(value);
11// 1
12// 2
13// 3
14// 'strings'
15// Object { a: 1, b: 2 }
16}
Set 同样有 delete() 和 clear() 方法。
WeakSet
类似于 WeakMap,WeakSet 对象可以让你在一个集合中保存对象的弱引用,在 WeakSet 中的对象只允许出现一次:
1varws =newWeakSet();
2varobj = {};
3varfoo = {};
4ws.add(window);
5ws.add(obj);
6ws.has(window);// true
7ws.has(foo);// false, foo 没有添加成功
8ws.delete(window);// 从结合中删除 window 对象
9ws.has(window);// false, window 对象已经被删除
20、 类
ES6 中有 class 语法。值得注意是,这里的 class 不是新的对象继承模型,它只是原型链的语法糖表现形式。
函数中使用 static 关键词定义构造函数的的方法和属性:
1classTask{
2constructor() {
3console.log("task instantiated!");
4}
5showId() {
6console.log(23);
7}
8staticloadAll() {
9console.log("Loading all tasks..");
10}
11}
12console.log(typeofTask);// function
13lettask =newTask();// "task instantiated!"
14task.showId();// 23
15Task.loadAll();// "Loading all tasks.."
类中的继承和超集:
1classCar{
2constructor() {
3console.log("Creating a new car");
4}
5}
6classPorscheextendsCar{
7constructor() {
8super();
9console.log("Creating Porsche");
10}
11}
12letc =newPorsche();
13// Creating a new car
14// Creating Porsche
extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。
当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。
有几点需要注意:
类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误;
在类中定义函数不需要使用 function 关键词;
总结,以上就是ES6常用特性,其实它的新特性远不止于此,但对于我们日常的开发来说,掌握以上内容足够了。如果想更加深入的学习,可以去学习阮一峰的ES6标准入门,据说这本书比较权威,我买了一本,还没有看完,感觉写的很不错,浅显易懂。网上也有电子版的,地址为http://es6.ruanyifeng.com/,大家可以在线学习。
领取专属 10元无门槛券
私享最新 技术干货