回望Javascript:原型与原型链

1.原型对象上的constructor指向哪里

每个函数都有一个prototype属性,这是一个引用,指向了函数的原型对象,每个原型对象都有一个constructor属性,这个属性指向函数对象本身。

验证一下:

  function Fun(params) {}
  console.log(Fun.prototype.constructor==Fun)//true
  var a=new Fun();
  console.log(a.__proto__.constructor===Fun)//true

2.显示原型与隐式原型

首先我们需要清楚原型对象的作用:定义所有对象实例所共享的属性和方法。

  • 每个函数function都有一个一个prototype,即显式属性(属性),它只是一个引用,指向了原型对象

  • 每个实例对象都有一个__proto__,称为隐式原型,它也是一个引用。最终也指向原型对象

实例对象上的隐式原型的值是和实例对应的构造函数prototype的是值相同的,也就是说,这两个不同的引用变量属性指向同一块地址。都指向原型对象

也就是说再下面的栗子中:控制台输出的结果是true

  function Fun(params) {
  }
  var a=new Fun();
  console.log(a.__proto__===Fun.prototype)

我们可以理解为

  • 在new的时候执行了这条语句:this.__proto__=Fun.prototype

它在内存中是这样的:

image-20210602174730840.png

3.谈一谈原型与原型链

以下的所有概念都是自己理解的,如果有什么错误请指出。

原型:在我们JavaScript中的每一个函数,都有一个prototype属性,这个属性保存了一个引用,指向内存中的一个空的Object对象,这个对象其实就是我们这个函数的原型对象,它的作用一般就是用来共享属性和变量。怎么样共享?那肯定就是要使用到实例对象的概念,我们实例化这个函数,此时这个函数也叫做构造方法,我们实例化多个实例对象,那么我们这些实例就都可以访问到我们原型对象上的属性或者变量。

首先要知道两个概念:隐式原型(__proto__)和显式原型(prototype)。隐式原型是实例对象具有的属性,而显式原型是函数具才有的。但是相同的是它们都是一个引用,并别都指向了同一块内存,就是我们的原型对象。

原型链:原型链为什么要叫做原型链?只是因为它的执行是一个链式的调用顺序。总结为一句话实际上就是沿着隐式原型寻找属性或者方法,所以也叫隐式原型链。当我们使用实例方法来执行一个属性或者方法时,现在假设我们想要的属性和方法都在原型对象中,则JavaScript内存中的执行顺序是,先通过当前实例的__proto__引用进行到构造函数的原型对象中,然后再此对象中寻找该属性或者方法,如果此方法中没有我们要找的属性或方法,就继续在当前原型对象中的__proto__属性中进入到__prote__所引用的内存中,也就是Object函数的原型对象(因为我们前面说到了,每个函数的prototype属性其实都是指向了一个空的Object实例,因此这个实例中肯定就有了指向Object原型对象的引用__proto__)因此我们再继续在Object的原型对象中寻找我们需要的属性或方法。如果这里依然没有,那么就进行一些错误的处理。因为Object.prototy所指向的原型对象中的__proto__值为空。

接下来我们可以写一些代码来分析一下它在内存中的状态:

function Fn() {
    this.test1=function(){
      console.log('test()')
    }
    Fn.prototype.test2=function(){
      console.log('test2()')
    }
  }
  let fn=new Fn();
  fn.test1();
  fn.test2();
  console.log(fn.toString())
  console.log(fn.test3)

在内存中的图大概就是这样的
image-20210603155306347.png

  • fn.test1(),实例对象中有这个方法,所以直接直接执行
  • fn.test2(),首先再示例对象中查找,发现没有,然后在通过__proto__在原型对象中查找,发现有这个属性。执行
  • fn.toString(),按照上一个步骤,找到原型对象发现并没有这个属性,则在__proto__指向的原型对象中查找,发现,执行
  • fn.test3,找到Object的原型对象后,发现依然没有,则进入Object的隐式原型,这里就已经到了尽头,因此为undefined

