(六) js中的原型与原型链
导言
函数也是对象, 所以当我们提到 函数或构造函数
时, 要能想到这一点
1. 原型 (prototype)
1.1 函数的prototype属性
-
每个函数都有一个
prototype属性
, 它默认指向一个空
, 的Object对象 (称为原型对象)注: 这里的
空
, 是说没有我们自定义的属性和方法 -
原型对象中有一个属性
constructor
, 它指向该原型对象的构造函数实例的构造函数属性(constructor)指向构造函数。
// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype); // {} 'object'
function fn() { }
console.log(fn.prototype, fn.prototype.constructor === fn); // {} true
// 默认指向一个Object空对象(没有我们的属性)
console.log(Function.prototype); // {}
// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Function.prototype.constructor === Function); // {} true
1.2 为原型对象添加属性
给原型对象添加属性, 作用是可以让所有的实例对象共享原型对象中的属性和方法
function Obj() { }
Obj.prototype.sing = function () {
console.log('i can sing');
}
const obj1 = new Obj()
const obj2 = new Obj()
obj1.sing() // i can sing
obj2.sing() // i can sing
对于构造函数而言, 每个自定义方法都要在每个实例里面重新创建一遍, 即: 每个Obj实例中都包含一个不同的Function实例, 但是这种方式会导致不同的作用域链和一些其他问题, 另外显而易见的是, 内存造成了浪费 !
而通过为原型对象添加属性的方式, 可以避免这一问题, 至于为什么,接着看完文章~
2.显式原型与隐式原型
2.1 prototype与__proto__
-
每个函数都有一个
prototype属性
, 即: 显式原型(属性) -
每个实例对象(对象)都有一个
__proto__属性
, 即: 隐式原型(属性)实例也是对象, 也就是说: 每个对象都有一个
__proto__
属性 -
实例对象的隐式原型的值为其对应的构造函数的显式原型的值
以上三句话要牢记
function Obj() { } // 内部语句: this.prototype = {}
// 1. 每个函数都有一个prototype属性, 即显式原型(属性)
console.log(Obj.prototype);
// 2. 每个实例对象都有一个__proto__属性, 即隐式原型(属性)
const obj = new Obj() // 内部语句: this.__proto__ = Obj.prototype
console.log(obj.);
// 3. 函数也是对象
console.log(Obj.__proto__);
// 4. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(obj.__proto__ === Obj.prototype); // true
简单示意图:
2.2 属性查找规则
当实例调用方法时, 现在自身属性中查找, 找不到则从__proto__中查找
function Persion() {
this.test = function () {
console.log('Persion test');
}
}
Persion.prototype.test = function () {
console.log('prototype test');
}
Persion.prototype.eat = function () {
console.log('prototype eat');
}
let p = new Persion()
p.test() // Persion test
p.eat() // prototype eat
3. 原型链
3.1 代码引入
先看几段简单的代码:
function Person(){ }
const p = new Person()
const obj = new Object()
我们分别解析这两行代码,然后引出原型链
的概念
先把上面几句重要的结论拿下来:
- 每个函数都有一个
prototype属性
, 它默认指向一个空
, 的Object对象 (称为原型对象) - 每个对象都有一个
__proto__属性
, 它指向构造函数的显式原型对象 - 函数也是对象, 因此每个函数都有
__proto__属性
- 对象的隐式原型的值为其对应的构造函数的显式原型的值
当我们分析的时候, 脑海里要有以上几个结论
3.2 function Person()
function是定义函数的一种方式, 因此Person是一个函数, 而在Js中, 以大写字母开头的函数被称为构造函数
,以此与普通函数做区分, 函数也是一个对象, 因此:
- Person的prototype属性指向Person的显式原型对象 (Person.prototype)
- Person的__proto__属性指向
Person的构造函数的显式原型对象
, 而所有的函数都是Function类型的实例, 因此 Person的__proto__
属性指向Function的显式原型对象 (Function.prototype) - 既然所有的函数都是Function类型的实例, 那么Function函数也是它本身的实例吗 ? 结果为true
- Function的显式原型对象的__proto__指向谁 ?
3.3 const obj = new Object()
-
obj这个实例对象的__proto__属性指向构造函数Object的显式原型 (Object.prototype)
-
既然Object()是一个构造函数 ,那么其__proto__属性(函数也是对象)指向Function的显式原型对象 (Function.prototype)
-
我们知道, 在JavaScript中,几乎所有的对象都是
Object
类型的实例(Object.prototype除外),也就意味着Object()是除Object.prototype外所有对象的构造函数, 这也就解决了上面的问题: Function的显式原型对象的__proto__指向Object的显式原型对象(Object.prototype) -
而显然,Object.prototype作为一个对象,必然有一个__proto__属性, 那这个
__proto__
属性指向谁? -
是null值
3.4 const p = new Person()
- 实例对象p的__proto__属性指向构造函数Person的显式原型对象 (Person.prototype)
- 同Object.prototype, Person.prototype也应有一个__proto__属性, 这个属性指向谁?
- 我们上面提到: Object()是除Object.prototype外所有对象的构造函数, 因此Person.prototype也应有一个__proto__属性指向Object的显式原型对象 (Object.prototype)
3.5 原型链
把上面的一连串分析,串联起来,就形成了所谓的 原型链
, 如图所示:
当创建函数时,JS会为这个函数自动添加prototype
属性,值是空对象 值是一个有构造函数的对象,不是空对象。