【ES6 笔记】JavaScript中的类

2018-11-07  本文已影响0人  ___Jing___

ES5中的近类结构

自定义类型:创建一个构造函数,然后定义另一个方法并赋值给构造函数的原型:

function PersonType(name){
    this.name = name
}
PersonType.prototype.sayName = function (){
    console.log(this.name)
}

var person = new PersonType('欧阳不乖');
person.sayName();   // '欧阳不乖'

类的声明

class PersonClass{
    // 等价于PersonType构造类型
    constructor(name){
        this.name = name ;
    }
    // 等价于PersonType.prototype.sayName
    sayName(){
        console.log(this.name)
    }
}
let person = new PersonClass('欧阳不乖');   
person.sayName();   // '欧阳不乖'

直接在类中通过特殊的constructor方法名来定义构造函数,由于这种类使用简洁语法来定义方法,因而不需要添加function关键字,除constructor外没有其他保留的方法名。
自有属性是实例中的属性,不会出现在原型上,且只能在类的构造函数活方法中创建,建议在构造函数中创建所有的自有属性,从而只通过一处就可以控制类中的所有自有属性。
类声明仅仅是基于已有自定义类型声明的语法糖,实际上创建了一个具有构造函数方法行为的函数。
与函数不同的是,类属性不可被赋予新值,在之前的示例中,PersonClass.prototype就是这样一个只读的类属性。

class Foo{
    constructor(){
        Foo = "bar";  // 执行时会抛出错误
    }
}
// 但在类声明结束后就可以修改
Foo = "bar"

类表达式

类和函数都有两种存在形式:声明形式和表达式形式。
声明形式的函数和类都由相应的关键字(分别为function和class)进行定义,随后紧跟一个标识符;表达式形式的函数和类与之相似,只是不需要在关键字后添加标识符。

let PersonClass = class {
    // 等价于PersonType构造函数
    constructor(name){
        this.name = name ;
    }
    // 等价于PersonType.prototype.sayName
    sayName(){
        console.log(this.name)
    }
}

let person = new PersonClass('欧阳不乖');
person.sayName();   // '欧阳不乖'
let PersonClass = class PersonClass2{
     // 等价于PersonType构造函数
    constructor(name){
        this.name = name ;
    }
    // 等价于PersonType.prototype.sayName
    sayName(){
        console.log(this.name)
    }   
}
console.log(typeof PersonClass);    // function
console.log(typeof PersonClass2);   // undefined

对于类声明来说,通过let定义的外部绑定与通过const定义的内部绑定具有相同的名称;而命名类表达式通过const定义名称,从而PersonClass2只能在类的内部使用。

作为一等公民的类

在程序中,一等公民是指一个可以传入函数,可以从函数返回,并且可以赋值给变量的值。
ES6中类也是一等公民。
可以将类作为参数传入函数中:

function createObject(classDef){
    return new classDef();
}

let obj = createObject( class {
    sayHi(){
        console.log('Hi!');
    }
})
obj.sayHi();    // Hi!

类表达式还有另外一种使用方式,通过立即调用类构造函数可以创建单例。用new调用类表达式,紧接着通过一对小括号调用这个表达式:

let person = new class{
    constructor(name){
        this.name = name ;
    }
    sayName(){
        console.log( this.name );
    }
}('欧阳不乖');
person.sayName();    // '欧阳不乖'

访问器属性

类支持直接在原型上定义访问器属性。
创建getter时,需要在关键字get后紧跟一个空格和相应的标识符;
创建setter时,需要在关键字set后紧跟一个空格和相应的标识符:

class CustomHTMLElement{
    constructor(element){
        this.element = element ;
    }
    get html(){
        return this.element.innerHTML ;
    }
    set html(value){
        return this.element.innerHTML = value ;
    }
}

var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype,"html");

console.log( 'get' in descriptor );   // true
console.log( 'set' in descriptor );   // true

可计算成员名称

let methodName = 'sayName';

class PersonClass {
    constructor(name){
         this.name = name;
    }
    [methodName](){
        console.log(this.name)
    }
}

let me = new PersonClass('欧阳不乖');
me.sayName();  //'欧阳不乖'

生成器方法

class Collection{
    constructor(){
        this.items = [];
    }
    *[Symbol.iterator](){
         yield *this.items.values(); 
    }
}

let collection = new Collection();
collection.items.push(1);
collection.items.push(2);

for (val of collection){
    console.log(val)
}
 //  1
 //  2

静态成员

在ES5中直接将方法添加到构造函数中来模拟静态成员

function PersonType(name){
    this.name = name ;
}
// 静态方法
PersonType.create = function(name){
    return new PersonType(name);
}
//实例方法
PersonType.prototype.sayName = function(){
    console.log(this.name)
}

var person = PersonType.create('欧阳不乖')

在ES6的类方法简化了创建静态成员的过程,在方法或访问器属性名前使用正式的静态注释即可:

