看完这个你还不懂prototype、__proto__你来打我

189 阅读9分钟

如果你想彻底搞明白 原型prototype、隐式原型__proto__、函数的构造函数、构造该对象的构造函数、如何通过构造函数的原型来让每个构造函数创建出来的构造对象都拥有相同的属性和方法。等等这些,建议你复制我文中的html代码,自己动手并参考一下我的文章,你想不懂都难。但是前提是你得思考,你得操作

一上来不要就想着打我,一下子想要搞明白这个东西有点难度。我写这篇文章还用了半天呢,所以你就不要想几分钟就能搞明白这个东西了。这个文章底部手写防抖和节流的文章是一样的,都需要操作的。

首先,要明确几个点:

  1. 在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。注意这里的Function.prototype是叫做原型,叫做原型,原型。 即:对象具有属性__proto__,可称为隐式原型,

    一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

  2. 方法(Function) 方法这个特殊的对象,是对象也就会有__proto_这个属性,当然他还有自己特有的属性原型属性(prototype),原型属性是一个指针,它指向了一个对象,指向的这个对象的用途就是让构造函数new出来的的实例对象都可以共享的到我们所定义的属性和方法。我们把方法的原型属性(Function.prototype)的指针所指向的对象叫做原型对象,注意这里说的是Function.prototype(方法的原型属性)指向的对象是原型对象。

    原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

创建一个构造函数

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>06原型对象</title>
</head>
<body>
  <script>
    function Person (name) {
      this.name = name
    }
    let p1 = new Person('akangwu')
  </script>
</body>
</html>

上面的html代码创建了一个最普通的构造函数,我们就是基于上面的html代码来对原型、原型对象、构造函数等之间的关系。

对象

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>06原型对象</title>
</head>
<body>
  <script>
      function Person (name) {
        this.name = name
      }
      let p1 = new Person('akangwu')
      console.log(p1) // p1对象
      console.log(p1.__proto__) // p1对象的隐式原型。一个对象的隐式原型:
      console.log(Person.prototype) // 构造p1对象的构造函数的原型。构造该对象的构造函数的原型
      console.log(p1.__proto__ === Person.prototype) // 一个对象的隐式原型是否指向构造该对象的构造函数的原型
    </script>
</body>
</html>

开头我们说的这句话我们来解析一下:一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

一个对象:

就是我们上面的通过构造函数let p1 = new Person('akangwu')new出来的实例对象,就是我们所说的一个对象。看一下这个对象p1输出的是什么 console.log(p1)

上面的图就是p1的输出,从这里我们看到p1里面是有__proto__的,这是每个对象都存在的属性。

一个对象的隐式原型:

__proto__是隐式原型。这里是一个对象的隐式原型,是所有对象都有的属性。 那么一个对象的隐式原型就是p1.__proto__。我们来看一下p1.__proto__是什么 console.log(p1.__proto__)

构造该对象的构造函数:

这个就很容易理解了吧。p1对象是由Person这个构造函数来构建的。所以构造该对象的构造函数=Person

指向:

指向的意思就是想等的意思,所以后续验证:一个对象的隐式原型是不是指向构造该对象的构造函数的原型,就变成了验证:一个对象的隐式原型是不是等于构造该对象的构造函数的原型

构造该对象的构造函数的原型:

原型,开头说的prototype就是原型。那么构造函数的原型是什么呢?就是这样Function.prototype。在我们本例中就是Person.prototype,所以构造该对象的构造函数的原型=Person.prototype。我们来看一下这个输出的是什么 console.log(Person.prototype)

综上:一个对象的隐式原型指向构造该对象的构造函数的原型

就可以理解为:一个对象的隐式原型=构造该对象的构造函数的原型

我们来看一下是不是想等的 console.log(p1.__proto__ === Person.prototype)

最后我们通过一个图片来看一下,这个是之前我们所有的输出,从这里我们可以很容易看出关系。

一个对象的隐式原型指向构造该对象的构造函数的原型 是不是理解就稳了

一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

