浅学JavaScript原型对象

201 阅读3分钟

浅学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(); // 继承Fatherlet 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身上是否存在