【深入理解es6——js中的类】

深入理解es6——js中的类


一、类由来、es5的类

java、c++等面向对象的语言支持类和类继承,但是js(基于对象)不支持,只能关联多个对象。直到es5出现近类结构,但写法或许会让初学者产生困惑。尽管js开发者坚持不需要类,但是类似的库层出不穷(或者还有资本的力量,老板强烈要求要仿java)。所以,最终es6出现了类。
es5的类如:

// 创建构造函数
function PersonType(name){
	this.name = name;
}
PersonType.prototype.sayname = function(){
	console.log(this.name);
}
var person = new PersonType('lisa'); // new 操作符创建实例
person.sayname() // lisa
console.log(person instanceof PersonType); //true
console.log(person instanceof Object);// true(原型继承)

二、类声明

class PersonClass {
    // 等价于构造函数
    constructor(name) {
        this.name = name;
    }
    // 等价于 personType.prototype.sayName
    sayName() {
        console.log(this.name)
    }
}
let person = new PersonClass("lisa");
person.sayName(); // lisa
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object);// true
console.log(typeof PersonClass);// "function"
console.log(typeof PersonClass.prototype.sayName);// "function"

1、在类中通过constructor方法来定义构造函数。
2、私有属性是实例中的属性,不会出现在原型上,且只能在类的原型或方法创建,如name就是私有属性(建议在构造函数中创建所有的私有属性)。
3、PersonClass 声明实际上创建了一个具有构造函数方法行为的函数。sayname实际上是PersonClass原型上的方法。
尽管类语法与自定义类型有相似之处,但我们仍要记住这些差异
1、类跟let声明类似,不会提升,具有临时死区(尽管底层是函数)
2、类中所有代码自动运行在严格模式下,无法强行脱离严格模式
3、类中所有方法不可枚举
4、每个类都有一个名为construct的内部方法,new调用不含construct的方法会抛出错误
5、new以外的方式调用类的构造函数会抛出错误。
6、类中修改类名会导致程序报错。
用类以外的方法编写等价代码:

// 外部作用域使用let声明
// 使用闭包(可说明以上第6点)
let PersonType2 = (function () {
    "use strict";// 严格模式(第2点)
    const PersonType2 = function (name) {
        // 确保通过关键字new调用该函数(以上第5点)
        if(typeof new.target === "undefined") {
            throw new Error("必须通过关键字new调用构造函数噢!");
        }
        this.name = name;
    }
    Object.defineProperty(PersonType2.prototype, "sayName", {
        value: function () {
            // 确保不会通过关键字new调用该方法(以上第4点)
            if(typeof new.target !== "undefined") {
                throw new Error("不可使用关键字new调用该方法!");
            }
            console.log(this.name)
        },
        enumerable: false,// 不可枚举(第3点)
        writable: true,
        configurable: true
    });
    return PersonType2;
}())

三、类表达式

基本类表达式语法

// 类表达式在功能上等同于类的声明
let PersonClass = class{
    // 等价于构造函数
    constructor(name) {
        this.name = name;
    }
    // 等价于 personType.prototype.sayName
    sayName() {
        console.log(this.name)
    }
}
let person = new PersonClass("lisa");
person.sayName();
console.log(person instanceof PersonClass);
console.log(person instanceof Object);
console.log(typeof PersonClass);
console.log(typeof PersonClass.prototype.sayName);

命名类表达式

let PersonClass = class PersonClass2{
    // 等价于构造函数
    constructor(name) {
        this.name = name;
    }
    // 等价于 personType.prototype.sayName
    sayName() {
        console.log(this.name)
    }
}
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass2);// "undefined"  类的外部不存在PersonClass2的定义

四、作为一等公民的类

在程序中,一等公民是指一个可传入函数,可从函数返回,并可赋值给变量的值。函数是一等公民(也被称为头等函数)
类也是,允许通过多种方式使用类特性:

function createObject(classDef) {
    return new classDef();
}
let obj = createObject(class {
    // constructor必须要写,没写就隐式定义,会被默认添加到类
    // java中没写的话,默认会构建无参构造函数
    sayHi() {
        console.log('hi');
    }
})
obj.sayHi();

类表达式另一个使用方式:通过立即执行函数创建单例:

let person = new class {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name)
    }
}('lisa')
person.sayName()
// 实用类语法创建单例,不会再作用域中暴露类的引用

五、访问器属性

(待补充)

六、可计算成员名称、生成器方法

(待补充)

七、静态成员

静态的是指向类自身,而不是指向实例对象,主要是归属不同,这是静态属性的核心。
静态方法使用:在方法前加上static关键字(唯一限制是不能将static用于定义构造函数方法)。
为什么使用静态方法:阻止方法被实例继承,类的内部相当于实例的原型,所有在类中直接定义的方法相当于在原型上定义方法,都会被类的实例继承,但是使用static静态方法定义的不会被实例继承,例如:

class User{
   static call(){
        console.log('你好呀');
     }
 }
User.call()//你好呀

let ff = new User()
ff.call()//TypeError: ff.call is not a function
// 不能在实例中访问静态成员,必须在类中访问

八、继承与派生类

extend和super

es6之前,实现继承需要多个步骤,且常让人感到困惑。es6中,就简单多了,使用extend可以指定类继承的函数。原型会自动调整,通过super方法访问基类的构造函数:

