[JavaScript] 搞懂Object.create()和new一个对象的过程
这是Js系列的第一篇文章,也是我的第一篇博文,之所以想写博客,旨在记录自己的学习,锻炼自己的表述能力和逻辑水平,同时帮助大家更好的理解相关的知识,这个系列会有几篇暂时还没确定,我会尽量讲的详细。
本文会讲述Object.create和new的原理,模拟实现,以及它们的区别
说到Object.create()和new,我们都知道他们可以用来创建一个对象,但是他们创建对象的过程其实是有区别的,在Js继承的多种方法中可见一斑,关于js的继承我在以后的文章中会专门讲到。
我们先来看看这两者的实际过程吧
一. new的原理及过程
首先,我们要知道new操作到底发生了什么
直观上看,new一个构造函数,会创建一个实例对象,该实例对象具有构造函数的内置对象(构造器属性)以及原型上的属性
举个例子:
function Parent(name,sex,age){
this.name = "zong";
this.sex = "boy";
this.age = 22
}
const child = new Parent('zong','boy','22');
console.log(child.name + ' is a ' + child.age + " years " + child.sex);//zong is a 22 years boy
在这其中会有一些隐性的步骤,我们把它们显现出来:
let Praent = function(name,sex,age){
//创建一个新的对象this
let this = {}
//将构造函数中的构造属性赋予this
this.name = name;
this.sex = age;
this.age =age;
//构造函数中没有手动返回对象,则默认返回this
return this;
}
上述过程中,主要描述了实例对象如何继承构造函数中的构造器属性,但对于原型上的属性,又是如何继承的呢?
我们来看一下MDN上对new操作的描述
- 创建一个空的简单JavaScript对象(即
**{}**
);- 链接该对象(设置该对象的constructor)到另一个对象 ;
- 将步骤1新创建的对象作为
**this**
的上下文 ;- 如果该函数没有返回对象,则返回
**this**
。
这里的第一步应该很好理解,结合我们的例子就是创建了一个新的对象,将他的引用赋给this
第二步链接对象的意思其实是将Parent.protptype的引用赋给实例对象的原型,(MDN的描述中涉及到原型链的知识,简单说一下:构造函数.prototype.constructor指向该构造函数)即:
child.__proto__ = Parent.prototype;
第三步:将我们创建的对象this和调用参数传给构造函数,执行
第四步:若构造函数没有手动返回对象,则默认返回this
二. new的模拟实现
好了,到这里我们就了解了new操作的整个过程,让我们来模拟实现一下吧
let newMethod = function(Parent,...rest){
let this = Object.create(Parent.prototype);
let result = Parent.apply(this,rest);
return typeof result === 'Object' ? result : this;
}
三. Object.create()的原理及过程
说完了new操作,再来说说Object.create()方法,Object.create()方法也是创建一个新的对象,使用传入的现有对象作为新建对象的__proto__属性的引用
即实现了:
var Parent = {
name:"Zong"
};
let child = Object.create(Parent);
console.log(child.name)//Zong
//这里的"Zong"其实不是child自带的name属性,而是child顺着原型链找到的,child.__proto__ => Parent
Object.create()实现的步骤:
- 创建一个空的构造函数F
- F的prototype对象指向传入对象的__proto__
- new一个F的实例并返回(new的作用我们在上面已经说明了,实际上是让实例的__proto__指向F的prototype)(还不理解的话,可以想成是实现了继承,创建的对象是以传入的对象为原型继承得来的)
四. Object.create()模拟实现
Object.create = function(proto){
let F = function() {};
F.prototype = proto;
return new F();
}
五. Object.create()和new操作的区别
先说结论吧:
- Object.creat()创建一个新的实例对象,该实例的实例原型指向Object.create()接收的参数本身
- new 构造函数(),这个操作创建一个新的实例对象,该实例的实例原型指向构造函数的prototype
知道了结论之后我们再来体会他们之间的区别吧
我们来看一个例子:
var Parent = function(){
this.name = "Zong"
}
var Parent2 = {
name:"zong"
};
let child1 = new Parent();
let child2 = Object.create(Parent);
let child3 = Object.create(Parent2);
console.log(child1);//Parent {name: "Zong"}
console.log(child2);//Function {}
console.log(child3);//{},这里返回了一个对象但这个对象的__proto__.name为"Zong"
在这个例子中我们可以看到child1这个实例对象继承了Praent构造函数中的属性name,和Parent.prototype
child2这个对象是由Object.create()创建的,而Object.create(Parent)传入的是一个函数Parent,也就是说最终返回的对象child2的__proto__指向的是Parent这个函数,而不是Parent.prototype这个本应该是实例原型的东西,顺着原型链chile2.proto => Parent => Parent.proto => Function.prototype => Function.prototype.proto => Object.prototype.proto => null
child3也是由Object.create()创建的,与child2区别在于Object.create(Parent2)传入的是一个对象,因此child3.__proto__指向这个对象,所以打印child3时顺着原型链可以找到name这个属性
从上面的例子中,我们可以看到Object.create(Base),如果Base是一个构造函数,采用这种方法创建对象没有意义,如果Base是一个实例对象或者字面量,那么相当于实现了对象的浅拷贝
这里还要提一点,我们前面在模拟Object.create()时新建了一个构造函数F,并将它的prototype指向传入的参数可以理解为F.prototype是一个对象,由于它指向传入的参数(这里的参数也是一个对象),因此F.prototype.constructor指向的是Object(),但是我们需要的是F.prototype.constructor指向F本身,因此我们还应手动改变F.prototype.constructor
代码实现如下:
Object.create = function(proto){
var F = function() {};
F.prototype = proto;
F.prototype.constructor = F;//手动将F赋值给F.prototype.constructor
return new F();
}
好了,这篇文章就写到这里,可以看到要想理解清楚其中的原理,需要掌握js原型链以及继承的知识,这一些我在之后的文章中也会慢慢补充
文章中若有表述不当或是错误之处,还望指正。