深入 JS 之造 new 轮子

发布于 2022-10-02 15:28:15 字数 4178 浏览 170 评论 0

先搞清楚 new,首先,我们要知道,创建一个用户自定义对象需要两步:

  1. 通过编写函数来定义对象类型。
  2. 通过 new 来创建对象实例。

通过此处引出了 new 的描述,new 是干嘛的呢?引用 MDN 的一句话:

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

初看时可能会有点懵,我们可以对这段话修枝剪叶留下枝干: new 运算符可以创建 对象实例 。(之后再通过 new 的运用,补全其枝叶。)

这句话就符合最开头说的创建一个用户自定义对象的第二步了。实际应用也是,new运算符经常是与一个函数结合使用。

来看个例子:

// example
function Foo(name, age) {
  this.name = name;
  this.age = age;
  this.sex = 'male';
}
Foo.prototype.brother = "宇智波鼬";

Foo.prototype.chat = function () {
  console.log('how are you? 鸣人');
}

var person = new Foo('佐助', 21);

console.log(person.name);
console.log(person.sex);
console.log(person.brother);
person.chat();
// 佐助
// male
// 宇智波鼬
// how are you? 鸣人

从这个例子中我们可以看出,:

  1. 生成了新对象(实例)person。
  2. Foo 函数里的 this 指向该实例对象person。
  3. 这个对象实例可以访问 Foo.prototype 上的属性。

MDN 上帮我们总结的更清楚,当代码 new Foo(...) 执行时,会发生以下事情:

  1. 一个继承自 Foo.prototype 的新对象被创建。(继承 用得不是很准确,应该叫委托才好,具体参见 you-dont-konw-js)
  2. 使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

注: 由同一个构造函数创建的实例各个独立且其原型相同,原型对象都等于 构造函数.prototype === 实例.__proto__

第一步

有了对 new 的认识后,咱们开始仿写。因为 new 是个关键字,没法像之前的 call、bind 那样子写方法覆盖。我们可以写个方法 copyNew()。

以上面例子为例,我们让 new Foo(xxx) 的效果等于 copyNew(Foo, xxx) 就行。

来第一步代码:

// codes
function copyNew() {
  var obj = new Object();

  var constructor = [].shift.call(arguments); // 取出第一个构造函数参数。注意shift可以改变原数组。

  obj.__proto__ = constructor.prototype; // 这样obj就可以访问在构造函数的prototype上的属性

  // 根据apply经典继承来让函数的this指向实例
  constructor.apply(obj, arguments);

  return obj
}

have a test:

// example
function Foo(name, age) {
  this.name = name;
  this.age = age;
  this.sex = 'male';
}
Foo.prototype.brother = "宇智波鼬";

Foo.prototype.chat = function () {
  console.log('how are you? 鸣人');
}

var person = copyNew(Foo, '佐助', 21);

console.log(person.name);
console.log(person.sex);
console.log(person.brother);
person.chat();
// 佐助
// male
// 宇智波鼬
// how are you? 鸣人

第一步成功。

第二步

我们现在考虑下构造函数有返回值的情况。

1、假设构造函数的返回值是对象。

举个例子:

// 假设构造函数的返回值是对象
function Foo(name, age) {
  this.brother = '宇智波鼬';
  this.age = age
  return {
    habit: 'coding',
    name: name
  }
}

const person = new Foo('佐助', 21);

console.log(person.age);
console.log(person.brother);
console.log('**********华丽的分割线**********');
console.log(person.habit);
console.log(person.name);
// undefined
// undefined
//**********华丽的分割线**********
// coding
// 佐助

我们可以发现,构造函数 Foo 的 person 实例完全只能访问 Foo 返回的对象中的属性。

那构造函数的返回值不是对象呢? 我们试一个基本类型的值。

2、假设构造函数的返回值是基本类型的值

// 假设构造函数的返回值是一个基本类型的值
function Foo(name, age) {
  this.brother = '宇智波鼬';
  this.age = age
  return 'sadhu'
}

const person = new Foo('佐助', 21);

console.log(person.age);
console.log(person.brother);
console.log('**********华丽的分割线**********');
console.log(person.habit);
console.log(person.name);
// 21
// 宇智波鼬
//**********华丽的分割线**********
// undefined
// undefined

结果刚刚相反,相当于无返回值时的处理。

所以我们可以根据这两个例子的结果去完善最后的代码,根据这个思路:

若当构造函数返回值是对象时,则 new 调用后也返回该对象实例。若当构造函数的返回值是基本类型或者无返回值时,都当作无返回值情况处理,该返回什么就返回什么。

最终代码:

function copyNew() {
  var obj = new Object();
  var constructor = [].shift.call(arguments);
  obj.__proto__ = constructor.prototype;
  var result = constructor.apply(obj, arguments);
  return typeof result === 'object' ? result : obj;
}

参考

  1. MDN
  2. JavaScript 深入之 new 的模拟实现

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

溺深海

暂无简介

文章
评论
28 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文