
这篇文章介绍了5个 ES6 特性,使你的 JavaScript 代码变的更好。不用说,咱们大多数前端开发工程师很是关注 JavaScript 的性能和特性,这就是为何 ES6 对于咱们来讲是如此使人兴奋。css
ES6的变化是巨大的,是使人兴奋的,也有使人困惑的地方。在本文中,我将介绍5个 ES6 的新特性,您能够当即使用它们来加强你的 JavaScript代码,以及哪些特性不建议使用。前端
Ecma是什么?
JavaScript 多年来已经有了不少版本,但整体而言,进化很缓慢,直到最近。 我以为在过去的两年里,发生了比过去二十年更多的变化。 ES6 是 EcmaScript 6 的简称。它也称为 EcmaScript 2015 或 ES 2015。这些都是“新的Javascript”的不一样名称。java
我不彻底肯定为何最近 JS 改变如此之大,但彷佛是由于主要的 JavaScript 引擎厂商终于有兴趣推进该语言的发展了。es6
此外,像 TypeScript 这样的 转移器 的出现,使得在将其用于浏览器以前,可使用新的语言特性。这两个组合将大大推进 JavaScript 的发展。web
JS很重要,由于它是 web 的构造者,并愈来愈多的服务器端使用开始 Node ,愈来愈多的人使用 Cordova,React Native 和 Electron 来开发手机和桌面应用程序。typescript
简而言之:JS无处不在。shell
因此重要的是咱们来推进它。不演化的语言开始走向死亡。改善语言意味着咱们能够改进咱们的代码。咱们的应用程序能够更少地出现错误。其中一些特性能使代码更快地运行。因此让咱们从变量开始,来一块儿来看看 ES6 的新特性。数据库
变量的问题
在 JavaScript 中,当您要建立一个变量时,可使用 var
关键字。 var
很是强大,但它有一些问题。首先,变量老是不断变化的。没有办法在 JavaScript 中定义一个真正的常量。您能够经过建立具备不可变属性的对象来近似伪造常量。您能够覆盖该属性的setter,使其不能由外部代码设置,但它须要不少额外的代码,须要一个完整的对象来代替一个简单的变量。json
JavaScript 代码:
- var VARS = {
- foo
- set = function() { }
- get = function() { return 42 }
- }
- VARS.foo = 42; // now I have a constant
常规Javascript变量还有一个做用域问题。 看看这段代码。
JavaScript 代码:
- function badCode() {
- if(foo) {
- var x = 5;
- }
- if(bar) {
- console.log(x);
- }
- }
在函数体内经过 var
声明的变量,在函数体内全局可用, var
声明的变量没有块级做用域。若是你认为上面的 console.log
语句不能正常执行,由于在 bar
定义在不一样的块中。那么我告诉你,在相似于 java 或 C# 等语言中,你是对的,可是在 JavaScript 中,var
声明的变量,在函数体内全局可用,而不是在语句块中可用。
当咱们添加变量提高时,它会变得更糟。 考虑这个代码:
JavaScript 代码:
- function badCode() {
- console.log("foo is",foo);
- var foo = 'bar';
- }
我在 foo
定义以前使用它。 这段代码意味着什么? JavaScript 引擎会将变量声明提高到函数块的顶部。 但变量提高很容易引起一些难以诊断的细微错误。
看看这个 for
循环:
JavaScript 代码:
- for(var i=0; i<5; i++) {
- console.log(i);
- }
- console.log(i);
该变量 i
仅在循环体内使用,但我仍然能够在循环体外部引用它。 这可能会引起大量的 bug 。
好消息是咱们不用再必须使用 var
定义变量了,咱们可使用 const
和 let
。
介绍 const 和 let
新的关键字 const
彻底符合这个名字的意思。它能够定义一个真正的常量。若是您尝试从新设置常量值,您将收到运行时错误。更好的是,Lint语法检查工具能够在编译时检测出这种错误,因此您能够在开发时较早的发现这种错误。
另外一种新的变量定义方式是使用 let
关键字。let
就像 var
,可是它是块级做用域而不是函数做用域。下面代码console.log(x)
会报错:
JavaScript 代码:
- function goodCode() {
- if(foo) {
- let x = 5;
- }
- if(bar) {
- console.log(x); //error
- }
- }
让咱们回顾一下 for
循环的例子:
JavaScript 代码:
- function goodCode() {
- for(let i=0; i<5; i++) {
- console.log(i);
- }
- console.log(i); //error
- }
如今,个人变量只限于for循环体内使用。没有办法能够不经意地使用。另外,let
定义的变量不能提高,因此全部这些难以想象的移动变量都消失了。
新的关键字 const
和 let
能够彻底替代 var
。 使用现代浏览器和最新版本的Node,没有任何理由使用 var
了,甩掉历史包袱。(愚人码头注:我的认为仍是要看使用环境。)
超级字符串
ES6引入了一种新类型的字符串,称为模板字面量。我更喜欢称之为超级字符串。你使用一个超级字符串就像一个常规的字符串,可是你不光可使用单引号或双引号,你还可使用反引号`
。
JavaScript 代码:
- var q = 'foo';
- var qq = "foo";
- var bq = `foo`;
- var qq = "Sally sells \"sea shells\"";
- var bq = `Sally sells "sea shells"`;
到如今为止还挺好,但没有什么非同寻常的地方。不过它确实有一个直接的优点。若是您须要在字符串中使用双引号或单引号,则没必要再转义(愚人码头注:原来单引号双引号配对不是也能够吗?不知道做者为何这样写。嘎嘎)。然而,超级字符串还有其余一些技巧。
多行字符串串联
终于咱们能够有真正的多行字符串了。若是你须要引用几行的字符串?你没必要再使用加号的方式连接字符串。直接放入换行符,它就能正常工做。
JavaScript 代码:
- var q = 'foo';
- var qq = "foo";
- var bq = `foo`;
- var qq = "Sally sells \"sea shells\"";
- var bq = `Sally sells "sea shells"`;
表达式换码
另外一个新特性是表达式换码(愚人码头注:前端说的比较多的是替换符)。在超级字符串中,您能够在${}
的括号内放入任何有效的 JavaScript 表达式。这比双引号更加干净,最新的IDE通常都会语法高亮显示这些表达式。
JavaScript 代码:
- var name = "Alice";
- var greeting = `Good morning ${name}`;
- var amount = 5;
- var price = 3;
- var sales = `That costs ${amount*price} dollars`;
将表达式换码与多行字符串支持相结合,为咱们提供了很棒的HTML模板。
JavaScript 代码:
- var template = `
- <div>
- <h3>Good Morning ${name}</h3>
- <p>
- That item will cost you
- <b>${amount*price}</b>
- dollars.
- </p>
- </div>`
JavaScript 代码:
箭头函数
上面说的是字符串和变量,如今让咱们来看看函数。若是您之前听过ES6,不少可能都是关于箭头函数的。这是更紧凑的常规函数语法。他们也有一个很是重要的区别:this
变量指向的是不一样的东西。
假设你想循环一个数组使其元素的值乘2,而且生成一个新数组。你能够用下面的 for
循环来作,可是这样会产生额外的变量,而且能够很容易写错。
JavaScript 代码:
- var output = [];
- for(var i=0; i<;input.length; i++) {
- output[i] = input[i] * 2;
- }
JavaScript数组有一个新方法 map
,它在每一个元素上调用一个函数来生成一个新的元素,而后将其放入新数组中。代码以下:
JavaScript 代码:
- var output = input.map(function(x) {
- return x * 2;
- });
这看起来更好,更简洁。x * 2
部分是唯一实际工做的部分。 其他的是语法开销。若是使用箭头功能,咱们能够这样作:
JavaScript 代码:
- var output = input.map((x)=>x*2);
哇!这个更加简洁了。让我解释一下上面的代码。箭头函数能够重写函数,而不须要实际的function
关键字。而是在包含函数参数的括号后面跟 =>
符号。
JavaScript 代码:
- //常规函数
- function (x) {
- return x * 2;
- }
- //箭头函数样式
- (x) => {
- return x * 2;
- }
箭头函数让咱们写一样的代码更加简洁。但咱们还能够继续简化。删除空格,相同的更简短。
JavaScript 代码:
- (x) => { return x * 2; }
咱们还可使它更简短。若是箭头函数只包含一个表达式,咱们能够删除return
,大括号和分号,写成一个单行表达式,它会自动返回它的值。这样就简化到极致了。
JavaScript 代码:
- (x) => x * 2
- var output = input.map((x)=>x*2);
箭头函数可使您的代码很是紧凑和强大。 可是还有一个很是重要的事情是:它修复了 this
。
this 魔咒
在 JavaScript 中,最难以想象是变量 this
老是引用函数被调用的那个对象。因此像下面的代码肯能不会是你认为的那样执行。
JavaScript 代码:
- function App() {
- this.clicked = false;
- var button = document.getElementById('button');
- button.addEventListener('click', function(evt) {
- this.clicked = true; //不会安装你想的那样执行
- });
- }
当您使用其余对象时, this
上下文可能与预期的不一样。当你将一个函数传递到其余地方被调用时,它可能会使用不一样的 this
调用该函数。若是您将一个事件处理程序添加到 button , this
将指向 button 。有时这就是你想要的,但在上面的代码中不是。咱们但愿 this
指向 App
对象,而不是 button 。
this
是JavaScript长期存在的问题。很常见的作法是,开发人员创造了一种 self 模式,使用临时变量 self
保存对 this
的正确引用。看起来很恶心,但它能正常工做。
JavaScript 代码:
- function App() {
- this.clicked = false;
- var button = document.getElementById('button');
- var self = this; //特别注意这一行
- button.addEventListener('click', function(evt) {
- self.clicked = true;
- });
- }
另外一种解决问题的方法是 bind 绑定这个函数。bind
方法强制 this
执行一个特定的对象,无论函数后面如何被调用。(愚人码头注: http://www.css88.com/archives/5611 )
JavaScript 代码:
- function App() {
- this.clicked = false;
- var button = document.getElementById('button');
- var callback = (function(evt) {
- this.clicked = true
- }).bind(this);//特别注意这一行
- button.addEventListener('click',callback);
- }
再次执行, this
能正常指向 App
对象,但它不是很好。咱们须要写额外的代码,而且 bind
会带来额外的开销。箭头函数为咱们提供了更好的方法。
JavaScript 代码:
- function App() {
- this.clicked = false;
- var button = document.getElementById('button');
- button.addEventListener('click',()=>{
- this.clicked = true;
- });
- }
箭头函数自动捕获该函数定义时做用域中的 this
变量,而不是来自函数被调用的做用域中。这意味着您能够将函数传递给其余地方,使用时要知道 this
正确的指向。在上面的代码中,没有任何的 hack ,就能按照咱们所指望的那样执行。
简而言之,箭头函数真的很棒。我能够尽量地使用它。它们使您的代码更短,更容易阅读,并且 this
也变得很明智。
Promises
箭头函数的另外一个很强大的特性是它们能与 Promises 配合的很好。Promise 是 JavaScript 中一种新的对象,旨在帮助须要很长时间执行的事情。JavaScript 没有线程,因此若是你想作一些可能须要很长时间的事情,那么你必须使用回调。
例如,在Node中,您可能须要加载文件,解析它,作一个数据库请求,而后写一个新的文件。这些都必须按顺序完成,但它们都是异步的,因此你必须嵌套你的回调。这就产生了被JS开发人员称之为“金字塔”式的代码风格。你须要大量的嵌套代码。
JavaScript 代码:
- fs.readFile("file.txt", function(err, file) {
- db.fetchNames(function(err, names) {
- processFile(file, names, function(err, outfile) {
- fs.writeFile('file.txt',outfile, function(err, status) {
- console.log("we are done writing the file");
- })
- });
- })
- });
这段代码很丑,很难理解,并且有不少讨厌的隐藏 bug。 Promises 能够帮助咱们击败 “金字塔” 式的代码风格。
JavaScript Promise
表示一个值当前可能不可用,可是未来有值的对象。使用 then
函数能够添加回调,当最终值 ready 时,调用这个回调。
JavaScript 代码:
- var prom = makeSomePromise();
- //value not ready yet
- prom.then((value)=>{
- //do something with the value
- })
Promises then
回调很像传统的回调,但 Promises 增长了一个额外的转变 :他们能够链式调用。让咱们重温一下上面的代码。每一个函数必须按顺序调用,每一个函数都取决于前一个函数的结果。使用Promises ,咱们能够这样作。
JavaScript 代码:
- fs.readFile("file.txt")
- .then((file) => {
- return db.fetchNames().then((names)=>{
- return processFile(file,names)
- })
- })
- .then((outfile)=>{
- return fs.writeFile('file.txt',outfile);
- })
- .then(()=>{
- console.log("we are done writing the file");
- });
请注意看,箭头函数如何使这个代码变得漂亮干净。每一个 then
回调返回一个值。这个值传递给下一个函数,因此咱们全部的函数均可以很容易的链式调用。
如今你会注意到,processFile
命令须要前面两个值的结果,可是 Promises 只传递一个值。咱们也不用关心readFile
和fetchNames
执行的顺序。咱们只想知道什么时候完成。Promises 能够作到这一点。
Promise.all()
假设要从文件名数组中加载每一个文件,并在完成时通知。咱们能够用 Promise.all()
来作到这一点。all
都是 Promise 中的一个实用方法,它获取一系列 Promises ,并返回了一个新的 Promises ,当全部的子 Promises 完成时,它将获得 resolves 状态。如下是咱们如何使用 Promise.all
加载全部文件的示例。(假设readFile
是一个读取文件返回 Promises 的函数)。
JavaScript 代码:
- var proms = filenames.map((name)=> readFile(name));
- Promise.all(proms).then((files)=>{
- console.log(`we have ${files.length} files`);
- });
如今咱们能够重写咱们原来的Node示例:
JavaScript 代码:
- Promise.all([
- fs.readFile("file.txt"),
- db.fetchNames()
- ])
- .then((vals) => processFile(vals[0],vals[1]))
- .then((outfile)=> fs.writeFile('file.txt',outfile))
- .then(()=> console.log("we are done writing the file"));
我将读取的文件和数据库调用合并到使用Promise.all
的单个 Promise 中。它将返回的值是一个数组,包含两个子 Promise 的结果,因此我能够把它们放到 processFile
中。请注意,我使用了缩写箭头语法使代码更小更干净。
处理失败
如今考虑若是这些 Promises 中有一个失败会发生什么?为了处理第一个失败,咱们能够将其放入 try
/ catch
语句块中,但下一个 then
仍然会被调用。若是第一个失败,咱们但愿一切都中止。Promises有另一个技巧:捕获回调。
在下面的新版本的代码中,若是有任何失败,它将当即跳过其他的 Promises ,跳转到结尾的 catch
回调中。catch
回调中,咱们还能够添加更多的子句。
JavaScript 代码:
- Promise.all([
- fs.readFile("file.txt"),
- db.fetchNames()
- ])
- .then((vals) => processFile(vals[0],vals[1]))
- .then((outfile)=> fs.writeFile('file.txt',outfile))
- .then(()=> console.log("we are done writing the file"))
- .catch((e) => {
- console.log("some error happened");
- });
编写自定义Promises
固然 Promises 只有在咱们调用的API 实际使用 Promises 的状况下才能工做。咱们能够期待许多库开始转换为 Promises,固然咱们也能够编写本身的 Promises 。咱们使用 Promise
构造函数来实现。
JavaScript 代码:
- function makePromise(foo,bar) {
- return new Promise((resolve, reject) => {
- try {
- //do long stuff
- resolve(value);
- } catch {
- reject(error);
- }
- });
- }
它须要两个值:resolve
和 reject
。这些都是回调函数。回调里面你能够作任何须要花很长时间执行的事情,即便是它涉及多个回调。当彻底完成后,调用具备最终值的 resolve()
。而后,这将发送到任何你使用 Promises 的第一个 then
子句。
若是发生错误,而且您想 reject 该值,而不是抛出错误,请使用 reject()
,传递所需的任何替代值。
这是一个现实中的例子。 我一直使用 AJAX 调用,但它们可能很是丑陋,像这样。
JavaScript 代码:
- var url = "http://api.silly.io/api/list/e3da7b3b-976d-4de1-a743-e0124ce973b8?format=json";
- var xml = new XMLHttpRequest();
- xml.addEventListener('load', function() {
- var result = JSON.parse(this.responseText);
- console.log(result);
- });
- xml.addEventListener('error',function(error) {
- console.log("something bad happened");
- });
- xml.open("GET",url);
- xml.send();
让咱们把这个代码包装成一个承诺 Promise。
JavaScript 代码:
- function doGet(url) {
- return new Promise((resolve,rej)=>{
- var xml = new XMLHttpRequest();
- xml.addEventListener('load', function() {
- var result = JSON.parse(this.responseText);
- resolve(result);
- });
- xml.addEventListener('error',function(error) {
- reject(error);
- });
- xml.open("GET",url);
- xml.send();
- });
- }
基本上是相同的代码,但我能够像这样调用它。
JavaScript 代码:
- var url = "someapi.com/dostuff?foo=bar";
- doGet(url).then((obj)=>{
- console.log(obj);
- });
哇,这样更干净。实际上,咱们不须要本身编写 AJAX Promise 包装器,由于有一个新的Web标准 fetch 已经为我作了这些事情。可是如今全部浏览器都还没支持 fetch ,因此咱们可使用咱们本身编写的 Promise ,直到浏览器支持 fetch 的时候。
第一次包装 Promise 可能有点困难,但一旦你开始使用它们,我想你会很喜欢他们。它们能够很是容易的将多个函数集成到一个具备逻辑意义的单个工做流中,并正确地捕获全部错误。
Arrays
最后我想向你们展现一些新的 Array 功能。大多数功能对于ES6 来讲不能算是新的,其实有些是至关老了。然而,它们终于获得了各方面的支持,并与 箭头函数和 Promise 结合的很好。因此我认为他们是新功能。
假设你想对数组的每一个元素作一些事情。可使用 forEach
或 map
函数代替 for
循环。
JavaScript 代码:
- var values = [1,2,3,4,5,6,7,8,9,10];
- values.forEach((x)=>console.log(x));
- var doubled = values.map((x) => x*2);
forEach
函数在数组中的每一个元素上运行回调。map
函数也在每一个元素上运行,但它将每一个回调的结果存储到一个新的数组中。
若是要从数组中过滤出某些值,请使用filter
函数。
JavaScript 代码:
- //查找匹配 filter 全部的值
- var evens = values.filter((x)=>x%2==0);
Array还具备基于某些标准在数组中查找单个值的功能。
JavaScript 代码:
- //查找第一个匹配的值
- var six = values.find((x) => x >= 6);
- //找到第一个匹配的索引
- var index = values.findIndex((x) => x >= 6);
- //若是至少有一项匹配,则为true
- var any = values.some((x) => x > 10);
最后,可使用 reduce
函数将数组减小到单个值。reduce
很是强大,能够用来作不少事情,好比一个数组求和或 flatten 嵌套数组(愚人码头注:下降数组嵌套,例如将[1,[2,3],4]
转换为[1,2,3,4]
,这就叫 flatten)。
JavaScript 代码:
- //将数组减小到单个值
- var sum = values.reduce((a,b) => a+b, 0);
- //flatten 嵌套数组
- var list1 = [[0, 1], [2, 3], [4, 5]];
- var list2 = [0, [1, [2, [3, [4, [5]]]]]];
- const flatten = arr => arr.reduce(
- (acc, val) => acc.concat(
- Array.isArray(val) ? flatten(val) : val
- ),
- []
- );
- flatten(list1); // returns [0, 1, 2, 3, 4, 5]
- flatten(list2); // returns [0, 1, 2, 3, 4, 5]
循环对象中的属性,您可使用 Object.keys
获取一个包含全部属性名称数组,而后用 forEach
循环它
JavaScript 代码:
- var obj = {
- first:'Alice',
- middle:'N',
- last:'Wonderland',
- age:8,
- height:45,
- }
- Object.keys(obj).forEach((key)=>{
- console.log("key = ", key);
- console.log("value = ", obj[key]);
- });
要避免的事情
我已经介绍了 ES6 今天就可使用的五个功能,但ES6中还有许多其余功能,这个阶段你应该避免?或者是由于它们没有提供有价值的东西,或者尚未获得很好的支持。这些包括:
- Destructuring
- Modules
- Default Parameters
- Spread Operator
- Symbols
- Generators and Iterators
- Weak Maps and Weak Sets
- Proxies
解构容许你经过名称从对象中引值。它在几种状况下有用,可是我发现最好的用法是从模块中提取函数。不幸的是模块仍然是一团糟并且不要在任何地方使用,因此如今应该避免使用他们。
除了解构,建议你不要使用默认参数,和扩展操做符(...
)。我发现这些比他们使用价值更麻烦,至少如今是。
Symbols,Generators(生成器),Iterators(迭代器),Weak Maps (弱映射)和 Weak Sets(弱集合),Proxies(代理)是很是有用的,可是它们在任何地方都不支持,因此我建议你等一下子再能使用它们。
还有一个新的类语法。它仍然使用JavaScript的原型继承,但它使得定义一个类的语法更加清晰和一致。然而,若是没有新的模块支持,它也没有价值,因此我建议等一下子。
JavaScript 代码:
- class Foo extends Bar {
- constructor(stuff) {
- super(stuff);
- this.first = "first name"
- }
- doStuff() {
- }
- doMoreStuff() {
- }
- }
我能够用它吗?
大多数桌面和移动端的浏览器都支持我向你展现的全部内容。可是,根据您的用户状况,您可能须要支持旧的浏览器/旧版移动操做系统。每当你想知道他们的支持状况,去 caniuse.com。它会告诉你每一个浏览器的什么版本支持什么。
若是您须要支持IE 10 如下的浏览器,那么可使用像 TypScript 或 Babel 这样的转换器。
因此这些ES6的五个很棒的功能,你能够当即开始使用。
示例代码
咱们建立了一个使用 箭头函数 和 Promises 的 示例代码的目录 ,还有许多实用的Web服务进行通讯,如实时语言翻译,地理编码和chatbot apis等等。