一、new原理
new
的实现步骤(原理)如下:
- 第一步:创建一个空对象,作为将要返回的对象。
- 第二步:将这个空对象的原型指向构造函数的
prototype
属性,也就是将对象的__proto__
属性指向构造函数的prototype
。 【让对象能沿着原型链去使用构造函数中prototype
上的方法】 - 第三步:将这个空对象赋值给构造函数内部的
this
关键字,执行构造函数。【让构造器中设置的属性和方法设置在这个对象上】 - 第四步:返回这个对象。
javascript
function F() {}
let f = new F()
以F
构造函数为例,上面原理转换为伪代码大概是这样的:
let obj = {}
obj.__proto__ = F.prototype
F.apply(obj, 参数)
return obj
因此,我们可以手搓一个new
方法试试:
javascript
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
效果如何:
javascript
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的区别
function
和class
都可以作为构造函数,但它们之间也有不少区别:
-
funtion
定义构造函数存在提升,可以先使用后定义;class
定义构造函数不存在提升,只能先定义后使用,否则会报错。javascript// funtion定义构造函数存在提升,可以先使用后定义 console.log(new F4()); function F4() { this.name = 1; } // class定义构造函数不存在提升,只能先定义后使用 class F5 { constructor() { this.name = 1; } } console.log(new F5());
输出结果:
2.class
不能调用call、apply、bind
改变执行上下文。javascriptfunction F5() { console.log(this.name); } const obj1 = { name: 'Jack', }; F5.call(obj1); // Jack class F6 { constructor() { console.log(this.name); } } const obj2 = { name: 'Jack', }; F6.call(obj2); // Class constructor F6 cannot be invoked without 'new'
三、function作为构造函数的注意事项
-
function
尽量别有返回值,如果有返回值会根据返回值按如下处理:- 返回值不是对象:无视返回值,输出的实例对象结果是
this
对象。 - 返回值是对象:将
function
当成方法处理,就不再是构造函数了。
javascriptfunction 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对象
- 返回值不是对象:无视返回值,输出的实例对象结果是
-
实例化对象需要加
new
,加new
后构造函数里的this就指向该实例,不加的话指向的是window
javascriptfunction F9(name, age) { this.name = name; this.age = age; } // 加new let f9_1 = new F9("Jack-1", 18); console.log(f9_1.name); // Jack-1 // 不加new let f9_2 = F9("Jack-2", 18); // console.log(f9_2.name); // 报错 console.log(window.name); // Jack-2 在window上 说明this指向window
为了避免第二个注意事项,我们可以在定义构造函数时添加一个判断来处理忘记加new
的情况:
javascript
function F9(name, age) {
// 处理漏加new的情况
if (!(this instanceof F9)) {
return new F9(name, age);
}
this.name = name;
this.age = age;
}
测试结果:
javascript
// 加new
let f9_1 = new F9("Jack-1", 18);
console.log(f9_1.name); // Jack-1
// 不加new
let f9_2 = F9("Jack-2", 18);
console.log(f9_2.name); // Jack-2
可以看到这种方式是没有问题。
四、new一个箭头函数会怎么样
箭头函数是ES6中提出来的,它没有prototype
,也没有自己的this
指向,更可以使用arguments
参数,所以不能new
一个箭头函数。它会报如下错误: