likes
comments
collection
share

探究new和Object.create()在对象继承中的差异

作者站长头像
站长
· 阅读数 31

前言

在JavaScript中,创建对象和实现继承是核心概念之一,对于深入理解语言特性和高效编程至关重要。通过封装、继承和多态等机制来增强代码的复用性、可维护性和灵活性。JavaScript作为一种支持面向对象特性的脚本语言,提供了多种实现对象和继承的方式,其中new操作符和Object.create()方法尤为突出。

  • 使用new关键字:这是JavaScript中最直观、最常见的创建对象实例的方式。通过构造函数,可以初始化新对象并设置其属性和方法,同时自动将新对象的原型链指向构造函数的原型对象,从而实现继承。
  • Object.create()方法:这是一种更直接地基于原型链实现继承的方式。它允许我们创建一个新对象,并将其原型直接指定为传入的对象。这种方式绕过了构造函数,为原型继承提供了一种更为灵活和低级别的控制。

进入正题:

new关键字的深入解析:

要想知道new的继承方式,我们先要清楚new创建对象是什么工作原理,new操作符的工作原理可以概括为以下几个步骤:

1.创建新对象: 当使用new关键字调用一个构造函数时,JavaScript引擎首先会创建一个新的空对象。这个新对象的类型通常是Object,但实际类型由构造函数决定。这意味着,如果构造函数是某个特定类型的构造器(比如ArrayFunction),那么创建的新对象将是相应类型。

2.设置原型链: 新创建的对象的内部[[Prototype]]属性(也称为__proto__或可以间接通过Object.getPrototypeOf()访问)会被链接到构造函数的prototype属性所指向的对象。这样,新对象就可以继承构造函数原型上的所有属性和方法。

3.绑定this: 在构造函数内部,this关键字会被绑定到新创建的对象。这意味着,构造函数内部可以通过this来给新对象添加属性和方法,或者调用方法时,这些操作都是针对新对象进行的。

4.执行构造函数体: 在接着,v8引擎会执行构造函数的代码体。在执行过程中,构造函数可以修改新对象的属性,执行一些初始化逻辑等。例如构造函数身上所需要的参数将被放入赋值给新对象的属性。

5.返回新对象: 最后,如果构造函数没有显式返回一个对象(或者返回nullundefined),则new操作符会自动返回新创建的对象。但是,如果构造函数返回了一个非null对象,则这个对象将替代默认创建的新对象被返回。

new的代码基本实现:

function myNew(Constructor, ...args) {
  // 创建一个空对象
  const obj = {};
  //空对象原型指向Constructor的prototype
  obj.__proto__ = Constructor.prototype;

  // 将构造函数内部的this绑定到新创建的对象,并执行构造函数
  const result = Constructor.apply(obj, args);

  // 检查构造函数是否有返回值,并且返回值是一个对象(非null和undefined)
  // 如果是,返回这个对象;否则,返回我们最初创建的对象
  return typeof result === 'object' && result !== null ? result : obj;
}

Object.create()的深入解析

通过mdn的文档查阅[查看Object.create()方法的使用](Object.create() - JavaScript | MDN (mozilla.org))Object.create()可以放两个参数,第二个参数propertiesObjectObject.create()还会在这个新对象上定义指定的属性及其特性(如是否可枚举、是否可配置、是否可写等)。这些属性直接位于新对象上,而非其原型链上。所以这里我们不设计第二个参数的实现。 探究new和Object.create()在对象继承中的差异

1.创建新对象 Object.create()首先会创建一个新的空对象,这个对象最初没有任何自有属性。

2.设置原型链 然后,这个新对象的内部__Proto__属性被设置为proto参数所指向的对象。这意味着新对象将继承proto对象的所有属性和方法。

Object.create()的代码基本实现:

function simpleCreate(proto, props) {
  // 创建一个空对象
  const obj = {};

  // 设置新对象的原型为传入的proto
  if (typeof proto === 'object' || typeof proto === 'function') {
    obj.__proto__ = proto; 
  } else {
    throw new TypeError('Object prototype may only be an Object or null'); 
}

理解完成后,知道了差异吗?new在创建对象时使用到了指针this,它不仅仅创建一个新对象,它还会调用构造函数来初始化这个新对象。构造函数中的this会被绑定到新创建的对象,并且可以接收传递给构造函数的参数。而Object.create()不会执行任何构造函数。它只是简单地创建一个新对象并设置其原型,不执行其他额外的初始化逻辑。

这就出现了一个有趣的事情了:

当我们使用new来继承父类时

function Grand(){
  this.lastName='三'
}

    //Father继承Grand
    Father.prototype=new Grand()

function Father(){
  this.age=40
}

    //Child继承Father
    Child.prototype=new Father()

function Child(){
  this.like='sing'
}
let son=new Child()
console.log(son.age)//   打印 “40”
console.log(son.lastName)//  打印 “三”

当我们使用Object.create()来继承父类时

function Grand(){
  this.lastName='三'
}

    //Father继承Grand
    Father.prototype=Object.create(Grand.prototype)

function Father(){
  this.age=40
}

    //Child继承Father
    Child.prototype=Object.create(Father.prototype)

function Child(){
  this.like='sing'
}
let son=new Child()
console.log(son.age)//  打印 “undefined”
console.log(son.lastName)//  打印 “undefined”

因为当使用Father.prototype = new Grand()时,实际上执行了Grand构造函数,因此Father.prototype上会拥有由Grand实例化的属性(如lastName)。同理,Child.prototype = new Father()也会执行Father的构造函数,初始化了age属性。最终,通过new Child()创建实例时,Child构造函数会被调用,同时因为原型链的存在,实例可以访问到FatherGrand构造函数通过原型赋予的属性。

而当使用Father.prototype = Object.create(Grand.prototype)时,没有执行Grand构造函数,只是简单地将Father.prototype的原型链指向了Grand.prototype。因此,Father.prototype上不会自动拥有Grand构造函数初始化的属性。

总结

  • new: 适用于需要执行特定初始化逻辑,并且希望每个实例都有一份独立属性拷贝的情况。
  • Object.create(): 适用于主要关注原型继承,需要灵活控制原型链,且不需要执行构造函数逻辑的场景。
转载自:https://juejin.cn/post/7376491813169676325
评论
请登录