es6+

195 阅读11分钟

es6

javaScript es6 简称ECMAScript 6是 2015年提出的

Let 声明


// 使用 let 声明的变量
let name = 'Sara';
{
   //只能在里面访问
    let name = 'Peter';

    console.log(name);//Peter
}
console.log(name);//Sara 

Const常量

const语句用于在JavaScript中声明常量。例如,

// 用 const 声明的名称不能更改
const name = 'Sara';

声明后,您将无法更改const变量的值。

箭头函数 在ES6版本中,可以使用箭头函数开创建函数表达式。

// 函数表达式
let x = function(x, y) {
   return x * y;
}

可以写成

// 使用箭头函数的函数表达式
let x = (x, y) => x * y;

箭头函数没有自己的this 也无法通过call apply bind 修改

Class类

JavaScript类用于创建对象。类似于构造函数例如,

class Person {
  constructor(name) {
    this.name = name;
  }
}

关键字class用于创建一个类。这些属性是在构造函数中分配的。

现在您可以创建一个对象。例如,

class Person {
  constructor(name) {
    this.name = name;
  }
}

const person1 = new Person('John');

console.log(person1.name);//John

constructor

  1. constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

  2. constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象

  3. 类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

class  Foo {

 constructor() {

   return  Object.create(null);

  }
}

super当作函数使用

super()执行父类的构造函数

class A {}
class B extends A {
  constructor() {
    super(); //ES6 要求,子类的构造函数必须执行一次super函数
  }
}

super() 返回的是子类的实例,即 super 内部的 this 指向的是B,此时 super() 相当于 A.prototype.constructor.call(this)