class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }
    getArea() {
        return this.height * this.width
    }
}
// 继承自其他类的类被称作派生类,如Square
class Square extends Rectangle{
    constructor(length) {
        super(length, length);
    }
}
var square = new Square(3);
console.log(square.getArea()) // 9
console.log(square instanceof Square) // true

派生类中制定了构造函数必须要调用super,否则程序报错。如果选择不使用构造函数,则创建新的类实例会自动调用super并传入所有参数。例如:

class Square extends Rectangle{
}
// 等价于:
class Square extends Rectangle{
    constructor(...args) {
        super(...args);
    }
}
// 由于本例Square只需要传一个参数,如果不写构造函数,就不太正确,最好手动定义构造函数

类方法遮蔽

派生类中的方法总会覆盖基类的同名方法。此时,优先调用派生类的方法,那要用基类的方法怎么办?,使用:super.getArea(), 即“super.父类方法名”调用。

静态成员继承

基类有静态成员,在派生类的也可用,跟其他语言中的继承一样。

派生自表达式的类

es6最强大的一面或许是从表达式导出类的功能了。只要表达式能解析成一个函数并具有[[Contruct]]属性和原型,就可以用extends派生。例如:

function Rectangle(length, width) {
    this.width = width;
    this.length = length;
}
Rectangle.prototype.getArea = function () {
    return this.length * this.width;
}
class Square extends Rectangle{
    constructor(length) {
        super(length, length);
    }
}
var x = new Square(3);
console.log(x.getArea()) // 3
console.log(x instanceof Rectangle) // true

extends使类可继承自任意类型的表达式,从而有更多可能性,如动态地确定类的继承目标。由于可以动态确定使用哪个基类,因为可以创建不同的继承方法,离开如,创建minxin:

let SerializableMinxin = {
    serialize() {
        return JSON.stringify(this);
    }
}
let AreaMixin = {
    getArea() {
        return this.length * this.length;
    }
}
function minxin(...minxins) {
    var base = function () {};
    Object.assign(base.prototype, ...minxins);
    return base;
}
class Square extends minxin(AreaMixin, SerializableMinxin) {
    constructor(length) {
        super();
        this.length = length;
        this.width = length;
    }
}
let x = new Square(3);
console.log(x.getArea()); // 9
console.log(x.serialize());// {"length":3,"width":3}

内建对象的继承

class MyArray extends Array{
    // 空
}
var colors = new MyArray();
colors[0] = "red";
console.log(colors.length)// 1
colors.length = 0;
console.log(colors[0]) // undefined

MyArray直接继承Array,其行为跟Array相似。通过继承Array对象来创建自己的派生数组类型,当然也可以继承其他的内建对象。

Symbol.species属性

内建对象继承的一个使用之处是,原本在内建对象中返回实例自身的方法将自动返回派生类的实例。所以,继承自Array的派生类MyArray,那么像slice()这一的方法也会返回一个MyArray的实例。例如:

class MyArray extends Array{
    // 空
}
let items = new MyArray(1,2,3,4);
let subitems = items.slice(1,3);
console.log(items instanceof MyArray);// true
console.log(subitems instanceof MyArray); // true

正常情况下,继承自Array的slice()方法应该返回Array实例,但是,这段代码中slice()方法返回的是MyArray的实例。在浏览器引擎背后是通过Symbol.species属性实现这一行为。

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

class Rectangle {
    constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
    }
}
// 简单情况下,new.target = 构造函数
// new.target = Rectangle
let obj = new Rectangle(3,4) // true
class Square extends Rectangle{
    constructor(length) {
        super(length, length);
        
    }
}
// Square调用Rectangle的构造函数,所以调用发生时,new.target=Square
// new.target 的值是 Square
let obj1 = new Square(3); // false

因此,每个构造函数都可以根据自身被调用的方式改变的行为。例如:

// 用new.target创建一个抽象基类(不能直接实例化,java中的抽象类也是不能直接实例化)
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

可用shape作为记类派生其他类,但是不能直接实例化。
因为类必须通过new关键字才能调用,所以new.target永远不会等于 undefined

总结

es6类语法让js中的继承更易于使用。es6类语法首先是作为es5传统继承模型的语法糖出现,但是添加了几个能降低风险的新特性。
es6类语法通过在类的原型上定义非静态方法来与原型继承协同工作,而静态方法最终放到构造函数上。所有方法都不可枚举,从而可以更好地匹配内建对象的行为,因为那些方法通常不可枚举的。此外,类构造函数必须通过new关键字调用,以确保不会意外将类作为函数去调用。
基于类的继承支持从其他类、函数、或表达式派生类,可以通过函数调用来确定最重要继承哪一个类,可以用minxin对象和其他不同的组合模式来创建新的类,也可以继承Array的内建对象并且像于其的那样运行。
在类的构造函数中,可以通过new.tatget来随着类被调用的多种方式而做出不同的对应。最常见的用法是创建一个抽象基类,如果直接实例化这个类会抛出错误,但是可以通过其他的类去实例化它。
总之,类是js新特性的一个重要组成部门,这一特性提供了更简洁的语法和更好的功能,可以让你通过一个安全、一致的方式来自定义对象类型。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值