【es6】系列(一)之 class |8月更文挑战

215 阅读5分钟

一 定义

class 是 ES6 的新特性,可以用来定义一个类,实际上,class 只是一种语法糖,它是构造函数的另一种写法。(什么是语法糖?是一种为避免编码出错和提高效率编码而生的语法层面的优雅解决方案,简单说就是,一种便携写法。)

新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

二 使用

用法和使用构造函数一样,通过 new 来生成对象实例

  • 构造函数 就是 通过new 关键字来调用的函数,其本质就是函数
  • 实例是一个对象。因为实例是构造函数new 调用后的返回值。构造函数返回的永远都是对象。你想修改为其他非对象的返回值 是修改不了的。

class Person {

}
let andy = new Person();
typeof Person // "function"
typeof andy // "object"

三 每个类必须有constructor【new时自动调用该方法】

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。 每个类都必须要有一个 constructor,如果没有显示声明,js 引擎会自动给它添加一个空的构造函数

constructor方法,这就是构造方法,this关键字代表实例对象。 类的数据类型就是函数,类本身就指向构造函数。

class Person {

}
// 等同于
class Person {
  constructor () {
    this.name = name;
  }
}

四 class 的属性和方法:【静态属性/方法,实例属性/方法】

1) 原型属性和方法

定义于 constructor 内的属性和方法,即定义在 this 上,属于实例属性和方法,否则属于原型属性和方法。

2) 静态属性 【不会被实例继承】

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。 写法是在实例属性的前面,加上static关键字。

3) 静态方法【不会被实例继承】

class A {
    static classMethod() {
        return 'hello';
    }
}
A.classMethod();
console.log(A.classMethod());
// 'hello'

const a = new A();
a.classMethod();
// TypeError: a.classMethod is not a function

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。 如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法"。

class Person {
  static test = 1; // 静态属性
  constructor (name) {
    this.name = name
  }

  say () {
    console.log('hello')
  }
}

let andy = new Person()

andy.hasOwnProperty('name') // true
andy.hasOwnProperty('say') // false

1)es5的写法

function Person () {
  this.name = name;
};
Person.prototype.say = function () {
    console.log('hello')
}
let andy = new Person()

andy.hasOwnProperty('name') // true
andy.hasOwnProperty('say') // false

五 继承

Class 可以通过extends关键字实现继承

1) 父类的静态方法,也会被子类继承。

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

class B extends A {
}

B.hello()  // hello world

2)

class Cat extends Animal {
    constructor(name, age, color) {
        // 调用父类的constructor(name, age)
        super(name, age);
        this.color = color;
    }
    toString() {
        return this.color + ' ' + super.toString(); // 调用父类的toString()
    }
}

constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

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

class Animal { /* ... */ }

class Cat extends Animal {
  constructor() {
  }
}

let cp = new Cat();
// ReferenceError

Cat 继承了父类 Animal,但是它的构造函数没有调用super方法,导致新建实例报错。

如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。


class Cat extends Animal {

}
// 等同于

class Cat extends Animal {
    constructor(...args) {
        super(...args);
    }
}

另一个需要注意的地方是,es5 的构造函数在调用父构造函数前可以访问 this, 但 es6 的构造函数在调用父构造函数(即 super)前不能访问 this。

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

class B extends A {
  constructor(x, y, name) {
    this.name = name; // ReferenceError
    super(x, y);
    this.name = name; // 正确
  }
}

上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

六  取值函数(getter)和存值函数(setter)

每个类都必须要有一个 constructor,如果没有显示声明,js 引擎会自动给它添加一个空的构造函数:

class Person {
  get name () {
    return '1'
  }
  set name(val) {
    console.log('set: ' + val)
  }
}

let andy = new Person()
andy.name = 'andy' // setter andy
andy.name // 1


七 class 表达式

如果需要,可为类定义一个类内部名字,如果不需要,可以省略:

// 需要在类内部使用类名
const Person = class Obj {
  getClassName () {
    return Obj.name
  }
}
// 不需要
const Person = class {}
复制代码

立即执行的 Class:

let andy = new class {
  constructor(name) {
    this.name = name
  }
  sayName() {
    console.log(this.name)
  }
}('andy')

andy.sayName() //andy

补充回顾 new 的实现

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

function _new(constructor, ...args) {
    // 构造函数类型合法判断
    if(typeof constructor !== 'function') {
      throw new Error('constructor must be a function');
    }
    // 新建空对象实例
    let obj = new Object();
    // 将构造函数的原型绑定到新创的对象实例上
    obj.__proto__ = Object.create(constructor.prototype);
    // 调用构造函数并判断返回值
    let res = constructor.apply(obj,  args);
    let isObject = typeof res === 'object' && res !== null;
    let isFunction = typeof res === 'function';
    // 如果有返回值且返回值是对象类型,那么就将它作为返回值,否则就返回之前新建的对象
    return isObject || isFunction ? res : obj;
};

function Person (name) {
  this.name = name;
};
Person.prototype.say = function () {
    console.log('hello')
};

let p1 = _new(Person, '111'); //  {name: "111"}
console.log(p1); // {name: "111"}

参考

总结

  • class是一个语法糖,其底层还是通过 构造函数 去创建的。

  • 类的所有方法都定义在类的prototype属性上面。

  • 静态方法:在方法前加static,表示该方法不会被实例继承,而是直接通过类来调用。

  • 静态属性:在属性前加static,指的是 Class 本身的属性,而不是定义在实例对象(this)上的属性。

  • es5 的构造函数在调用父构造函数前可以访问 this, 但 es6 的构造函数在调用父构造函数(即 super)前不能访问 this。

  • super

    • 作为函数调用,代表父类的构造函数
    • 作为对象调用,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
  • class不存在变量提升

new A(); // ReferenceError 
class A {};