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

一、new操作符所做的内容

先看一个例子:

function Person() {
    this.name = 'Andy';
    this.age = 20;
    this.say = function () {
        console.log('hello everybody');
    }
}
Person.prototype.test = function(){
    console.log('test');
};
let p1 = new Person(); 
console.log(p1.name);//'Andy' 
console.log(p1.age);//20 
p1.say();//'hello everybody' 
p1.test();//'test'
new操作符的作用如下:

1.创建一个空对象,即{}。
2.由this变量引用该对象。
3.该对象继承该函数的原型
4.把属性和方法加入到this引用的对象中
5.新创建的对象由this引用,最后隐式地返回this。

当代码new Person(...)执行时,会发生以下事情:

  1.一个继承自Person.prototype的新对象被创建。

  2.使用指定的参数调用构造函数Person,并将this绑定到新创建的对象。new Person等同于new Person(),也就是没有指定参数列表,Person不带任何参数调用的情况。

  3.由构造函数返回的对象就是new表达式的结果,如果构造函数没有显示返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

你可以对已定义的对象添加新的属性,例如,p1.color=“black”语句给p1添加一个新的属性color,这不会影响任何其他对象。如果要将新属性添加到相同类型的所有对象上,你必须将该属性添加到Person对象类型的定义中。

你可以使用Function.prototype属性将共享属性添加到以前定义的对象类型。这定义了一个由该函数创建的所有对象共享的属性,而不仅仅是对象类型的其中一个实例。下面的代码将一个值为null的color属性添加到Person类型的所有对象,然后仅在实例对象p1中用字符“black”覆盖值。

function Person() {};
Person.prototype.color= red;
let p1 = new Person(); 
let p2 = new Person();
console.log(p1.color);//'red' 
console.log(p2.color);//'red'
p1.color = 'black';
console.log(p1.color);//black
console.log(p1.__proto__.color);//'red'
console.log(p2.__proto__.color);//red
console.log(p1.color);//black
console.log(p2.color);//red

如果你没有使用new运算符,构造函数会像其他的常规函数一样被调用,并不会创建一个对象。在这种情况下,this的指向也是不一样的。

二、模拟new操作符

var mockNew = function (constructor) {
  var o = new Object(); // 创建一个新对象
   constructor.apply(o, Array.prototype.slice.call(arguments, 1)) // 赋作用域 执行代码
   return o // 返回新对象
}
//然后我们用这个模拟new操作符的函数来创造一个对象试试
function (name, age) { 
this.name = name 
    this.age = age 
  } 
function () { 
this.name) 
 } 
'MeloGuo', 22) 
// 'MeloGuo' 
// 22 
// Uncaught TypeError: person1.sayName is not a function

看起来我的模拟函数虽然能访问实例中的属性,但是却不能访问sayName方法,而且当我使用instanceof操作符检测时却得到了这样的结果。

console.log(person1 instanceof Person) // false
console.log(person1 instanceof Object) // true

三、改进模拟函数

(一)第一次改进

可见person1并不是Person的实例,mockNew函数缺少了一个步骤,即绑定构造函数原型。所以person1实例是无法访问到Person原型中的sayName方法,同时instanceof操作符的结果也为false。因为instanceof操作符是用来检测一个对象在其原型链中是否存在一个构造函数的prototype属性的,而person1的原型链中并不存在Person.prototype,所以返回值为false。因此,我们改造mockNew函数如下:

var mockNew = function (constructor) {
  var o = new Object(); // 创建一个新对象
  o.__proto__ = constructor.prototype // 绑定构造函数原型,但是生产代码中千万别用.__proto__
   constructor.apply(o, Array.prototype.slice.call(arguments, 1)) // 赋作用域 执行代码
   return o // 返回新对象
}
//然后我们用这个模拟new操作符的函数来创造一个对象试试
var Person = function (name, age) {
  this.name = name
   this.age = age
}
Person.prototype.sayName = function () {
  console.log(this.name)
}
var person1 = mockNew(Person, 'MeloGuo', 22)
console.log(person1.name) // 'MeloGuo'
console.log(person1.age) // 22
person1.sayName() // 'MeloGuo'
console.log(person1 instanceof Person) // true 
console.log(person1 instanceof Object) // true

(二)第二次改进

function Person (name) {
  this.name = name
  return { age: 22 }
}

var person1 = new Person('MeloGuo')
var person2 = mockNew(Person, 'MeloGuo')

console.log(person1) // {age: 22}
console.log(person2) // Person {name: "MeloGuo"}
console.log(person1 instanceof Person) // false
console.log(person2 instanceof Person) // true

用new操作符调用的Person构造函数并没有按照预期返回带有name属性并且在Person.prototype上的对象,而是返回了我们手动return的带有age属性的对象,但是我们的mockNew函数是正常返回了。

var mockNew = function (constructor,...args) {
  const isPrimitive = result => { 
     // 如果result为值类型则返回true 
     // 如果result为引用类型则返回false 
   }
  var o = new Object(constructor.prototype); // 创建一个新对象
  //o.__proto__ = constructor.prototype // 绑定构造函数原型,但是生产代码中千万别用.__proto__
   const result = constructor.apply(o, args) // 赋作用域 执行代码
   return isPrimitive(result) ? o : result
}