ES6 新特性

作者: 陌盍 | 来源:发表于2019-02-20 18:18 被阅读133次

一、let 和 const 命令

  • let命令: 用于声明对象,用法类似于var,但是 let 所声明的变量只在let命令所在的代码块内有效
{
  var a = 1;
  let b = 2;
}
console.log(a);     // 1
console.log(b);     // 报错  Uncaught ReferenceError: b is not defined
  • const命令: 声明一个只读的常量,一旦声明,常量的值就不能改变,这也意味着,const一旦声明变量,就必须立即初始化,不能以后再赋值。且constlet的作用域相同,只在声明所在的块级作用域内有效
const c = 10;
c;     // 10
c= 100;     // 报错 Uncaught TypeError: Assignment to constant variable.

if(true) {
    const d = 5;
}
console.log(d);    // 报错 ReferenceError: d is not defined
  • 特点:
    1.letconst 命令声明的变量都不存在变量提升,即其声明的变量一定要在声明后使用,否则报错;
    2.都存在暂时性死区,即只要块级作用域内存在letconst命令,它所声明的变量就“绑定”这个区域,不再受外部的影响。
var tmp = 111;
if (true) {
    tmp = 'abc';    // 报错   ReferenceError: tmp is not defined
    let tmp = 5;
}

二、解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

1.数组的解构赋值
  • 模式匹配:只要等号两边的模式相同,左边的变量就会被赋予对应的值。
let [a, b, c] = [1, 2, 3];
console.log(a, b, c);     // 1   2   3
  • 使用嵌套数组进行解构
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

如果解构不成功,变量的值就等于 undefined

let [foo] = [];
let [bar, foo] = [1];
//  以上两种情况都属于解构不成功,foo的值都会等于undefined
  • 不完全解构:即等号左边的模式,只匹配一部分的等号右边的数组,这种情况下,解构依然可以成功。
let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
2.对象的解构赋值

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
//  等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined     变量没有对应的同名属性,导致取不到值,最后等于undefined

如果变量名与属性名不一致,必须写成下面这样:

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。上面第一个例子,foo是匹配的模式,baz才是变量,真正被赋值的是变量baz,而不是模式foo。

3.字符串的解构赋值

字符串也可以解构赋值,此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

三、Iterator(遍历器) 和 for...of 循环

遍历器(Iterator)是一种用来统一处理所有不同数据结构的接口机制,为各种不同的数据结构提供统一的访问机制。遍历器有三个作用,一是为各种数据结构提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环,遍历器Iterator 接口主要供for...of消费。

var a = ['A', 'B', 'C'];
var b = new Set(['A', 'B', 'C']);
var c = new Map([[1, 'A'], [2, 'B'], [3, 'C']]);
for (var x of a) { // 遍历Array
    console.log(x);    //   A  B  C
}
for (var x of b) { // 遍历Set
    console.log(x);      // A  B  C
}
for (var x of c) { // 遍历Map
    console.log(x[0] + '=' + x[1]);    // 1=A   2=B   3=C
}

四、箭头函数

ES6 允许使用“箭头”(=>)定义函数。如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;    // 函数不需要参数
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;   // 函数需要多个参数
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

使用注意点

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

说明:箭头函数没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

五、模板字符串

模板字符串(template string)是增强版的字符串,用反引号(``)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中用${变量名}的方式嵌入变量。

使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。比如<ul>标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`.trim());

六、Promise对象

Promise 是异步编程的一种解决方案,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise状态pending(进行中)、fulfilled(已成功)和rejected(已失败)

Promise特点:

  • 对象的状态不受外界影响,Promise对象代表一个异步操作,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态;
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

Promise优缺点

  • 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
  • Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

Promise 新建后立即执行,所以首先输出的是 Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');
// 依次打印:Promise   Hi   resolved

下面用Promise对象实现Ajax操作:

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);
      } 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("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

七、Class 类

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法。

// js 中生成实例对象的传统方法是通过构造函数
function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);

//  ES6 的class改写上面代码:定义一个“类”,里面一个constructor方法,这就是构造方法,而this关键字则代表实例对象
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

生成类的实例的写法,与 ES5 完全一样,也是使用new命令,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

//定义类
class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }

}

var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

Class 可以通过extends关键字实现继承

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);  // 调用父类的constructor(x, y)
    this.color = color;
  }
  toString() {
    return this.color + ' ' + super.toString();  // 调用了父类的toString()
  }
}

上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}
class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}

上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

相关文章

网友评论

    本文标题:ES6 新特性

    本文链接:https://www.haomeiwen.com/subject/waphyqtx.html