JS高级 - 原型链

100 阅读5分钟

对象的原型

JavaScript当中每个对象都有一个特殊的内置隐藏属性 [[prototype]],这个特殊的对象可以指向另外一个对象

  • 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作
  • 这个操作会首先检查该对象是否有对应的属性,如果有的话就使用它
  • 如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性
  • 如果依旧没有,就会去原型对象的原型去进行查找
  • 。。。
  • 直到找到Object对象的原型,Object对象的原型的原型为null,所以其原型对象为顶层原型对象
  • 如果依旧没有找到就报错

获取原型的方式

获取方式说明
__proto__早期获取原型对象的方法
不是标准中的属性,所以可能存在一定兼容性问题
Object.getPrototypeOf 方法ES6后添加的,专门用于获取对象原型的方法,推荐使用
const user = {
  name: 'Klaus',
  age: 23
}

console.log(Object.getPrototypeOf(user))

函数的原型

所有的函数都有一个prototype的属性,这个属性是标准中的属性,一般被称之为函数原型对象或显示原型对象

同样函数作为对象,也有自己的对象原型__proto__,这个对象原型也被称之为隐式原型

当我们通过new操作符创建一个对象的时候,其会执行如下操作:

  1. 创建一个空对象
  2. 将构造函数的显示原型对象赋值给实例对象的隐式原型对象
  3. 将空对象赋值给this
  4. 执行构造函数代码
  5. 如果构造函数没有返回对象类型值的时候,会直接返回这个新创建的空对象

其中 第2步 操作 和 第3步操作 没有先后顺序,谁先谁后皆可

这也就意味着,所有的实例对象的隐式原型对象和构造函数的显示原型对象是同一个对象

此时如果我们有多个实例对象都具有的属性或方法的时候,可以将他们都放置到对应构造函数的显示原型对象上

这样由该构造函数所创建出来的所有实例对象,就可以复用这些共同的属性和方法

function User(name) {
  this.name = name
}

const user1 = new User('Klaus')
const user2 = new User('Alex')

console.log(user1.__proto__ === user2.__proto__) // => true
console.log(user1.__proto__ === User.prototype) // => true

所以简而言之

隐式原型对象,是为了形成原型链,从而便于对象查找对应的属性和方法

显示原型对象,是为了在创建实例对象的时候,对实例对象的隐式元素对象进行初始化操作

他们的共同作用是为了使一些所有实例对象共有的属性和方法可以保存到一个公共的对象上,从而实现代码的复用

constructor

原型对象上面是有一个属性的:constructor

默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象

function User(name) {
  this.name = name
}

console.log(User === User.prototype.constructor) // => true

重写原型对象

如果我们需要在原型上添加过多的属性,通常我们会重写整个原型对象

function User(name) {
  this.name = name
}

User.prototype = {
  running() {
    console.log('running')
  },

  eatting() {
    console.log('eatting')
  }
}

// 默认原型对象上,存在默认属性constructor
// 如果重写原型对象,那么就需要手动添加对应的constructor属性
// 原型对象的constructor属性是不可枚举的,可配置的,可写的,值指向对应的构造函数
Object.defineProperty(User.prototype, 'constructor', {
  configurable: true,
  writable: true,
  value: User
})

箭头函数的原型

在ES6之前,任何一个函数即可以作为构造函数使用new来进行调用,也可以作为普通函数使用小括号进行调用

但是自ES6开始,为了职责单一,将构造函数调用和普通函数调用进行分离

对于ES6的箭头函数,是没有显示原型对象的,因此箭头函数只能通过普通调用方式进行调用,不可以通过new关键字进行调用

而对应的ES6中的类,虽然其本质依旧是普通function定义的构造函数,但是ES6中的类只能通过new关键字进行调用,不能通过普通方式进行调用

原型链

从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取

而显示原型对象本质也是一个对象,所以原型对象也有自己的隐式原型对象

所以JS在查找对象的属性和方法的时候遵循如下的规则:

  1. 在自身上进行查找
  2. 去自身的原型对象上查找
  3. 去自身原型对象的原型对象上查找
  4. 子自身原型对象的原型对象的原型对象上查找
  5. 。。。
  6. 因为Object是所有类的父类,所以最后会在 Object.prototype上进行查找
  7. 如果在Object.prototype上依旧没有找到,就会去Object.prototype.__proto__去进行查找
  8. Object.prototype.__proto__的值为null,表示的是查找到了顶层,此时就会返回undefined

这样的查找过程,就被称之为原型链