JavaScript的Class的继承的语法知识,和原型链的使用【ES6标准入门】

本文详细介绍了JavaScript中Class的继承机制,包括`extends`关键字、原型链、`Object.getPrototypeOf`的使用、super关键字在构造函数中的作用以及类的`__proto__`和`prototype`属性。特别强调了子类构造函数中调用`super`的必要性及其两种使用方式的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

😁 作者简介:一名大四的学生,致力学习前端开发技术
⭐️个人主页:夜宵饽饽的主页
❔ 系列专栏:前端js专栏
👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气

​🔥​前言:

这里是关于js中Class继承的使用和原理,其中有关原型链的部分非常好。 这是我自己的学习JavaScript的笔记,希望可以帮助到大家,欢迎大家的补充和纠正

第20章 Class的继承

20.1 简介

Class可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承更加清晰和方便

class Point{
}

class ColorPoint extends Point {
    
}

上述代码中定义了一个ColorPoint类,该类通过extend关键字继承了Point类的所有属性和方法

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

❗️ 注意点:

  1. 子类必须在constructor方法中调用super方法,否则新建实例时会报错,这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象

20.2 Object.getPrototypeOf()

这个方法可以用来从子类上获取父类,所以可以使用这个方法来判断一个类是否继承另一个类

20.3 super关键字

super这个关键字即可以当作函数使用,也可以当作对象使用,在这两种情况下,它的用法完全不同

第一种情况,super作为函数调用时代表父类的构造函数,ES6要求,子类的构造函数必须执行super函数

class A{}
class B extend A{
    constructor(){
        super()
    }
}

📝 使用细节:

  1. super虽然代表了父类的构造函数,但是返回的是子类的B的实例,即super内部的this指的是B,因为super()在这里相当于A.prototype.constructor.call(this)

  2. super()是可以有参数的,参数代表父类构造函数的参数

  3. 作为函数,super()只能用在子类的构造函数之中,用在其他地方会报错

第二种情况,super作为对象时在普通方法中指向父类的原型对象,在静态方法中指向父类

class A{
    p(){
        return 2
    }
}

class B extends A{
    constructor(){
        super();
        console.log(super.p())
    }
}

let b=new B()

上面的代码中,子类B中的super.p()就是将super当作一个对象来使用,这时,super在普通方法之中指向A.prototype,所以super.p()就相当于A.prototype.p()

❗️ 使用细节:

  1. 由于super指向父类的原型对象,所以定义在父类实例上的方法或属性是无法通过super调用的

  2. 通过super调用父类的方法时,super会绑定子类的this,因此如果通过super对某一个属性赋值,这时super就是this赋值的属性会变成子类实例的属性

    class A{
        constructor(){
            this.x=1
        }
    }
    
    class B extends A{
        constructor(){
    		super();
            this.x=2
            super.x=3
            console.log(super.x) //undefined
            console.log(this.x) //3
        }
    }
    
    let b=new B()
    
  3. 使用super时候,必须显示指定是作为函数还是担心使用,否则报错

20.4 类的prototypr属性和 __ proto __属性

在大多数浏览器的ES5实现之中,每一个对象都有 __ proto__ 属性,指向对应的构造函数的prototype属性,Class作为构造函数的语法糖,同时有prototype属性和 __ proto __ 属性,因此同时存在两条继承链

  • 子类的 __ proto __ 属性表示构造函数的继承,总是指向父类
  • 子类prototype属性的 __ proto__ 属性表示方法的继承,总是指向父类的prototype属性
class A{
    
}
class B extends A{
    
}
B.__proto__===A //true
B.prototype.__proto__ === A.prototype //true

造成这样的结果是因为类的继承是按照下面的模式实现的

class A{
    
}
class B{

}

//B的实例继承A的实例
Object.setPrototypeOf(B.prototype,A.prototype)

//B的实例继承A的静态属性
Object.setPrototype(B,A)

const b=new B()

//Object。setPrototypeOf方法的实现
Object.setPrototypeOf=function(obj,proto){
    obj.__proto__ =proto;
    return obj
}

根据setPrototypeOf的实现方法和设计模式才造成了下面的结果

B.prototype.__proto__ = A.prototype

B.__proto__=A

**这两条继承链可以这样理解:作为一个对象,子类(B)的原型( __ proto __)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。 **

20.4.1 extends的继承目标

extends关键字后面可以跟多种类型的值

下面我们来讨论3中特殊情况:

  1. 第一种情况,子类继承Object类

    class A extends Object{
        
    }
    A.__proto__ === Object //true
    A.prototype.__proto__===Object.prototype //true
    
    

    这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例

  2. 第二种情况,不存在任何继承

    class A{
    
    }
    A.__proto__ === Function.prototype //true
    A.prototype.__proto__===Object.prototype //true
    

    这种情况下,A作为一个基类就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象,所以A.prototype.__proto指向构造函数的prototype属性

  3. 第三种情况,子类继承null

    class A extends null{
        
    }
    A.__proto__ === Function.prototype //true
    A.prototype.__proto__===undefined //true
    

    这种情况下,A也是一个普通函数,但是A调用后返回的对象不继承任何方法,所以它的__ proto __指向Function.prototype,实际上执行了下面的代码

    class C extends null{
    	constructor(){return Object.create(null)}
    }
    
20.4.2 实例的 __ proto __属性

子类实例的 __ proto __ 属性的 __ proto __ 属性指向父类实例的 __ proto __属性。也就是说,子类的原型的原型是父类的原型

因此,可以通过子类实例的__ proto __ . __ proto __属性修改父类实例的行为

20.5 原生构造函数的继承

  • Boolean()
  • Number()
  • String()
  • Array()
  • Data()
  • Function()
  • RegExp()
  • Error()
  • OBject()

以前,这些原生构造函数是无法继承的,比如,不能自己定义一个Array的子类

之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行,原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性

现在 ES6可以自定义原生数据结构(比如 Array,String等)的子类,这就是ES5无法做到的

extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数,因此可以在原生数据结构的基础上定义自己的数据结构,以下代码定义一个带版本功能的数组

class VersioneArray extends Array{
    constructor(){
        super()
        this.history=[[]]
    }
    commit(){
		this.history.push(this.slice())
    }
    revert(){
        this.splice(0,this.length,this.history[this.history.length-1])
    }
    
}

var x= new VersionedArray()

x.push(1)
x.push(2)
x//[1,2]
x.history //[[]]

❗️ 注意:继承Object的子类有一个行为差异,

class NewOBj extends OBject{
	constructor(){
		super(...arguments)
 	}
}

var o=new NewObj({attr:true})
o.attr ===true

上面的代码中,NewObj继承了Object,但是无法通过super方法向fuelingObject传参,这是因为ES6改变了Object构造函数的行为,一旦发现Object方法不是通过new Object() 这种形式调用,ES6规定Object构造函数忽略参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值