ES 常用特性一览

82 阅读12分钟

本文是自 ES6 以来,对常用的特性的一个总结,关于每个特性的内容,都有单独的用法讲述,以便快速学习与查阅

ES6(ECMAScript 2015)

ES6 的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。

1. let 与 const

在 ES6 规范还没制定之前,我们是这么声明变量的: var number = 1
var 是没有块级作用域的,会导致一些不必要的麻烦

letconst 的声明方式和 var 是一样的

let

用于声明变量

使用

let number = 1
let string = 'hello'

const 与 var 的区别

都是用来声明 变量
let 有块级作用域,是更完美的 var
我基本不用var

1. var 可声明前置

并且可以重复声明

a = 1
var a
var a = 2
// 不报错
2. let 不可以声明前置
a = 1
let a
// 报错
3. let 不可重复声明
let a = 1
let a = 2
// 报错
let a = 1
var a = 2
// 报错 
4. let 存在块级作用域

作用域可以认为是两个大括号之间的区域

function fn() {
  let a = 1
  console.log(a)
}
console.log(a)
// 报错
5. let 暂时性死区

在 let 声明变量之前都是该变量的死区,在死区内不可使用该变量
这也需要拿出来说?

const

用来声明 常量
拥有 let 的特性,但是被声明的常量必须赋值,而且「值」不可改变!

被 const 声明的常量不可改变

示例1:
const a = 1
a = 2 // 报错

能理解!简单嘛!

示例2:
const obj = { a: 1 }
obj.a = 2 			// 不报错
obj = { a: 2 } 	// 报错

啊你骗人,你明明说了不可改变,那为什么 obj.a = 2 不报错?!?!

我们先来说说示例1:
示例1的 a 是简单类型,所以直接赋值的话,相当于改变 a 的值

再说说示例2:

  1. 这里的 obj 是属于引用类型,或者复杂类型的时候,那 obj 存的就是 { a: 1 } 的地址啊!那只要地址不变,那就没有问题啊!

那给 obj.a = 2,那 obj 里面存的地址变没变啊?
因为指向的还是这个对象,只不过对象里面的东西发生了改变。

  1. 但是这么写 obj = { a: 2 },这个意思是我新生成了个对象,然后赋值给 obj,那这个 obj 的地址是不是变了呀?那就会报错嘛!

哦哦...似懂非懂

2. 箭头函数

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的 thisargumentssupernew.target等,也不能用作构造函数。

语法上

let fn = n => {n + 1} // 只有一个参数的话,小括号可以省略
// 等价于
let fn = function (n) {return n + 1}

let fn = () => 1 // 注意对比返回值
// 等价于
let fn = function () {return 1}

let fn = (a, b) => a + b // 看出区别了吗,直接 return 可以省略大括号
// 等价于
let fn = function (a, b) {return a + b}

关于 this

箭头函数没有 this

普通函数中的this

  • 在简单调用中, 非严格模式 下指向window对象;
    严格模式 下为undefined
function normal() {
  console.log(this)
}
normal()
// => window {...}

// ------------------

'use strict';
function strictNormal() {
  console.log(this)
}
strictNormal()
// => undefined
  • 作为某个对象方法调用时,this指向该对象。
const obj = {
  normal: function() {
    console.log(this)
  }
}
obj.normal()
// => { normal: f() }
  • 被间接调用时,指向call/apply/bind的第一个参数;
    若第一个参数为nullthis的指向参考第一条的规则。
function normal() {
  console.log(this)
}

const obj = {
  name: 'Object'
}

normal.call(obj)
// => { name: 'Object' }
  • 普通函数被用作构造函数,用new关键字构造实例时,this指向新建的实例。
function Normal() {
  console.log(this)
}

const normal = new Normal()
// => Normal {}

箭头函数中的this

  • 箭头函数没有自己的this,箭头函数的this指向在定义(注意: 是定义时,不是调用时)的时候继承自外层第一个普通函数的this。所以,箭头函数中 this 的指向在它被定义的时候就已经确定了,之后永远不会改变。

由于箭头函数的this由外部非箭头函数的this决定,因此,若需要将一个函数作为回调函数去执行,并且不希望函数中的this发生改变,这时用箭头函数最适合不过。如果是普通函数,则需要用bind()进行this绑定。

let obj = {
  a: 10,
  b: () => {
    console.log(this.a) // undefined
    console.log(this) // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
  },
  c: function() {
    console.log(this.a) // 10
    console.log(this) // {a: 10, b: ƒ, c: ƒ}
  }
}
obj.b(); 
obj.c();

arguments 对象

普通函数: 普通函数内部,arguments 为特殊的类数组对象。包含了函数被调用时的参数列表。

