new的作用:实例化并且继承这个对象
一个非常简单的new实例:
function Person( name, age ){ //构造函数
this.name = name;
this.age = age;
}
Person.prototype.sex = 'male'; //原型链上的属性
Person.prototype.getName = function(){ //原型链上的方法
console.log('my name is : ' , this.name)
}
var person = new Person('Jachin', '18')
console.log(person.name);
console.log(person.age);
console.log(person.sex);
person.getName();
想必大家都非常熟悉new的使用方法,但是自己去实现new过程却并不是一件简单的事情;这其中最重要的就是了解new实例的原理。
Part1:new出来的实例有什么特点
- 实例能访问到Person构造函数里面的属性
- 实例能访问到Person原型上的属性
解决第一个问题:实例能访问到Person构造函数里面的属性 ( 经典继承 )
function Parent(){
this.firstName = 'Zhou';
}
function Child(){
Parent.call(this)
}
var child1 = new Child();
console.log(child1.firstName)
结合第一个问题的思想解决第二个问题:实例能访问到Person原型上的属性
new函数的第一版本实现
function newFun(){ //模拟new的函数
var obj = {}; //创建一个空对象
Constructor = [].shift.call(arguments); //删除并获取arguments第一个参数
obj.__proto__ = Constructor.prototype; //将obj原型链指向构造函数
Constructor.apply(obj, arguments); //使用apply改变Constructor的this指向obj,obj就可以访问Constructor中的属性;
return obj;
}
第一版本的测试用例
function Person( name, age ){ //构造函数
this.name = name;
this.age = age;
}
Person.prototype.sex = 'male'; //原型链上的属性
Person.prototype.getName = function(){ //原型链上的方法
console.log('my name is : ' , this.name)
}
var person = newFun(Person, 'Jachin', '18') //此处使用自定义的newFun
console.log(person.name);
console.log(person.age);
console.log(person.sex);
person.getName();
重点:由于构造函数可以有return值,所以在使用new实例的时候,构造函数有返回值,并且返回值为对象的话,原型上的属性和方法是不能够被继承的。
因此如果使用new函数的第一版本实现,是不符合规定的。如图所示
原型上的方法和属性都被展示出来了,继续改造newFun函数
new函数的第二版本实现 (填坑)
只需要判断Constructor调用obj时候的返回值是否object对象
但是此刻还得堤防 null 这个返回值,因为 typeof null === ‘object’ -> true
function newFun(){ //模拟new的函数
var obj = {}; //创建一个空对象
Constructor = [].shift.call(arguments); //删除并获取arguments第一个参数
obj.__proto__ = Constructor.prototype; //将obj原型链指向构造函数
var result = Constructor.apply(obj, arguments); //使用apply改变Constructor的this指向obj,obj就可以访问Constructor中的属性;
return typeof result === 'object' ? result || obj : obj;
}
Part2:newFun函数里的一些坑
function newFun(){ //模拟new的函数
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
var obj = {}; //创建一个空对象
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
Constructor = [].shift.call(arguments); //删除并获取arguments第一个参数
obj.__proto__ = Constructor.prototype; //将obj原型链指向构造函数
var result = Constructor.apply(obj, arguments); //使用apply改变Constructor的this指向obj,obj就可以访问Constructor中的属性;
return typeof result === 'object' ? result || obj : obj;
}
var obj = {} 是书面表达,其实本质上等价于 var obj = new Object() 这种构造表达式
但是我们是要实现new的过程
所以此处不应该用 var obj = {} 来命名一个空对象
因此可以改造为
var obj = Object.create(null)
!!!但是创建出来的这个空对象,是没有prototype这个原型的,因为对象都是没有原型,只有构造函数才有原型,因此此处的null应该改为目标构造函数;
function newFun(){ //模拟new的函数
Constructor = [].shift.call(arguments); //删除并获取arguments第一个参数
var obj = Object.create(Constructor.prototype); //将对象指向构造函数的原型上
var result = Constructor.apply(obj, arguments); //使用apply改变Constructor的this指向obj,obj就可以访问Constructor中的属性;
return typeof result === 'object' ? result || obj : obj;
}
此处才能大功告成完成一个new的模拟
此处感谢网易大神:肖恒敏的讲解