vortesnail/blog

从头到尾给你讲清楚如何实现一个new

Opened this issue · 0 comments

从头到尾给你讲清楚如何实现一个new

前言

“诶,你讲讲如何实现一个new吧,有必要也可以写一下。”,面试官微笑(或严肃)着脸说道,并递给了你纸和笔。
“emmmm,那个,新建一个对象,emm,然后....emmm..不好意思,有点记不得了”。你也尴尬而不失礼貌地笑笑😊。

那我们今天就来聊聊这个东西怎么写呗~

对于原型链继承关系可参考我的一篇博客:
原型链继承详解

原生的 new 写法:

我们平常用new的时候,是这样用的:

function People(name) {
  this.name = name;
  this.job = '前端工程师';
}

People.prototype.showName = function() {
  console.log(this.name);
}

const coder = new People('vortesnail');
coder.showName();	// vortesnail
console.log(coder.job);	// '前端工程师'

我们可以看到:
1.实例可以访问到构造函数的属性和方法(此处访问到的是 this.name )
2.实例可以访问到构造函数原型中的属性和方法(此处访问到的是原型中的 showName  这个方法)

重写 new

我们依然先来构建我们的构造函数,与上面一样:

function People(name) {
  this.name = name;
  this.job = '前端工程师';
}

People.prototype.showName = function() {
  console.log(this.name);
}

这个时候,内存分布是这样的:
Untitled1.png

分析原生的 new 我们可以知道他都做了哪些工作:
1.毫无疑问,首先我们有一个新的实例对象产生了。

let newObj = new Object();	// 或 let newObj = [];

2.它可以访问到构造函数原型中属性和方法,那一定是这个新的实例对象与构造函数的原型构成了原型链继承关系。

newObj.__proto__ = People.prototype;

这个时候,内存分配是这样的:
Untitled1 (2).png

3.它还访问到了构造函数中的属性和方法,那就要改变 People 中的 this 指向了。

People.apply(obj, arg);	// 或 People.call(obj, ...arg);
  1. return 出这个新对象 newObj

因为原生 new 是一个关键字,我们无法用 new Foo 这中写法,我们可将我们自己写的 _new 作为一个函数。

初步的实现如下:

function People(name) {
  this.name = name;
  this.job = '前端工程师';
}

People.prototype.showName = function() {
  console.log(this.name);
}

function _new(constructer, ...arg) {
  let newObj = new Object();
  newObj.__proto__ = constructer.prototype;
  constructer.apply(newObj, arg);
  return newObj;
}

let coder = _new(People, 'vortesnail');
coder.showName();	// vortesnail
console.log(coder.job);	// '前端工程师'

至此,我们写的 _new 与原生 new 似乎实现了相同的功能,此时内存分布如下:
Untitled1 (3).png

当然了,以上的代码是可以进行优化的,如果构造函数有返回且返回的是个指定对象呢,比如:

function People(name) {
  this.name = name;
  this.job = '前端工程师';
  this.result = 'People返回了一个对象';
  return {
  	result: this.result
  }
}

const coder = new People('vortesnail');	// 原生 new
console.log(coder);		                         // { result: 'People返回了一个对象' }
console.log(coder.name, coder.job);		// undefined undefined

如果还是老样子的写法,依然可以访问到构造函数内部的属性,这显然不是程序设计者的初衷,那怎么改进呢?

function _new(constructer, ...arg) {
  let newObj = new Object();
  newObj.__proto__ = constructer.prototype;
  const ret = constructer.apply(newObj, arg);
  return ret instanceof Object ? ret : newObj;
}

分析上面代码,可以看到 constructer.apply(newObj, arg); 之后有一个返回值,根据官方文档:

js中的call(), apply()和bind()是Function.prototype下的方法,都是用于改变函数运行时上下文,最终的返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined

上面这句话是啥意思?假如我们原先 People 这个构造函数没有返回值,就是后来写的那个碍眼的 return { result: this.result} ,那返回值就是 undefined ,现在有了,那就是构造函数中返回的这个对象。

所以, return ret instanceof Object ? ret : newObj; 就是说:如果你有返回对象,那就将这个对象 return 出去;没有返回对象,那就像原来一样返回我们一开始建的那个新对象。

至此,我们已经实现了完整的功能,但是还可以这样写:

function _new(constructer, ...arg) {
  let newObj = Object.create(constructer.prototype);	// 这句话顶了之前写的两句话
  const ret = constructer.apply(newObj, arg);
  return ret instanceof Object ? ret : newObj;
}

关于 Object.create() 的用法,大家就自行Google吧~~

完整实现:

function People(name) {
  this.name = name;
  this.job = '前端工程师';
  this.result = 'People这个构造函数返回了一个对象';
  // return {
  //   result: this.result
  // }
}

People.prototype.showName = function() {
  console.log(this.name);
}

function _new(constructer, ...arg) {
  let newObj = Object.create(constructer.prototype);
  const ret = constructer.apply(newObj, arg);
  return ret instanceof Object ? ret : newObj;
}

// let coder = _new(People, 'vortesnail');
let coder = _new(People, 'vortesnail');
coder.showName();
console.log(coder.job);

强调:返回的若是对象,属性和方法都是调用不了的,我想这个大家应该都清楚吧?