Javascript ES6 特性概述(即ECMAScript 6和ES2015+)

Javascript在过去的一年里变化了很多,从现在开始,有12个新的特性可以开始用了!

1. 历史

ECMAScript 6是对Javascript语言的增强,有时候也被称为ES6或者ES2015+。

Javascript诞生在1995年,从那以后一直在缓慢地演变,每隔几年会有一些新的增强特性出现。ECMAScript出现在1997年,目的是指导Javascript的演化路径,它已经发布了好几个版本,如ES3、ES5、ES6,等等。

可以看到,在ES3、ES5和ES6之间分别有10年和6年的空当。最新的模式是每年有一些小的增强,而不是像ES6一样突然出现巨大的变化。

2. 浏览器支持

所有现代浏览器和环境都已经支持ES6了!

Chrome, MS Edge, Firefox, Safari, Node等已经默认支持了Javascript ES6的大多数特性。 所以,在接下来的这篇概述中你们看到的所有东西都可以马上用上了。

那就进入正题。

3. ES6 核心特性

你可以在浏览器console窗口中测试接下来的所有代码片段!

不要贸然相信我的话,自己测试一下ES5和ES6的每个例子。我们开始干吧??

3.1 块变量

在ES6中,声明变量不再用var而用let/const

var到底做错了什么?

var的问题在于它会泄露变量到其他代码块中,比如for循环或者if语句:

var x = ‘outer‘;
function test(inner) {
  if (inner) {
    var x = ‘inner‘; // scope whole function
    return x;
  }
  return x; // gets redefined on line 4
}
test(false); // undefined ??
test(true); // inner

比如test(false),本来希望返回outer,结果却不是,你得到undefined

为什么?

因为即使if语句块没有执行到,代码第4行仍然重新定义了var x,值为undefined

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不会被重定义。

* IIFE *

在解释IIFE之前我们来看个例子。看下面的代码:

{
  var private = 1;
}
console.log(private); // 1

能够看到,private变量泄露了出去。你需要使用IIFE(立即执行函数)来包含它:

(function(){
  var private2 = 1;
})();
console.log(private2); // Uncaught ReferenceError

如果你看一下jQuery/lodash或者其它开源项目就会发现他们使用IIFE来避免污染全局命名空间,而仅定义了有限的几个全局变量,如_$或者jQuery

ES6要清爽多了,我们不再需要使用IIFE,只要用代码块和let就够了:

{
  let private3 = 1;
}
console.log(private3); // Uncaught ReferenceError

* Const *

如果根本不希望变量改变也可以使用const。

底线: 抛弃var,要用letconst

- 所有引用都用const,避免使用var

- 如果一定要重新赋值,使用let而非var

3.2 模板字面量(Literals)

有了模板字面量,我们不再需要搞一堆嵌套连接了。看这个例子:

var first = ‘Adrian‘;
var last = ‘Mejia‘;
console.log(‘Your name is ‘ + first + ‘ ‘ + last + ‘.‘);

