JavaScript的原型、原型链(学习笔记)

161 阅读3分钟

四个规则

  1. 引用类型,都具有对象特征,即(可自由扩展属性);
  2. 引用类型,都有一个隐式原型__proto__属性,属性值是一个普通的对象;
  3. 引用类型,隐式原型__proto__的属性值指向它的构造函数的显式原型prototype属性值;
  4. 当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型__proto__(也就是它的构造用函数的显示原型prototype)中寻找;

引用类型:Object,Array,Function,Date,RegExp,这里的__proto__我们称为隐式原型,没有官方的中文统一叫法;

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

const obj = {}
const arr = []
const fn = function() {}

obj.a = 1
arr.a = 1
fn.a = 1

console.log(obj.a) // 1
console.log(arr.a) // 1
console.log(fn.a) // 1

这个规则很好理解,Date和RegExp也一样的,就不再说明了;

规则二:引用类型,都有一个隐式原型__proto__属性,属性值时一个普通的对象;

const obj = {}
const arr = []
const fn = function() {}

console.log('obj.__proto__', obj.__proto__) // 
console.log('arr.__proto__', arr.__proto__)
console.log('fn.__proto__', fn.__proto__)

规则三:引用类型,隐式原型__proto__的属性值指向它的构造函数的显示原型prototype属性值;

const obj = {}
const arr = []
const fn = function() {}

console.log('obj.__proto__ === Object.prototype', obj.__proto__ === Object.prototype) // true
console.log('arr.__proto__ === Array.prototype', arr.__proto__ === Array.prototype) // true
console.log('fn.__proto__ === Function.prototype', fn.__proto__ === Function.prototype) // true

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

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

首先,obj对象上并没有 toString属性,之所以能获取到toString属性,是遵循了第四条规则,从它的构造函数Object的prototype中获取;


一个特例

  • 我试图想推翻上面的规则,看下面这段代码:
function Person(name) {
   this.name = name
   return this // 其实这里可以不写的,默认返回this对象
}
var nick = new Person('nick')
console.log(nick.toString) // ƒ toString() { [native code] }

按理说,nick是Person构造函数生成的实例,而Person的prototype并没有toString方法,那么为什么,nick能获取到toString方法呢?

这里就引出原型链的概念了,nick实例先从自身出发寻找,发现并没有toString方法。找不到,就往上找,找到它的构造函数的prototype中,还是没找到,构造函数的prototype也是一个对象,所以就找到它的__proto__,也就是对象的构造函Object的原型,所以就找到了Object.prototype下的toString方法;


一张图片

  • 用图片描述原型链:

最后一个是 null , 设计上为了避免死循环而设置的,Object.prototype的隐式原型指向的是 null


一个方法

  • instanceof运算符:用于测试构造函数的Prototype属性是否出现在对象原型链中的任何位置。

instanceof的简易手写板,如下所示:

function instance_of(L, R) {
      // 验证如果为几百数据类型,就直接返回false
      const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']
      if (baseType.includes(typeof(L))) { return false }

      let RP = R.prototype // 取R的显示原型
      L = L.__proto__ // 取L的隐式原型

      while(true) {
          if (L === null) { // 找到最顶层
              return false
          }
          if(L === RP) { // 严格相等
              return true
          }
          L = L.__proto__ // 没找到继续向上一层原型链查找
      }
  }

我们再来看下面这段代码

function Foo(name) {
      this.name = name
  }
  var f = new Foo('nick')
  console.log(f instanceof Foo) // true
  console.log(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就是一个普通对象;