在 JavaScript 中,每个对象都具有一个隐式原型
__proto__
,这个原型还能有他自己的原型,以此类推,就会形成一条原型链。要理解 JavaScript 中的原型和原型链,清晰知道__proto__
、prototype
以及constructor
概念至关重要。
本文围绕以下代码进行原型及原型链讲解
// 函数
function Animal(name){
this.name = name
}
// 在Animal原型对象上加入 animalSay 方法
Animal.prototype.animalSay = function(say){
console.log(`${this.name}说:${say}`)
}
let cat = new Animal('小猫')
let dog = new Animal('小狗')
console.log(cat.__proto__)
概念
__proto__
隐式原型 ⬇
__proto__
是原型链查询实际用到的,它总是指向prototype
原型对象,也就是构造函数的原型对象。由构造函数创建的对象的属性,不是对象本身的属性。
在一般的打印中时常可以见到[[prototype]]
,它指向该对象的原型,与__proto__
的指向一致,__proto__
之所以叫 隐式原型,是因为它能间接访问对象的原型,而不需要显式地使用 Object.getPrototypeOf()
方法或直接访问 [[Prototype]]
属性。
prototype
显式原型(原型对象) ⬇
prototype
是函数独有的属性,它总是从一个函数指向一个对象,含义就是某个函数的原型对象。说白了就是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法,我们把这个的一个对象称作原型对象。
constructor
构造 ⬇
constructor
构造,它存在于原型对象中,每个函数都有一个原型对象,原型对象中又有个constructor
属性,指向创建函数的本身(谁构造的它就指向谁)。
图解关系
先看看Animal.prototype内部关系 ⬇
Animal.prototype内部存在__proto__,说明原型对象prototype也是经过某个函数构造出来的(后面可以知道是Object函数)
继续往深处探究,当前位置为Object.prototype中,对原型链有过了解的朋友可能已经知道,Object.prototype.__proto__ === null
是的没错,Object的原型对象位于原型链的顶部,它的__proto__
将指向null,所以在上图中打开__proto__
属性一看,理应是一个null,事实真是如此吗?。
好嘛,博主骗人…先别急,这里是通过打印 console.log(car.__proto__)
慢慢展开显示的结果。我们可以尝试直接打印console.log(cat.__proto__.__proto__)
或者console.log(Object.prototype)
,可看下图 ⬇
???这算什么事?直接打印和展开观察的结果不一致,人要晕了。其实这里跟ECMAScript 规范有关,本篇文章我们只需要确定 Object.prototype.__proto__ === null
就好了。另外感兴趣的朋友可以看看这篇文章,参杂一些个人推测,有问题的地方还望指出,篇幅不长 ➡ 看我胡说八道
总结
- 其实从图解关系的第一幅图可以发现,除了null,每个实例,每个原型对象,每个函数都有一个
__proto__
隐式原型属性,它总是指向一个原型对象prototype,就像文章开头说的一个个__proto__
将原型对象串联起来,就是一条原型链。 __proto__
是由构造函数创建出来的对象的属性,除了null,每个实例,每个原型对象,每个函数都有一个__proto__
隐式原型属性,这也侧面说明,每个实例,每个原型对象,每个函数都不是凭空出现的无中生有的。举个例子,看图一Object函数__proto__
指向Function的原型对象,说明Object这种 js 内部函数是由Function函数创造的,而Function函数中也有__proto__
,输出验证它指向自己的原型对象,创建它的是它自己,是不是有点悖论,但事实是如此。- 在图解关系部分,我也是着重介绍了图一左半部分的原型关系,还挖出个坑来,尝试打印以下,把图一的右半部分管理理顺,会对你对原型链的理解大有帮助。
原型链一般会运用到我们查找特定属性的时候,这个特定属性可以是普通的属性,也可以是数组啊,或是函数,或是方法,他就会依据
__proto__
去这个对象里面去找,源对象里面没找到,就会去源对象的原型对象里面去找,还没找到,就会去原型对象的原型对象去找,以此类推,这个操作被委托在整个原型链上,这个就是原型链了
拓展 new操作符
面试有可能要知道…
在上面原型链介绍的时候,就会有
new
这个概念,new这个操作符,就是给构造函数创建一个实例。函数有个特性,当函数返回一个 原始数据 的时候,这个返回值是没有作用的,但当这个返回值是 一个对象 的时候,这个返回值又会被正常使用。所以说,new
操作符在创建实例的时候就会有这方面的处理。
那么总的来说new做了什么工作呢?⬇
- 创建了一个实例
- 将实例的proto和构造函数的原型对象连接起来
- 将构造函数的this绑定到新实例中
- 根据构造函数返回类型作判断,如果是原始值就忽略,是对象就是引用
// 手写 new 操作符
/**
* @param func 构造函数
* @param ...args 其他参数,拓展运算符
*/
function myNew(func,...args){
// 创建了一个实例
const newObj = {}
// 将实例的proto和构造函数的原型对象连接起来
newObj.__proto__ = func.prototype
// 将构造函数的this绑定到新实例中
let result = func.apply(newObj,args)
// 根据构造函数返回类型作判断,如果是原始值就忽略,是对象就是引用
return result typeof Object ? result : newObj
}
文章有问题之处还望评论斧正!