开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言
跟着文章,动手画一画图,一定可以理解什么是构造函数(constructor
)、显式原型(prototype
)、隐式原型(__proto__
)、原型链
构造函数(constructor)
JavaScript语言使用构造函数(constructor)作为对象的模版,所谓“构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。
意义:使用对象字面量创建一系列同一类型的对象时,这些对象可能具有一些相似的特征(属性)和行为(方法),此时会产生很多重复的代码,把这些重复的特征(属性)和行为(方法)抽象出来,做成构造函数,可以实现代码复用。
使用:用new关键字来进行调用,一般首字母要大写,constructor
方法默认返回实例对象(即this
),完全可以指定返回另外一个对象。
创建一个构造函数
function Person(name){
this.name=name
}
const zhansanObj = new Person('张三')
const lisiObj = new Person('李四')
上面Person
就是zhansanObj
和lisiObj
的构造函数,zhansanObj
和lisiObj
是构造函数创建出来的函数对象[Function: Object] ,凡是通过 new Function()
创建的对象都是函数对象,其他的都是普通对象
注:Object
和Function
是内置的函数对象,JS自带的,这两是特殊的,后面会讲到
查看创建的对象
console.log(zhansanObj);
查看创建该对象的构造函数
console.log(zhansanObj.constructor);
// 得到下面这个函数
ƒ Person(name) {
this.name = name
}
注:对于引用类型来说 constructor
属性值是可以修改的,但是对于基本类型来说是只读的,这说明,依赖一个引用对象的 constructor 属性,并不是安全的。另外**null
和 undefined
是没有 constructor
属性的。**
函数也是对象
console.log(Person.constructor)
console.log(Function.constructor)
console.log(Object.constructor)
让我们来思考一下,这三个分别会打印什么?
- Person是一个声明函数,那这个函数有没有对应的构造函数呢?如果有是什么?
- 前面说到Function是js的内置函数对象,那这个函数对象有没有对应的构造函数呢?如果有是什么?
- Object也是js的内置函数对象,那这个函数对象有没有对应的构造函数呢?如果有是什么?
这三个打印出来的都是ƒ Function() { [native code] }
,也就是Function对象
- Person的构造函数是Function,说明函数是Function的实例对象,即
function Person(){ ... }
其实是var Person= new Function(...)
,另外let a = {}
其实是let a = new Object()
的语法糖,let a = []
其实是let a = new Array()
的语法糖 - Function的构造函数是它自己,说明Function是构造函数的源头
- Object的构造函数也是Function,说明对象由Function创建
画个图
所以constructor属性其实就是一个拿来保存自己构造函数引用的属性,没有其他特殊的地方。
注意: constructor属性不一定是对象本身的属性,这里只为方便理解将其泛化成对象本身属性,所以用虚线框,真正的constructor属性藏在哪呢,等会儿讲
显示原型(prototype)
所有的函数都有一个prototype
(显式原型)属性,属性值是一个普通对象。对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器的prototype
属性上,而非对象实例本身(所以每个实例能使用原型上的方法和属性而且不会占内存)
当我们创建一个函数时,prototype
属性就被自动创建了,还可以在prototype
对象上定义其他属性
function Person(name){
this.name=name
}
Person.prototype.sayHi = function sayHi(){
console.log("你好")
}
从上面的图,我们发现几点
- 原型上默认就有一个属性
constructor
,是不是就是前面图中虚线框中的constructor
? - 我们可以在原型上自定义属性和方法,这个会共享
- 构造函数
Person
的Person.prototype
指向原型,Person.prototype.constructor
指向构造函数Person
;如下图
这就是一个循环引用,即Person.prototype.constructor === Person
所以按照上面推断出的,我们前面画的图应该变成这样
看完图,所以虚线框的constructor
到底是啥?至少我们知道它和显式原型上的constructor
并不是一回事,继续往下看,弄明白虚线框的constructor
到底是啥
隐式原型(__proto__
)
每个实例对象都有一个隐式原型(__proto__
),它指向创建该对象的构造函数的原型,也就是指向构造函数的prototype
属性
function Person(name){
this.name=name
}
Person.prototype.sayHi = function sayHi(){
console.log("你好")
}
const zhansanObj = new Person('张三')
当new Person('张三')
时,__proto__
就被自动创建,打印看看
zhansanObj.__proto__等于Person.prototype
那么前面我们发的图就要再变一变
看完这张图,我们再想一下,zhansanObj.__proto__等于Person.prototype
,所以zhansanObj.__proto__.constructor
等于Person.prototype.constructor
等于构造函数Person
,所以虚线框里的constructor
就是zhansanObj.__proto__.constructor
,也就是Person.prototype.constructor
,套娃结束....
看完上面的图,有没有以下几个疑问?
-
之前我们虚线框里的
constructor
,使用zhansanObj.constructor
表示的,然后你又告诉我这个其实是zhansanObj.__proto__.constructor这不就说明zhansanObj.constructor
就是zhansanObj.__proto__.constructor
么?为什么?答:这是JS的内部操作了,当一个实例对象找不到某个属性的时候,JS就会去它的原型对象上去找是否有相关的共享属性或方法,虽然
zhansanObj
实例上没有constructor
属性,但是原型对象上有,所以可以直接使用。后面还涉及原型链的知识,等会看完你就懂了; -
prototype也是一个对象,这个对象上有没有
__proto__
答:是的,函数内的prototype其实就是一个普通的对象,并且默认都是
Object
对象的实例Person.prototype.__proto__.constructor
打印出ƒ Object() { [native code] }
,所以Person.prototype.__proto__.constructor===Object
或者Person.prototype.__proto__.constructor===Object.prototype.constructor
Person.prototype.__proto__.__proto__
打印出null
答完这两个疑问,我们的图又要变一变了
由图可知,
-
所有函数的
__proto__
指向他们的原型对象 -
最后一个prototype对象是Object函数内的prototype对象
Object
函数是所有对象通过原型链追溯到最根的构造函数 -
Object
函数的prototype中的__proto__
指向null如果沿着原型链寻找不到某一属性,这个null其实就是个跳出条件
接下来,看一下什么是原型链
原型链
每个对象都有一个原型对象,通过__proto__
指向上一个原型,并从中继承方法和属性,同时被指向的原型对象也可能有原型,这样一层一层,最终指向null,这种关系被称为原型链。根据定义,null
没有原型,并作为原型链中的最后一个环节。
接下来,用不同的箭头颜色,标注出原型链
- 第一条原型链:
zhansanObj.__proto__.__proto__.__proto__
- 第二条原型链:
lisiObj.__proto__.__proto__.__proto__
- 第三条原型链:
Person.__proto__.__proto__.__proto__
- 第四条原型链:
Function.__proto__.__proto__.__proto__
- 第五条原型链:
Object.__proto__.__proto__.__proto__
这些原型链最终都是指向null,看到这里应该就已经懂了原型链是什么了,通过__proto__
指针指向上一个原型,一层层,直到null。
当试图得到一个对象的属性时,先在这个对象本身找,找不到就去它的原型中找,找不到就继续往上找,直到找到匹配的属性或到达原型链尾部也没找到返回null
写个new
学完这一些,可以试着实现以下new操作符的功能,我们来分析一下new操作符做了什么
- new返回一个实例对象
- 实例对象的
__proto__
指向构造函数的原型 - this指向实例对象
function Person(name) {
this.name = name
}
function myNew(fn, ...args) {
const obj = {} // 创建一个实例对象
obj.__proto__ = fn.prototype //实例对象的`__proto__`指向构造函数的原型
fn.call(obj, ...args) // this指向实例对象
return obj // 返回一个实例对象
}
let wangwuObj = myNew(Person, '王五')
是不是再也不怕面试官让你手写实现new了
总结
- 构造函数,就是专门用来生成实例对象的函数,它就是对象的模板,描述实例对象的基本结构。
Object
和Function
是内置的函数对象,JS自带的,记住这两个特殊的家伙- 凡是通过
new Function()
创建的对象都是函数对象,其他的都是普通对象 - 所有的函数都有一个
prototype
(显式原型)属性,属性值是一个普通对象 - 每个对象都有一个隐式原型(
__proto__
),它指向创建该对象的构造函数的原型,也就是指向构造函数的prototype
属性 - 每个对象都有一个原型对象,通过
__proto__
指向上一个原型,并从中继承方法和属性,同时被指向的原型对象也可能有原型,这样一层一层,最终指向null,这种关系被称为原型链 - 当试图得到一个对象的属性时,先在这个对象本身找,找不到就去它的原型中找,找不到就继续往上找,直到找到匹配的属性或到达原型链尾部也没找到返回null,这就是原型链向上查找,
instanceof
的原理