资讯专栏INFORMATION COLUMN

JavaScript进阶之模拟new Object过程

chengtao1633 / 681人阅读

摘要:后续我将推出进阶系列,一方面是一个监督自己学习的一个过程,另一方面也会给看到的童鞋一些启发。第二步链接到原型中现在把构造函数和参数都打印出来了。

原文:https://zhehuaxuan.github.io/...  
作者:zhehuaxuan
写在前面的话

前端的入门相对简单,相对于其他方向天花板可能会相对较低。但是在市场上一个优秀的前端依旧是很抢手的。能够站在金字塔上的人往往寥寥无几。

目前前端也已经一年半了,在公司的知识栈相对落后,就业形势不容乐观,所以有必要自己琢磨,往中高级前端进阶。后续我将推出《JavaScript进阶系列》,一方面是一个监督自己学习的一个过程,另一方面也会给看到的童鞋一些启发。

JavaScript新建对象的过程

在ES5中定义一个函数来创建对象,如下:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    return name;
}
var person = new Person("xuan");
console.log(person.name);//输出:xuan
console.log(person.getName());//输出:xuan

我们看到当我们新建一个对象,我们就可以访问构造器中的指向this的属性,还可以访问原型中的属性。我们不妨把JavaScript调用new的过程主要由下面四步组成:

新生成一个空对象

将空对象链接到原型中

绑定this

返回新对象

下面跟着我按照这个思路来创建对象:

function create(){
    //Todo
}
person = create(Person,"xuan");//create(ObjectName,...arguments)

我们使用如上所示的函数来模拟new关键字。

首先第一步新建一个对象:

function create(){
    var obj = new Object();
    return obj;
}
person = create(Person,"xuan");

现在已经创建并返回一个对象,当然现在打印出来肯定是一个普通的对象,毕竟流程还没有走完,我们接着往下看。

第二步链接到原型中:

function create(){
    var obj = new Object();
    var constructor = [].shift.call(arguments);
    console.log(constructor);
    console.log(arguments);
    obj.__proto__ = constructor.prototype;
    return obj;
}

person = create(Person,"xuan");

现在把构造函数和参数都打印出来了。没问题!

第三步绑定this,如下:

function create() {
  let obj = new Object();
  let constructor = [].shift.call(arguments)
  obj.__proto__ = constructor.prototype
  constructor.apply(obj, arguments);
  console.log(obj);  
  return obj;
}
person = create(Person,"xuan");

打印结果实现new对象的效果。

现在改一下构造函数代码:

function Person(name){
    this.name = name;
    return {
        name:"abc"
    }
}
var person = new Person("xuan");
console.log(person);
console.log(Object.prototype.toString.call(person));

效果如下:


我们执行一下我们构建的函数效果如下:

发现不一致,所以我们要处理第三步绑定this中apply函数的返回值:

function create() {
  let obj = new Object();
  let constructor = [].shift.call(arguments)
  obj.__proto__ = constructor.prototype
  //constructor.apply(obj, arguments);
  let res = constructor.apply(obj, arguments);
  if(res){
     return res;
  }else{
     return obj;
  }
}
person = create(Person,"xuan");

效果如下:


完美!

现在我们思考一下这里的res返回值有三种情况:undefined,基本类型,对象。

如果res是undefined时,返回obj;

如果res是基本类型我们也返回obj;

如果res是对象我们返回res对象;

综合一下:

如果返回的res对象是Object类型那么返回res,否则返回obj。当然其他的判断条件也是可以的。最后代码优化如下:

function create() {
  let obj = new Object();
  let constructor = [].shift.call(arguments)
  obj.__proto__ = constructor.prototype
  //constructor.apply(obj, arguments);
  let res = constructor.apply(obj, arguments);
  return res instanceof Object?res:obj;
}
person = create(Person,"xuan");
几个问题

现在的代码已经完美了么?我们先来提几个问题。

new Object()创建的对象纯净么?

为啥使用[].shift.call()来进行参数分割?arguments是一个数组么?

new Object()创建的对象纯净么?

首先什么是纯净?我们定义一个对象的__proto__属性为空的对象是一个纯净的对象。