class PersonClass {
    constructor(name){
        this.name = name;
    }
    sayName(){
        console.log(this.name);
    }
    // 等价于PersonType.create
    static create(name){
        return new PersonClass(name);
    }
}
let person = PersonClass.create('欧阳不乖');

类中的所有方法和访问器属性都可以用static关键字来定义,唯一的限制是不能将static用于定义构造函数方法。
不可在实例中访问静态成员,必须要直接在类中访问静态成员。

继承和派生类

ES5中的继承是这样实现的:

function Rectangle (length, width){
    this.length = length;
    this.width = width;
}
Rectangle.prototype.getArea = function(){
    return this.length*this.width;
}
function Square(length){
    Rectangle.call(this, length, length)
}

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

let square = new Square(3);
console.log(square.getArea());  // 9
console.log(square instanceof Square);  // true
console.log(square instanceof Rectangle);  // true

ES6中通过extends关键字可以指定类继承的函数,原型会自动调整,通过调用super()方法即可访问基类的构造函数:

class Rectangle{
    constructor(length, width){
        this.length = length;
        this.width = width;
    }
    getArea(){
        return this.length*this.width;
    }
}

class Square extends Rectangle{
    constructor(length){
        // 等价于 Rectangle.call(this, length, length)
        super(length, length)
    }
}

let square = new Square(3);

console.log(square.getArea());  // 9
console.log(square instanceof Square);  // true
console.log(square instanceof Rectangle);  // true 

在Square构造函数中通过调用super()调用Rectangle构造函数并传入相应参数。

继承自其它类的类被称作派生类,如果在派生类中指定了构造函数则必须要调用super(),如果不这样做程序就会报错。如果选择不使用构造函数,则当创建新的类实例时会自动调用super()并传入所有参数。

class Square extends Rectangle{
    constructor(length){
        super(length,length)
    }
    // 覆盖并遮蔽Rectangle.prototype.getArea()方法
    getArea(){
        return this.length* this.length
    }
    // 覆盖遮蔽后调用Rectangle.prototype.getArea()方法
    getArea(){
        return super.getArea();
    }
}
class Rectangle{
    constructor(length, width){
        this.length = length;
        this.width = width;
    }
    getArea(){
        return this.length*this.width;
    }
    static create(length, width){
        return new Rectangle(length, width)
    }
}

class Square extends Rectangle{
    constructor(length){
        // 等价于 Rectangle.call(this, length, length)
        super(length, length)
    }
}

let rect = Square.create(3,4);
console.log(rect.getArea());  // 12
console.log(rect instanceof Rectangle);  // true 
console.log(rect instanceof Square);  // false
function Rectangle(length, width){
    this.length = length;
    this.width = width;
}
Rectangle.prototype.getArea = function (){
    return this.length*this.width;
}

class Square extends Rectangle{
    constructor(length){
        // 等价于 Rectangle.call(this, length, length)
        super(length, length)
    }
}

let x = new Square(3);
console.log(x.getArea()); // 9
console.log(x instanceof Rectangle);  // true

extends强大的功能使得类可以继承自任意类型的表达式,从而创造更多可能性,例如动态地确定类的继承目标:

function Rectangle(length, width){
    this.length = length;
    this.width = width;
}
Rectangle.prototype.getArea = function(){
    return this.length*this.width
};
function getBase(){
    return Rectangle;
}
class Square extends getBase(){
    constructor(length){
          super(length,length)
    }
}

getBase()函数是类声明的一部分,直接调用后返回Rectangle,此示例实现的功能与之前的示例等价。

由于可以动态确定使用哪个基类,因而可以创建不同的继承方法:

let SerializableMixin = {
    serialize(){
        return JSON.stringify(this);
    }
};

let AreaMixin = {
    getArea(){
        return this.length*this.width
    }
}

function mixin(...mixins){
    var base = function(){};
    Object.assign(base.prototype,...mixins);
    return base;
}

class Square extends mixin(AreaMixin, SerializableMixin){
    constructor(length){
        super();
        this.length = length;
        this.width = length;
    }
}

var x = new Square(3);   
console.log(x.getArea());  // 9
console.log(x.Serialize())   //{"length":3,"width":3}
class MyArray extends Array{
    // do something
}
var colors = new MyArray();
colors[0]= "red";
console.log(colors.length);// 1
colors.length = 0;
console.log(colors[0]);  // undefined

在类的构造函数中使用new.target

class Shape{
    constructor(){
        if(new.target === Shape){
              throw new Error('这个类不能被直接实例化')
        }
    }
}
class Rectangle extends Shape{
    constructor(length, width){
        super();
        this.length = length;
        this.width = width;
    }
}

var x = new Shape();  // 抛出错误
var y = new Rectangle(3,4);   //不会抛出错误
console.log(y instanceof Shape);  // true

因为类必须通过new关键字才能调用,所以在类的构造函数中,new.target属性永远不会是undefined

上一篇 下一篇

猜你喜欢

热点阅读