关于JavaScript的原型

我们先来了解下面引用类型的四个规则:
1、引用类型,都具有对象特性,即可自由扩展属性。
2、引用类型,都有一个隐式原型 proto 属性,属性值是一个普通的对象。
3、引用类型,隐式原型 proto 的属性值指向它的构造函数的显式原型 prototype 属性值。
4、当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 proto(也就是它的构造函数的显式原型 prototype)中寻找。
引用类型:Object、Array、Function、Date、RegExp。这里我姑且称 proto 为隐式原型,没有官方中文叫法,大家都瞎叫居多。

规则一

引用类型,都具有对象特性,即可自由扩展属性:

  1. const obj = {}
  2. const arr = []
  3. const fn = function () {}
  4. obj.a = 1
  5. arr.a = 1
  6. fn.a = 1
  7. console.log(obj.a) // 1
  8. console.log(arr.a) // 1
  9. console.log(fn.a) // 1

规则二

引用类型,都有一个隐式原型 proto 属性,属性值是一个普通的对象:

规则三

引用类型,隐式原型 proto 的属性值指向它的构造函数的显式原型 prototype 属性值:

规则四

当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 proto(也就是它的构造函数的显式原型 prototype)中寻找:

  1. const obj = { a:1 }
  2. obj.toString
  3. // ƒ toString() { [native code] }

首先, obj 对象并没有 toString 属性,之所以能获取到 toString 属性,是遵循了第四条规则,从它的构造函数 Object 的 prototype 里去获取。
我试图想推翻上面的规则,看下面这段代码:

  1. function Person(name) {
  2. this.name = name
  3. return this // 其实这行可以不写,默认返回 this 对象
  4. }
  5. var nick = new Person("nick")
  6. nick.toString
  7. // ƒ toString() { [native code] }

按理说, nick 是 Person 构造函数生成的实例,而 Person 的 prototype 并没有 toString 方法,那么为什么, nick 能获取到 toString 方法?
这里就引出 原型链 的概念了, nick 实例先从自身出发检讨自己,发现并没有 toString 方法。找不到,就往上走,找 Person 构造函数的 prototype 属性,还是没找到。构造函数的 prototype 也是一个对象嘛,那对象的构造函数是 Object ,所以就找到了 Object.prototype 下的 toString 方法。
原型/继承 - 图1
上述寻找的过程就形成了原型链的概念,我理解的原型链就是这样一个过程。也不知道哪个人说过一句,JavaScript 里万物皆对象。从上述情况看来,好像是这么个理。🤔

关于JavaScript的原型链

用图片描述
原型/继承 - 图2

instanceof 运算符用于测试构造函数的 prototype 属性是否出现在对象原型链中的任何位置。 instanceof 的简易手写版,如下所示:

  1. // 变量R的原型 存在于 变量L的原型链上
  2. function instance_of (L, R) {
  3. // 验证如果为基本数据类型,就直接返回 false
  4. const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']
  5. if(baseType.includes(typeof(L))) { return false }
  6. let RP = R.prototype; // 取 R 的显示原型
  7. L = L.__proto__; // 取 L 的隐式原型
  8. while (true) {
  9. if (L === null) { // 找到最顶层
  10. return false;
  11. }
  12. if (L === RP) { // 严格相等
  13. return true;
  14. }
  15. L = L.__proto__; // 没找到继续向上一层原型链查找
  16. }
  17. }
  18. 作者:尼克陈
  19. 链接:https://juejin.cn/post/6934498361475072014
  20. 来源:稀土掘金
  21. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

我们再来看下面这段代码:

  1. function Foo(name) {
  2. this.name = name;
  3. }
  4. var f = new Foo('nick')
  5. f instanceof Foo // true
  6. f instanceof Object // true

上述代码判断流程大致如下:
1、 f instanceof Foo: f 的隐式原型 proto 和 Foo.prototype ,是相等的,所以返回 true 。
2、 f instanceof Object: f 的隐式原型 proto ,和 Object.prototype 不等,所以继续往上走。 f 的隐式原型 proto 指向 Foo.prototype ,所以继续用 Foo.prototype.proto 去对比 Object.prototype ,这会儿就相等了,因为 Foo.prototype 就是一个普通的对象。
再一次验证万物皆对象。。。。

总结

通过四个特性、一个例子、一张图片、一个方法,大家应该对原型和原型链的关系有了大概的认知。我的认知就是,原型链就是一个过程,原型是原型链这个过程中的一个单位,贯穿整个原型链。就好像你要是看完了不点个赞,我可以顺着网线找到你。
prototype是函数才有的属性,proto是每个对象都有的指针,指向它构造函数的原型。