[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操作的描述

  1. 创建一个空的简单JavaScript对象(即**{}**);
  2. 链接该对象(设置该对象的constructor)到另一个对象 ;
  3. 将步骤1新创建的对象作为**this**的上下文 ;
  4. 如果该函数没有返回对象,则返回**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()实现的步骤:

  1. 创建一个空的构造函数F
  2. F的prototype对象指向传入对象的__proto__
  3. 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原型链以及继承的知识,这一些我在之后的文章中也会慢慢补充

​ 文章中若有表述不当或是错误之处,还望指正。

posted @ 2021-05-11 21:45  是棕啊  阅读(599)  评论(0编辑  收藏  举报