现在你可以用反引号(`)和字符串插值(interpolation)${}:

const first = ‘Adrian‘;
const last = ‘Mejia‘;
console.log(`Your name is ${first} ${last}.`);

3.3 多行字符串

我们不再需要这样拼字符串了:string + \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);

两段代码作用完全一样。

3.4 解构赋值

ES6解构非常有用、明确。看下面这个例子:

* 从一个数组中得到元素 *

var array = [1, 2, 3, 4];
var first = array[0];
var third = array[2];
console.log(first, third); // 1 3

等同于

const array = [1, 2, 3, 4];
const [first, ,third] = array;
console.log(first, third); // 1 3

* 交换两个值 *

var a = 1;
var b = 2;
var tmp = a;
a = b;
b = tmp;
console.log(a, b); // 2 1

等同于

let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

* 多个返回值的解构 *

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行):

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相比有多简洁。够酷吧?

* 参数匹配的解构 *

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

等同于(但更简洁):

const user = {firstName: ‘Adrian‘, lastName: ‘Mejia‘};
function getFullName({ firstName, lastName }) {
  return `${firstName} ${lastName}`;
}
console.log(getFullName(user)); // Adrian Mejia

* 深度匹配 *

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

等同于(但更简洁):

function settings() {
  return { display: { color: ‘red‘ }, keyboard: { layout: ‘querty‘} };
}
const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();
console.log(displayColor, keyboardLayout); // red querty

这也叫做对象解构。

如你所见,解构非常有用,而且有利于写出更好的编码风格。

优秀实践:

- 使用数组解构来获取元素或者交换变量,省了定义临时变量的麻烦。

- 不要使用数组解构来获取多个返回值,用对象解构。

3.5 类和对象

有了ES6,我们可以用“类”??来替换“构造函数”??。

Javascriptscript中,每个对象都有一个原型,那是另外的一个对象。所有Javascript对象从它们的原型对象中继承方法和属性。

在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中,我们有了些语法糖,能够用更少的模式化代码和新的关键字来实现同样的功能,比如classconstructor。同样,可以比较一下哪种方式更清晰:constructor.prototype.speak = function() vs speak():

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.

我们看到,两种方式(ES6/6)在幕后输出了同样的结果,用起来没有差别。

最佳实践:

- 尽量使用class语法而避免直接操作prototype。这能够令代码更简洁易懂。

- 避免使用空的构造函数。如果没有指定,class有默认实现。

3.6 继承

在前面Animal类的基础上,假设我们想扩展它,定义一个Lion类。

在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.

我不会每个细节都过一遍,但要注意:

- 第3行, 我们显式地带参数调用Animal的构造函数。

- 第7-8行,我们将Lion的原型设置为Animal。

- 第11行,我们调用父类Animal的speak方法。

在ES6中,我们有了新的关键字extendssuper

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.

相比ES5,同样的事情用ES6有多么清晰易读。简直妙不可言!

最佳实践:

- 使用内置的extends来实现继承。

3.7 Native Promises

我们经历过回调黑洞?? ,终于等来了promises ??

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后需要调用一个回调函数。它要执行两次,在执行一次之后还要再执行一次,这就是为什么在回调函数printfAfterTimeout中我们又一次调用它。

假设我们需要第3个或第4个回调函数,这会很快变得混乱不堪。来看一下如果用promises可以怎么做:

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);
});

你能看到,通过使用promise,我们可以在完成一件事情后使用then来做另一件事。不再需要嵌套的回调方法了。

3.8 箭头函数

ES6没有删除函数表达式,而是增加了一种新方式,叫做箭头函数。

在ES5中,this有一些要注意的地方:

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来引用外面的this,或者使用bind。在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}`);

3.9 For … of

我们经历过从forforeach,然后到for...of

// 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...of也是用来处理循环的。

// for...of
const array = [‘a‘, ‘b‘, ‘c‘, ‘d‘];
for (const element of array) {
    console.log(element);
}

3.10 默认参数

以前我们经常要检查一个变量是否已定义,没定义就赋一个默认值。你做过类似下面的事情吗?

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

如果你有一个boolean类型的默认参数或者设置值为0,这种情况下就有问题。你知道为啥吗???我会在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,因为falsenullundefined0都是布尔false的值。只有这样我们才能勉强处理好参数是number的情况。

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后,工作符合预期。

3.11 剩余参数

我们见识过剩余参数和展开操作符.

在ES5中,获取剩余参数的方式有点笨拙:

function printf(format) {
  var params = [].slice.call(arguments, 1);
  console.log(‘params: ‘, params);
  console.log(‘format: ‘, format);
}
printf(‘%s %d %.2f‘, ‘adrian‘, 321, Math.PI);

我们可以用剩余操作符…来做同样的事情。

function printf(format, ...params) {
  console.log(‘params: ‘, params);
  console.log(‘format: ‘, format);
}
printf(‘%s %d %.2f‘, ‘adrian‘, 321, Math.PI);

3.12 展开操作符

我们可以用展开操作符代替apply()。又是...帮了大忙:

记住:我们用apply()来将数组转换为一个参数列表。比如说,Math.max()需要一个参数列表,但是如果我们只有一个数组,这时就可以用apply来转换。

像前面看到那样,我们用apply将数组转换为参数列表:

Math.max.apply(Math, [2,100,1,6,43]) // 100

在ES6中,你可以用展开操作符:

Math.max(...[2,100,1,6,43]) // 100

同样,我们也可以用展开操作符来替换concat数组:

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中,你可以用展开操作符展平(flatten)内嵌的数组:

const array1 = [2,100,1,6,43];
const array2 = [‘a‘, ‘b‘, ‘c‘, ‘d‘];
const array3 = [false, true, null, undefined];
console.log([...array1, ...array2, ...array3]);

4. 结论

Javascript经历过太多的变化。这篇文章讲到了所有Javascript开发者都应该了解的核心特性。同时,我们也讲了一些优秀实践,能让你的代码更加简洁明了。

如果你认为还有其他MUST KNOW的特性,麻烦在评论中吱一声,我来更新。

原文

译注:

貌似markdown编辑器不是太好用啊,发现了几个问题

  • 编辑器中预览和发布后的正文显示效果不一致
  • 目录中的编号显示不对
  • 强调显示的不对,旁边还有星号
  • 图标字符显示不出来
  • 代码块不显示语言名称
  • 最蛋疼的是,发表后点再修改,编辑器里面只有半拉文章了,其他的给我吃了啊
时间: 2024-10-08 13:07:10

Javascript ES6 特性概述(即ECMAScript 6和ES2015+)的相关文章