箭头函数: 箭头函数内部是没有 arguments 对象,依赖于外部非箭头函数。 如果箭头函数想要拿到全部参数,也可以通过 剩余参数语法 获取到全部参数。

const arrow = (...arg) => {
  console.log(arg)
}
arrow(1, [1, 2], [1, 2, 3])
// => [1, [1, 2], [1, 2, 3]]

3. Promise

Promise 是异步编程的一种解决方案,比传统的解决方案 callback 更加的优雅。它最早由社区提出和实现的,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。

不使用 ES6

嵌套两个 setTimeout 回调函数:

setTimeout(function()
{
    console.log('Hello'); // 1秒后输出"Hello"
    setTimeout(function()
    {
        console.log('Hi'); // 2秒后输出"Hi"
    }, 1000);
}, 1000);

使用 ES6

  • 第一步

    • return new Promise((resolve, reject) => { ... })
    • 成功则调用 resolve(result)
    • 失败则调用 reject(error)
    • resolvereject 会再去调用成功和失败函数
  • 第二步

    • 使用 .then(success, fail) 传入成功和失败函数
let isGreenLight = true
const promise = new Promise((resolve, reject) => {
  isGreenLight ? resolve('绿灯') : reject('红灯')
})

promise
  .then((light) => {
    console.log(`现在交通灯为${light},你可以大胆往前走了`)
  })
  .catch((light) => {
    console.log(`现在交通灯为${light},你想走?那祝你好运!`)
  })
  .finally(() => {
    console.log('但是最终 promise 还是执行了,灯也亮了')
  })

// 现在交通灯为绿灯,你可以大胆往前走了
// 但是最终 promise 还是执行了,灯也是亮着的

上面的的代码使用两个 then 来进行异步编程串行化,避免了回调地狱

4. 解构赋值

解构赋值语法是 JavaScript 的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。

获取数组中的值

从数组中获取值并赋值到变量中,变量的顺序与数组中对象顺序对应。

const foo = ["one", "two", "three", "four"];

var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"

//如果你要忽略某些值,你可以按照下面的写法获取你想要的值
const [first, , , last] = foo;
console.log(first); // "one"
console.log(last); // "four"

//你也可以这样写
const a, b; //先声明变量

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

如果没有从数组中的获取到值,你可以为变量设置一个默认值。

const a, b;

