阅读 178
JS原型与原型链

JS原型与原型链

一、函数的prototype(原型)

Javascript没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承。这个属性包含一个对象(prototype对象),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。 实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。

  1. 每个函数都有一个prototype(显式原型)属性,在定义函数的时候添加,它默认指向一个Object空对象(prototype对象)。
  2. 每个实例对象都有一个__proto__(隐式原型)属性,在创建对象的时候添加,默认值为构造函数的prototype属性的值。
  3. 原型对象中有一个constructor属性,它指向函数对象。
  4. 函数的所有实例对象自动拥有原型中的属性(方法)
  5. 对象的隐式原型的值等于其对应的构造函数显式原型的值

下面通过例子来对显式原型和隐式原型加深理解:

//定义一个构造函数
function Fun() {

}
//创建一个实例对象
let f = new Fun();
console.log(Fun.prototype)
console.log(f.__proto__)
复制代码

结果如下图,自己创建的构造函数Fun在没有添加属性或者方法的情况下是一个空的Object对象。 Snipaste_2021-04-26_17-29-18.png

//Date为内置的构造函数
console.log(Date.prototype)
//创建一个Date的实例对象date
let date = new Date()
//date实例拥有原型(prototype)中的方法
console.log(date.getFullYear())//2021
console.log(date.__proto__ === Date.prototype) //true
console.log(Date.prototype.constructor === Date) //true
复制代码

结果如下图: Snipaste_2021-04-26_12-49-18.png Snipaste_2021-04-26_11-54-00.png 分析: Date是内置的构造函数,所以Date.prototype指向的不再是一个Object空对象,因为系统自动添加了很多供实例对象使用的方法,简单画了个图便于理解。 Snipaste_2021-04-26_12-45-28.png 简单回顾一下构造函数、原型对象和实例对象的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

二、原型链(隐式原型链)

接下来让我们再仔细分析下面这个例子:

function Fn() {
    this.text1 = function () {
        console.log('text1')
    }
}
Fn.prototype.text2 = function () {
    console.log('text2')
}
let f = new Fn();
f.text1() //text1
f.text2() //text2
console.log(f.toString()) //[object Object]
f.text3() //Uncaught TypeError: f.text3 is not a function
console.log(f.text3);//undefind
console.log(Object.prototype.__proto__) //null
复制代码

Snipaste_2021-04-26_18-14-38.png 当js脚本运行的时候,会自动创建一个Object函数对象并且自动添加一些方法。当函数调用属性或者方法的时候,首先会在自己的区域找,如果没有就会根据__proto__(隐式原型)往上面查找原型对象,直到找到Object函数对象的原型对象,因为这里面的__proro__ = null,所以Object函数对象的原型对象就是做原型链的尽头,如果最终没找到就会返回undefind,因为整个查找过程都只和隐式原型链有关,所以原型链又叫做隐式原型链。 知道了原型链的原理以后就不难分析上面的结果了。


进阶图

B781B6775C592F36162F2DF3867ACC8E.jpg 现在从上往下依次分为3个区域:

  1. f1、f2是通过自己设置的构造函数Foo()产生的实例对象,它们都有__proto__属性并且指向构造函数的原型对象(prototype),而构造函数Foo()的原型对象里面有一个constructor属性指向Foo()。Foo函数是Function的实例,所以函数都有两个属性__proto__ prototype所有函数的__proto__ 属性都相等并且等于Function的原型对象
function Foo() {

}
//上面的写法相当于 let Foo = new Function()
let f1 = new Foo()
let f2 = new Foo()
console.log(Foo.prototype.constructor === Foo)  //true
console.log(f1.__proto__ === Foo.prototype)  //true
console.log(f2.__proto__ === Foo.prototype)  //true
复制代码
  1. o1、o2是通过内置的构造函数Object()产生的实例对象,它们都有__proto__属性并且指向Object函数的原型对象(prototype),构造函数Object()的原型对象里面有一个constructor属性指向Foo(),注意这里的__proto__ = null
  2. 一个函数的显式原型和隐式原型相等说明这个函数是Function,Function()由系统提供,相当于Function = new Function()
  3. 可以看到Object()实例对象的隐式原型__proto__指向Function()的显式原型,前面我们说过:实例对象的隐式原型等于构造函数的显式原型,说明Object()是Function()的实例对象,任何函数都是通过new Function()产生的。
  4. 所有函数的显式原型值向的对象默认是空Object实例对象(但是Object不满足)。
  5. 所有函数都是Function()的实例(包含Function)
  6. Object的原型对象是原型链尽头
文章分类
前端
文章标签