ES6常用方法解析

作者: 程序员小哥哥 | 来源:发表于2018-07-25 16:24 被阅读10次
    ES6常用方法解析
    • const和let
    • 解构赋值
    • 模板字符串
    • 函数
    • 扩展对象
    • import和export
    • Promise
    • async与await

    一:const和let

    let

    ES6的块级作用域

    let

    function f1() {
      let n = 5;
      if (true) {
        let n = 10;
      }
      console.log(n); // 5
    }
    

    上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。

    变量提升

    let不像var那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。

    console.log(foo); // 输出undefined
    console.log(bar); // 报错ReferenceError
    
    var foo = 2;
    let bar = 2;
    

    上面代码中,变量foovar命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量barlet命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。

    const命令

    const声明一个只读的常量。一旦声明,常量的值就不能改变。

    const PI = 3.1415;
    PI // 3.1415
    
    PI = 3;
    // TypeError: Assignment to constant variable.
    

    const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

    const foo;
    // SyntaxError: Missing initializer in const declaration
    

    const的作用域与let命令相同:只在声明所在的块级作用域内有效。

    if (true) {
      const MAX = 5;
    }
    
    MAX // Uncaught ReferenceError: MAX is not defined
    

    const声明的常量,也与let一样不可重复声明。

    var message = "Hello!";
    let age = 25;
    
    // 以下两行都会报错
    const message = "Goodbye!";
    const age = 30;
    

    总结

    const声明一个只读常量,一旦声明,常量的值就不能改变

    const num1 = {
        a:12
    };
    num1.a=1; //可改变常量的属性值,但无法更改常量值
    

    let用来声明变量,变量值可随意更改

    {
        let a = 12;
        var b = 1;
        console.log(a);
        a=a+12;
        console.log(a)
    }
    

    var命令存在变量提升效用,let命令没有这个问题

    constlet声明的常量及变量只在所在的块级作用域内有效,常量变量是不提升,同样存在暂时性死区,只能在声明的位置后面使用

    letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量

    二:结构赋值

    基本用法

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

    以前,为变量赋值,只能直接指定值。

    var a = 1;
    var b = 2;
    var c = 3;
    

    ES6允许写成下面这样。

    var [a, b, c] = [1, 2, 3];
    

    也就是说ES6 里允许给数组进行赋值

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

    本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值.

    默认值

    var [foo = true] = [];
    foo // true
    
    [x, y = 'b'] = ['a']; // x='a', y='b'
    [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
    

    ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。

    var [x = 1] = [undefined];
    x // 1
    
    var [x = 1] = [null];
    x // null
    

    上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined

    如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

    function f() {
      console.log('aaa');
    }
    
    let [x = f()] = [1];
    

    上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。

    对象的解构赋值

    解构不仅可以用于数组,还可以用于对象。

    var { foo, bar } = { foo: "aaa", bar: "bbb" };
    foo // "aaa"
    bar // "bbb"
    

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

    var { bar, foo } = { foo: "aaa", bar: "bbb" };
    foo // "aaa"
    bar // "bbb"
    
    var { baz } = { foo: "aaa", bar: "bbb" };
    baz // undefined
    

    上面代码的第一个例子,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。第二个例子的变量没有对应的同名属性,导致取不到值,最后等于undefined
    如果变量名与属性名不一致,必须写成下面这样。

    var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
    baz // "aaa"
    
    let obj = { first: 'hello', last: 'world' };
    let { first: f, last: l } = obj;
    f // 'hello'
    l // 'world'
    

    这实际上说明,对象的解构赋值是下面形式的简写(参见《对象的扩展》一章)。

    var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
    

    也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

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

    上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo

    三:模版字符串

    传统的JavaScript语言,输出模板通常是这样写的。

    $('#result').append(
      'There are <b>' + basket.count + '</b> ' +
      'items in your basket, ' +
      '<em>' + basket.onSale +
      '</em> are on sale!'
    );
    

    上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。

    $('#result').append(`
      There are <b>${basket.count}</b> items
       in your basket, <em>${basket.onSale}</em>
      are on sale!
    `);
    

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

    // 普通字符串
    `In JavaScript '\n' is a line-feed.`
    
    // 多行字符串
    `In JavaScript this is
     not legal.`
    
    console.log(`string text line 1
    string text line 2`);
    
    // 字符串中嵌入变量
    var name = "Bob", time = "today";
    `Hello ${name}, how are you ${time}?`
    

    上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。

    var greeting = `\`Yo\` World!`;
    

    如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

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

    上面代码中,所有模板字符串的空格和换行,都是被保留的,比如<ul>标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。

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

    模板字符串中嵌入变量,需要将变量名写在${}之中。

    function authorize(user, action) {
      if (!user.hasPrivilege(action)) {
        throw new Error(
          // 传统写法为
          // 'User '
          // + user.name
          // + ' is not authorized to do '
          // + action
          // + '.'
          `User ${user.name} is not authorized to do ${action}.`);
      }
    }
    

    大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。

    var x = 1;
    var y = 2;
    
    `${x} + ${y} = ${x + y}`
    // "1 + 2 = 3"
    
    `${x} + ${y * 2} = ${x + y * 2}`
    // "1 + 4 = 5"
    
    var obj = {x: 1, y: 2};
    `${obj.x + obj.y}`
    // 3
    

    模板字符串之中还能调用函数。

    function fn() {
      return "Hello World";
    }
    
    `foo ${fn()} bar`
    // foo Hello World bar
    

    如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。

    如果模板字符串中的变量没有声明,将报错。

    // 变量place没有声明
    var msg = `Hello, ${place}`;
    // 报错
    

    由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。

    `Hello ${'World'}`
    // "Hello World"
    

    模板字符串甚至还能嵌套。

    const tmpl = addrs => `
      <table>
      ${addrs.map(addr => `
        <tr><td>${addr.first}</td></tr>
        <tr><td>${addr.last}</td></tr>
      `).join('')}
      </table>
    `;
    

    上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。

    const data = [
        { first: '<Jane>', last: 'Bond' },
        { first: 'Lars', last: '<Croft>' },
    ];
    
    console.log(tmpl(data));
    // <table>
    //
    //   <tr><td><Jane></td></tr>
    //   <tr><td>Bond</td></tr>
    //
    //   <tr><td>Lars</td></tr>
    //   <tr><td><Croft></td></tr>
    //
    // </table>
    

    如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。

    // 写法一
    let str = 'return ' + '`Hello ${name}!`';
    let func = new Function('name', str);
    func('Jack') // "Hello Jack!"
    
    // 写法二
    let str = '(name) => `Hello ${name}!`';
    let func = eval.call(null, str);
    func('Jack') // "Hello Jack!"
    

    四:函数&箭头函数

    ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

    function actions(num = 200) {
      console.log(num)
    }
    actions() //200
    actions(300) //300
    

    两种默认写法的对比:

    function Fend1({w=0,y=0}={}){
      return [w,y]
    }
    function Fend2({w,y}={w:0,y:0}){
      return [w,y]
    }
    console.log(Fend1())    // [0, 0]
    console.log(Fend2())    // [0, 0]
    
    console.log(Fend1({w:3,y:8}))   // [3, 8]
    console.log(Fend2({w:3,y:8}))   // [3, 8]
    
    console.log(Fend1({w:2}))   //[2, 0]
    console.log(Fend2({w:2}))   //[2, undefined]
    
    console.log(Fend1({}))  //[0, 0]
    console.log(Fend2({}))  //[undefined, undefined]
    
    console.log(Fend1({y:8}))   //[0, 8]
    console.log(Fend2({y:8}))   //[undefined, 8]
    

    建议使用第一种方式设置默认值,防止参数为undefined

    箭头函数

    ES6允许使用“箭头”(=>)定义函数。

    var f = v => v;
    

    上面的箭头函数等同于:

    var f = function(v) {
      return v;
    };
    

    如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

    var f = () => 5;
    // 等同于
    var f = function () { return 5 };
    
    var sum = (num1, num2) => num1 + num2;
    // 等同于
    var sum = function(num1, num2) {
      return num1 + num2;
    };
    

    如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

    var sum = (num1, num2) => { return num1 + num2; }
    

    由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。

    var getTempItem = id => ({ id: id, name: "Temp" });
    

    箭头函数可以与变量解构结合使用。

    const full = ({ first, last }) => first + ' ' + last;
    
    // 等同于
    function full(person) {
      return person.first + ' ' + person.last;
    }
    

    箭头函数使得表达更加简洁。

    const isEven = n => n % 2 == 0;
    const square = n => n * n;
    

    五:扩展对象

    ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值

    var ser1 = (name,age) => ({name,age})//es6箭头函数写法
    var ser2 = (name,age) => ({name:name,age:age})
    console.log(ser1(12,"45"))
    console.log(ser2(12,"45"))
    

    除了属性外,方法也可以简写

    var people = {
    name : 'Lux',
        senGet (){
            console.log(this.name)
        }
    }
    people.senGet();//  Lux
    

    此方法同样适用于返回值

    var getPoint = ()=>{
        const x = 1;
        const y = 2;
        return {x,y}
    }
    console.log(getPoint())//{x: 1, y: 2}
    

    ES6 对象提供了Object.assign()这个方法来实现浅复制
    Object.assign()可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象

    const object1 = {a:12,b:13};
    const object2 = {b:16,c:15};
    const object3 = {d:17,e:14};
    const object4 = {f:19,e:18};
    const copy = Object.assign(object1,object2,object3,object4);//依次传入所浅复制的对象
    console.log(copy)//{a: 12, b: 16, c: 15, d: 17, e: 18, …}
    

    如果只有一个参数,Object.assign会直接返回该参数
    如果该参数不是对象,则会先转成对象,然后返回
    由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。

    console.log(Object.assign(undefined)) // 报错
    console.log(Object.assign(null)) // 报错
    

    如果undefinednull不在首参数,就不会报错

    let obj = {a: 1};
    console.log(Object.assign(obj, undefined) === obj )// true
    console.log(Object.assign(obj, null) === obj )// true
    

    其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错
    但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。

    const v1 = 'abc';
    const v2 = true;
    const v3 = 10;
    const objs = Object.assign({}, v1, v2, v3);
    console.log(objs); //{0: "a", 1: "b", 2: "c"}
    

    只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性

    注意点:

    • Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
    • 对于Object.assign这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加
    • Object.assign可以用来处理数组,但是会把数组视为对象。(用于数组合并去重)
    • Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制

    六:import和export

    JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来ES6 模块不是对象,需要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this

    模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能

    封装对外暴露的接口:

    // utils.js
    export let counter = 3;
    export function incCounter() {....};
    

    当文档中有且只有一个默认函数时可使用

    export default function incCounter() {....};
    

    二者不能同时出现在一个文件中

    引入封装好的模块接口:
    模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。import { stat, exists, readFile } from 'fs';

    上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高

    ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致。

    <script type="modules">
      import utils from "./utils.js";
    </script>
    

    七:Promise函数

    简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果

    Promise对象有以下两个特点:

    • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
    • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果

    Promise也有一些缺点:

    • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
    • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
    • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

    Promise 异步操作

    // 创建一个promise实例
    const promise = new Promise(function(resolve, reject) {
      // ... some code
        let se =12;
      if (se == 12){ //状态成立的时候
        resolve(se);
      } else {//状态失败的时候
        reject(se +1);
      }
    });
    

    Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数

    promise.then(function(value) {
      // success
      console.log(value)
    }, function(error) {
      // failure
      console.log(error)
    });
    

    then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用,其中,第二个函数是可选的,不一定要提供

    八:async与await

    ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
    async 函数是什么?一句话,它就是 Generator 函数的语法糖。

    async函数对 Generator 函数的改进,体现在以下四点:

    • 内置执行器,Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器
    • 更好的语义,async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    • 更广的适用性,async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
      async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖
      async函数返回一个 Promise 对象,可以使用then方法添加回调函数:
    function timeout(ms) {
      return new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }
    
    async function asyncPrint(value, ms) {
      await timeout(ms);
      console.log(value);
    }
    
    asyncPrint('hello world', 50);
    

    由于async函数返回的是Promise 对象,可以作为await命令的参数。所以,上面的例子也可以写成下面的形式。

    async function timeout(ms) {
      await new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }
    
    async function asyncPrint(value, ms) {
      await timeout(ms);
      console.log(value);
    }
    asyncPrint('hello world', 50);
    

    async函数有多种使用形式:

    // 函数声明
    async function foo() {}
    
    // 函数表达式
    const foo = async function () {};
    
    // 对象的方法
    let obj = { async foo() {} };
    obj.foo().then(...)
    
    // Class 的方法
    class Storage {
      constructor() {
        this.cachePromise = caches.open('avatars');
      }
    
      async getAvatar(name) {
        const cache = await this.cachePromise;
        return cache.match(`/avatars/${name}.jpg`);
      }
    }
    
    const storage = new Storage();
    storage.getAvatar('jake').then(…);
    
    // 箭头函数
    const foo = async () => {};
    async函数的语法:
    async函数返回一个 Promise 对象。
    async函数内部return语句返回的值,会成为then方法回调函数的参数。
    async function f() {
      return 'hello world';
    }
    
    f().then(v => console.log(v))
    // "hello world"
    

    上面代码中,函数f内部return命令返回的值,会被then方法回调函数接收到async函数的难点是错误处理机制:

    async函数返回一个 Promise 对象,async函数内部return语句返回的值,会成为then方法回调函数的参数

    async function sfs() {
      try {
        await new Promise(function (resolve, reject) {
          throw new Error('出错了');
        });
      } catch(e) {
        
      }
      return await('hello world');
    }
    
    console.log(sfs())
    
    sfs().then(v=>console.log(v)).catch(v=>console.log(v))
    

    相关文章

      网友评论

        本文标题:ES6常用方法解析

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