1. 首页
  2. 前端

ES6入门到进阶新增知识点详细介绍和案例分析

一、简介

ES6(全称ECMAScript 6.0)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

  1. ECMAScript 和 JavaScript 的关系

ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。ECMA是“European Computer Manufactures Association”的缩写,中文称“欧洲计算机制造联合会”。

  1. ES6和ES2015的关系

ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。

  1. ES6声明变量的六种方法

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

  1. JavaScript的数据类型

javaScript的数据类型分为数字、字符串、布尔、function、object、undefined,6种类型。

验证类型如下:

       typeof 10;  // number 数字

       typeof '哈哈'  // string 字符串

       typeof true // boolean 布尔值 true / false

       typeof function(){} // function 函数

       typeof [1,'2','3'] // object 对象

       typeof document // object 对象

       typeof {} // object 对象

       typeof null; // object 对象

       typeof undefined // undefined 未定义

二、ES6声明和解构

1.let命令和const命令

1.1    let命令

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

{
    let a = 10;
    var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

for循环的计数器,就很合适使用let命令。

    var a = [];
    for (var i = 0; i < 10; i++) {
        a[i] = function () {
           console.log(i);
        };
    }
    a[6](); // 10

上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。

如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。

     var a = [];
     for (let i = 0; i < 10; i++) {
         a[i] = function () {
             console.log(i);
         };
     }
     a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

    for (let i = 0; i < 3; i++) {
         let i = 'abc';
         console.log(i);
     }
    // abc
    // abc
    // abc

上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。

1.2不存在变量提升问题

var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

1.3不允许重复声明

       // 报错
        function func() {
            let a = 10;
            var a = 1;
        }
        // 报错
        function func() {
            let a = 10;
            let a = 1;
        }

1.4块级作用域

ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

第一种场景,内层变量可能会覆盖外层变量。

        var tmp = new Date();
        function f() {
            console.log(tmp);
            if (false) {
                var tmp = 'hello world';
            }
        }
        f(); // undefined

上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

第二种场景,用来计数的循环变量泄露为全局变量。

        var s = 'hello';
        for (var i = 0; i < s.length; i++) {
            console.log(s[i]);
        }
        console.log(i); // 5

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

1.5 ES6的块级作用域

let实际上为 JavaScript 新增了块级作用域。

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

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

ES6 允许块级作用域的任意嵌套。外层作用域无法读取内层作用域的变量。内层作用域可以定义外层作用域的同名变量。

   {{{{

        let insane = 'Hello World';

        {let insane = 'Hello World'}

    }}}};

块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

        // IIFE 写法
        (function () {
            var tmp = 123;
        }());
        // 块级作用域写法
        {
            let tmp = 123;
        }

1.6 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来说,只声明不赋值,就会报错。

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

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

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

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

1.7 const的本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

       const foo = {};
        // 为 foo 添加一个属性,可以成功
        foo.prop = 123;
        foo.prop // 123
        // 将 foo 指向另一个对象,就会报错
        foo = {}; // TypeError: "foo" is read-only

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

另一个例子:常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

        const a = [];
        a.push('Hello'); // 可执行
        a.length = 0;    // 可执行
        a = ['Dave'];    // 报错

如果真的想将对象冻结,应该使用Object.freeze方法。

        const foo = Object.freeze({});
        // 常规模式时,下面一行不起作用;
        // 严格模式时,该行会报错
        foo.prop = 123;

上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。

除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

        var constantize = (obj) => {
            Object.freeze(obj);
            Object.keys(obj).forEach((key, i) => {
                if (typeof obj[key] === 'object') {
                    constantize(obj[key]);
                }
            });
        };

1.8 顶层对象的属性

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

        window.a = 1;
        a // 1
        a = 2;
        window.a // 2

上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。

顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

       var a = 1;
        // 如果在 Node 的 REPL 环境,可以写成 global.a
        // 或者采用通用方法,写成 this.a
        window.a // 1
        let b = 1;
        window.b // undefined

上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。

1.9 global 对象

ES5 的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。

  • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
  • Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

  • 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
  • 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。

根据上面存在的情况,很难找到一种方法,可以在所有情况下,都取到顶层对象。现在有一个提案,在语言标准的层面,引入global作为顶层对象。也就是说,在所有环境下,global都是存在的,都可以从它拿到顶层对象。

垫片库system.global模拟了这个提案,可以在所有环境拿到global。

        // CommonJS 的写法
        require('system.global/shim')();
        // ES6 模块的写法
        import shim from 'system.global/shim'; shim();

上面代码可以保证各种环境里面,global对象都是存在的。

        // CommonJS 的写法
        var global = require('system.global')();
        // ES6 模块的写法
        import getGlobal from 'system.global';
        const global = getGlobal();

上面代码将顶层对象放入变量global。

2.变量的解构赋值

2.1    数组变量的解构赋值

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

        //以前,为变量赋值,只能直接指定值。
        let a = 1;
        let b = 2;
        let c = 3;
        //ES6 允许写成下面这样
        let [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 // []

       let [foo] = [];         //foo  => undefined
       let [bar, foo] = [1];   //foo  => undefined

如果解构不成功,变量的值就等于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

如果等号的右边不是数组(或者严格地说,不是可遍历的结构,那么将会报错。

        // 报错
        let [foo] = 1;
        let [foo] = false;
        let [foo] = NaN;
        let [foo] = undefined;
        let [foo] = null;
        let [foo] = {};

上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。

2.2    默认值

解构赋值允许指定默认值。

        let [foo = true] = [];
        foo // true

        let [x, y = 'b'] = ['a']; // x='a', y='b'
        let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

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

        let [x = 1] = [undefined];
        x // 1

        let [x = 1] = [null];
        x // null

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

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

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

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

        let x;
        if ([1][0] === undefined) {
            x = f();
        } else {
            x = [1][0];
        }

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

     let [x = 1, y = x] = [];     // x=1; y=1
     let [x = 1, y = x] = [2];    // x=2; y=2
     let [x = 1, y = x] = [1, 2]; // x=1; y=2
     let [x = y, y = 1] = [];     // ReferenceError: y is not defined

上面最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明。

2.3    对象的解构赋值

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

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

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

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

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

     let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
     baz // "aaa"


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

对象的解构赋值是下面形式的简写

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

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

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

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

与数组一样,解构也可以用于嵌套结构的对象。

   const node = {
      loc: {
          start: {
              line: 1,
              column: 5
          }
      }
   };
   let { loc, loc: { start }, loc: { start: { line } } } = node;
   line // 1
   loc  // Object {start: Object}
   start // Object {line: 1, column: 5}

上面代码有三次解构赋值,分别是对loc、start、line三个属性的解构赋值。注意,最后一次对line属性的解构赋值之中,只有line是变量,loc和start都是模式,不是变量。

对象的解构也可以指定默认值。

        var { x = 3 } = {};
        x // 3
        var { x, y = 5 } = { x: 1 };
        x // 1     y // 5
        var { x: y = 3 } = {};
        y // 3
        var { x: y = 3 } = { x: 5 };
        y // 5
        var { message: msg = 'Something went wrong' } = {};
        msg // "Something went wrong"

2.4    结构的其他用法

默认值生效的条件是,对象的属性值严格等于undefined。

    var { x = 3 } = { x: undefined };
     x // 3
    var { x = 3 } = { x: null };
     x // null

上面代码中,属性x等于null,因为null与undefined不严格相等,所以是个有效的赋值,导致默认值3不会生效。

如果解构失败,变量的值等于undefined。

   let { foo } = { bar: 'baz' };
   foo // undefined

如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。

    // 报错
   let { foo: { bar } } = { baz: 'baz' };

上面代码中,等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错,请看下面的代码。

    let _tmp = { baz: 'baz' };
   _tmp.foo.bar // 报错

如果要将一个已经声明的变量用于解构赋值,必须非常小心。

     // 错误的写法
     let x;
     { x } = { x: 1 };
    // SyntaxError: syntax error

上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

   ({} = [true, false]);
   ({} = 'abc');
   ({} = []);

上面的表达式虽然毫无意义,但是语法是合法的,可以执行。

2.5    字符串的解构赋值

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

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

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

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

2.6    数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

   let { toString: s } = 123;
   s === Number.prototype.toString // true
   let { toString: s } = true;
   s === Boolean.prototype.toString // true

上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

   let { prop: x } = undefined; // TypeError
   let { prop: y } = null; // TypeError

三、新增扩展

1.字符串的扩展

1.1字符的 Unicode 表示法

ES6 加强了对 Unicode 的支持,并且扩展了字符串对象。允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。这种表示法只限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。如果直接在\u后面跟上超过0xFFFF的数值(比如\u20CC7),JavaScript 会理解成\u20CC+7。由于\u20CC是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。

ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。

“\u{20cc7}”    //
“\u{41}”       //A

1.2    codePointAt()

codePointAt方法会正确返回 32 位的 UTF-16 字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt方法相同。

codePointAt方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString方法转换一下。

    let s = '';
     s.codePointAt(0) // 134071

1.3    String.fromCodePoint()

ES5 提供String.fromCharCode方法,用于从码点返回对应字符,但是这个方法不能识别 32 位的 UTF-16 字符(Unicode 编号大于0xFFFF)。

ES6 提供了String.fromCodePoint方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。

  String.fromCodePoint(0x20BB7)

1.4    字符串的遍历器接口

ES6 为字符串添加了遍历器接口(Iterator),使得字符串可以被for…of循环遍历。

        for (let codePoint of 'foo') {
            console.log(codePoint)
        }
        // "f"
        // "o"
        // "o"

除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。

如0x20BB7, for循环会认为它包含两个字符(都不可打印),而for…of循环会正确识别出这一个字符。

1.5    normalize()

许多欧洲语言有语调符号和重音符号。为了表示它们,Unicode 提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。

这两种表示方法,在视觉和语义上都等价,但是 JavaScript 不能识别。

     '\u01D1' === '\u004F\u030C' //false
     '\u01D1'.length // 1
     '\u004F\u030C'.length // 2

上面代码表示,JavaScript 将合成字符视为两个字符,导致两种表示方法不相等。

ES6 提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。

  '\u01D1'.normalize() === '\u004F\u030C'.normalize()
  // true

不过,normalize方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过 Unicode 编号区间判断。

1.6    includes(), startsWith(), endsWith()

传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
    let s = 'Hello world!';
    s.startsWith('Hello') // true
    s.endsWith('!') // true
    s.includes('o') // true

这三个方法都支持第二个参数,表示开始搜索的位置。startsWith和includes的第二个参数表示从第n个位置直到字符串结束,endsWith针对前n个字符。

1.7    repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。

    'x'.repeat(3) // "xxx"
    'hello'.repeat(2.9) // "hellohello"
    'na'.repeat(0) // ""

参数如果是小数,会被取整。如果参数是负数或者Infinity,会报错。如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。参数NaN等同于 0。如果repeat的参数是字符串,则会先转换成数字。

    'ha'.repeat('na') // ""
    'ha'.repeat('3') // "hahaha"

1.8    padStart(),padEnd()

ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

    'x'.padStart(5, 'ab') // 'ababx'
    'x'.padStart(4, 'ab') // 'abax'

上面代码中,padStart()和padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。

如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。

如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。

如果省略第二个参数,默认使用空格补全长度。

1.9    matchAll()

matchAll方法返回一个正则表达式在当前字符串的所有匹配。

1.10   模板字符串

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

    $('#box').append(
      '<ul>'+
        '<li>公司名字叫:'+gosuncn.name+'</li>'+
        '<li>公司行业:'+gosuncn.it+'</li>'+
        '<li>部门:'+gosuncn.IoT+'</li>'+
      '</ul>'
    );

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

   $('#box').append(`
      <ul>
        <li>公司名字叫:${gosuncn.name}</li>
        <li>公司行业:${gosuncn.it}</li>
        <li>部门:${gosuncn.IoT}</li>
      </ul>
    `);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。模板字符串中嵌入变量,需要将变量名写在${}之中。大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。

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

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

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

如果模板字符串中的变量没有声明,将报错。如果大括号内部是一个字符串,将会原样输出。模板字符串甚至还能嵌套。

2.数值的扩展

先来看一组数据:

0.7*8   =>5.6
0.8*7   =>5.6000000000000005
0.3-0.2 =>0.09999999999999998
0.1+0.2 =>0.30000000000000004
0.6/3   =>0.19999999999999998
0.1 + 0.2 - 0.3  =>5.551115123125783e-17
9007199254740993 === 9007199254740992  =>true
  • isFinite(),Number.isNaN()

ES6 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。

Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。如果参数类型不是数值,Number.isFinite一律返回false。

Number.isNaN()用来检查一个值是否为NaN。如果参数类型不是NaN,Number.isNaN一律返回false。

它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。

        isFinite(25) // true
        isFinite("25") // true

        Number.isFinite(25) // true
        Number.isFinite("25") // false

        isNaN(NaN) // true
        isNaN("NaN") // true


        Number.isNaN(NaN) // true
        Number.isNaN("NaN") // false
        Number.isNaN(1) // false
  • parseInt(), Number.parseFloat()

ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。

        // ES5的写法
        parseInt('12.34') // 12
        parseFloat('123.45#') // 123.45

        // ES6的写法
        Number.parseInt('12.34') // 12
        Number.parseFloat('123.45#') // 123.45

这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

  • isInteger()

Number.isInteger()用来判断一个数值是否为整数。

JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。

如果参数不是数值,Number.isInteger返回false。

注意,由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。

Number.isInteger(3.0000000000000002) // true
//二进制:10000000110010010010111011010101000110111010000000000000000

上面代码中,Number.isInteger的参数明明不是整数,但是会返回true。原因就是这个小数的精度达到了小数点后16个十进制位,转成二进制位超过了53个二进制位,导致最后的那个2被丢弃了。

如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数。

  • EPSILON

ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。

对于 64 位浮点数来说,大于 1 的最小浮点数相当于二进制的1.00..001,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的 -52 次方。

Number.EPSILON实际上是 JavaScript 能够表示的最小精度,它实质是一个可以接受的最小误差范围。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。

   5.551115123125783e-17 < Number.EPSILON * Math.pow(2, 2)
   // true  2.220446049250313e-16   8.881784197001252e-16
  • 安全整数和isSafeInteger()

JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。

Math.pow(2, 53) === Math.pow(2, 53) + 1    //true

ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

=============下期更新=======================

  1. 函数的扩展
  2. 数组的扩展
  3. 对象的扩展
  4. Set 和 Map 数据结构
  5. Proxy
  6. Reflect
  7. Promise 对象
  8. Iterator 和..of 循环
  9. Generator 函数的语法
  10. Generator 函数的异步应用
  11. async 函数
  12. Class 的基本语法
  13. Class 的继承
  14. Decorator
  15. Module 的语法
  16. Module 的加载实现
  17. ……

 

原创文章,作者:Ferrycoln,如若转载,请注明出处:https://ms200.cn/archives/732

发表评论

电子邮件地址不会被公开。 必填项已用*标注

联系我们

在线咨询:点击这里给我发消息

邮件:499661635@qq.com.com

工作时间:周一至周五,9:30-18:30

QR code