class A {
  constructor() {
    console.log(new.target.name); //new.target指向当前正在执行的函数
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

在上面的例子中,super() 执行时,它指向的是子类的构造函数

当作对象使用

在普通方法中,指向父类的原型对象;在静态方法中,指向父类

class A {
  c() {
    return 2;
  }
}
class B extends A {
  constructor() {
    super();
    console.log(super.c()); // 2
  }
}
let b = new B();

上面代码中,子类B当中的super.c(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.c()就相当于A.prototype.c()

模板文字

模板文字使在字符串中包含变量变得更加容易。例如,在您必须做之前:

const first_name = "Jack";
const last_name = "Sparrow";

console.log('Hello ' + first_name + ' ' + last_name);

可以通过以下方式使用模板文字来实现:

const first_name = "Jack";
const last_name = "Sparrow";

console.log(`Hello ${first_name} ${last_name}`);

Import和Export

您可以导出(export)函数或程序,然后通过导入(import)将其用于其他程序。这有助于制造可重复使用的组件。例如,如果您有两个名为contact.js和home.js的JavaScript文件。

在contact.js文件中,您可以export contact()函数:

// export
export default function contact(name, age) {
    console.log(`The name is ${name}. And age is ${age}.`);
}

然后,当您想在另一个文件中使用contact()函数时,只需导入 import 该函数即可。例如,在home.js文件中:

import contact from './contact.js';

contact('Sara', 25);
// The name is Sara. And age is 25

Promise

Promise用于处理异步任务。例如,

// 返回一个Promise
let countValue = new Promise(function (resolve, reject) {
   reject('Promise rejected'); 
});

// 当 promise 成功解决时执行
countValue.then(
    function successValue(result) {
        console.log(result);
    },
 )

默认参数值

在ES6版本中,可以在函数参数中传递默认值。例如,

function sum(x, y = 5) {

   //take sum
   //the value of y is 5 if not passed
    console.log(x + y);
}

sum(5);//10
sum(5, 15);//20

Rest参数

您可以使用 rest parameter 将不确定数量的参数表示为数组。例如,

function show(a, b, ...args) {
  console.log(a);//one
  console.log(b);//two
  console.log(args);//["three", "four", "five", "six"]
}

show('one', 'two', 'three', 'four', 'five', 'six')

您使用 ... 语法传递其余参数。因此,名称为rest parameter

Array.prototype.includes()

includes()作用,是查找一个值在不在数组里,若是存在则返回true,不存在返回false.

1.基本用法:

['a', 'b', 'c'].includes('a')     // true
['a', 'b', 'c'].includes('d')     // false

2.接收俩个参数:要搜索的值和搜索的开始索引

['a', 'b', 'c', 'd'].includes('b')         // true
['a', 'b', 'c', 'd'].includes('b', 1)      // true
['a', 'b', 'c', 'd'].includes('b', 2)      // false

精确性

两者都是采用===的操作符来作比较的,不同之处在于:对于NaN的处理结果不同。

我们知道js中 NaN === NaN 的结果是false,indexOf()也是这样处理的,但是includes()不是这样的。

**

let demo = [1, NaN, 2, 3]

demo.indexOf(NaN)        //-1
demo.includes(NaN)       //true

async await

异步函数async function()

避免有更多的请求操作,出现多重嵌套,也就是俗称的“回调地狱”

因此提出了ES6的Promise,将回调函数的嵌套,改为了链式调用:

**

var promise = new Promise((resolve, reject) => {
  this.login(resolve);
})
.then(() => {
  this.getInfo()
})
.catch(() => {
  console.log('Error')
})

异步函数存在以下四种使用形式:

  • 函数声明: async function foo() {}
  • 函数表达式: const foo = async function() {}
  • 对象的方式: let obj = { async foo() {} }
  • 箭头函数: const foo = async () => {}

支持返回Promise和同步的值

async用于定义一个异步函数,该函数返回一个Promise。
如果async函数返回的是一个同步的值,这个值将被包装成一个理解resolve的Promise,等同于return Promise.resolve(value)
await用于一个异步操作之前,表示要“等待”这个异步操作的返回值。await也可以用于一个同步的值。

我们的async函数中可以包含多个异步操作,其异常和Promise链有相同之处,如果有一个Promise被reject()那么后面的将不会再进行。

**

    let count = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('promise故意抛出异常')
            }, 1000);
        })
    }
    let list = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve([1, 2, 3])
            }, 1000);
        })
    }

    let getList = async () => {
        let c = await count()
        console.log('async')    //此段代码并没有执行
        let l = await list()
        return { count: c, list: l }
    }
    console.time('start');
    getList().then(res => {
        console.log(res)
    })
    .catch(err => {
        console.timeEnd('start')
        console.log(err)
    })
    
    //start: 1000.81494140625ms
    //promise故意抛出异常

可以看到上面的案例,async捕获到了一个错误之后就会立马进入.catch()中,不执行之后的代码

并行

上面的案例中,async采用的是串行处理

count()和list()是有先后顺序的

**

let c = await count()
let l = await list()

实际用法中,若是请求的两个异步操作没有关联和先后顺序性可以采用下面的做法

**

let res = await Promise.all([count(), list()])
return res

//res的结果为
//[ 100, [ 1, 2, 3 ] ]

与Generator的关系

先来回顾一下ES6中Generator函数的用法:

**

    function* getList() {
        const c = yield count()
        const l = yield list()
        return 'end'
    }
    var gl = getList()
    console.log(gl.next()) // {value: Promise, done: false}
    console.log(gl.next()) // {value: Promise, done: false}
    console.log(gl.next()) // {value: 'end', done: true}

虽然Generator将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。此时,我们便希望能出现一种能自动执行Generator函数的方法。我们的主角来了:async/await。

ES8引入了async函数,使得异步操作变得更加方便。简单说来,它就是Generator函数的语法糖。

**

let getList = async () => {
  const c = await count()
  const l = await list()
}

Object.entries()

作用

作用:将一个对象中可枚举属性的键名和键值按照二维数组的方式返回。

若对象是数组,则会将数组的下标作为键值返回。

**

