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

前言

本文章源自《JavaScript知识小册》专栏,感兴趣的话还请关注点赞收藏.

上一篇文章:《JavaScript中的伪数组

原型和原型链

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

通过class声明并实例化对象

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

typescript 复制代码
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实例化对象是这样的

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实现继承关系,达到代码复用的效果

scala 复制代码
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那样基于类实现面向对象。

kotlin 复制代码
class WechatUser{}

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

csharp 复制代码
function WechatUser() {}

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

隐式原型和显式原型

隐式原型

arduino 复制代码
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)都有显式原型,结果如下

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

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

javascript 复制代码
console.log('equals ', user.__proto__ === WechatUser.prototype)

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

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

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

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

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

原型链

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

arduino 复制代码
const miniUser = new MiniProgramUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg', "appId13322")

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

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

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

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

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

javascript 复制代码
console.log('equals',  MiniProgramUser.prototype.__proto__ === WechatUser.prototype)

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

原型链的尽头

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

javascript 复制代码
console.log(WechatUser.prototype.__proto__ === Object.prototype)

结果为true

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

javascript 复制代码
console.log('Object.prototype.__proto__ ', Object.prototype.__proto__)

输出 Object.prototype.__proto__ null

原型链总结

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

typeof vs instanceof

typeof

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

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

判断值类型

javascript 复制代码
const name = 'Bruse'   typeof name // 输出'string'
const sym = Symbol('sym')   typeof sym // 输出'symbol'
const done = false  typeof done // 输出'boolean'

识别函数

javascript 复制代码
typeof console.log   // 'function'

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

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

csharp 复制代码
typeof null  // 'object'
typeof [1,2,3] // 'object'
typeof {name: 'Bruse', age: 16}  // 'object'

instanceof

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

javascript 复制代码
[]  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

相关推荐
wearegogog1234 小时前
基于 MATLAB 的卡尔曼滤波器实现,用于消除噪声并估算信号
前端·算法·matlab
Drawing stars4 小时前
JAVA后端 前端 大模型应用 学习路线
java·前端·学习
品克缤4 小时前
Element UI MessageBox 增加第三个按钮(DOM Hack 方案)
前端·javascript·vue.js
小二·4 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus
小沐°4 小时前
vue-设置不同环境的打包和运行
前端·javascript·vue.js
qq_419854055 小时前
CSS动效
前端·javascript·css
烛阴5 小时前
3D字体TextGeometry
前端·webgl·three.js
桜吹雪5 小时前
markstream-vue实战踩坑笔记
前端
好好沉淀5 小时前
1.13草花互动面试
面试·职场和发展
南村群童欺我老无力.5 小时前
Flutter应用鸿蒙迁移实战:性能优化与渐进式迁移指南
javascript·flutter·ci/cd·华为·性能优化·typescript·harmonyos