先来这么一段代码
function Preson(name, age) {
this.name = name;
this.age = age;
this.say = function () {
console.log("我叫:" + this.name, "今年:" + this.age + "岁了!");
};
}
let p = new Preson("朱莉", 19);
console.log("p", p);
定义一个p去接收实例化出来的结果,可以看到p上面有三个属性值,那么say是p上面的函数
调用say函数
p.say();
展开一下,可以看出p的原型链结构:
我们打印一下这两个值发现,是一样的
console.log(Preson.prototype);
console.log(p.__proto__);
那么也就是说 p.__proto__ === Preson.prototype,当然在控制台输出是true
console.log(p.__proto__ === Preson.prototype);
那么知道new实例,其实就是操作原型链继承,那么我们就知道原理是怎么样的了
那么我们来定义一个函数来模拟一下new实例的过程:
function myNew() {
const child = {}; //新定义一个空对象
const parent = [].shift.call(arguments); //取出要实例化的函数,剩余的就是参数
child.__proto__ = parent.prototype; //原型链指向要实例化的函数
parent.apply(child, arguments); //执行函数把this指向新的对象,传入参数
return child; //返回新创建的对象
}
运行一下,发现结果满足需求
let child = myNew(Preson, "朱莉", 19);
console.log("child", child);
child.say();
那么还有一个问题就是,构造函数可能有返回值,那么构造函数可能下面这种情况
function Preson() {
return function (name) {
console.log("hello:" + name);
};
}
那么刚刚写的myNew函数就会出现下面这种情况
let child = myNew(Preson, "朱莉", 19);
child("朱莉");
那么我们需要考虑返回值是什么类型,有可能是基础类型也可能是引用类型
测试发现:返回值如果是引用类型的话(包括函数,数组和对象)返回的就是构造函数的返回值,否则就是我们继承原型链的新对象,那么我们修改一下代码得到最终代码
function myNew() {
const child = {}; //新定义一个空对象,这里不采用Object.create(null)是因为创建出来的没有原型链
const parent = [].shift.call(arguments); //取出要实例化的函数,剩余的就是参数
child.__proto__ = parent.prototype; //原型链指向要实例化的函数
const result = parent.apply(child, arguments); //执行函数把this指向新的对象,传入参数接收一下,因为可能构造函数有返回值
return result instanceof Object ? result : child; //如果构造函数的返回值是对象,直接返回返回值,如果不是就返回新创建的对象
}
let child = myNew(Preson);
child("朱莉");
还可以使用Object.create
function myNew() {
const parent = [].shift.call(arguments); //取出要实例化的函数,剩余的arguments就是参数
const child = Object.create(parent.prototype);//创建一个空对象,并使它的原型链指向要new出来的构造函数的实例
const result = parent.call(child, ...arguments); //执行函数把this指向新的对象,传入参数接收一下,因为可能构造函数有返回值
return result instanceof Object ? result : child; //如果构造函数的返回值是对象,直接返回返回值,如果不是就返回新创建的对象
}