ES6 Class 继承

继承是一个类扩展成另一个类的基础,ES6 在推出 Class 之后,也实现了 extends 来看看,继承在 ES6 中的效果吧~

简单谈谈

ES6 规定了 extends 关键字,用该关键字就可以实现继承,对比 ES5 通过修改原型链来实现继承,可以说简洁了不少。

class Point {
};

class ColorPoint extends Point {
    constructor(x, y, color) {
        // 调用父类的constructor(x, y)
        super(x, y);
        this.color = color;
    }
    
    toString() {
        // 调用父类的toString()
        return this.color + ' ' + super.toString();
    }
}

当然子类如果没有定义 constructor 方法,这个方法会被默认添加。

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
    constructor(...args) {
        super(...args);
    }
}

子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。

如上所述,如果不调用 super 方法,子类就得不到 this 对象。那么如果在调用 super 方法前使用 this ,那么就会报错。

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

class ColorPoint extends Point {
    constructor(x, y, color) {
        this.color = color;         // ReferenceError
        super(x, y);
        this.color = color;         // 正确
    }
}

父类中的静态方法,同样也会被子类继承。

class A {
    static hello() {
        console.log('hello world');
    }
}

class B extends A {
}

B.hello();                          // hello world

getPrototypeOf

用于从子类上获取父类

Object.getPrototypeOf(ColorPoint) === Point;
// true

super 关键字

super 关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

函数调用

super 作为函数调用时,代表父类的构造函数。
而且作为子类必须调用 super 函数,但是需要注意的是虽然是父类的构造函数,但返回的是子类的实例。

class A {}

class B extends A {
    constructor() {
        super();
    }
}

super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B ,因此 super() 在这里相当于 A.prototype.constructor.call(this)

作为函数时, super() 只能用在子类的构造函数之中,用在其他地方就会报错。

作为对象使用

super 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

在方法中使用

class A {
    p() {
        return 2;
    }
}

class B extends A {
    constructor() {
        super();
        console.log(super.p());
        // 2
    }
}

let b = new B();

在静态方法中使用

class A {
    static p1() {
        return 2;
    }
}

class B extends A {
    static p2() {
        super.p1();
        // 2
    }
}

super 当成对象使用时,如果在子类方法中使用,super 的指向为父类的 prototype ,如果在子类的静态方法中使用则指向父类。

进一步理解:子类方法其实也是在子类的 prototype 下,所以对应关系为:在子类的 prototype 中使用 super 那么就是指向父类的 prototype ,在子类下(也就是静态方法)使用 super 那么就是指向父类。

所以 super 的表现和 this 是差不多一致的,唯一不同的是 super 是针对父类的引用。

还需要注意一点的是:如果通过 super 调用父类的方法时,方法内部的 this 是指向子类的(不论是不是在静态方法中使用)。

class Parent {
    static staticMethod() {
        console.log(this.staticType);
    }
    
    constructor() {
        this.type = 'parent';
    }

    method() {
        console.log(this.type);
    }
}
Parent.staticType = 'parent static';

class Child extends Parent {
    static staticMethod2() {
        super.staticMethod();
    }
    
    constructor() {
        super();
        this.type = 'child';
    }

    method2() {
        super.method();
    }
}
Child.staticType = 'child static';

Child.staticMethod2();                      // child static

var child = new Child();
child.method2();                            // child

proto

ES5 中每一个对象都拥有 __proto__ 属性,指向其构造函数的 prototype。通过 __proto__ 可以实现对象的继承。

ES6 中,出现了类层面上的继承,因此规定了另外一条继承链,构造函数的继承。

class A {
}

class B extends A {
}

// ES6 中新出现的构造函数的继承
B.__proto__ === A                           // true

// 与 ES5 一致,原型链的继承
B.prototype.__proto__ === A.prototype       // true

因此在 ES6 中,__proto__ 即可以用来表示原型链的继承关系,也可以用来表示构造函数的继承关系。

当然出现这条继承链的原因是因为 ES6 中继承是按照下面的模式实现的。

class A {
}

class B {
}

// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);

// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);

const b = new B();

setPrototypeOf 是这样实现的。

Object.setPrototypeOf = function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
}

所以当 B 继承 A 的静态属性时,就会顺便在 B 类下设置一个 __proto__

