美文网首页
类的基础语法

类的基础语法

作者: jluemmmm | 来源:发表于2021-04-04 17:37 被阅读0次

    ES5中生成实例对象的方法是通过构造函数,首先创建一个构造函数,定义另一个方法并赋值给构造函数的原型,使用new操作符创建一个构造函数的实例,所有实例都将共享这个方法。ES6中通过class关键字定义类,作为对象的模板。类中的constructor方法就是构造方法,通过new命令生成对象实例时,自动调用该方法。this关键字代表实例对象,定义在this上的属性是实例属性,否则就是定义在原型对象上的属性。

    class PersonClass {
        //在类中定义构造函数 
        constructor(name) {
            this.name = name
        }
        
        sayName(){
            console.log(this.name)
        }
    }
    let person = new PersonClass('siyuan')
    person.sayName() //siyuan
    

    类的内部定义的所有方法都是不可枚举的,Object.keys(obj)返回对象自身可枚举属性组成的数组,Object.getOwnPropertyNames(obj)返回对象自身属性名组成的数组(包括不可枚举属性但不包括Symbol值作为名称的属性)

    Object.keys(PersonClass.prototype) //[]
    Object.getOwnPropertyNames(PersonClass.prototype) // ["constructor", "sayName"]
    

    实例上的__proto__属性指向原型对象,可以通过该属性为类添加方法。也可通过Object.getPrototypeOf(obj)方法获取实例对象的原型。

    注意点

    1. 严格模式
    • 类声明中的所有代码将自动运行在严格模式下。
    1. 不存在提升
    • 函数声明可以被提升,类声明与let声明类似,不能被提升。真正执行声明语句之前,他们会一直存在于临时死区中。

    js引擎在扫描代码发现变量声明时,会将var声明提升至作用域顶部,将letconst声明放到临时性死区(Temporal dead zone: TDZ)中,访问TDZ中的变量会触发运行时错误,只有执行过变量声明语句后,变量才会从TDZ中移出,然后方可正常访问。

    1. 不可枚举
    • 自定义类型中,需要通过Object.defineProperty()方法指定某个方法不可枚举,类中的所有方法都不可枚举。
    1. this指向
    • 类的方法内部如果含有this,默认指向类的实例。可以使用箭头函数绑定this,箭头函数内部的this总是指向定义时所在的对象,箭头函数位于构造函数内部,定义生效时是在构造函数执行的时候,此时箭头函数所在的运行环境是实例对象,this会总是指向实例对象。
    1. 除用new关键字以外的方法调用类的构造函数会导致程序抛出错误
    //等价于PersonClass
    let PersonType = (function(){
        "use strict"
        const PersonType = function(name){
            //确保通过关键字new调用函数
            if(typeof new.target === "undefined") {
                throw new Error("必须通过关键字new调用构造函数")
            }
            this.name = name
        }
        Object.defineProperty(PersonType.prototype, "sayName", {
            value: function(){
                //确保不会通过关键字new调用该方法
                if(typeof new.target !== "undefined") {
                    throw new Error("不可使用关键字new调用该方法")
                }
                console.log(this.name)
            },
            enumerable: false,
            writable: true,
            configurable: true
        })
        return PersonType
    }())
    

    其中,new.target属性允许检测函数或构造函数是否通过new运算符调用。在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用,在普通函数的调用中,new.target的值是undefined

    Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。通过赋值操作添加的普通属性是可枚举的(for...inObject.keys方法),属性的值可以被改变,也可以被删除。

    访问器属性

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

    class CustomHTMLElement {
        constructor (element){
            this.element = element
        }
        get html(){
            return this.element.innerHTML
        }
        set html(value) {
            this.element.innerHTML = value
        }
    }
    var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html")
    console.log("get" in descriptor) //true
    console.log("set" in descriptor) //true
    

    其中,Object.getOwnPropertyDescriptor(obj, prop)返回指定对象上一个自有属性对应的属性描述符(自有属性是指直接赋予该对象的属性,不需要从原型链上进行查找的属性)

    静态方法

    ES5及早期版本中,直接将方法添加到构造函数中模拟静态成员,ES6中创建静态成员,在方法或访问器属性名前使用静态注释static即可。

    类相当于实例的原型,所有在类中定义的方法都会被实例继承。在方法前加上static关键字,表示该方法直接通过类调用,不会被实例继承,称为''静态方法''。静态方法中的this指的是类。父类的静态方法可以被子类继承。

    class Foo {
        static bar() {
            this.baz()
        }
        static baz() {
            console.log('hello')
        }
        baz() {
            console.log('world')
        }
    }
    Foo.bar() //hello
    

    实例属性新写法

    私有属性是实例中的属性,不会出现在原型上,只能在类的构造函数或方法中创建。
    实例属性除了定义在constructor()方法里的this上,也可以定义在类的最顶层。

    class foo {
        bar = 'hello'
        baz = 'world'
    
        constructor(){
        //...
        }
    }
    

    私有方法和私有属性

    私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码封装。

    • 在私有方法前面加_,表示这是一个只限于内部使用的私有方法
    • 利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
    const bar = Symbol('bar')
    const snaf = Symbol('snaf')
    
    class myClass {
        //公有方法
        foo(baz) {
            this[bar](baz)  
        }
        //私有方法
        [bar](baz) {
            return this[snaf] = baz
        }
    }
    const inst = new myClass()
    Reflect.ownKeys(myClass.prototype) //["constructor", "foo", Symbol(bar)]
    

    barsnaf都是 Symbol值,一般情况下无法获取,因此达到了私有方法和私有属性的效果。而Reflect.ownKeys()返回一个对象自身属性键组成的数组 ,依然可以获取。

    类的继承

    ES5组合继承

    ES5及早期版本借用构造函数实现对实例属性的继承,通过原型式继承实现对原型属性和方法的继承。

    //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
        }
    })
    var square = new Square(3)
    console.log(square.getArea())
    console.log(square instanceof Square)
    console.log(square instanceof Rectangle)
    

    类的继承

    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)
        }
    }
    var square = new Square(3)
    console.log(square.getArea())  //9
    console.log(square instanceof Square) //true
    console.log(square instanceof Rectangle)  //true
    

    super()的用法

    ES5的继承,先创造了子类实例对象的this,将父类的方法添加到this上(parent.apply(this))。ES6的继承机制不同,现将父类实例对象的属性和方法加到this上,所以必须先调用super()方法,然后再用子类的构造函数修改this

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

    class Square extends Rectangle {
        //没有构造函数
    }
    //等价于
    class Square extends Rectangle {
        constructor(...args) {
            super(...args)
        }
    }
    
    • 只能在extends声明的类中使用super()
    • 在构造函数中使用this之前一定要调用super(),它负责初始化this。子类的this对象,必须通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后对其加工,加上子类自己的实例属性和方法。
    • 如果基类有静态成员,那么静态成员在派生类中也可用。
    • 只要表达式可以被解析为一个函数并且具有[[construct]]属性和原型,就可以使用extends进行派生
    • super作为函数调用时,只能用于子类的构造函数中,代表父类的构造函数。在子类的普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例;在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类
    class A {
        constructor() {
        this.x = 1
            console.log(new.target.name)
        }
        print() {
        console.log(this.x)
        }
        static prints(){
            console.log(this.x)
        }
    }
    class B extends A {
        constructor() {
            super()
            this.x = 2
            //super虽然表示父类的构造函数,返回的是子类B的实例,即super()内部的this指向的是B,super()在这里相当于A.prototype.constructor.call(this)
        }
        m () {
            super.print()
        }
        static n() {
            super.prints()      
        }
    }
    let a = new A() //A
    let b = new B() //B
    b.m() //2
    B.x = 3
    B.n() // 3
    >```
    > -  `super`作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
    > ```javascript
    class A {
        constructor(){
            this.p = 2
        }
        q() {
            return 3
        }
    }
    class B extends A {
        constructor(){
            super()
        }
        get m(){
            console.log(super.p)
            console.log(super.q())
        }
    }
    let b = new B()
    b.m // 3 undefined
    //这里super指向父类的原型对象,定义在父类实例上的方法或属性无法通过super调用
    

    内建对象的继承

    • ES5的继承中,先由子类创建this的值,然后调用父类的构造函数,如Array.apply()方法,即this开始指向的是子类的实例,然后被来自父类的其它属性修饰,无法实现内建对象的继承。
    • ES6中的继承,由父类创建this的值 ,然后子类的构造函数修改这个值,因此初始可以通过this访问基类的所有内建功能 ,然后正确接收所有与之相关的功能

    Symbol.species用于定义返回函数的静态访问器属性,被返回的函数是一个构造函数,当要在实例的方法中创建类的实例时必须使用这个构造函数。它被构造函数用于创建派生对象,允许子类覆盖对象的默认构造函数。

    class Array1 extends Array {
        static get [Symbol.species]() {
            return Array
        }
    }
    
    const arr = new Array1(1, 2, 3)
    const mapped = arr.map(x => x * x)
    console.log(mapped instanceof Array1)//false
    console.log(mapped instanceof Array) //true
    

    相关文章

      网友评论

          本文标题:类的基础语法

          本文链接:https://www.haomeiwen.com/subject/ffulkltx.html