new Person()? new关键字?不来看看new的模拟实现吗? | 七日打卡

863 阅读3分钟

又是新的征程,冲冲冲。

前言

本章的主角是new关键字,首先我们先来看看关于new关键字的使用,先直接怼代码。

function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 在构造函数的原型上挂载方法
Person.prototype.say = function() {
  console.log('姓名:' + this.name + ',年龄:' + this.age);
}
var p = new Person('橙某人', 18);

console.log(Person.prototype)
console.log(p)
console.log(Person.prototype === p.__proto__)
console.log(typeof p)
console.log(p.name);
console.log(p.age);
p.say();

我们从上面的例子能分析得到如下结论:

  • 通过new关键字来实例化构造函数的实例,其实也就是返回了一个新对象。
  • 构造函数的this要指向新对象,这样我们才能通过p.name访问到相应属性,因为我们在构造函数是把name属性放置构造函数this上的。
  • 新对象的原型(__proto__)指向了构造函数的原型(prototype),因此我们能直接访问到构造函数原型上的say()方法。关于原型还不的小伙伴可以看看它。传送门

所以new关键字大致就干了怎么三件事情:
返回一个新对象、构造函数的this指向新对象,新对象原型指向构造函数原型!!!
返回一个新对象、构造函数的this指向新对象,新对象原型指向构造函数原型!!!
返回一个新对象、构造函数的this指向新对象,新对象原型指向构造函数原型!!!

模拟实现

实现一

通过上面的分析,是不是突然感觉new关键字也没那么高大尚了? 一直就没有?Em...好吧。

好,话不多说,我们来试着实现这三件事情,不就返回一个新对象与改变相关指向吗?简单,我们先上代码观察一波先(注意看注解)。

function myNew(constructor) {
  // 定义一个新对象
  let newObject = new Object();

  // 改变新对象的原型指向构造函数
  newObject.__proto__ = constructor.prototype;

  // 改变构造函数的指向, 传递相关参数
  constructor.apply(newObject, [].slice.call(arguments, 1))

  // 返回新对象
  return newObject;
}

var p = myNew(Person, '橙某人', 18);
console.log(Person.prototype)
console.log(p)
console.log(Person.prototype === p.__proto__)
console.log(typeof p)
console.log(p.name);
console.log(p.age);

代码是不多的,测试代码结果也和原来上面图是一样的,这说明成功啦。
因为 new 是一个关键字,不能像其他原始方法(call())一样进行覆盖,所以我们用一个函数来模拟 new 的效果。

function Person(){}
// 原来的new用法
var p = new Person(parmas, ...);
// 模拟的new用法
var p = myNew(Person, parmas, ...);

上面代码主要是关注myNew()方面,里面也就寥寥四行代码,是不是真的很简单。唯一比较需要关注的也是call()与apply()方法,关于他俩不熟悉的小伙伴可以看看它。传送门

这就完了吗? 完了吗? 肯定是没有啦,继续冲它。

实现二

下面我们主要来谈论下关于构造函数返回值问题,先来看看下面的代码。

function Person(params){
  return params;
}

let string = new Person('string');
let number = new Person(100);
let boolean = new Person(true);
let object = new Person({a: 1});
let array = new Person([1]);
let fun = new Person(() => {});

console.log(string);
console.log(number);
console.log(boolean);
console.log(object);
console.log(array);
console.log(fun);

我们根据输入的结果,大致可以知道,使用new关键字调动函数时,如果return的是StringNumberBoolean类型的话则返回构造函数实例化的对象,而如果return的是ObjectArrayFunction等引用类型的对象的话,则返回这些值本身。

根据上面的测试,原来我们实现的myNew()方法是一直就只是返回那个新对象,现在我们就需要改造改造了。

function myNew(constructor) {
  let newObject = new Object();
  newObject.__proto__ = constructor.prototype;

  // 获取到构造函数的返回值
  let resutl = constructor.apply(newObject, [].slice.call(arguments, 1))

  // 判断是否是引用类型
  return resutl instanceof Object ? newObject : resutl;
}

最后我们上测试代码看看。

function Person(params){
  return params;
}

let string = myNew(Person, 'string');
let number = myNew(Person, 100);
let boolean = myNew(Person, true);
let object = myNew(Person, {a: 1});
let array = myNew(Person, [1]);
let fun = myNew(Person, () => {});

console.log(string);
console.log(number);
console.log(boolean);
console.log(object);
console.log(array);
console.log(fun);

至此,本章内容就真的讲完啦,感谢你的观看。