挪用MDN对new运算符的定义:new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象实例。光看这句话有些不理解。我们举例说明吧。

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. this.favorate = 'Games';
  5. }
  6. // 向Person构造函数的原型中添加属性
  7. Person.prototype.strength = 60;
  8. Person.prototype.sayYourName = function() {
  9. console.log('I am' + this.name)
  10. }
  11. var person1 = new Person('kevin', 18);
  12. console.log(person1.name); // 'kevin'
  13. console.log(person1.favorate); // 'Games'
  14. console.log(person1.strength); 60
  15. person1.sayYourName(); // 'I am kevin'

从这个例子中我们可以看到,实例person1可以:

  • 访问到Person构造函数里的属性
  • 访问到Person.prototype中的属性

接下来,我们可以尝试着模拟一下,因为new是关键字,所以无法像bind函数一样直接覆盖。我们用一个函数来实现,命名为objectFactory,来模拟new的效果。用的时候是这样的:

  1. function Otaku() {
  2. ……
  3. }
  4. // 使用new
  5. var person = new Person(……);
  6. // 使用objectFactory
  7. var person = objectFactory(Otaku, ……)

初步实现

因为new的结果是一个新对象,所以在模拟实现的时候,我们也要建立一个新对象,假设这个对象叫obj,因为obj会具有Otaku构造函数里的属性,想想经典继承的例子,我们可以使用Otaku.apply(obj, arguments)来给obj添加新的属性。
我们知道原型和原型链中,示例的_proto_属性会指向构造函数的prototype,也正式因为建立这样的关系,实例可以访问原型上的属性。所以我们来简单实现一下:

  1. function objectFacatory() {
  2. var obj = new Object();
  3. // arguments是类数组,没有slice方法,用[].shift来去arguments从第二个到最后一个的值
  4. Constructor = [].shift.call(arguments);
  5. obj._proto_ = Constructor.prototype;
  6. Constructor.apply(obj, arguments);
  7. return obj;
  8. }

在以上示例中,我们做了五步:
1、用{}或new Object()的方式创建了一个对象obj
2、取出第一个参数,就是我们要传入的构造函数,此外因为shfit会修改原数组,所以arguments会被取出第一个参数
3、将obj原型指向构造函数,这样obj就可以访问到构造函数原型中的属性
4、使用apply,改变构造函数this的指向到新建的对象,这样obj就可以访问到构造函数的属性
5、返回obj
这里涉及到的概念:原型和原型链apply经典继承
下面我们来做一下简单的测试:

  1. function Otaku(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. this.habit = 'Games';
  5. }
  6. Otaku.prototype.strength = 60;
  7. Otaku.prototype.sayYourName = function() {
  8. console.log('I am' + this.name);
  9. }
  10. function objectFactory() {
  11. var obj = new Object();
  12. Constructor = [].shift.call(arguments);
  13. obj._proto_ = Constructor.prototype;
  14. Constructor.apply(obj, arguments);
  15. return obj;
  16. }
  17. var person = objectFactory(Otaku, 'kevin', 12);
  18. console.log(person.name); // kevin
  19. console.logg(person.habit); // Games
  20. console.log(person.strength); // 60
  21. person.sayYourName(): // I am kevin

返回值效果的实现

先来看下带返回值的new方法的示例:

  1. function Otaku(name, age) {
  2. this.strenth = 60;
  3. this.age = age;
  4. return {
  5. name: name,
  6. habit: 'Games'
  7. }
  8. }
  9. var person = new Otaku('kevin', 12);
  10. console.log(person.name); // kevin
  11. console.log(person.habit); // Games
  12. console.log(person.age); // undefined
  13. console.log(person.strength); // undefined

在这个例子中,构造函数返回一个对象,实例peroson中只能访问到返回对象的属性。
如果返回的是一个基本类型呢?我们再来看一个例子:

  1. function Otaku(name, age) {
  2. this.strenth = 60;
  3. this.age = age;
  4. return 'this is a simple text'
  5. }
  6. var person = new Otaku('kevin', 12);
  7. console.log(person.name); // undefined
  8. console.log(person.habit); // undefined
  9. console.log(person.age); // 12
  10. console.log(person.strength); // 60

这次尽管有返回值,但是跟没有返回值的效果是一样的,所以我们最后只需要到返回值是否为对象进行判断就行了。

最后一版模拟

  1. function objectFactory() {
  2. var obj = new Object();
  3. Constructor = [].shift.call(arguments);
  4. obj._proto_ = Constructor.prototype;
  5. var ret = Constructor.apply(obj, arguments);
  6. return typeof ret === 'object' ? ret : obj;
  7. }

到这里就实现了new的模拟了🎉🎉🎉

:::info 本文档参考自:冴羽大神的博客 :::