JavaScript进阶之模拟new Object过程

原文:zhehuaxuan.github.io/2019/02/21/…
做者:zhehuaxuanjavascript

写在前面的话

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

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

JavaScript新建对象的过程

在ES5中定义一个函数来建立对象,以下:git

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的过程主要由下面四步组成:github

  1. 新生成一个空对象
  2. 将空对象连接到原型中
  3. 绑定this
  4. 返回新对象

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

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

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

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

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");
复制代码

image-20190221235358202

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

第三步绑定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");
复制代码

几个问题

如今的代码已经完美了么?咱们先来提几个问题。

  1. new Object()建立的对象纯净么?
  2. 为啥使用[].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");
复制代码

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

相关文章
相关标签/搜索