在第二步的时候中已经改变的obj的原型链,所以无论它前面的原型链是咋样的都无所谓,但是为了保证对象的纯净性,我们有必要引出Object.create(),该方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。我们来看一下:

var person1 = Object.create({});

打印如下:

我们看到person1的__proto__指向了{}对象,所以我们在上述代码中直接修改如下:

function create() {
  let constructor = [].shift.call(arguments);
  let obj = Object.create(constructor.prototype);
  let res = constructor.apply(obj, arguments);
  return res instanceof Object?res:obj;
}
person = create(Person,"xuan");
为啥使用[].shift.call()来进行参数分割?arguments是一个数组么?

首先我们知道arguments是函数传入的参数,那么这个参数是数组么?我们打印一下便知:

console.log(arguments);
console.log(Object.prototype.toString.call(arguments));
console.log(arguments instanceof Array);

结果如下

不是数组。我们展开发现他跟数组很像,查一下资料发现这个对象是类数组。里面没有shift函数,直接调用shift会报错。我们使用使用Array.from(arguments)将arguments转成数组,然后在调用shift函数也是一种思路。但是在这里我们使用apply最适合。所以下述代码是模拟new Object()的最优代码:

function create() {
  let constructor = [].shift.call(arguments);
  let obj = Object.create(constructor.prototype);
  let res = constructor.apply(obj, arguments);
  return res instanceof Object?res:obj;
}
person = create(Person,"xuan");

还有更优的实现方法,请大佬们不吝拍砖!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102538.html

相关文章

  • 进阶3-5期】深度解析 new 原理及模拟实现

    摘要:使用指定的参数调用构造函数,并将绑定到新创建的对象。由构造函数返回的对象就是表达式的结果。情况返回以外的基本类型实例中只能访问到构造函数中的属性,和情况完全相反,结果相当于没有返回值。 定义 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。 ——(来自于MDN) 举个栗子 function Car(color) { this.color = co...

    Baaaan 评论0 收藏0
  • 进阶3-3期】深度解析 call 和 apply 原理、使用场景及实现

    摘要:之前文章详细介绍了的使用,不了解的查看进阶期。不同的引擎有不同的限制,核心限制在,有些引擎会抛出异常,有些不抛出异常但丢失多余参数。存储的对象能动态增多和减少,并且可以存储任何值。这边采用方法来实现,拼成一个函数。 之前文章详细介绍了 this 的使用,不了解的查看【进阶3-1期】。 call() 和 apply() call() 方法调用一个函数, 其具有一个指定的 this 值和分...

    godlong_X 评论0 收藏0
  • 进阶3-4期】深度解析bind原理、使用场景及模拟实现

    摘要:返回的绑定函数也能使用操作符创建对象这种行为就像把原函数当成构造器,提供的值被忽略,同时调用时的参数被提供给模拟函数。 bind() bind() 方法会创建一个新函数,当这个新函数被调用时,它的 this 值是传递给 bind() 的第一个参数,传入bind方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。bind返回的绑定函数也能使用 n...

    guyan0319 评论0 收藏0
  • 进阶4-3期】面试题如何实现一个深拷贝

    摘要:今天这篇文章我们来看看一道必会面试题,即如何实现一个深拷贝。木易杨注意这里使用上面测试用例测试一下一个简单的深拷贝就完成了,但是这个实现还存在很多问题。 引言 上篇文章详细介绍了浅拷贝 Object.assign,并对其进行了模拟实现,在实现的过程中,介绍了很多基础知识。今天这篇文章我们来看看一道必会面试题,即如何实现一个深拷贝。本文会详细介绍对象、数组、循环引用、引用丢失、Symbo...

    longmon 评论0 收藏0
  • 进阶 6-2 期】深入高阶函数应用柯里化

    摘要:引言上一节介绍了高阶函数的定义,并结合实例说明了使用高阶函数和不使用高阶函数的情况。我们期望函数输出,但是实际上调用柯里化函数时,所以调用时就已经执行并输出了,而不是理想中的返回闭包函数,所以后续调用将会报错。引言 上一节介绍了高阶函数的定义,并结合实例说明了使用高阶函数和不使用高阶函数的情况。后面几部分将结合实际应用场景介绍高阶函数的应用,本节先来聊聊函数柯里化,通过介绍其定义、比较常见的...

    stackvoid 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<