JavaScript在过去几年中发生了很大的变化。这些是您今天能够开始使用的12项新功能!html
该语言的新增内容称为ECMAScript 6.它也称为ES6或ES2015 +。es6
自1995年JavaScript构思以来,它一直在缓慢发展。每隔几年就会发生新增事件。 ECMAScript于1997年成立,旨在指导JavaScript的发展方向。它已经发布了ES3,ES5,ES6等版本。编程
如您所见,ES3,ES5和ES6之间存在10年和6年的差距。此后每一年进行小幅增量变动。而不是像ES6那样一次作大规模的改变。数组
全部现代浏览器和环境都支持ES6!promise
Chrome,MS Edge,Firefox,Safari,Node等等都支持 JavaScript ES6大多数功能 。所以,您将在本教程中学习的全部内容均可以当即开始使用。
浏览器
让咱们开始使用ECMAScript 6!app
您能够在浏览器控制台上测试全部这些代码段!函数
使用ES6,咱们从使用var声明变量到使用let / const。学习
var出了什么问题?测试
var的问题是变量泄漏到其余代码块中,例如for循环或if块。
ES5 var x = 'outer'; function test(inner) { if (inner) { var x = 'inner';// scope whole function return x; } return x;// gets redefined because line 4 declaration is hoisted } test(false);// undefined 😱 test(true);// inner
对于test(false)你会指望返回outer,可是你却获得undefined。
为何?
由于即便没有执行if-block,第4行中的表达式“var x”也会被提高。
var是函数做用域。它甚至在被声明以前就能够在整个功能中使用。
声明已被挂载。所以,您能够在声明变量以前使用它。
初始化不会被提高。若是您使用var ,那么总会将变量声明在顶部。
在应用挂载规则后,咱们能够更好地了解发生的状况:
ES5 var x = 'outer'; function test(inner) { var x;// HOISTED DECLARATION if (inner) { x = 'inner';// INITIALIZATION NOT HOISTED return x; } return x; }
ES6 let x = 'outer'; function test(inner) { if (inner) { let x = 'inner'; return x; } return x;// gets result from line 1 as expected } test(false);// outer test(true);// inner
用let代替var会使事情按预期工做。若是未调用if块,则变量x不会从块中提高。
hoisting和 “temporal dead zone”
在ES6中,let将变量提高到块的顶部(不是像ES5那样位于函数的顶部)。
可是,在变量声明以前引用块中的变量会致使“ReferenceError(系统报错)”。
let被限制为块级做用域。在声明以前不能使用它。
“Temporal dead zone” 是从块开始到声明变量的区域。
让咱们在解释IIFE以前展现一个例子。 看看这里:
ES5 { var private = 1; } console.log(private);// 1
如你所见,private漏掉了。 您须要使用IIFE(当即调用的函数表达式)来包含它:
ES5 (function(){ var private2 = 1;})(); console.log(private2);// Uncaught ReferenceError
若是你看过jQuery / lodash或其余开源项目,你会发现他们有IIFE来避免污染全局环境,只是在全局定义,如_,$或jQuery。
在ES6上更清洁,当咱们只使用块和let时,咱们也不须要再使用IIFE:
ES6 { let private3 = 1; } console.log(private3);// Uncaught ReferenceError
若是你根本不想改变变量,你也可使用const。
用'let和const代替var`。
对全部引用使用const;避免使用var。
若是必须从新分配引用,请使用let而不是const。
模板文字(字符串拼接)
当咱们有模板文字时,咱们不须要作更多的嵌套链接。看一看:
ES5 var first = 'Adrian'; var last = 'Mejia'; console.log('Your name is ' + first + ' ' + last + '.');``` 如今你可使用backtick()和字符串插值$ {}:
const first = 'Adrian';
const last = 'Mejia';
console.log(Your name is ${first} ${last}.);`
咱们没必要再链接字符串+ n了:
var template = '<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >\n' + ' <div class="view">\n' + ' <input class="toggle" type="checkbox" [checked]="todo.isDone">\n' + ' <label></label>\n' + ' <button class="destroy"></button>\n' + ' </div>\n' + ' <input class="edit" value="">\n' + '</li>'; console.log(template);
在ES6上咱们能够再次使用反引号来解决这个问题:
const template = `<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" > <div class="view"> <input class="toggle" type="checkbox" [checked]="todo.isDone"> <label></label> <button class="destroy"></button> </div> <input class="edit" value=""> </li>`; console.log(template);
两段代码都会有彻底相同的结果。
ES6解构很是有用和简洁。请遵循如下示例:
从数组中获取元素
ES5 var array = [1, 2, 3, 4]; var first = array[0]; var third = array[2]; console.log(first, third);// 1 3
es6的写法:
ES6 const array = [1, 2, 3, 4]; const [first, ,third] = array; console.log(first, third);// 1 3
交换 values
ES5 var a = 1; var b = 2; var tmp = a; a = b; b = tmp; console.log(a, b);// 2 1``` es6的写法:
ES6
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b);// 2 1
### 屡次返回值的解构
ES5
function margin() {
var left=1, right=2, top=3, bottom=4;
return {
left: left, right: right, top: top, bottom: bottom };
}
var data = margin();
var left = data.left;
var bottom = data.bottom;
console.log(left, bottom);// 1 4`
在第3行中,您还能够将其返回到这样的数组中(并保存一些输入):
return [left, right, top, bottom];
可是,调用者须要考虑返回数据的顺序。
var left = data[0]; var bottom = data[3];
使用ES6,调用者只选择他们须要的数据(第6行):
ES6 function margin() { const left=1, right=2, top=3, bottom=4; return { left, right, top, bottom }; } const { left, bottom } = margin(); console.log(left, bottom);// 1 4
注意:第3行,咱们还有其余一些ES6功能正在进行中。咱们能够将{left:left}压缩为{left}。看看它与ES5版本相比有多简洁。那不是很酷吗?
ES5 var user = { firstName: 'Adrian', lastName: 'Mejia' }; function getFullName(user) { var firstName = user.firstName; var lastName = user.lastName; return firstName + ' ' + lastName; } console.log(getFullName(user));// Adrian Mejia
es6(但更简洁)相同:
ES6 const user = { firstName: 'Adrian', lastName: 'Mejia' }; function getFullName({ firstName, lastName }) { return ${firstName} ${lastName};} console.log(getFullName(user));// Adrian Mejia
深拷贝
ES5 function settings() { return { display: { color: 'red' }, keyboard: { layout: 'querty'} };} var tmp = settings(); var displayColor = tmp.display.color;var keyboardLayout = tmp.keyboard.layout; console.log(displayColor, keyboardLayout);// red querty
与es6(但更简洁)相同:
ES6 function settings() { return { display: { color: 'red' }, keyboard: { layout: 'querty'} }; } const { display: { color: displayColor }, keyboard: { layout: keyboardLayout } } = settings(); console.log(displayColor, keyboardLayout);// red querty
这也称为对象解构。
如您所见,这很是有用,并鼓励良好的编码风格。
最佳作法:
使用ECMAScript 6,咱们从“构造函数”went到“类”went。
在JavaScript中,每一个对象都有一个原型,这是另外一个对象。全部JavaScript对象都从其原型继承其方法和属性。
在ES5中,咱们使用构造函数来建立面向对象编程(OOP),以建立对象,以下所示:
ES5 var Animal = (function () { function MyConstructor(name) { this.name = name; } MyConstructor.prototype.speak = function speak() { console.log(this.name + ' makes a noise.'); }; return MyConstructor;})(); var animal = new Animal('animal'); animal.speak();// animal makes a noise.
在ES6中,咱们有一些语法糖。咱们能够用更少的样板和新的关键字来作一样的事情,好比class和constructor。另外,请注意咱们如何定义方法
constructor.prototype.speak = function()vsspeed():
ES6 class Animal { constructor(name) { this.name = name;} speak() { console.log(this.name + ' makes a noise.'); } } const animal = new Animal('animal'); animal.speak();// animal makes a noise.
正如咱们所看到的,两种风格(ES5 / 6)在幕后产生相同的结果,并以相同的方式使用。
最佳作法:
创建在以前的Animal类上。假设咱们想要扩展它并定义一个“Lion”类
在ES5中,它更多地涉及原型继承。
ES5 var Lion = (function () { function MyConstructor(name){ Animal.call(this, name); } // prototypal inheritance MyConstructor.prototype = Object.create(Animal.prototype); MyConstructor.prototype.constructor = Animal; MyConstructor.prototype.speak = function speak() { Animal.prototype.speak.call(this); console.log(this.name + ' roars 🦁'); }; return MyConstructor;})(); var lion = new Lion('Simba');lion.speak();// Simba makes a noise. // Simba roars.
我不会详细介绍全部细节,但请注意:
在ES6中,咱们有一个新的关键字extends和super。
class Lion extends Animal { speak() { super.speak(); console.log(this.name + ' roars 🦁'); }} const lion = new Lion('Simba'); lion.speak();// Simba makes a noise. // Simba roars.
看起来这个ES6代码与ES5相比看起来有多清晰,它们彻底相同。
最佳作法:
使用内置的方式继承extends。
咱们从回调地狱👹逃出来了。
ES5 function printAfterTimeout(string, timeout, done){ setTimeout(function(){ done(string); }, timeout);} printAfterTimeout('Hello ', 2e3, function(result){ console.log(result);// nested callback printAfterTimeout(result + 'Reader', 2e3, function(result){ console.log(result); }); });
咱们有一个函数接收回调,当done时执行。咱们必须一个接一个地执行它。这就是咱们在回调中第二次调用'printAfterTimeout`的缘由。
若是您须要第3次或第4次回调,这可能会很快变得混乱。让咱们看看咱们如何经过Promises来作到这一点:
ES6 function printAfterTimeout(string, timeout){ return new Promise((resolve, reject) => { setTimeout(function(){ resolve(string); }, timeout); });} printAfterTimeout('Hello ', 2e3).then((result) => { console.log(result); return printAfterTimeout(result + 'Reader', 2e3); }).then((result) => { console.log(result); });
正如你所看到的,使用promises,咱们可使用then在另外一个函数完成后执行某些操做。 再也不须要保持嵌套功能。
ES6没有删除函数表达式,但它添加了一个名为箭头函数的新表达式。
在ES5中,咱们对this有一些疑问:
ES5 var _this = this;// need to hold a reference $('.btn').click(function(event){ _this.sendData();// reference outer this }); $('.input').on('change',function(event){ this.sendData();// reference outer this }.bind(this));// bind to outer this
你须要使用一个临时的this来引用一个函数或使用bind。在ES6中,您可使用箭头函数来实现这个功能!
ES6 // this will reference the outer one $('.btn').click((event) => this.sendData()); // implicit returns const ids = [291, 288, 984]; const messages = ids.map(value => `ID is ${value}`);
咱们从for转到forEach而后转到for ... of:
ES5 // for var array = ['a', 'b', 'c', 'd']; for (var i = 0;i < array.length;i++) { var element = array[i]; console.log(element);}// forEach array.forEach(function (element) { console.log(element);});
ES6 for ...也容许咱们进行迭代。
ES6 // for ...of const array = ['a', 'b', 'c', 'd']; for (const element of array) { console.log(element); }
咱们检查是否认义了变量以将值赋给“默认参数”。你以前作过这样的事吗?
ES5 function point(x, y, isFlag){ x = x || 0; y = y || -1; isFlag = isFlag || true; console.log(x,y, isFlag); } point(0, 0) // 0 -1 true 😱 point(0, 0, false) // 0 -1 true 😱😱 point(1) // 1 -1 true point() // 0 -1 true
检查的常见模式是变量具备值或指定默认值。然而,请注意有一些问题:
第8行,咱们传递0,0并获得'0,-1`
第9行,咱们传递false但获得'true`。
若是您将布尔值做为默认参数或将值设置为零,则它不起做用。你知道为何吗???我会在ES6例子后告诉你;)
使用ES6,如今您能够用更少的代码作得更好!
ES6 function point(x = 0, y = -1, isFlag = true){ console.log(x,y, isFlag); } point(0, 0) // 0 0 true point(0, 0, false) // 0 0 false point(1) // 1 -1 true point() // 0 -1 true
注意第5行和第6行咱们获得了预期的结果。ES5示例不起做用。咱们必须首先检查undefined,由于false,null,undefined和0是假值。咱们能够逃脱数字:
ES5 function point(x, y, isFlag){ x = x || 0; y = typeof(y) === 'undefined' ? -1 : y; isFlag = typeof(isFlag) === 'undefined' ? true : isFlag; console.log(x,y, isFlag); } point(0, 0) // 0 0 true point(0, 0, false) // 0 0 false point(1) // 1 -1 true point() // 0 -1 true
如今,当咱们检查undefined时,它按预期工做。
咱们从参数到休息参数和传播运算符。
在ES5上,获取任意数量的参数是成熟的:
ES5 function printf(format) { var params = [].slice.call(arguments, 1); console.log('params: ', params); console.log('format: ', format); }
咱们可使用rest运算符...来作一样的事情。
ES6 function printf(format, ...params) { console.log('params: ', params); console.log('format: ', format); }
咱们从apply()转到了扩展运算符。
提醒:咱们使用apply()将数组转换为参数列表。例如,Math.max()接受一个参数列表,可是若是咱们有一个数组,咱们可使用apply来使它工做。
正如咱们在前面所看到的,咱们可使用apply来传递数组做为参数列表:
ES5 Math.max.apply(Math, [2,100,1,6,43]) // 100
在ES6中,您可使用扩展运算符:
ES6 Math.max(...[2,100,1,6,43]) // 100
另外,咱们从concat数组开始使用spread运算符:
ES5 var array1 = [2,100,1,6,43]; var array2 = ['a', 'b', 'c', 'd']; var array3 = [false, true, null, undefined]; console.log(array1.concat(array2, array3));
在ES6中,您可使用spread运算符展平嵌套数组:
ES6 const array1 = [2,100,1,6,43]; const array2 = ['a', 'b', 'c', 'd']; const array3 = [false, true, null, undefined]; console.log([...array1, ...array2, ...array3]);
JavaScript经历了不少变化。本文介绍了每一个JavaScript开发人员应该了解的大多数核心功能。此外,咱们还介绍了一些最佳实践,以使您的代码更简洁,更容易推理。