一、new原理

new的实现步骤(原理)如下:

  1. 第一步:创建一个空对象,作为将要返回的对象
  2. 第二步:将这个空对象的原型指向构造函数的prototype属性,也就是将对象的__proto__属性指向构造函数的prototype【让对象能沿着原型链去使用构造函数中prototype上的方法】
  3. 第三步:将这个空对象赋值给构造函数内部的this关键字,执行构造函数。【让构造器中设置的属性和方法设置在这个对象上】
  4. 第四步:返回这个对象。
function F() {}let f = new F()

F构造函数为例,上面原理转换为伪代码大概是这样的:

  1. let obj = {}
  2. obj.__proto__ = F.prototype
  3. F.apply(obj, 参数)
  4. return obj

因此,我们可以手搓一个new方法试试:

let _new = function (F, ...args) {// let obj = {};// obj.__proto__ = F.prototype;let obj = Object.create(F.prototype);// 简写F.apply(obj, args);return obj;};

下面我们通过对比原生的new和我们手搓的_new的输出结果以验证手搓的_new效果如何:

let F = function (val, num) {// 构造函数this.val = num;this.num = val;this.hello = function hello() {};function hi() {}};let _new = function (F, ...args) {// 手搓new方法// let obj = {};// obj.__proto__ = F.prototype;let obj = Object.create(F.prototype);// 简写F.apply(obj, args);return obj;};let f1 = new F(1, 2);console.log(new F(1, 2));// 原生的new的实例化输出结果console.log(_new(F, 1, 2));// 手搓的_new的实例化输出结果

上述代码运行结果如下:

可以看到手搓的_new方法实现效果是和原生new一样的。

二、new function和new class的区别

functionclass都可以作为构造函数,但它们之间也有不少区别:

  1. funtion定义构造函数存在提升,可以先使用后定义;class定义构造函数不存在提升,只能先定义后使用,否则会报错。

    // funtion定义构造函数存在提升,可以先使用后定义console.log(new F4());function F4() {this.name = 1;}// class定义构造函数不存在提升,只能先定义后使用class F5 {constructor() {this.name = 1;}}console.log(new F5());

    输出结果:

    1. class不能调用call、apply、bind改变执行上下文。

      function F5() {console.log(this.name);}const obj1 = {name: 'Jack',};F5.call(obj1); // Jackclass F6 {constructor() {console.log(this.name);}}const obj2 = {name: 'Jack',};F6.call(obj2); // Class constructor F6 cannot be invoked without 'new'

三、function作为构造函数的注意事项

  1. function尽量别有返回值,如果有返回值会根据返回值按如下处理:

    • 返回值不是对象:无视返回值,输出的实例对象结果是this对象。
    • 返回值是对象:将function当成方法处理,就不再是构造函数了。
    function F7() {this.name = "Jack";this.age = 18;return { name: "AAA" };}let f7 = new F7();console.log(f7); // {name: 'AAA'}function F8() {this.name = "Jack";this.age = 18;console.log(this); // F8 {name: 'Jack', age: 18}这是this对象return 1;}let f8 = new F8();console.log(f8); // F8 {name: 'Jack', age: 18} 这是this对象
  2. 实例化对象需要加new,加new后构造函数里的this就指向该实例,不加的话指向的是window

    function F9(name, age) {this.name = name;this.age = age;}// 加newlet f9_1 = new F9("Jack-1", 18);console.log(f9_1.name); // Jack-1// 不加newlet f9_2 = F9("Jack-2", 18);// console.log(f9_2.name); // 报错console.log(window.name); // Jack-2 在window上 说明this指向window

为了避免第二个注意事项,我们可以在定义构造函数时添加一个判断来处理忘记加new的情况:

function F9(name, age) {// 处理漏加new的情况if (!(this instanceof F9)) {return new F9(name, age);}this.name = name;this.age = age;}

测试结果:

// 加newlet f9_1 = new F9("Jack-1", 18);console.log(f9_1.name); // Jack-1// 不加newlet f9_2 = F9("Jack-2", 18);console.log(f9_2.name); // Jack-2

可以看到这种方式是没有问题。

四、new一个箭头函数会怎么样

箭头函数是ES6中提出来的,它没有prototype,也没有自己的this指向,更可以使用arguments参数,所以不能new一个箭头函数。它会报如下错误: