原型 原型链

442 阅读6分钟

写在前面的tips:

var obj ={} 相当于 var obj = new Object(); 不过在正常情况下,不会去这么定义一个对象,因为不仅麻烦,可读性也会变差。

Object、Function、Array、RegExp、Date、Boolean、Number、String都是 js 的内置函数

引1: 原型

引2: 原型

引3: 阮一峰 Javascript继承机制的设计思想

原型是什么

原型实际上就是一个普通对象,继承于 Object 类,由 JavaScript 自动创建并依附于每个函数身上。

任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。

原型在 JavaScript 对象系统中的位置和关系如图所示

image.png

Object 和 Function 是两个不同类型的构造函数,利用运算符 new 可以创建不同类型的实例对象。实例对象、类、Object 和 Function 之间的关系如图所示。

let obj = {}
let arr = []
function fn () {}
console.log(obj.prototype)
console.log(arr.prototype)
console.log(fn.prototype)
console.log(Object.prototype)
console.log(Array.prototype)
console.log(Function.prototype)

1630579080(1).png

要点

  • 它是函数( Object 是 js 内置函数)所独有的属性,从一个函数指向一个对象。
  • 它是函数的原型对象,也就是 用这个构造函数创建的实例 的原型对象。
  • 原型对象 prototype 有一个默认的 constructor 属性,指向构造函数本身,用于记录实例是由哪个构造函数创建。
  • 一个构造函数的prototype属性,就是这个构造函数制造(即new)的实例的原型对象。Function.prototype,就是所有函数的原型,因为通常函数都可以认为是通过new Function制造出来的。换句话说,Function.prototype上面承载了用于继承给所有函数的那些属性,例如:call、bind、apply等。
  • Function.prototype 是一个函数, 但是 Function.prototype.__proto__ === Object.prototype。(此处可佐证上文的 原型实际上就是一个普通对象,继承于 Object
  • Function.prototype 位于所有函数的原型链上,Function.prototype 又通过 __proto__ 指向 Object.prototype ,所以所有的函数既是 Function 的实例又是 Object 的实例,并分别从这两个内置函数继承了很多属性和方法。

访问原型对象

访问原型对象有 3 种方法,简单说明如下:

  • obj.__proto__ // obj 表示一个实例对象
  • obj.constructor.prototype
  • Object.getPrototypeOf(obj)

1630581843(1).png

__proto__是什么

上文提到了Function.prototype.proto === Object.prototype,那么,proto 是什么?

__proto__是浏览器提供出来便捷访问构造函数原型的属性。

let obj = {}
let arr = []
function fn () {}
console.log(obj.__proto__)
console.log(arr.__proto__)
console.log(fn.__proto__)
console.log(Object.__proto__)
console.log(Array.__proto__)
console.log(Function.__proto__)

1630579861(1).png

console.log(Object.__proto__ === Function.prototype)
console.log(Array.__proto__ === Function.prototype)
console.log(Function.__proto__ === Function.prototype)

可以看出Object.__proto__Array.__proto__Function.__proto__的值是一个函数,打印发现Object.__proto__ === Function.prototypeArray.__proto__ === Function.prototypeFunction.__proto__ === Function.prototype(此处和上文的Object、Array、Function都是js内置对象一致)。

1630580203(1).png

要点

  • 它是对象所独有的属性,从一个对象指向一个对象,指向它们的原型对象(也可以理解为父对象)。
  • 该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。
  • 它是一个私有属性,存在浏览器兼容性问题,以及缺乏非浏览器环境的支持。使用 obj.constructor.prototype 也存在一定风险,如果 obj 对象的 constructor 属性值被覆盖,则 obj.constructor.prototype 将会失效。因此,以确保Web浏览器的兼容性。为了更好的支持,比较安全的用法是使用 Object.getPrototypeOf(obj)(MDN)。

constructor是什么

constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数。

let obj = {}
let arr = []
function fn () {}
console.log(obj.constructor)
console.log(arr.constructor)
console.log(fn.constructor)
console.log(Object.constructor)
console.log(Array.constructor)
console.log(Function.constructor)

1630581054(1).png

要点

  • 每个对象都有构造函数,或者说 每个对象都可以找到其对应的constructor(本身拥有或继承而来,继承而来的要结合__proto__属性查看)
  • Function这个对象比较特殊,它的构造函数就是它自己(Function可以看成是一个函数,也可以是一个对象)
  • 所有函数和对象最终都是由Function构造函数得来,所以constructor属性的终点就是Function这个函数。

image.png

几个规则

  • 所有的引用类型,都具有对象特性,即可自由扩展属性(除了null以外)
  • 所有的引用类型,都有一个隐式原型 __proto__ 属性,属性值是一个普通的对象
  • 所有的引用类型,隐式原型 __proto__ 属性值指向它的构造函数的显式原型 prototype 属性值
  • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 __proto__(也就是它的构造函数的显式原型 prototype )中寻找

引用类型:数组,对象,函数,Date,RegExp

创建引用变量值

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

规则一

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

obj.a = 100;
console.log(obj) // {a: 100}
console.log(obj.a) // 100

arr.a =100;
console.log(arr) // [a: 100]
console.log(arr.a) // 100

fn.a = 100;
console.log(fn) // ƒ fn(){}
console.log(fn.a) // 100

除了null以外

console.log(typeof null) //  "object"
null.a // Uncaught TypeError: Cannot read property 'a' of null

规则二

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

console.log(obj.__proto__)
console.log(arr.__proto__)
console.log(fn.__proto__)

1630581421(1).png

规则三

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

// obj是由Object函数构建出的对象,由此可以证明 obj.__proto__ 指向的是构造它的函数的显式原型
console.log(obj.__proto__ === Object.prototype) // true

// arr由Array函数构建
console.log(arr.__proto__ === Array.prototype) // true

// fn由Function函数构建
console.log(fn.__proto__ === Function.prototype) // true

规则四

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

console.log(obj) // {a: 100}
console.log(Object.toString()) // "function Object() { [native code] }"
console.log(obj.toString()) // "[object Object]"

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

原型链

原型链是什么

原型与原型层层相链接的过程即为原型链。

原型链图解

image.png

原型链查找方式

step1

function Father (name) {
    this.name = name;
    this.ouputName = function () {
        console.log(this.name + ' is child 1')
    }
}

Father.prototype.ouputName = function () {
    console.log(this.name + ' is child 2')
}

Object.prototype.ouputName = function () {
    console.log(this.name + ' is child 3')
}

let child = new Father('明明')
child.ouputName() // 明明 is child 1
  • 首先查找child本身,发现了ouputName方法,执行,结束。

step2

function Father (name) {
    this.name = name;
}

Father.prototype.ouputName = function () {
    console.log(this.name + ' is child 2')
}

Object.prototype.ouputName = function () {
    console.log(this.name + ' is child 3')
}

let child = new Father('明明')
child.ouputName() // 明明 is child 2
  • 首先查找child本身,没有ouputName方法,往上查找
  • 因为childFather构造函数生成的实例,根据child.__proto__ === Father.prototype,就去Father构造函数原型对象prototype身上去查找ouputName这个方法,找到,执行,结束。

step3

function Father (name) {
    this.name = name;
}

Object.prototype.ouputName = function () {
    console.log(this.name + ' is child 3')
}

let child = new Father('明明')
child.ouputName() // 明明 is child 3
  • 首先查找child本身,没有ouputName方法,往上查找;
  • 因为childFather构造函数生成的实例,根据child.__proto__ === Father.prototype,就去Father构造函数原型对象prototype身上去查找ouputName这个方法,没有ouputName方法,往上查找;
  • 因为Father.prototype也是一个对象,该对象是Object构造函数生成的实例,根据Father.prototype.__proto__ === Object.prototype,就去Object原型对象prototype身上去查找ouputName这个方法,找到,执行,结束。

step4

function Father (name) {
    this.name = name;
}

let child = new Father('明明')
child.ouputName() // Uncaught TypeError: child.ouputName is not a function
  • 首先查找child本身,没有ouputName方法,往上查找;
  • 因为childFather构造函数生成的实例,根据child.__proto__ === Father.prototype,就去Father构造函数原型对象prototype身上去查找ouputName这个方法,没有ouputName方法,往上查找;
  • 因为Father.prototype也是一个对象,该对象是Object构造函数生成的实例,根据Father.prototype.__proto__ === Object.prototype,就去Object原型对象prototype身上去查找ouputName这个方法,没有ouputName方法,往上查找;
  • 因为Object.prototype.__proto__ === null,原型链指向null,此时报错,结束。