[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

通过解构赋值可以方便的交换两个变量的值。

const a = 1;
const b = 3;

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

获取对象中的值

const student = {
  name:'Ming',
  age:'18',
  city:'Shanghai'
};

const {name,age,city} = student;
console.log(name); // "Ming"
console.log(age); // "18"
console.log(city); // "Shanghai"

5. 函数参数默认值

ES6 支持在定义函数的时候为其设置默认值:

function foo(height = 50, color = 'red')
{
    // ...
}

不使用默认值:

function foo(height, color)
{
    var height = height || 50;
    var color = color || 'red';
    //...
}

这样写一般没问题,但当 参数的布尔值为false 时,就会有问题了。比如,我们这样调用 foo 函数:

foo(0, "")

因为 0的布尔值为false,这样 height 的取值将是 50。同理 color 的取值为‘red’。

所以说,函数参数默认值 不仅能是代码变得更加简洁而且能规避一些问题。

6. 延展操作符

延展操作符...可以在函数调用/数组构造时, 将数组表达式或者 string 在语法层面展开;还可以在构造对象时, 将对象表达式按 key-value 的方式展开。

语法

函数调用:

fn(...iterableObj)

数组构造或字符串:

[...iterableObj, '4', ...'hello', 6]

构造对象时,进行克隆或者属性拷贝(ECMAScript 2018 规范新增特性):

const objClone = { ...obj }

7. 模板字符串

ES6 支持模板字符串,使得字符串的拼接更加的简洁、直观。

不使用模板字符串:

var name = 'Your name is ' + first + ' ' + last + '.'

使用模板字符串:

var name = `Your name is ${first} ${last}.`

8. 模块化

ES5 不支持原生的模块化,在 ES6 中模块作为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过 import 来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。

导出(export)

ES6 允许在一个模块中使用 export 来导出多个变量或函数。

导出变量

//test.js
export var name = 'Rainbow'

心得:ES6 不仅支持变量的导出,也支持常量的导出。 export const sqrt = Math.sqrt;//导出常量

ES6 将一个文件视为一个模块,上面的模块通过 export 向外输出了一个变量。一个模块也可以同时往外面输出多个变量。

 //test.js
 var name = 'Rainbow';
 var age = '24';
 export {name, age};

导出函数

// myModule.js
export function myModule(someArg) {
  return someArg;
}

导入(import)

定义好模块的输出以后就可以在另外一个模块通过 import 引用。

import {myModule} from 'myModule';// main.js
import {name,age} from 'test';// test.js

9. 类

对熟悉 Java,object-c,c#等纯面向对象语言的开发者来说,都会对 class 有一种特殊的情怀。ES6 引入了 class(类),让 JavaScript 的面向对象编程变得更加简单和易于理解。

  class Animal {
    // 构造函数,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数.
    constructor(name,color) {
      this.name = name;
      this.color = color;
    }
    // toString 是原型对象上的属性
    toString() {
      console.log('name:' + this.name + ',color:' + this.color);

    }
  }

 var animal = new Animal('dog','white');//实例化Animal
 animal.toString();

 console.log(animal.hasOwnProperty('name')); //true
 console.log(animal.hasOwnProperty('toString')); // false
 console.log(animal.__proto__.hasOwnProperty('toString')); // true

 class Cat extends Animal {
  constructor(action) {
    // 子类必须要在constructor中指定super 函数,否则在新建实例的时候会报错.
    // 如果没有置顶consructor,默认带super函数的constructor将会被添加、
    super('cat','white');
    this.action = action;
  }
  toString() {
    console.log(super.toString());
  }
 }

 var cat = new Cat('catch')
 cat.toString();

 // 实例cat 是 Cat 和 Animal 的实例,和Es5完全一致。
 console.log(cat instanceof Cat); // true
 console.log(cat instanceof Animal); // true

10. 对象属性简写

在 ES6 中允许我们在设置一个对象的属性的时候不指定属性名。

不使用 ES6

const name='Ming',age='18',city='Shanghai';

const student = {
    name:name,
    age:age,
    city:city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}

对象中必须包含属性和值,显得非常冗余。

使用 ES6

const name='Ming',age='18',city='Shanghai';

const student = {
    name,
    age,
    city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}

对象中直接写变量,非常简洁。

ES7(ECMAScript 2016)

1. Array.prototype.includes()

includes() 函数用来判断一个数组是否包含一个指定的值,如果包含则返回 true,否则返回false

includes 函数与 indexOf 函数很相似,下面两个表达式是等价的:

arr.includes(x)
arr.indexOf(x) >= 0

复制代码

接下来我们来判断数字中是否包含某个元素:

在 ES7 之前的做法

使用indexOf()验证数组中是否存在某个元素,这时需要根据返回值是否为-1 来判断:

let arr = ['react', 'angular', 'vue'];

if (arr.indexOf('react') !== -1) {
  console.log('react存在');
}

使用 ES7 的 includes()

使用 includes()验证数组中是否存在某个元素,这样更加直观简单:

let arr = ['react', 'angular', 'vue'];

if (arr.includes('react')) {
  console.log('react存在');
}

2. 指数操作符

ES8(ECMAScript 2017)

1. async/await

async/await 是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。

基本语法

前面添加了async的函数在执行后都会自动返回一个Promise对象:

function foo() {
    return 'imooc'
}
console.log(foo()) // 'imooc'

相当于

async function foo() {
    return 'imooc' // Promise.resolve('imooc')

    // let res =  Promise.resolve('imooc')
    // console.log(res)
}
console.log(foo()) // Promise
foo()

await后面需要跟异步操作,不然就没有意义,而且await后面的Promise对象不必写then,因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数。

function timeout() {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(1)
            resolve() // resolve('success')
        }, 1000)
    })
}

// 不加async和await是2、1   加了是1、2
async function foo() {
    await timeout() // let res = await timeout() res是success
    console.log(2)
}
foo()

对于失败的处理

function timeout() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve('success')
            reject('error')
        }, 1000)
    })
}
async function foo() {
    return await timeout()
}
foo().then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

在async函数中使用await,那么await这里的代码就会变成同步的了,意思就是说只有等await后面的Promise执行完成得到结果才会继续下去,await就是等待。

2. Object.values()

Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for...in,但是for...in还会遍历原型上的属性值)。

const obj = {
    name: 'baidu',
    web: 'www.baidu.com'
}

console.log(Object.values(obj))
// ["baidu", "www.baidu.com"]

3. Object.entries()

4. String padding

5. 函数参数列表结尾允许逗号

此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。

function clownsEverywhere(
    param1,
    param2
) {
    /* ... */
}

clownsEverywhere(
    'foo',
    'bar'
)

上面代码中,如果在param2或bar后面加一个逗号,就会报错。

如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数clownsEverywhere添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。

function clownsEverywhere(
    param1,
    param2,
) {
    /* ... */
}

clownsEverywhere(
    'foo',
    'bar',
)

这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。

6. Object.getOwnPropertyDescriptors()

7. SharedArrayBuffer 对象

8. Atomics 对象

ES9(ECMAScript 2018)

ES10(ECMAScript 2019)

ES11(ECMAScript 2020)

ES12(ECMAScript 2021)

ES13(ECMAScript 2022)