03月01, 2018

JavaScript 的 Class

简介

  class声明创建一个基于原型继承的具有给定名称的新类。class是ES6中新出现的一个关键字,但是它大部分功能在ES5中都可以做到。

//es5写法
function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

//es6写法
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

和Java的不同

  • (1)Java中的Class是Class对象,而js中的Class对象是函数。是一个构造函数。
console.log(typeof ClassA) // function
  • (2) Class 内部只能定义方法,不能定义属性。定义属性需要放到构造方法中。
//错误的写法
class A{
    a :1 // Unexpected identifier
    a = 2 // Unexpected identifier
}

//正确的写法
class A{
    constructor(a){
        this.a = a
    }
}
  • (3) Class 内部可以有静态方法,且可以被子类继承,但是无法被创建的实例继承
class A{
    static sum(a,b){
        return a + b;
    }

}

class B extends A{

}

console.log(A.sum(1,2))
console.log(B.sum(3,4))
console.log(new A().sum(a,b)) //Identifier 'A' has already been declared
  • (4) Class对象的属性称为静态属性,且只能在class外定义
class A{
}
A.a = 10;
A.b = 20;

class 的本质是函数

类的数据类型就是函数,类本身就指向构造函数。在使用class创建对象的时候,必须要使用new关键字,不然会报错

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

//========================
let point = Point();//Class constructor Point cannot be invoked without 'new'

定义在class中的所有的方法,除非显示的把方法定义到this,class中所有的方法都是定义在类对象的prototype上。

class Point {
    constructor() {
        this.sum = function sum() {

        }
    }
    toString() {
        // ...
    }
    toValue() {
        // ...
    }
}

console.log(Point.prototype.hasOwnProperty('toString')) //true
console.log(Point.prototype.hasOwnProperty('toValue')) //true
console.log(Point.prototype.hasOwnProperty('sum')) //false
console.log("=========")
console.log(new Point().hasOwnProperty("toString"))//false
console.log(new Point().hasOwnProperty("toValue"))//false
console.log(new Point().hasOwnProperty("sum")) //true

但是有一点不同的是,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

console.log(Object.keys(Point.prototype))// []
console.log(Object.getOwnPropertyNames(Point.prototype))//["constructor","toString"]

//===================================
function Point(){
}
Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};
console.log(Object.keys(Point.prototype))// [ 'constructor', 'toString', 'toValue' ]
console.log(Object.getOwnPropertyNames(Point.prototype))//[ 'constructor', 'toString', 'toValue' ]

Class表达式

类也可以和函数一样使用表达式的形式定义。但是此时类名变成了变量的名字,而不是calss关键字后边的名字。

const MyClass = class Me {
};
console.log(new Me())//Me is not defined

所以我们可以这样写

const Apple = class {
};
console.log(new Apple())

this 的指向

在class的内部,所有方法中的this默认指向它的实例,但是如果在一个方法内使用了该类的其他方法,比如printName方法中用了this.print方法,正常情况下使用Logger的实例调用是没有问题,但是js的函数也是一个对象,且js没有权限的控制,所有在其他环境下调用printName会因为找不到print方法报错。但是如果在这个环境中有一个同名方法叫print那么就会调用当前环境下的print方法。


class Logger {
    printName(name) {
        this.print(`Hello ${name}`);
    }

    print(text) {
        console.log(`Logger ${text}`);
    }
}

const logger = new Logger();
const printName = logger.printName;
let objHavePrint = {
    printName : printName,
    print(text) {
        console.log(`objHavePrint ${text}`);
    }
};
let objNotHavenPrint = {
    printName : printName,
};

//logger 对象中有print方法
logger.printName("Logger")//Hello there
//objHavePrint 对象中有print方法
printName.call(objHavePrint,"obj") //objHavePrint Hello obj

//================报错的用法

//objHavePrint 对象中没有有print方法
printName.call(objNotHavenPrint,"obj")//报错 this.print is not a function
//当前执行环境中没有有print方法
printName("Main")//报错 Cannot read property 'print' of undefined

new.target 属性

new.targer是ES6引入的新属性,代表着新建该实例对象使用的构造方法

function Apple(x,y) {
    if(new.target){
        console.log(`是通过new ${new.target == Apple}`)
    }else {
        console.log('不是通过new')
    }
}

let apple = Apple(1,2);
let apple1 = new Apple(1,3)

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类。

class A{
    constructor(x,y){
        this._x = x;
        this._y = y;
    }
}
class B extends A{
    constructor(x,y) {
        super(x,y)
    }
}

console.log(Object.getPrototypeOf(B))
console.log(B.prototype === A)

class中的this和super

class A{
    constructor(x,y){
        this.x = x;
        this.y = y;
    }
    sayHellow(){
        console.log('A hello')
    }

    static staticSayHellow(){
        console.log('A static hello')
    }

}
class B extends A{
    constructor(x,y) {
        super(x,y)
    }

    static staticSayHellow(){
        console.log('B static hello')
    }

    sayHellow(){
        console.log('A hello')
    }

    static staticPrint(){
        this.staticSayHellow()
        super.staticSayHellow()
    }

    print(){
        this.sayHellow()
        super.sayHellow()
    }
}

B.staticPrint()
console.log("===============")
new B().print()

/** 打印结果
 B static hello
 A static hello
 ===============
 A hello
 A hello
 */

继承内置对象

可以直接继承,js的内置对象。例如我们可以自定义一个BusinessError来区别与其他的Error。需要注意的是,直接继承自Object时,无法将参数直接传递给父类也就是Object的构造方法。不过几乎不会有这种写法,所有对象默认都是继承自Object的

class BusinessError extends Error{
    constructor(... args){
        super(args);
    }
}
throw new BusinessError("业务异常")//Error: 业务异常

//继承自Object,super(arg)中的arg不起作用
console.log(new Object([1,2,3]))
class MyObject extends Object{
    constructor(...arg){
        super(arg);
    }
}
console.log(new Object([1,2,3]))//[ 1, 2, 3 ]
console.log(new MyObject([1,2,3]))//MyObject {}

本文链接:https://www.qiangshuidiyu.xin/post/js-class.html

-- EOF --

Comments