我们经常会使用new
运算符创建一个数组(Array
)、对象(Object
)等,那么new
到底是什么?
new的定义
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
new的作用
根据new
的定义,我们很容易地得出new
的作用就是创建一个对象,其实并不是这样,new
是用来继承的,我们通过这样的实例理解new
继承
- new的使用实例
function person(name, age){
this.name = name;
this.age = age;
}
person.prototype.job = 'web前端开发工程师';
person.prototype.salary = function(){
return `${this.name}的薪资是20k`;
}
const Person = new person('小明','22');
console.log(Person); // person对象
console.log('name:', Person.name); // 小明
console.log('age:', Person.age); // 22
console.log('job:', Person.job); // web前端开发工程师
console.log('salary:', Person.salary()); // 小明的薪资是20k
我们使用
new
关键字创建了person
对象,并赋值给Person
,使Person
具有了person
中的属性和方法,从而得出结论:new
关键字用来继承。上述的代码中,person具有的特点
可以访问构造函数(
person
)中的属性可以访问到原型中(
person.prototype
)的方法如何实现访问构造函数中的属性
对于这个问题,我们可以利用call
和apply
实现继承,假设首先有一个父类函数
function parent() {
// 此时this指向Child
this.name = '小明';
this.age = 22;
}
接下来,有一个子类方法要继承上面的父类
function child() {
parent.call(this); //call的作用:对象的冒充,可实现继承
}
我们看看发生了什么
const Child = new child();
console.log(Child); //child: {name: '小明', age: 22}
console.log(Child.name); // '小明'
console.log(Child.age); // 22
这样,child
拥有了parent
的属性,Child
也拥有了parent
的属性,那么我们就实现了访问构造函数中的属性,也进一步说明了new
是用来继承的
- 如何实现访问构造函数中的方法
通过上面例子中的输出,我们发现,person
构造函数的方法是在原型链中,那么我们就可以直接使用赋值的方法实现访问构造函数中的方法,像这样:
let object = {};
object.__proto__ = Person.__proto__;
console.log(object, 'object');
它的输出:
我们可以看到在object的原型链上拥有了person
构造函数中的方法。
解决了这两个难题,我们就可以自己写一个方法来模拟实现new
运算符。
- 模拟实现new运算符
第一步
const Person = NEW(person, '小明', '22');
第二步
function NEW() {
let obj = {};
construct = [].shift.call(arguments); // 得到构造函数 Array.protptype原型上的方法对传入参数进行操作
obj.__proto__ = construct.prototype; // 访问方法
construct.apply(obj, arguments); // 让obj拥有Construct中的属性
return obj;
}
插播,这里我们没有使用上面的call
方法,而是使用了apply
,这是因为下面的方法我们需要让obj
拥有构造函数中的属性,这个属性它是以类数组形式接收,所以我们要使用apply
方法传递数组参数。我们看一下结果
console.log(Person); // person对象
console.log('name:', Person.name); // 小明
console.log('age:', Person.age); // 22
console.log('job:', Person.job); // web前端开发工程师
console.log('salary:', Person.salary()); // 小明的薪资是20k
到这里,看似是已经实现了new
的方法,但是,设想一下,如果有人把构造函数写成了这样,我们看到name
和age
为undefined
function person(name, age){
return {
name: '小明',
age: 22
}
}
console.log('name:', Person.name); // undefined
console.log('age:', Person.age); // undefined
那么,针对这个问题,要如何改进代码?
function NEW() {
let obj = {};
construct = [].shift.call(arguments); // 得到构造函数 Array.protptype原型上的方法对传入参数进行操作
obj.__proto__ = construct.prototype; //访问方法
const result = construct.apply(obj, arguments); // 让obj拥有Construct中的属性
return typeof result === 'object' ? result : obj;
}
又如果有人把代码写成了这样呢
function person(name, age){
return 1;
}
console.log('name:', Person.name); // undefined
console.log('age:', Person.age); // undefined
可以看到,return
数字的时候是没有问题的。如果返回一个null
呢
function person(name, age){
return null; // null是一个对象
}
这个时候就会报错
应该如何解决这个问题,null
是一个对象,也就是说类型检测它会返回object
,所以执行了三目运算中正确的条件,所以我们用||
即可解决问题:
function NEW() {
let obj = {};
construct = [].shift.call(arguments); // 得到构造函数 Array.protptype原型上的方法对传入参数进行操作
obj.__proto__ = construct.prototype; //访问方法
const result = construct.apply(obj, arguments); // 让obj拥有Construct中的属性
return typeof result === 'object' ? result || obj : obj;
}
这个时候,我们要思考这个代码还有优化的余地么?答案显然是肯定的
function NEW() {
construct = [].shift.call(arguments); // 得到构造函数 Array.protptype原型上的方法对传入参数进行操作
let obj = Object.create(construct.prototype);
// obj.__proto__ = construct.prototype; //访问方法
const result = construct.apply(obj, arguments); // 让obj拥有Construct中的属性
return typeof result === 'object' ? result || obj : obj;
}
const Person = NEW(person, '小明', '22');
console.log(Person); // person对象
console.log('name:', Person.name); // 小明
console.log('age:', Person.age); // 22
console.log('job:', Person.job); // web前端开发工程师
console.log('salary:', Person.salary()); // 小明的薪资是20k
上面的方法中,运用了Object.create()方法创建对象,它创建的对象可以直接使得新对象拥有原型上面的方法,所以可用其替换掉赋值访问原型的办法
上述文章如有错误之处,还请大家指正出来,我们共同进步~
最后,分享一下我的个人公众号「web前端日记」~