likes
comments
collection
share

深入理解JS的new关键字

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

手撕代码

手写new是面试题高频考点,实现代码也非常简单,但在实现后我对其为什么要这样设计有着很深的疑惑,也看了许多文章,今天也来讲一下为什么要这样设计new的实现

先来看下实现代码:

function MyNew(Con,...args){
    const obj = Object.create(Con); 
    const result = Con.apply(obj, args);
    return result instanceof Object ? result : obj;
}

我最初的感受是三行代码很费解,又是原型继承,又是this绑定,又是判断类型的,其实这其中涉及到的知识点很多,虽然实现了new的功能,但不知道为什么要这样做

知其所以然

举例

假设我们要开始全国人口普查,所以我们要制造一个表示人信息的对象,有名字,年龄,省份等信息,会吃东西,说话,行走,睡觉等行为

const person = {
  ID: i,
  name: "pangyu",
  age: "18",
  province: "山东",
  say() {
    console.log("说话");
  },
  eat() {
    console.log("吃东西");
  },
  walk() {
    console.log("行走");
  },
  sleep() {
    console.log("睡觉");
  },
};

接下来查了100个人,那我们就利用for循环创建100个人的对象,实现批量统计

const roster = []; // 花名册

for (let i = 0; i < 100; i++) {
  const person = {
    ID: i,
    name: "pangyu",
    age: "18",
    province: "山东",
    say() {
      console.log("说话");
    },
    eat() {
      console.log("吃东西");
    },
    walk() {
      console.log("行走");
    },
    sleep() {
      console.log("睡觉");
    },
  };
  roster.push(person);
}

避免重复

统计了100个人之后,暴露了一个问题:

每个人的say,eat,walk,sleep方法都是一样的,但每创建一个人都会重新创建这四个方法,那么100个人就创建了400个函数,要是统计成千上万人,消耗的内存相当大,这样不妥

如何解决:

既然方法都是一样的,那么就单独抽离到一个对象中,实现复用这个对象就不会再重新创建了

const roster = [];  // 花名册

const personBehavior = {
  say() {
    console.log("说话");
  },
  eat() {
    console.log("吃东西");
  },
  walk() {
    console.log("行走");
  },
  sleep() {
    console.log("睡觉");
  },
};

for (let i = 0; i < 100; i++) {
  const person = {
    ID: i,
    name: "pangyu",
    age: "18",
    province: "山东",
    __proto__: personBehavior,
  };
  roster.push(person);
}

现在person的__proto__属性是personBehavior公共方法,这其实就是原型的作用,避免公共方法的重复声明,节省内存,利于维护

功能模块化

这样实现了复用,但还不够完美,我们可以把创建人信息放在一个单独的createPerson函数中,并让公共属性和这个方法联系起来

const roster = []; // 花名册

function createPerson(i) {
  const person = {}; // 创建临时对象

  person.__proto__ = createPerson.prototype; // 临时对象绑定原型(公共属性)

  person.ID = i;
  person.name = "pangyu";
  person.age = "18";
  person.province = "山东";

  return person; // 返回临时对象
}

createPerson.prototype = {
  say() {
    console.log("说话");
  },
  eat() {
    console.log("吃东西");
  },
  walk() {
    console.log("行走");
  },
  sleep() {
    console.log("睡觉");
  },
};

for (let i = 0; i < 100; i++) {
  roster.push(createPerson(i));
}

这样的功能模块划分较为清晰,那么在createPerson函数中,JS之父发现这样创建对象的操作非常常见,于是他就发明了new关键字来简化createPerson中的操作

深入理解JS的new关键字

其中红框内的代码其实属于模版代码,每创建一个对象都会走这样的一套逻辑,只有给person赋值自身属性时才需要用户手动编写,所以红框中的代码片段都会在new中实现

使用new创建person

const roster = []; // 花名册

function createPerson(i) {
  // const person = {}; // 【省略】创建临时对象

  // person.__proto__ = createPerson.prototype; // 【省略】临时对象绑定原型

  // 绑定自身属性
  this.ID = i;  
  this.name = "pangyu";
  this.age = "18";
  this.province = "山东";

  // return person; // 【省略】返回临时对象
}

createPerson.prototype = {
  say() {
    console.log("说话");
  },
  eat() {
    console.log("吃东西");
  },
  walk() {
    console.log("行走");
  },
  sleep() {
    console.log("睡觉");
  },
};

for (let i = 0; i < 100; i++) {
  roster.push(new createPerson(i));
}

回看new的实现

function MyNew(Con,...args){
    const obj = Object.create(Con); // 创建临时对象,绑定原型
    const result = Con.apply(obj, args); // 调用构造函数,绑定this到临时对象(赋值this自身属性)
    return result instanceof Object ? result : obj; // 如果构造函数返回值为对象类型,那么就返回,否则返回临时对象
}

现在是不是对于new有了更深的理解,其实它就是一个语法糖,把生成对象的模版代码实现了, 用户只需要关注自身属性即可

总结

  • new执行后做了什么
    • 创建临时对象/新对象
    • 绑定原型(公共属性)
    • 指定this = 临时对象(为this赋值自身属性做准备)
    • 执行构造函数(this赋值自身属性)
    • 返回临时对象(判断构造函数返回值是否为对象)
  • new的本质是什么
    • 语法糖,省略了创建对象所需的模版代码

参考链接:

zhuanlan.zhihu.com/p/23987456

developer.mozilla.org/zh-CN/docs/…

juejin.cn/post/699400…

转载自:https://juejin.cn/post/7235493849072975930
评论
请登录