美文网首页
类的基础语法

类的基础语法

作者: 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

相关文章

  • 软帝学院:80道java基础部分面试题(四)

    Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集...

  • Java面试题知识点

    1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...

  • Java初级面试题

    1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...

  • java学习笔记第一天

    java基础语法 java基础语法 对象:对象是类的一个实例,有状态和行为。 类:类是一个模板,它描述一类对象的行...

  • 软帝学院:80道java基础部分面试题(一)

    11道java基础部分面试题 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相...

  • 类的基础语法

    1. 构造函数 单参数构造函数,需要加explicit, 避免隐式转换 初始化列表初始化列表可以用来对于 cons...

  • 类的基础语法

    ES5中生成实例对象的方法是通过构造函数,首先创建一个构造函数,定义另一个方法并赋值给构造函数的原型,使用new操...

  • Java 基础语法

    Java 基础语法 基本语法 编写 Java 程序时,应注意以下几点: 大小写敏感 类名 对于所有的类来说,类名的...

  • Python基础入门6_文件和异常

    Python 基础入门前五篇: Python 基础入门--简介和环境配置 Python基础入门_2基础语法和变量类...

  • Python 基础入门 5--面向对象

    Python 基础入门前四篇: Python 基础入门--简介和环境配置 Python基础入门_2基础语法和变量类...

网友评论

      本文标题:类的基础语法

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