Object.entries({ one: 1, two: 2 })    //[['one', 1], ['two', 2]]
Object.entries([1, 2])                //[['0', 1], ['1', 2]]

要点

1.若是键名是Symbol,编译时会被自动忽略

Object.entries({[Symbol()]:1, two: 2})  //[['two', 2]]

2.entries()返回的数组顺序和for循环一样,即如果对象的key值是数字,则返回值会对key值进行排序,返回的是排序后的结果

Object.entries({ 3: 'a', 4: 'b', 1: 'c' })    //[['1', 'c'], ['3', 'a'], ['4', 'b']]

3.利用Object.entries()创建一个真正的Map

    var obj = { foo: 'bar', baz: 42 };
    
    var map1 = new Map([['foo', 'bar'], ['baz', 42]]); //原本的创建方式
    var map2 = new Map(Object.entries(obj));    //等同于map1

    console.log(map1);// Map { foo: "bar", baz: 42 }
    console.log(map2);// Map { foo: "bar", baz: 42 }

Object.values()

作用

作用:只返回自己的键值对中属性的值。它返回的数组顺序,也跟Object.entries()保持一致

Object.values({ one: 1, two: 2 })            //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' })    //['c', 'a', 'b']

与Object.keys()比较

ES6中的Object.keys()返回的是键名

    var obj = { foo: 'bar', baz: 42 };
    console.log(Object.keys(obj)) //["foo", "baz"]
    console.log(Object.values(obj)) //["bar", 42]
    
    //Object.keys()的作用就类似于for...in
    function myKeys() {
        let keyArr = []
        for (let key in obj1) {
            keyArr.push(key)
            console.log(key)
        }
        return keyArr
    }
    console.log(myKeys(obj1)) //["foo", "baz"]

entries()、values()总结

**

    var obj = { foo: 'bar', baz: 42 };
    console.log(Object.keys(obj)) //["foo", "baz"]
    console.log(Object.values(obj)) //["bar", 42]
    console.log(Object.entries(obj)) //[["foo", "bar"], ["baz", 42]]

修饰器Decorator

ES8神器Decorator,修饰器,也称修饰器模式

网上使用Decorator的教材有很多,大多都是要需要使用插件来让浏览器支持Decorator。这里长话短说,贴上一个最精简的使用教程:

1.创建一个名为:Decorator的文件夹

2.在文件夹目录下执行命令行

**

npm i babel-plugin-transform-decorators-legacy babel-register --save-dev

此时文件夹下会出现俩个文件: node_modules 依赖文件夹和package.json-lock.json

3.创建文件 complie.js

**

require('babel-register')({
    plugins: ['transform-decorators-legacy']
});
require("./app.js")

4.创建文件 app.js

**

@addSkill
class Person { }
function addSkill(target) {
    target.say = "hello world";
}
console.log(Person.say)   //'hello world'

5.在根目录下执行指令:

**

node complie.js

此时可以看到命令行中打印出了 hello world

简单介绍下上面步骤的原理:

第二步中使用了俩个基础插件:

**

transform-decorators-legacy:
//是第三方插件,用于支持decorators

babel-register//用于接入node api

第三步、第四步创建的俩个文件

**

complie.js  //用来编译app
app.js   //使用了装饰器的js文件

第五步:

**

原理:
1,node执行complie.js文件;
2,complie文件改写了node的require方法;
3,complie在引用app.js,使用了新的require方法;
4,app.js在加载过程中被编译,并执行。

当然你也可以将app.js替换为app.ts 不过别忘了把complie.js中的app.js修改为app.ts

**

// app.ts
@addSkill
class Person { }
function addSkill(target) {
    target.say = "hello world";
}
console.log(Person['say'])   
//这里如果直接使用Person.say会提示say属性不存在,如我使用的vscode编辑器就会报错,是因为ts的原因,只需要用[]的形式获取对象属性即可。

注:ts中有些语法是和js中不一样的,比如有些对象上提示没有属性的时候,只需要换一种获取对象属性的方式即可。

