js中new一个函数结果返回一个对象,这会混淆大家对原型链的理解
1.先弄清楚原型链是什么
原型链设计就是为了让new出来的对象有构造函数的属性
直接看看new做了什么
function myNew(func, ...args) {
const obj = {}; // 新建一个空对象
const result = func.call(obj, ...args); // 执行构造函数
obj.__proto__ = func.prototype; // 设置原型链
// 注意如果原构造函数有Object类型的返回值,包括Functoin, Array, Date, RegExg, Error
// 那么应该返回这个返回值
const isObject = typeof result === 'object' && result !== null;
const isFunction = typeof result === 'function';
if(isObject || isFunction) {
return result;
}
// 原构造函数没有Object类型的返回值,返回我们的新对象
return obj;
}
function Puppy(age) {
this.puppyAge = age;
}
Puppy.prototype.say = function() {
console.log("汪汪汪");
}
const myPuppy3 = myNew(Puppy, 2);
console.log(myPuppy3.puppyAge); // 2
console.log(myPuppy3.say()); // 汪汪汪
new会返回一个对象,如果构造函数没有返回Object类型的返回值,会返回一个新对象这个对象继承了构造函数的属性
2.函数的继承
js设计函数有两个默认的属性prototype和__proto__,object则有__proto__ 这个__proto__就是为了找构造函数的属性object.proto = Func.prototype
function Parent() {}
function Child() {}
Child.prototype.__proto__ = Parent.prototype;
const obj = new Child();
console.log(obj instanceof Child ); // true
console.log(obj instanceof Parent ); // true
上述继承方法只是让Child访问到了Parent原型链,但是没有执行Parent的构造函数。
function Parent() {
this.parentAge = 50;
}
function Child() {}
Child.prototype.__proto__ = Parent.prototype;
const obj = new Child();
console.log(obj.parentAge); // undefined
看new方法知道会执行Child的构造方法,Father的parentAge不会赋值给this,new出来的child没有parentAge,但是你用obj.parentAge是可以访问到的。
为了解决这个问题,我们不能单纯的修改Child.prototype.__proto__指向,还需要用new执行下Parent的构造函数:
function Parent() {
this.parentAge = 50;
}
function Child() {}
Child.prototype.__proto__ = new Parent();
const obj = new Child();
console.log(obj.parentAge); // 50
上述方法会多一个__proto__层级,可以换成修改Child.prototype的指向来解决,注意将Child.prototype.constructor重置回来:
function Parent() {
this.parentAge = 50;
}
function Child() {}
Child.prototype = new Parent();
Child.prototype.constructor = Child; // 注意重置constructor
const obj = new Child();
console.log(obj.parentAge); // 50
当然还有很多其他的继承方式,他们的原理都差不多,只是实现方式不一样,核心都是让子类拥有父类的方法和属性,感兴趣的朋友可以自行查阅。
3.instanceof
知道了原理,其实我们也知道了instanceof是干啥的。instanceof不就是检查一个对象是不是某个类的实例吗?换句话说就是检查一个对象的的原型链上有没有这个类的prototype,知道了这个我们就可以自己实现一个了:
function myInstanceof(targetObj, targetClass) {
// 参数检查
if(!targetObj || !targetClass || !targetObj.__proto__ || !targetClass.prototype){
return false;
}
let current = targetObj;
while(current) { // 一直往原型链上面找
if(current.__proto__ === targetClass.prototype) {
return true; // 找到了返回true
}
current = current.__proto__;
}
return false; // 没找到返回false
}
// 用我们前面的继承实验下
function Parent() {}
function Child() {}
Child.prototype.__proto__ = Parent.prototype;
const obj = new Child();
console.log(myInstanceof(obj, Child) ); // true
console.log(myInstanceof(obj, Parent) ); // true
console.log(myInstanceof({}, Parent) ); // false
5.总结
- JS中的函数可以作为函数使用,也可以作为类使用
- 作为类使用的函数实例化时需要使用new
- 为了让函数具有类的功能,函数都具有
prototype属性。 - 为了让实例化出来的对象能够访问到
prototype上的属性和方法,实例对象的__proto__指向了类的prototype。所以prototype是函数的属性,不是对象的。对象拥有的是__proto__,是用来查找prototype的。 prototype.constructor指向的是构造函数,也就是类函数本身。改变这个指针并不能改变构造函数。- 对象本身并没有
constructor属性,你访问到的是原型链上的prototype.constructor。 - 函数本身也是对象,也具有
__proto__,他指向的是JS内置对象Function的原型Function.prototype。所以你才能调用func.call,func.apply这些方法,你调用的其实是Function.prototype.call和Function.prototype.apply。 prototype本身也是对象,所以他也有__proto__,指向了他父级的prototype。__proto__和prototype的这种链式指向构成了JS的原型链。原型链的最终指向是Object的原型。Object上面原型链是null,即Object.prototype.__proto__ === null。Function.__proto__ === Function.prototype。这是因为JS中所有函数的原型都是Function.prototype,也就是说所有函数都是Function的实例。Function本身也是可以作为函数使用的----Function(),所以他也是Function的一个实例。类似的还有Object,Array等,他们也可以作为函数使用:Object(),Array()。所以他们本身的原型也是Function.prototype,即Object.__proto__ === Function.prototype。换句话说,这些可以new的内置对象其实都是一个类,就像我们的Puppy类一样。
再来看一下完整图:
看重点【Function.__proto__ === Function.prototype】
希望可以帮到你理解原型链!