当然如果一个类没有继承任何的类那么 __proto__ 会指向哪?

class A {
}

A.__proto__ === Function.prototype;             // true
A.prototype.__proto__ === Object.prototype;     // true

这种情况下, A 作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承 Function.prototype 。但是, A 调用后返回一个空对象(即 Object 实例),所以 A.prototype.__proto__ 指向构造函数( Object )的 prototype 属性。

原生构造函数的继承

目前常用的 ECMAScript 原生构造函数大致如下

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

ES5 中,这些构造函数是无法被继承的。

function MyArray() {
    Array.apply(this, arguments)
}

MyArray.prototype = Object.create(Array.prototype, {
    constructor: {
        value: MyArray,
        writable: true,
        configurable: true,
        enumerable: true
    }
})

上述代码生成了一个继承的 MyArray 类,但这个类的行为与 Array 完全不一致

var colors = new MyArray();
colors[0] = "red";
colors.length;          // 0

colors.length = 0;
colors[0];              // "red"

之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过 Array.apply() 或者分配给原型对象都不行。原生构造函数会忽略 apply 方法传入的 this ,也就是说,原生构造函数的 this 无法绑定,导致拿不到内部属性。

照成这样的根本原因是:ES5 创建实例时,先由子类生成 this 对象,在用这个对象去添加父类下的方法,而父类的内部属性仅仅只能在父类生成的 this 对象下使用,这样就会导致子类生成的 this 对象访问不到父类的内部属性,导致子类的行为异常。

而在 ES6 中,构造函数生成实例时,是先由父类生成 this 对象,然后用子类的构造函数去修改/添加 this 对象下的方法或属性,这样就使得 this 对象可以获得父类的所有行为。以下例子可以说明

class MyArray extends Array {
    constructor(...args) {
        super(...args);
    }
}

var arr = new MyArray();
arr[0] = 12;
arr.length;             // 1

arr.length = 0;
arr[0];                 // undefined

上述代码就完成了对原生对象的继承。

可以定义一个 Error 的子类,指定相应的报错信息

class ExtendableError extends Error {
    constructor(message) {
        super();
        this.message = message;
        this.stack = (new Error()).stack;
        this.name = this.constructor.name;
    }
}

class MyError extends ExtendableError {
    constructor(m) {
        super(m);
    }
}

var myerror = new MyError('ll')
myerror.message;                // "ll"
myerror instanceof Error;       // true
myerror.name;                   // "MyError"
myerror.stack;
// Error
//     at MyError.ExtendableError
//     ...

继承 Object 构造函数时需要注意。

class NewObj extends Object{
  constructor(){
    super(...arguments);
  }
}

var o = new NewObj({attr: true});
o.attr === true                 // false

上面代码中,NewObj 继承了 Object ,但是无法通过 super 方法向父类 Object 传参。这是因为 ES6 改变了 Object 构造函数的行为,一旦发现 Object 方法不是通过 new Object() 这种形式调用,ES6 规定 Object 构造函数会忽略参数。

Read more

Gitlab 搭建

Gitlab 搭建

为什么? 想要自己搭建一个代码仓库无非是以下几点原因: 1. 公司内部项目 2. 自己的项目,但不适合在公网 3. 大部分的 git 仓库虽然有私有服务,但价格都不便宜,甚至不如一台云服务器来的便宜 配置及安装文档 Gitlab * 由于 gitlab 会用到 22 端口端口转发的化就走不了 git clone 的默认配置,且占用内存较高,不推荐使用 docker 进行部署; * 由于 gitlab 自带 nginx 默认情况下会与属主机的 nginx 冲突,因此推荐只使用 gitlab 自带的 nginx 进行端口转发; 最小化配置 # path /etc/gitlab/gitlab.rb external_url 'http://git.

By breeze
NPM 私服

NPM 私服

verdaccio 私服搭建,搭建过程中的一些问题以及解决: * docker compose 启动后,可能会报错:配置找不到,添加 config.yaml 文件到映射的 ./conf 目录即可,config.yaml 具体内容可在官网找到,下方也有最小化配置。 services: verdaccio: image: verdaccio/verdaccio container_name: 'verdaccio' networks: - node-network environment: - VERDACCIO_PORT=4873 - VERDACCIO_PUBLIC_URL=http://npm.demo.com ports: - '10001:4873'

By breeze