浅学JavaScript原型对象
JavaScript的原型对象是面试中非常常见的知识点,并且掌握了JavaScript原型对象对面向对象也能很好理解。
我们来看这么一段代码:
function Star(name) {
this.name = name;
this.sing = function () {
console.log(this.name);
}
}
let d = new Star('断')
let x = new Star('轩')
d.sing() // 断
x.sing() // 轩
console.log(x.sing === d.sing); // false
同一个构造函数函数的同一个方法却不属于同一块开辟空间,存在浪费内存的问题,那有什么办法能让他们共享呢?有,那就是原型对象prototype
function Star(name) {
this.name = name;
}
Star.prototype.sing = function() {
console.log(this.name);
}
let d = new Star('断')
let x = new Star('轩')
d.sing() // 断
x.sing() // 轩
console.log(x.sing === d.sing); // true
可以看到sing方法被共享了,但为什么d实例对象能访问到Star的prototype呢?原因就是d被实例化出来的时候身上加了__proto__属性,而__proto__属性指向Star的prototype,也就是说它们是等价的
console.log(d.__proto__ == Star.prototype); // true
console.log(x.__proto__ == Star.prototype); // true
当d实例对象身上不存在sing方法时就会去__proto__身上查找。这下你明白为什么sing方法会共享了吧,因为d实例对象和x实例对象的sing方法指向同一个地址Star.prototype.sing()
在对象原型(__proto__)和构造函数(prototype)原型对象里面都有一个constructor属性,它指回构造函数本身。
function Star(name) {
this.name = name;
}
let star = new Star();
console.log(star.constructor == Star); // true
那它有什么作用呢?我们来看下面这段代码:
function Father() {
this.name = '父亲'
}
function Child() {
}
Child.prototype = new Father(); // 继承Father
let child = new Child();
console.log(child.name); // 父亲
我们可以通过修改Child的prototype而达到继承的目的,从而可以访问Father身上的name属性。但如果我们用的Child是别人的框架,我们想要在Child实例对象身上扩展方法或属性要怎么做呢?
function Father() {
this.name = '父亲'
}
function Child() {
}
Child.prototype = new Father(); // 继承Father
let child = new Child();
console.log(child.name); // 父亲
// 找到构造函数本身,在构造函数本身prototype添加方法
// child.constructor 此时应该理论等于 Child。
child.constructor.prototype.xuan = function () {
console.log("哈哈哈");
};
child.xuan(); // 哈哈哈
let father = new Father();
father.xuan(); // 哈哈哈
看到最后一段代码你可能会疑惑,为什么father的原型对象上也会有xuan这个方法?因为你要知道Child在继承那一步我们将prototype的constructor覆盖掉了,变成了Father实例对象的__proto__,而Father实例对象身上的__proto__的constructor指向Father,所以就相当于在Father的prototype身上添加属性,当child实例对象调用xuan方法的时候,首先会去找child实例本身上是否存在,如果有就调用,如果没有就会向上查找原型对象上是否存在,这时候就找到了Father的原型对象,因为我们是添加了的,所以就找到了。
我们只需将Child.prototype.constructor重新指回Child即可
function Father() {
this.name = "父亲";
}
function Child() {}
Child.prototype = new Father();
Child.prototype.constructor = Child; // 重新指向Child本身
let child = new Child();
console.log(child.name); // 父亲
// child.constructor = child.__proto__.constructor = Child
child.constructor.prototype.xuan = function () {
console.log("哈哈哈");
};
child.xuan(); // 哈哈哈
let father = new Father();
father.xuan(); // Uncaught TypeError: father.xuan is not a function
关于对象成员查找规则看以下代码
function Star(name) {
this.name = name;
}
Star.prototype.name = '我是prototype上的属性'
Star.prototype.qwe = '我是prototype上的属性'
Object.prototype.qwe = '我是object上的属性'
Object.prototype.xuan = '我是object上的属性'
let star = new Star('我是实例对象本身存在的属性')
console.log(star.name); // 我是实例对象本身存在的属性
console.log(star.qwe); // 我是prototype上的属性
console.log(star.xuan); // 我是object上的属性
console.log(star.noexist); // undefined
查找规则就是
star对象实例 -> star.proto(Star.prototype) -> Object.prototype -> null
因为所有对象都继承Object,所以还会查找一下Object身上是否存在