class

简介
由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
constructor 方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
类的实例对象
必须new
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
可以通过实例的proto属性为“类”添加方法。但是所添加的所有方法都会给所有被实例对象使用
Class 表达式
与函数一样,类也可以写成表达式
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类。
如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式。
const MyClass = class { // };
用Class表达式,可以写出立即执行的class
let person = new class {
constructor(name) {
this.name = name;
}

    sayName() {
        console.log(this.name);
    }
}('张三');

person.sayName(); // "张三"

不存在变量提升
私有方法和私有属性
一种是通过把私有方法写在类外面,通过call(this)调用。
另一种是通过symbol来私有化变量,在通过变量来调用方法和属性变量
const bar = Symbol(‘bar’);
const snaf = Symbol(‘snaf’);

    export default class myClass{

        // 公有方法
        foo(baz) {
            this[bar](baz);
        }

        // 私有方法
        [bar](baz) {
            return this[snaf] = baz;
        }

        // ...
    };
私有属性和私有方法在前面家#

this 的指向
class test {
constructor(){
//为了printName被分离出去后也能被调用,this指向还是test
this.printName = this.printName.bind(this);
}
}
如果是箭头函数的话,this就是指向声明时的this
还有一种解决方法是使用Proxy,获取方法的时候,自动绑定this。
name 属性
由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。
Class 的取值函数(getter)和存值函数(setter)
都是被默认添加到了描述对象prototypedescription上
Class 的 Generator 方法
前面加个*
Class 的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。静态方法可以和普通方法重名
父类的静态方法可以被继承
Class 的静态属性和实例属性
可在类里直接定义实例属性
静态属性直接在前面加个static
new.target 属性
new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
class类中调用new.target返回当前class,在子类中调用则父类也返回子类的class
利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error(‘本类不能实例化’);
}
}
}

class Rectangle extends Shape {
    constructor(length, width) {
        super();
        // ...
    }
}

var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确

函数的扩展

