likes
comments
collection
share

JavaScript一文让你搞清什么是原型和原型链

作者站长头像
站长
· 阅读数 16

前言

原型和原型链

JavaScriptJava这种面向对象的语言不太一样,JavaScript基于原型继承的语言。虽然在ES6及之后,classextend语法也渐渐取代了之前修改prototype实现继承的方式,但本质上还是通过修改prototype来实现继承的。本文则是重点对prototype相关知识点做拆解和梳理

通过class声明并实例化对象

java中声明并实例化对象是这样的

package geek.springboot.application.entity;

public class WechatUser {

    private String name;

    private String openId;

    private String avatar;

    public WechatUser(String name, String openId, String avatar) {
        this.name = name;
        this.openId = openId;
        this.avatar = avatar;
    }
    
    // 打印输出当前微信用户信息
    public void print() {
        System.out.println("name: " + this.name + " openId: " + this.openId + " avatar: " + this.avatar);
    }

    // java程序启动入口
    public static void main(String[] args) {
        WechatUser user = new WechatUser("Bruse", "opwrogfajadfoa113", "avatar-1.png");
        user.print();
    }

}

JavaScript实例化对象是这样的

class WechatUser {

    constructor(name, openId, avatar) {
        this.name = name
        this.openId = openId
        this.avatar = avatar
    }

    print(){
        console.log(`name: ${this.name} openId:${this.openId} avatar: ${this.avatar}`)
    }

}

const user = new WechatUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg')
user.print()

输出name: Bruse openId:sfoqioiooa1 avatar: avatar-1.jpg

从语法上看两者差别并不大,class定义对象模板,定义了对象该有的属性和方法,然后通过new关键字将对象进行实例化

extend继承

通过extend便可让不同的class实现继承关系,达到代码复用的效果

class MiniProgramUser extends WechatUser {

    constructor(name, openId, avatar, appId) {
        // 调用父类的构造函数
        super(name, openId, avatar);
        this.appId = appId
    }

    // 重写父类方法
    print() {
        console.log(`name: ${this.name} openId:${this.openId} avatar: ${this.avatar} appId: ${this.appId}`)
    }

}

输出name: Bruse openId:sfoqioiooa1 avatar: avatar-1.jpg appId: appId13322

原型

以上示例演示了如何用class进行声明和实例化对象,但其实class只不过是所谓的语法糖,本质上JavaScript并不会像Java那样基于类实现面向对象。

class WechatUser{}

实际上也还是个函数,class只是个语法糖,它等同于

function WechatUser() {}

Tips:可以用ES6转ES5的在线网站转换尝试一下ES6->ES5

隐式原型和显式原型

隐式原型

const user = new WechatUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg')
console.log('user.__proto__ ', user.__proto__)

以上边的代码为例,其实在创建出来的user对象中,有一个__protocol__属性,这个即每个实例都有的隐式原型,打印输出如下

JavaScript一文让你搞清什么是原型和原型链

显式原型

console.log('WechatUser.prototype ',WechatUser.prototype)

输出WechatUserprototype属性,prototype原型的意思,每个class(function)都有显式原型,结果如下

JavaScript一文让你搞清什么是原型和原型链

隐式原型和显式原型的关系

可以看到无论是user.__proto__还是WechatUser.prototype,都有print方法,constructor都是WechatUser,那么是否也就意味着user.__proto__[实例的隐式原型]===WechatUser.prototype[class的显式原型]

console.log('equals ', user.__proto__ === WechatUser.prototype)

输出为equals true,证明user.__proto__的确等于WechatUser.prototype,引用地址是同一个。

这里的关系可以用下图表示

JavaScript一文让你搞清什么是原型和原型链

  1. 每个class都有显式原型prototype
  2. 每个实例都有隐式原型__proto__
  3. 实例的__proto__指向其所对应的class的prototype

基于原型的属性/方法查找

基于上边的内容,其实可以总结出:获取实例属性或执行方法时,会先在实例自身进行寻找有没有相关的属性或方法,有的话就获取或调用,没有的话,会顺着实例的__proto__往上找到实例对应的classprototype,并对prototype进行变量查找或方法调用。这也就是所谓的基于原型的继承方式

原型链

搞明白了基于原型是怎么回事后,那接下来就是多个原型之间的关联形成原型链

const miniUser = new MiniProgramUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg', "appId13322")

这里声明一个miniUser,它是基于MiniProgramUser实例化的,所以miniUser.__proto__相等于MiniProgramUser.prototype

但其实MiniProgramUser.prototype也有一个__proto__属性,输出如下

JavaScript一文让你搞清什么是原型和原型链

miniUser的隐式原型等于MiniProgramUser的显式原型,可MiniProgramUser的显式原型的隐式原型(是有点绕)又等于谁呢?

因为定义MiniProgramUser这个class的时候,使用了extend关键词,表示其继承于WechatUser,而且WechatUser也有自己的prototype,输出如下

JavaScript一文让你搞清什么是原型和原型链

那么尝试将MiniProgramUser.prototype.__proto__WechatUser.prototype比较,结果如下

console.log('equals',  MiniProgramUser.prototype.__proto__ === WechatUser.prototype)

输出equals true,证明MiniProgramUser显式原型隐式原型等于WechatUser的显示原型,隐约间形成了一条原型链

原型链的尽头

那么在这里其实也可以做一个举一反三,既然每个classprototype都会有一个__proto__,既然WechatUser这个class并没有在代码中显式指定继承于哪个class,那么WechatUser.prototype.__proto__应该就等同于Object.prototype,输出验证如下

console.log(WechatUser.prototype.__proto__ === Object.prototype)

结果为true

这里也有一个知识点,因为ObjectJavaScript所有对象中是最顶级的存在了,所以虽然Objectprototype也有__proto__,但它实际上不指向任何对象,仅为null

console.log('Object.prototype.__proto__ ', Object.prototype.__proto__)

输出 Object.prototype.__proto__ null

原型链总结

这里可以用一张图清楚表示形成的原型链是怎么样的

JavaScript一文让你搞清什么是原型和原型链

typeof vs instanceof

typeof

typeof是用来判断当前变量是何种类型?基本类型?引用类型?也就是说它能

  1. 识别所有值类型
  2. 识别函数
  3. 判断是否引用类型,但只能判断出为object,没法再细分

判断值类型

const name = 'Bruse'   typeof name // 输出'string'
const sym = Symbol('sym')   typeof sym // 输出'symbol'
const done = false  typeof done // 输出'boolean'

识别函数

typeof console.log   // 'function'

function print () { console.log(1+1) }
typeof print    // 'function'

引用类型则非常笼统地识别为object

typeof null  // 'object'
typeof [1,2,3] // 'object'
typeof {name: 'Bruse', age: 16}  // 'object'

instanceof

instanceof也是用作类型判断的,只不过比typeof更精准了,它可以判断出当前变量是否该class构建出来的。

[]  instanceof Array  // true
[]  instanceof Object // true
{}  instanceof Object // true

miniUser instanceof MiniProgramUser // true
miniUser instanceof WechatUser // true
miniUser instanceof Object // true

结合出上边原型链的知识,其实可以搞清楚instanceof的原理,其实就是根据instanceof左边变量miniUser的原型链一层层往上找,判断prototype__proto__是否等于instanceof右边的class WechatUserprototype