js 类、原型及class

 js   一直允许定义类。ES6新增了相关语法(包括class关键字)让创建类更容易。新语法创建的类和老式的类原理相同。js 的类和基于原型的继承机制与Java等语言中的类和继承机制有着本质区别。

1 类和原型

类意味着一组对象从同一个原型对象继承属性。因此,原型对象是类的核心特征。

用工厂函数创建和初始化该类的新实例:

function range(from, to) {
    let r = Object.create(range.methods);
    r.from = from;
    r.to = to;
    return r;
}

range.methods = {

    includes(x) {
        return this.from <= x && x <= this.to;
    },

    *[Symbol.iterator]() {
        for (let x = Math.ceil(this.from); x <= this.to; x++)
            yield x;
    },

    toString() {
        return "(" + this.from + "..." + this.to + ")";
    },

}

let r = range(1,3);
console.log(r.includes(2));
console.log(r + ""); // 调用的是toString()方法
console.log(r); // 打印对象本身,而非调用toString()方法
console.log([...r])

2 类和构造函数

只有函数(不包括箭头函数、生成器函数和异步函数)对象才有prototype属性。

上面定义类的写法非习惯写法,因为它没有定义构造函数。构造函数要使用new关键字调用,会自动创建新对象,因此构造函数本身只需初始化新对象的状态。构造函数调用的关键在于构造函数的prototype属性将被用作新对象的原型。

function Range(from,to) {
    this.from = from;
    this.to = to;
}

// 原型属性名必须命名为prototype
Range.prototype = {
    includes(x) {
        return this.from <= x && x <= this.to;
    },

    *[Symbol.iterator]() {
        for (let x = Math.ceil(this.from); x <= this.to; x++)
            yield x;
    },

    toString() {
        return "(" + this.from + "..." + this.to + ")";
    }
}

let r = new Range(1,3);
console.log(r.includes(2));
console.log(r + "");
console.log(r)
console.log([...r])

2.1 instanceof

当且仅当两个对象继承同一个原型对象时,它们才是同一个类的实例。instanceof 操作符用于检查某个对象是否是某个特定类的实例。

严格来讲,instanceof操作符并非检查对象是否通过某个构造函数初始化,而是检查对象是否继承了原型。(不一定是直接继承)

let prototype = {

}

function Person() {}

function Animal() {}

let p = new Person();

console.log( p instanceof Person); // true
console.log( p instanceof Animal); // false

Person.prototype = prototype
Animal.prototype = prototype

let p2 = new Person()
console.log( p2 instanceof Person); // true
console.log( p2 instanceof Animal); // true

2.2 constructor 属性

prototype属性的值是一个对象,其有一个不可枚举的constructor属性,该属性的值就是prototype所属的函数对象:

let F = function() {};

let p = F.prototype;

let c = p.constructor;

c === F; //true

3 使用class关键字定义类

class关键字是在ES6才引入的。

使用class来定义类:

class Person {

    constructor(age) {
        this.age = age;
    }

    showAge() {
        console.log(this.age);
    }

}

let p = new Person(12);
p.showAge();

let Animal = class {
    constructor(name) {
        this.name = name
    }
    showName() {
        console.log(this.name)
    }
}

let a = new Animal("大象");
a.showName()

与函数声明不同,类声明不会“提升”(函数定义就像是会被提升到包含文件或包含函数顶部一样,而类声明不会)。

类声明除了语句到形式外,还有表达式的形式。不过这种形式并不常用:

let Persopn = class { constructor() {} }

3.1 静态方法

在class体中,把static关键字放在方法声明前面可以定义静态方法。静态方法是作为构造函数而非原型的属性定义的。

class Person {
    static hello() {
        console.log("hello word")
    }
}

Person.hello(); // hello word
let p = new Person();
p.hello(); // 报错,p.hello is not a function

静态函数必须通过构造函数而非实例调用。

3.2 获取和设置方法

在class 体内,可像对象字面量中一样定义获取方法和设置方法,唯一的区别是类体内的方法后面不加逗号。

class Person {
    set age(val) {
        if (val <= 0) throw new TypeError("年龄不能小于0")
        this.age = val
    }
}

let person = new Person();
person.age = -1
console.log(person.age)

4 子类

ES6之前定义子类的方式是通过原型,ES6则通过extends定义子类。

4.1 子类与原型

function Person() {
    console.log("Person的构造器")
}

Person.prototype = {
    constructor: Person,
    showType() {
        console.log("Person")
    }
}

function Man() {
    console.log("Man的构造器")
}

Man.prototype = Object.create(Person.prototype)
let man = new Man();
man.showType()
console.log(man.constructor)

Man.prototype.constructor = Man
console.log(man.constructor)

只有在知道父类实现细节的前提下才可能这样定义子类,健壮的子类化机制应该允许类调用父类的方法和构造函数,在ES6之前,js中没有简单的方法做到这些。

4.2 通过extends和super创建子类

class Person {
    constructor() {
        console.log("Person:" + new.target)
    }
}

class Man extends Person {
    constructor() {
        super();
        console.log(this)
        console.log("Man")
    }
}

class Woman extends Person {
    constructor() {
        super();
        console.log("Woman")
    }
}

let man = new Man();
let woman = new Woman()

注意事项:

1)使用extends关键字定义了一个类,则该类的构造函数必须使用super()调用父类构造器。
    2)在通过super()调用父类构造器之前,不能在构造器中使用this关键字。
    3)new.target引用的是被调用的构造函数。

在实际开发中,并不建议创建很多子类,建议通过“组合”的方式来替代继承。

你可能感兴趣的:(JavaScript权威指南,原型模式,javascript,开发语言)