原型原型链

215 阅读5分钟

什么是构造函数

constructor 是一种用于创建和初始化 class 对象实例的特殊方法,构造函数其实就是一个普通的函数,当使用 new 关键字调用时他就是一个构造函数, 用于生成一个对象的模板, 描述了对象内部的基本结构。

原型的概念

原型就是每个对象都拥有一个属性(他是一个对象,它包含了原型链上共享的属性和方法),被称为原型,原型指向的对象称为“原型对象 或 对象原型”,原型可以分为两种类型:隐式原型 和 显示原型。

原型对象的理解:

是指没有 constructor 属性的函数,也可以理解成就是一个对象,指的是非函数对象(如通过字面量或new关键字创建的对象)。

     const a = {
         b: 1
     }
     
     const obj = Object.create(a) // 这里的这个 a 被称为原型对象

对象原型的理解:

指的是函数对象的原型,即函数的.prototype属性, 在 JavaScript 中函数也是一种对象

    function MyObject () {}
    
    const obj = new MyObject() 
    obj.__proto__ = MyObject.prototype
    MyObject.prototype = {
        constructor: MyObject
        ....
    }
    // 指的是这个对象
    {
        constructor: MyObject
        ....
    }

原型的理解:

原型:prototype

又称显示原型
1、原型是一个普通对象
2、只有构造函数才具备该属性
3、公有属性可操作

隐式原型:__proto__
1、只有对象(普通对象、函数对象)具备
2、私有的对象属性,不可操作

  • 隐式原型:

    每一个实例对象都有一个隐式原型[[prototype]], 我们可以借助浏览器提供功能__proto__(非官方提供)去访问他, 也可以使用官方提供的方法 Object.getPrototypeOf() 去访问

  • 显示原型:

    javascript 中每一个函数都有个 prototype 属性,它是函数对象的一个特殊属性,用于定义构造函数创建的实例对象的公共祖先。这意味着,通过构造函数产生的对象可以继承到该原型的属性和方法。

特点:

  • 构造函数通过原型分配的函数是所有对象所共享的

  • javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约了内存

    我们可以把那些不变的方法,直接定义在prototype对象上,这样所有的对象的实例就可以共享这些方法

  • 构造函数和原型对象中方法的this都指向实例化的对象

原型链的概念

js 并不是传统意义上的面向对象语言,但是他想引用面向对象这种思维去写,这导致 js 有一个原型链机制,原型链机制本身是 js 对面向对象特征的实现。

原型链的本质: 就是实例对象的隐式原型与构造函数的显示原型的连接

连接方式: 通过 new 关键字

new 关键字做了什么
  1. 绑定一个this指向空对象
  2. 会给对象添加一个 [[prototype]] 的隐式属性 ---> 指向函数对象的 prototype
  3. 执行函数上下文 this ---> 空对象
  4. 把this的引用返回(默认是返回一个undefined) 返回基本类型会把this的引用返回
// 简单实现, 大概意思
 function myNew (fn, ...args) {
     const empty = Object.create(null)
     this = empty
     this.__proto__ = fn.prototype // 实现对象的隐式原型和函数的显示原型的连接
     fn.apply(this, args)
     return this // 注意如果返回的是基本数据类型, 会返回 this, 如果是引用类型, 那他会返回当前指向的引用对象
 }

原型链的查找机制

分为两种

  • [[GET]] 右查询(RHS), 会沿着原型链往上查找, 如果找到原型链的终点还没有找到, 会返回一个 undefinde
  • [[PUT]] 左查询(LHS),执行左查询的时候, 会执行 put

[[GET]]: RHS 查找对象中有没有属性

这个查询比较简单, 就是沿着原型链往上查找, 先看对象身上是否存在(包含自身和构造函数中的), 如果没有, 在对象的原型身上查找, 如果没有在构造函数的原型身上查找, 直到终点 null

[[PUT]]: LHS 判断对象中有没有属性

  • 对象中有该属性

    如果对象身上有这个属性, 那就找到了, 修改内存中的数据

  • 对象身上没有该属性

沿着作用域链往上查找, 直到原型链终点

分两种情况:

  1. 没找到

    没有找到就给对象自身创建一个

  2. 找到了

    • 基本数据类型

      新增, 在对象身上创建这个属性, 不会修改原型链上的数据

    • 引用数据类型

      如果是对引用数据的引用, 那么新增, 在对象自身创建这个属性, 不会修改原型链上的数据 如果是对引用数据的访问, 那么覆盖, 会修改原型链上对应引用数据中对应的值

// [[PUT]] 操作的辅助理解
       function MyObj () {

        }

        MyObj.prototype.a = 1
        MyObj.prototype.b =  {
            val: 12
        }
        const obj = new MyObj()
        obj.a = 11212 // 新增
        obj.c = 10 // 新增
        console.log('🚀 ~ This is a result of console.log ~ ✨: ', obj); // {a: 11212, c: 10}
        obj.b.val = 11 // 这一步执行覆盖, 修改原型链上的数据
        console.log('🚀 ~ This is a result of console.log ~ ✨: ', obj); // {a: 11212, c: 10}

image.png

画出原型链经典图

image.png

原型和原型链的意义何在

  • 原型: 是实现对象继承以及共享属性和方法的关键机制, 原型是一个对象, 里面包含了原型链上所有共享的属性和方法, 通过原型指向的对象我们能够实现方法和属性的共享

  • 原型链:实现给类添加属性(方法, 值), 能够实现 javascrip 的继承, 通过原型链, 对象可以继承原型对象身上的所有共享的方法和属性, 实现了继承