类修饰器

直接作用在类上面的修饰器,我们可以称之为类修饰器。

如上面案例中的@addSkill就是一个类修饰器,它修改了Person这个类的行为,为它加上了静态属性say

addSkill函数的参数target是Person这个类本身。

1.修饰器的执行原理基本就是这样:

**

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

换句话说,类修饰器是一个对类进行处理的函数。

它的第一个参数target就是函数要处理的目标类。

2.多参数

当然如果你想要有多个参数也是可以的,我们可以在修饰器外面再封装一层函数:

**

@addSkill("hello world")
class Person { }
function addSkill(text) {
    return function(target) {
        target.say = text;
    }
}
console.log(Person.say)  //'hello world'

上面代码中,修饰器addSkill可以接受参数,这就等于可以修改修饰器的行为。

3.修饰器在什么时候执行。

先来看一个案例:

**

@looks
class Person { }
function looks(target) {
    console.log('I am handsome')
    target.looks = 'handsome'
}

console.log(Person['looks'])

//I am handsome
//handsome

在修饰器@looks中添加一个console.log()语句,却发现它是最早执行的,其次才打印出handsome

这是因为装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

装饰器是在编译时就执行的函数

方法修饰器

上面的案例中,修饰器作用的对象是类本身。

当然修饰器不仅仅这么简单,它也可以作用在类里的某个方法或者属性上,这样的修饰器我们称它为方法修饰器。

如下面的案例:

**

class Person {
    constructor() {}
    @myname  //方法修饰器
    name() {
        console.log('霖呆呆') 
    }
}
function myname(target, key, descriptor) {
    console.log(target);
    console.log(key);
    console.log(descriptor);
    descriptor.value = function() {
        console.log('霖呆呆')
    }
}

var personOne = new Person() //实例化
personOne.name() //调用name()方法


//打印结果:
Person {}
name
{ value: [Function: name],
  writable: true,
  enumerable: false,
  configurable: true 
 }
霖呆呆

上面案例中的修饰器@myname是放在name()方法上的,myname函数有三个参数:

**

target: 类的原型对象,上例是Person.prototype
key: 所要修饰的属性名  name
descriptor: 该属性的描述对象

我们改变了descriptor中的value,使之打印出霖呆呆。

多个修饰器的执行顺序

若是同一个方法上有多个修饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行。

**

class Person {
    constructor() {}
    @dec(1)
    @dec(2)
    name() {
        console.log('霖呆呆')
    }
}
function dec(id) {
    console.log('out', id);
    return function(target, key, descriptor) {
        console.log(id);
    }
}

var person = new Person()
person.name()
//结果
out 1
out 2
2
1
霖呆呆

如上所属,外层修饰器dec(1)先进入,但是内层修饰器dec(2)先执行。

不能作用于函数

修饰器不能作用于函数之上,这是因为函数和变量一样都会提升

**

var counter = 0;

var add = function () {
  counter++;
};

@add
function foo() {
}

如上面的例子所示,给函数foo()定义了修饰器@add,作用是想将counter++

预计的结果counter为1,但实际上却还是为0

原因:

定义的函数foo()会被提升至最上层,定义的变量counteradd也会被提升,效果如下:

**

@add
function foo() {
}

var counter;
var add;

counter = 0;

add = function () {
  counter++;
};

总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。

另一方面,如果一定要修饰函数,可以采用高阶函数的形式直接执行。

如在中的例子所示:

**

        function doSometing(name) {
            console.log('Hello' + name)
        }
        function myDecorator(fn) {
            return function() {
                console.log('start')
                const res = fn.apply(this, arguments)
                console.log('end')
                return res
            }
        }
        const wrapped = myDecorator(doSometing)
        doSometing('lindaidai')
        //Hello lindaidai
        
        wrapped('lindaidai')
        //start 
        //Hello lindaidai
        //end