JavaScript ES6功能概述(ECMAScript 6和ES2015 +)

JavaScript在过去几年中发生了很大的变化.这些是您今天可以开始使用的12项新功能! 该语言的新增内容称为ECMAScript 6.它也称为ES6或ES2015 +. 自1995年JavaScript构思以来,它一直在缓慢发展.每隔几年就会发生新增事件. ECMAScript于1997年成立,旨在指导JavaScript的发展方向.它已经发布了ES3,ES5,ES6等版本. 如您所见,ES3,ES5和ES6之间存在10年和6年的差距.此后每年??进行小幅增量变更.而不是像ES6那样一次做大

JavaScript ES6特性

ES6 核心特性 块级作用域 let : 声明的变量存在块级作用域  不会声明提前 ES5 // ES5 // 声明提前 var x = 'outer'; function test(inner) { if (inner) { var x = 'inner'; console.log(x); } console.log(x); } test(false) //undefined test(true) // inner inner ES6 // ES6 // 声明不提前 let x = 'oute

ES6:JavaScript 新特性

5月14日,国务院发布了<国务院关于同意建立深化收入分配制度改革部际联席会议制度的批复>,同意建立由发改委牵头的深化收入分配制度改革部际联席会议制度.这是对政府一年多前首次发布的关于收入分配制度改革若干意见的落实,标志着中国收入分配改革迈出了实质性一步.根据批复,联席会议由国家发改委.财政部.人力资源和社会保障部等21个部门和单位组成.发改委主任徐绍史担任联席会议召集人,其他成员单位有关负责人为联席会议成员. 国务院下发的文件显示,部际联席会议制度的主要职责包括:在国务院领导下,统筹协调做好深

逆转序列的递归/尾递归(+destructuring assignment)实现(JavaScript + ES6)

这里是用 JavaScript 做的逆转序列(数组/字符串)的递归/尾递归实现.另外还尝鲜用了一下 ES6 的destructuring assignment + spread operator 做了一个更 functional 的版本(只支持数组). 正确性能通过测试(参见 放在我 Github 上的 demo,顺手写了一个小小的测试框架),不过效率就要打问号了——特别是用了 ES6 特性的版本.这里主要是写来玩 JS 的函数式特性的. 1. 逆转序列的递归实现 先用 Haskell 实现做草

JavaScript Oriented[探究面向对象的JavaScript高级语言特性]

JavaScript Oriented 探究面向对象的JavaScript高级语言特性 Prologue . JavaScript Introduce 1.  JS Abstract JavaScript是由Netscape公司工程师Brendan Eich研发的脚本语言,经过推广和流行,兼容ECMA-262标准,至今用于描述HTML网页行为.(前端验证,检测,响应,触发,控制等动态行为) Knowledge Tree 2.     About Document 本文涉及到的概念有JavaScr

JavaScript ES6箭头函数指南

前言 胖箭头函数(Fat arrow functions),又称箭头函数,是一个来自ECMAScript 2015(又称ES6)的全新特性.有传闻说,箭头函数的语法=>,是受到了CoffeeScript 的影响,并且它与CoffeeScript中的=>语法一样,共享this上下文. 箭头函数的产生,主要由两个目的:更简洁的语法和与父作用域共享关键字this.接下来,让我们来看几个详细的例子. 新的函数语法 传统的JavaScript函数语法并没有提供任何的灵活性,每一次你需要定义一个函数时,你

JavaScript 对象的概述

JavaScript 对象的概述 最简单的概述:在JavaScript中对象仅仅是属性的容器. 每个对象都可以有零个或多个属性值, 这些属性持有一个基本类型的值,也可以是持有一个对象引用. JavaScript中创建对象的常见方法 JavaScript中常见的常见对象的方式有三种: . 使用对象字面量 var obj = {} . 使用new运算符 var obj = new Object() . 使用create()函数 var obj = Object.create(null); 这三种创建

JavaScript es6 class类的理解。

在本篇文章我将会把我对JavaScript  es6新特性class类的理解.本着互联网的分享精神,我就将我自己的理解分享给大家. 使用es写一个类(构造函数) 在es5中大家一般都这么写一个类(构造函数) 另外需要注意,class类不会被提升. // 创建一个User构造函数 function User(name, age) { this.name = name; this.age = age; } // User构造函数的静态方法. User.getClassName = function

JavaScript ES6迭代器指南

惰性执行 迭代器允许我们在第一次调用next()函数之后,再执行相应的逻辑.在上面的例子里,当我们调用迭代器的瞬间,我们就立刻执行了排序和取值的工作.但是,如果next()函数永远不被调用的话,我们就浪费了性能.所以让我们来优化它: 1 table[Symbol.iterator] = function () { 2 var _this = this; 3 var keys = null; 4 var index = 0; 5 6 return { 7 next: function () { 8