阅读 590

JavaScript进阶之原型和原型链

instanceof的底层实现

instanceof是我们开发中常用判断类型的方式,MDN是这样定义它的:instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置

举个例子:

 function Person() {
        this.name = "vnues";
      }
      let person = new Person();
      console.log(instanceOf(person, Person)); // true
复制代码

以上是我们开发中常用于检查A对象是否为B类或子类的实例(A instanceof B)位置不能颠倒,那么我们会好奇它的底层是怎么实现的,实际也不难,那么自己动手来实现一个吧

function _instanceOf(left, right) {
        // 获得类型的原型
        let prototype = right.prototype;
        // 获得对象的原型
        obj = left.__proto__;
        // 判断对象的类型是否等于类型的原型
        while (true) {
          if (obj === null) return false;
          if (obj === prototype) return true;
          obj = obj.__proto__;
        }
      }
      function Person() {
        this.name = "vnues";
      }
      let person = new Person();
      console.log(_instanceOf(person, Person)); // true
复制代码

实际上instanceof就是去判断person对象和Person类的原型是否相同,它会沿着原型链一层一层的去找原型,如果找到则返回true,这也是我们用无限循环的原因啦,关于原型方面的知识我这篇有详细讲到从Object.prototype.toString聊到原型,那要真正的理解instanceof的实现我们先来深入学习原型链

深入学习原型链

题外话:这里的函数指的都是构造函数,箭头函数排除

先来看一张经典的原型链图

看到这张图真的挺复杂,我们先简到难吧,分成三个模块来讲

原型链图模块一

再看图之前我们还是来温习下三个属性:

  • prototype:prototype是构造函数才会有的属性

  • 属性__proto__:每一个JavaScript对象都具有的一个属性,叫__proto__,这个属性会指向该对象的原型(一般是我们的实例化对象通过__proto__属性指向原型对象,所以使用这个属性之前,判断它是谁的实例化对象,对我们对原型链的理解会更好),在javascript中函数也是对象,但是你可以试试**Person.proto**指向的是啥(下面会追述)

  • constructor:指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数

有了👆的知识点,我们来分析模块一,首先f1是实例化对象,通过__proto__属性指向原型对象Foo.prototype,而Foo为构造函数通过prototype属性指向原型对象Foo.prototype,而原型对象通过constructor属性指向构造函数Foo,到这里我们的模块一就分析完了

原型链图模块二

还是老样子,再模块二图之前,我们来学习下新的知识点:

javascript有5种基础的内建对象(Fundamental Objects),Object、Function、Error、Symbol、Boolean,而Object/Function尤为特殊是定义其他内建对象或者普通对象和方法的基础

Object函数对象的属性

Object作为函数(构造函数),与普通函数一样,有length、prototype、proto、name属性

Object.prototype

Object.prototype的一个重要特性是,它是所有对象原型链的终点,因为Object.prototype.__proto__的值为null,即

Object.prototype.__proto__ === null
复制代码

一个对象的实例,沿着它的原型链,通过__proto__一层层往上找某一个属性,如果在Object.prototype上没找到,那就会返回undefined,所以,原型链不会无限的找下去

由👆的知识点,我们继续分析模块二,o1是Object实例化的对象,通过prototype属性指向Object.prototype原型对象,而这个原型对象通过__proto__属性指向Object.prototype.__proto__原型的原型,也就是原型链的最顶端(终点),这就是模块二图的分析

原型链图模块三

老样子还是介绍知识点再讲分析原型图:

抛砖引玉

在讲例子前,我们先来解决上述例子遗留的一个问题:

      function Person() {
        this.name = "vnues";
      }
      let person = new Person();
      console.log(Person.__proto__) // ƒ () { [native code] }
复制代码

效果图:

打印出来的是个函数对象,按照我们👆的知识点,prototype和__proto__指向的都是原型对象,也就是Person.__proto__指向的是一个函数也就是原型对象(但是原型对象并不一定是函数,就没有prototype属性),那么这个原型对象是谁?❗️就是Function.prototype。上述我们分析了Object对象,这次来分析下Function函数吧

Function的属性

在ES6标准中,Function 对象有两个属性:

  • length 值为1,这个属性的特性为{ [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true },即不可覆盖,不可被for...in遍历,但可以通过Object.defineProperty修改它的上面这些特性

  • prototype属性指向的是原型对象,(见ES最新标准说明 Function.prototpye)它跟一般函数的prototype的区别在于

    它不可写,不可配置,不可遍历。

    即它永远指向固定的一个对象,且是其他构造函数的原型对象,所有函数本身的__proto__指向它

Function.prototype

  • 函数和对象都有__proto__属性,指向构造函数的prototype属性所指向的对象,即它的原型对象。

  • 函数的__proto__属性(比如Person.proto)(❗️并非它的原型对象prototype上的__proto__属性)指向Function.prototype,所以Function.prototype上的属性和方法都会被函数对象(function object)所继承。

  • ❗️同时,因为函数对象(Function)本身有prototype属性,是Object的实例,所以也继承了Object.prototype的属性。

Function和Object

从👆的知识点我们知道Function内置函数(对象)是Object内置对象(函数)的构造实例(反过来说也行),而函数都是会继承Function.prototype上的属性和方法,我们知道Object即是内置对象又是函数,所以Object又会继承Function.prototype上的属性和方法

从👆的知识点我们就很容易了解这张原型链图了,我们从三条链来描述这张图:

  • 实例化对象o1通过__proto__属性指向Object.prototype原型对象,而再通过__proto__指向Object.prototype.__proto__对象

  • 内置对象Object(构造函数),你可以理解为实例化对象,通过__proto__属性指向Function.prototype原型对象

  • 回到Function.prototype原型对象(区分是对象还是函数,函数一定是对象,对象不一定是函数)这里,对象都具有__proto__属性,通过__proto__指Object.prototype,这是因为对象都会继承Object.prototype原型对象

到此我们的原型链已经分析完成了,理清楚分析实际是不会饶的

总结

原型链之所以会饶,是因为Object和Function的内置对象也是构造函数,它们之间的关系太复杂了,理清楚它们的关系对理解原型链会有帮助的

  • 原型链的形成是依靠__proto__这个链条形成的,也就是对象在原型上寻找属性是通过__proto__实现的,而prototype通过构造函数指向原型对象,所以原型链的形成跟prototype没关系的,是__proto__的这个链条,才会形成原型链

  • Obejct:对象(包括原型对象)都会继承Object.prototype原型对象

  • Function:构造函数都会继承我Function.prototype原型对象

  • 原型对象:原型对象也是对象,也是具有__proto__属性,去指向它们的原型

  • Function和Object两者的关系很绕,你可以说成Function内置函数(对象)是Object内置对象(函数)的构造实例(反过来说也行),这里不再追述

  • Function.prototype即是对象也是函数,Person.prototype不是函数

回过头看instanceof的实现最很容易,就是一直去原型链找原型,一直到null,找到一样的就返回true

关注下面的标签,发现更多相似文章
评论