函数的扩展

  • 1.函数参数的默认值

    • 参数变量是默认声明的,所以不能用let或const再次声明。
    • 使用参数默认值时,函数不能有同名参数。
    • 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
  • 2.函数参数默认值的位置

    • 通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
  • 3.函数的 length 属性

    • 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
    • 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
  • 4.作用域

    • 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
    1
    2
    3
    4
    5
    var x = 1;
    function f(x, y = x) {
    console.log(y);
    }
    f(2) // 2

    上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    var x = 1;
    function foo(x, y = function() { x = 2; }) {
    var x = 3;
    y();
    console.log(x);
    }
    foo() // 3
    x // 1
    ```
    **上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。**
    * 5.使用
    >* 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
    ```js
    function throwIfMissing() {
    throw new Error('Missing parameter');
    }
    function foo(mustBeProvided = throwIfMissing()) {
    return mustBeProvided;
    }
    foo()
    // Error: Missing parameter
  • 6.rest 参数

    • ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
    • 函数的length属性,不包括 rest 参数。
  • 7.严格模式
    • ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
  • 8.name 属性
    • 函数的name属性,返回该函数的函数名。
    • 需要注意的是,ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
    • Function构造函数返回的函数实例,name属性的值为anonymous。
    • bind返回的函数,name属性值会加上bound前缀。
  • 9.箭头函数
    • 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
    • (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    • (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
    • (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    • (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
      双冒号运算符
    • 函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
  • 10.尾调用优化

    • 尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
    • 优化,我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
    • 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
    • 尾递归
    • 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
      递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
      
      1
      2
      3
      4
      5
      6
      7
      function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
      if( n <= 1 ) {return ac2};
      return Fibonacci2 (n - 1, ac2, ac1 + ac2);
      }
      Fibonacci2(100) // 573147844013817200000
      Fibonacci2(1000) // 7.0330367711422765e+208
      Fibonacci2(10000) // Infinity
  • 10.函数参数的尾逗号

    • ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。

数值的扩展

数值的扩展

  • 1.二进制和八进制表示法
    • ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
  • 2.Number.isFinite(), Number.isNaN()
    • Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。注意,如果参数类型不是数值,Number.isFinite一律返回false。
    • Number.isNaN()用来检查一个值是否为NaN。注意,如果参数类型不是数值,Number.isNaN一律返回false。
    • Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。
  • 3.Number.parseInt(), Number.parseFloat()
    • ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
  • 4.Number.isInteger()
    • Number.isInteger()用来判断一个数值是否为整数。
  • 5.Number.EPSILON

    • Number.EPSILON可以用来设置“能够接受的误差范围”。比如,误差范围设为 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。
      1
      2
      3
      function withinErrorMargin (left, right) {
      return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
      }
  • 6.安全整数和 Number.isSafeInteger()

    • JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。
    • ES6 引入了Number.MAX_SAFE_INTEGER和* 7.Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。
  • 8.Math 对象的扩展

    • Math.trunc()返回去除小数点后的数
    • Math.sign()判断正数还是负数
    • Math.cbrt()立方根
    • Math.clz32()JavaScript 的整数使用 32 位二进制形式表示,Math.clz32方法返回一个数的 32 位无符号整数形式有多少个前导 0。
    • Math.imul()方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
    • Math.fround()方法返回一个数的32位单精度浮点数形式。
    • Math.hypot()方法返回所有参数的平方和的平方根
  • 9.指数运算符

    **

字符串扩展

字符串扩展

  • 1.字符的 Unicode 表示法
    • ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
  • 2.codePointAt()
    • JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为它们是两个字符。
    • ES6 提供了codePointAt方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。
  • 3.String.fromCodePoint()
    • ES5 提供String.fromCharCode方法,用于从码点返回对应字符,但是这个方法不能识别 32 位的 UTF-16 字符(Unicode 编号大于0xFFFF)
    • ES6 提供了String.fromCodePoint方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。
      字符串的遍历器接口
    • ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被for…of循环遍历。
    • 除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。
  • 4.at()
    • ES5 对字符串对象提供charAt方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF的字符。
    • 目前,有一个提案,提出字符串实例的at方法,可以识别 Unicode 编号大于0xFFFF的字符,返回正确的字符。
  • 5.normalize()
    • ES6 提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。
  • 6.includes(), startsWith(), endsWith()(2个参数,第一个字符串,第二个第几位开始)
    • includes():返回布尔值,表示是否找到了参数字符串。
    • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
    • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  • 7.repeat()(参数是数字)
    • repeat方法返回一个新字符串,表示将原字符串重复n次。
      padStart(),padEnd()(第一个参数是字符串长度,第二个参数是要循环的字符串)
    • ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
  • 8.matchAll()
    • matchAll方法返回一个正则表达式在当前字符串的所有匹配,详见《正则的扩展》的一章。
      模板字符串(可以保留标签或者变量值)
    • ` 会保留空格 ${(放入变量,甚至js代码)}
  • 9.实例:模板编译
  • 10.标签模板
  • 11.String.raw()
    • String.raw方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。
      模板字符串的限制

CommonJS、AMD、UMD、CMD

CommonJS(同步的,适用在node.js)

  • CommonJS是nodejs也就是服务器端广泛使用的模块化机制。
  • 该规范的主要内容是,模块必须通过module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。
定义模块

在每个模块内部,module变量代表当前模块。它的exports属性是对外的接口,将模块的接口暴露出去。其他文件加载该模块,实际上就是读取module.exports变量。

1
2
3
4
5
6
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
加载模块
1
2
//require方法用于加载模块,后缀名默认为.js
var app = require('./app.js');
入口文件
  • 一般都会有一个主文件(入口文件),在index.html中加载这个入口文件,然后在这个入口文件中加载其他文件。
  • 可以通过在package.json中配置main字段来指定入口文件。
模块缓存

第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。

加载机制

CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

AMD(异步,为浏览器设置)

  • AMD(异步模块定义)是为浏览器环境设计的,因为 CommonJS 模块系统是同步加载的,当前浏览器环境还没有准备好同步加载模块的条件。
  • requirejs即为遵循AMD规范的模块化工具。
  • RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。

定义模块

  • define方法用于定义模块,RequireJS要求每个模块放在一个单独的文件里。
  • 按照是否依赖其他模块,可以分成两种情况讨论。第一种情况是定义独立模块,即所定义的模块不依赖其他模块;第二种情况是定义非独立模块,即所定义的模块依赖于其他模块。
独立模块
1
2
3
4
5
6
7
define(function(){
……
return {
//返回接口
}
})
//define定义的模块可以返回任何值,不限于对象。
非独立模块
1
2
3
4
5
6
define(['module1','module2'],function(m1,m2){
……
return {
//返回接口
}
})
  • 要定义的模块依赖于module1和module2,那么第一个参数就是依赖的模块的数组。
  • 第二个参数是一个函数,仅当依赖的模块都加载成功后才会被调用。此函数的参数m1,m2与前面数组中的依赖模块一一对应。
  • 此模块必须返回一个对象,供其他模块调用。
加载模块

同样使用require()方法来加载模块,但由于是异步的,因此使用回调函数的形式。

1
2
3
require(['foo','bar'],function(foo,bar){
……
})

上面方法表示加载foo和bar两个模块,当这两个模块都加载成功后,执行一个回调函数。该回调函数就用来完成具体的任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//require方法也可以用在define方法内部。
define(function(require){
var otherModule = require('otherModule');
})
//require方法允许添加第三个参数,即错误处理的回调函数。
require(
[ "backbone" ],
function ( Backbone ) {
return Backbone.View.extend({ /* ... */ });
},
function (err) {
// ...
}
);
配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require.config({
paths: {
"backbone": "vendor/backbone",
"underscore": "vendor/underscore"
},
shim: {
"backbone": {
deps: [ "underscore" ],
exports: "Backbone"
},
"underscore": {
exports: "_"
}
}
});
使用
  • 在主页面index.html中先通过script标签引入require.min.js。
  • 再通过script标签引入一个入口文件main.js,此入口文件一般用于配置(require.config),以及引入其他模块。