原型和它的原型链

1,504 阅读5分钟

原型(prototype)

每个函数都有一个自己的prototype属性,默认是一个object空对象(即:函数的原型对象)

image.png 可以看出此时的object空对象是指内部不存在我们自己添加的属性和方法。原型对象中存在constructor、__proto__属性。

原型对象中constructor指向的是它的函数对象。原型对象、函数对象关系如下:

image.png

原型添加方法

我们可以给原型对象添加属性或方法,通过实例对象可以直接访问。

function Fun() { }
Fun.prototype.test = function () {
  console.log('test');
}
let f = new Fun()
f.test()

tip: new 一个对象背后做了什么?
创建一个空对象;给对象设置__proto__,值为构造函数对象的prototype属性值(this.__proto__ =Fun.prototype);执行构造函数体,给对象添加属性或方法。

function _new(fn, ...args) {// 类   参数
  // let obj = Object.create(null)  // 创建一个空对象
  // obj.__proto__ = fn.prototype // 设置[[prototype]]的值为fn的原型
  let obj = Object.create(fn.prototype) // 等于上面两行
  const res = fn.call(obj, ...args) // 执行方法
  return obj.instanceof Object ? res : obj // 返回该对象
}

如果直接给函数原型添加方法,函数只能通过prototype访问,调用prototype无意义。

 function fun() { }
 fun.prototype.tick = function () {
   console.log('tick');
 }
fun.prototype.tick()

显式原型与隐式原型

显示原型:每个函数都有一个prototype属性。在定义函数时自动添加,默认为object空对象。
隐式原型:每个实例对象都有一个__proto__属性。在创建对象时自动添加,默认指向构造函数的prototype属性值。
所有函数的__proto__都是一样的(函数都是new Function的实例)。

function Fun() { }
let f1 = new Fun()
let f2 = new Fun()
console.log(f1.__proto__ === Fun.prototype); // true
console.log(f2.__proto__ === Fun.prototype); // true

Fun函数在定义时,会自动生成一个原型对象,f1、f2是Fun的实例。

image.png Fun函数通过prototype指向自身的原型对象,f1、f2实例通过隐式调用__proto__,指向的是自己构造函数的原型对象。三者其实指向的是同一个地址(原型对象)。

原型对象:相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象。

显式原型与隐式原型关系:

实例.__proto__ === 构造函数.prototype

函数、实例、object关系

首先定义一个函数,函数的prototype属性,指向函数的原型对象。函数的原型对象中的__proto__指向Object的原型对象。

function Fun() { }
let f1 = new Fun()
let f2 = new Fun()

执行上面这段代码,首先会创建一个Object,Object指向自己的函数对象。Object函数对象的prototype指向Object的原型对象。Object的原型对象中包含一些属性方法。比如我们常用的:hasOwnProperty、valueOf、toString等。且Object的原型对象的原型为null。

image.png

其次,创建函数时,自动创建函数原型对象。函数原型的__proto__指向Object的函数对象。

image.png

可以通过以下语句可以验证:

console.log(Fun.prototype.__proto__ == Object.prototype);
console.log(f1.__proto__.__proto__ == Object.prototype);

如果上图不能够理解,可以转化成下图:

image.png

原型链

定义:访问一个对象的属性时,先在自身属性中查找,找到返回;否则再沿着__proto__这条链向上找,找到返回;如果最终没找到,返回undefined

本质:隐式原型链

作用:查找对象的属性(方法)

基础原型链

function Fun() { }
Fun.prototype.test = function () {
  console.log('Fun 中的 test');
}
let f1 = new Fun()
f1.test = function () {
  console.log('f1 实例中的 test');
}
let f2 = new Fun()
f1.test() // f1 实例中的 test
f2.test() // Fun 中的 test
console.log(f1.test2); // undefined
console.log(f1.test2()); // TypeError: f1.test2 is not a function

image.png f1实例中存在test方法所以直接使用。
而f2实例中不存在test方法,所以通过原型链(__proto__)向上查找,f2的上一级Fun中存在test方法,所以输出:Fun 中的 test。
f1实例中不存在test2、test2(),对于test2属性输出undefined,对于test2()函数为TypeError。

构造函数、原型、实例对象关系

function fun(){}

上面一行代码就相等于先创建Object、Function,并实例化。再实例化fun函数。 开始加载:

引入Object

image.png

引入Function

image.png 至于为什么会多一条隐式原型?
那是因为:所有函数的隐式原型(__proto__)都是一样的(函数就相当于是Function的实例)。所以Function的函数对象的隐式原型指向自身的原型对象。就相当于

Function = new Function();
Function.__proto__ = Function.prototype

Object与Function

Object函数对象,本身就是一个函数。Object函数就相当于是Function的实例。

image.png

fun执行

image.png fun函数就相当于是Function的实例。添加fun的原型指向Function的原型。

image.png

关系加强版

console.log(Function.prototype instanceof Object); // true

Function原型是一个Object对象,就相当于是Object函数的实例。所以Function原型的隐式原型等于Object的显式原型。
fun函数原型同样是Object对象。
得到关系如下: image.png

总结:

  • 函数的显式原型指向的都是默认的Object空对象(原型内部只有constructor和__proto__)。函数的原型对象都是Object实例。但是Object的原型对象为null。Object的原型对象是原型链的尽头。

image.png

  • 所有函数的隐式原型(__proto__)都是一样的(函数都是new Function的实例),指向Function函数的原型。实例对象的隐式原型等于构造函数的显示原型。

image.png

原型链继承

基础原型链中讲解了,一个构造函数有多个实例,实例继承了原型上的方法属性。
下面是两个不同的构造函数之间的父子继承。

function Fun1() {
  this.tag = 'div1'
}
Fun1.prototype.test = function () {
  console.log('Fun1 中的 test');
}
function Fun2() { }
Fun2.prototype = new Fun1()
Fun2.prototype.constructor = Fun2
let f2 = new Fun2()
console.log(f2.tag);  // div1
f2.test();  // Fun1 中的 test

两个构造函数之间,Fun2构造函数将原型指向Fun1的实例。在调用Fun2时,Fun2构造函数会通过原型链找到Fun1的原型对象,从而获取原型中的属性或方法。

image.png

特别要注意的是:我们还需要将Fun2的原型对象指向自身构造函数。如果不指回自身,那么自身的原型属性或方法将失效

image.png

instanceof

定义:检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

A instanceof B

A:实例。看A的原型链(__proto__
B:构造函数。看B的 prototype 属性。 判断是否正确,只要跟据下图找A、B两个指向是否能够指向同一个。

image.png

function fun() { }
let f1 = new fun()
console.log(f1 instanceof Fun); // true
console.log(f1 instanceof Object); // true
console.log(Object instanceof Function); // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(Object instanceof fun); // false

Object instanceof Function。Object为实例,Function为构造函数

image.png

Object instanceof fun。Object为实例,fun为构造函数。 image.png