看了上面这么多,其实我们还可以总结出两个注意事项:

  • **Object的原型对象不是Object的示例对象:**关于函数的显示原型默认是指向一个一个Object的实例对象。但是有一个特殊的地方就是Object自身的Prototype属性指向的实例对象,因为它的隐式原型属性是指向null的。
  • 原型链的尽头就是Object的原型对象,因为它的隐式原型为null

4.Function,Object与原型的关系

Function是一个相对特殊的函数,因为所有的函数都是Function的示例,所以每一个函数的隐式原型都指向Function函数的原型对象。

下面这张图是一个非常经典的图。从张图中我们可以看出以下这些点:

  • 所有函数都有两个属性,显式原型(指向Object的示例对象)和隐式原型(Function的原型对象)
  • Function函数实际是实例化自身的产物,因此它的隐式原型执行,因此才会有它自身的隐式原型和显式原型相等(这是一个特殊的情况)
  • Object函数是Function的示例,因此有了图中的Object.__Proto__指向了Function.protype的示例,实际上这个结果我们也可以看出来,因为所有的函数的都是Function的示例,Object也不例外

1615475711487-c474af95-b5e0-4778-a90b-9484208d724d.png

5.原型链指向题目

既然你已经看到这里了不如做几道题吧:

p.__proto__  // Person.prototype
Person.prototype.__proto__  // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p.__proto__.constructor // Person
Person.prototype.constructor  // Person

6.探索instanceOf(手写instanceof)

A instance B

首先先需要清楚instance是干什么的:如果B函数的显式原型对象在A对象的原型链上则返回true

面试官:小伙子,手写一个instanceof吧!

好的!

  function instancof(left, right) {
    let leftp = left.__proto__;//拿到left的隐式原型
    let rightp = right.prototype;//拿到right的显式原型对象的引用
    //找就完了
    while (true) {
      if (leftp == null) {
        false
      }
      if (leftp == rightp) {
        return
        true
      }
      leftp = leftp.__proto__
    }
  }

其实手写一个instance并不算什么难点,下面我们在来出几道题来刺激刺激你:

  console.log(Object instanceof Function)//true
  console.log(Object instanceof Object)//true
  console.log(Function instanceof Function)//true
  console.log(Function instanceof Object)//true
  function Foo(params) { }
  console.log(Object instanceof Foo);//false

是不是感到头大,其实这个结合上面的图很容易得出答案,只要我们谨记,instance的原理是判断后面的函数对象的原型对象是否在前面的示例对象的原型链上的,这样我们就可以很容易的写出原理和判断结果了。

7.两道面试题

第一道:

  function A() {}
  A.prototype.n=1;
  var b=new A();
  A.prototype={
    n:2,
    m:3
  }
  var c=new A();
  console.log(b.n,b.m,c.n,c.m)

分析一下:大概意思是先给A的原型对象上放了一个n属性,然后实例化对象,那么顾名思义现在这个实例化的对象的隐式原型是指向这个原型对象的;下一步A给它的函数对象重新开辟了一个原型对象,再赋两个属性并且实例化,此时的这个实例化指向的是这个原型对象,因此我们这里的两个示例对象是指向两个不同的原型对象

因此输出结果是:

1 undefined 2 3

第二道:

function F() { }
  Object.prototype.a = function () {
    console.log('a')
  }
  Function.prototype.b = function () {
    console.log('b')
  }
  var f = new F();
  f.a();
  f.b();
  F.a();
  F.b();

分析:首先第一个是给Object的原型对象上加了一个方法,其实这就和toString()哪一类方法是同类的;第二个给Function的原型对象上加了一个方法

f.a():f实例对象在它的原型链上找对应方法,一直找到了Object中,因此正常执行
f.b(): f实例对象直到找至原型链的重点Object.prototype.__proto__也没有找到,因此报错
F.a(): 通过F实例对象的隐式原型链找,顺序依次是Function.prototype.__proto__->Object.prototype.a()
F.b(): 每一个函数都是Function函数的实例对象,所以F作为实例对象有自己的原型链,它的隐式原型指向Function的显示原型所指的原型对象中

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值