对象如何继承构造函数的原型的属性和方法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>06原型对象</title>
</head>
<body>
    <script>
      function Person (name) {
        this.name = name
      }
      let p1 = new Person('akangwu')
      console.log(p1) // p1对象
      console.log(p1.__proto__) // p1对象的隐式原型。一个对象的隐式原型:
      console.log(Person.prototype) // 构造p1对象的构造函数的原型。构造该对象的构造函数的原型
      console.log(p1.__proto__ === Person.prototype) // 一个对象的隐式原型是否指向构造该对象的构造函数的原型
    // 所以我们在构造函数的原型上面创建一个方法,通过构造函数所创建的实例对象也会用个这个方法
      Person.prototype.sayName = function() { // 我们这里通过原型在构造函数Person的原型上添加了一个sayName的方法,我们通过p1调用,会直接输出 akangwu
        console.log(this.name)
      }
      p1.sayName() // akangwu
    </script>

</body>
</html>

看一下下面这张图

我们可以看出来,当我们在Person的prototype中增加了一个sayName的方法,我们是直接调用p1的sayName(),就有结果了。但是我们看到p1里面并没有sayName的方法,他是在p1的__proto__中的。

所以**一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。**现在看是不是理解就更深刻了一点呢?因为p1.__proto__隐式原型的存在,保证了我们能够在Person的prototype上定义的方法可以在每个实例对象里面得到使用。

__proto__对象的隐式原型可能很多都叫对象的原型对象,也就是说隐式原型就叫做原型对象。

一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。就变成了一个对象的原型对象指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

我们再看点别的

后面的理解全部是基于这个代码来理解的。建议复制一下自己查看并参考本文理解一下。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>06原型对象</title>
</head>
<body>
    <script>
      function Person (name) {
        this.name = name
      }
      let p1 = new Person('akangwu1')
      let p2 = new Person('akangwu2')
      console.log(p1,'p1') // p1对象
      console.log(p1.__proto__,'p1.__proto__') // p1对象的隐式原型。一个对象的隐式原型:
      console.log(Person.prototype,'Person.prototype') // 构造p1对象的构造函数的原型。构造该对象的构造函数的原型
      console.log(p1.__proto__ === Person.prototype,'p1.__proto__ === Person.prototype') // 一个对象的隐式原型是否指向构造该对象的构造函数的原型


      console.log(p2,'p2') // p2对象
      console.log(p2.__proto__,'p2.__proto__') // p2对象的隐式原型。一个对象的隐式原型:
      console.log(Person.prototype,'Person.prototype') // 构造p2对象的构造函数的原型。构造该对象的构造函数的原型
      console.log(p2.__proto__ === Person.prototype,'p2.__proto__ === Person.prototype') // 一个对象的隐式原型是否指向构造该对象的构造函数的原型
    // 所以我们在构造函数的原型上面创建一个方法,通过构造函数所创建的实例对象也会用个这个方法
      Person.prototype.sayName = function() {
        console.log(this.name)
      }
      p1.sayName()
      p2.sayName()
    </script>

</body>
</html>

构造函数Person()

构造函数的原型属性Person.prototype指向了对象的原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是p1和p2)都可以共享这个方法。结合下面的图理解一下

原型对象Foo.prototype

Foo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。

实例

p1和p2是Person这个对象的两个实例,这两个对象p1和p2也有属性__proto__,指向构造函数的原型Person.prototype,这样子就可以像上面所说的访问原型对象的所有方法啦

另外:

如果你理解了上面的所有内容,下面的这些对你来说就是小菜了不是?

没有写后续的内容有两个:原因一后面的和前面的都是一样的道理,说多了也是废话;原因二主要是写这些就花了半天的时间,懒了,不太想写了。

构造函数Person()除了是方法,也是对象啊,它也有__proto__属性,指向谁呢? 指向它的构造函数的原型对象呗。函数的构造函数不就是Function嘛,因此这里的__proto__指向了Function.prototype。 其实除了Person(),Function(), Object()也是一样的道理。

原型对象也是对象啊,它的__proto__属性,又指向谁呢? 同理,指向它的构造函数的原型对象呗。这里是Object.prototype.

最后,Object.prototype__proto__属性指向null。

总结: 1.对象有属性__proto__,指向该对象的构造函数的原型。 2.方法除了有属性__proto__,还有属性prototype,prototype指向该方法的原型。

没有比这个更详细的手写防抖函数了吧

没有比这个更详细的手写节流函数了吧