Failure is part of learning. we should never give up the struggle in life

原型链与继承

什么是原型链

基本概念:
js的函数也是对象相比于普通对象,有 prototype 属性 ,但对象都会拥有 proto 属性
Object、Function都是js内置的函数, 类似的还有我们常用到的Array、RegExp、Date、Boolean、Number、String

原型对象 prototype 有一个默认的 constructor 属性,用于记录实例是由哪个构造函数创建
构造函数就是使用了 new 关键字的函数如:

// 一个普通函数
function Father(){

};
// = 左边是实例化后的对象, = 右边就是构造函数,从左到右的过程就是实例化
let father = new Father();

// 左边对象有两个属性,constructor和__ proto __
// 右边的构造函数有prototype称为原型对象,其有一个默认的constructor属性,用于记录实例是由哪个构造函数创建
    console.log(father.__proto__);// 存在
    console.log(father.constructor);// Father()函数
    console.log(father.prototype);// undefined

    console.log(Father.prototype);// 存在
    console.log(Father.prototype.constructor);// Father()函数
    console.log(Father.__proto__);// 存在

这下你应该看明白了,当然还有最重要的一点就是

console.log(father.__proto__ === Father.prototype);// true

它们是一个东西只是在 = 两边的它的名字不同

总结一下:原型对象就是某个实例的构造函数的prototype ,对象原型就是某个对象的__proto__

我们上面代码加一点内容:

function Father(){

};
Father.prototype.name = 'Father';
let father = new Father();
console.log(father.name);// Father

这里能打印出 Father,是因为 father 访问属性 name 时发现并不存在 name 属性,但是它这个对象有__proto__它是指向 Father 的 prototype ,所以通过__proto__去到Father.prototype 里寻找 name,这样他就找到了,如果我们并没有在Father.prototype 定义 name,那它就会去 Object.prototype 找,因为 Object.prototype 对象是 Object 构造函数实例化得到,如果还没有就会去 null 寻找
打印下面

    console.dir(father.__proto__);
    console.dir(Father.prototype);
    console.dir(father.__proto__.__proto__);
    console.log(father.__proto__.__proto__.__proto__);

如图

06.png

这样一层一层形成的链路就是原型链

总结:

函数会天然拥有一个 prototype 它就是这个函数的原型对象,当把函数使用 new 实例化成一个对象后,

原来的函数就成为了构造函数,而实例化对象会出现一个__ proto __ 指向那个原型对象也就是该构造函数的 prototype ,

抽象理解他是这个构造函数和这个对象的公用仓库只要塞了东西进去就可以用不同的方式拿到,

而拿的顺序都是遵循先在自己内部找,没有就去这个仓库找,还没有就寻找这个仓库的父仓库去寻找,

遵循这个寻找方法所走的路就叫做原型链

原型链继承

通过上面的了解我们就知道它的作用了,就是为了实现继承

不多逼逼直接看代码

function Father() {
        this.name = 'Father';
    }

    Father.prototype.getName = function () {
        return this.name;
    }

    function Child() {
        this.name = 'Child';
    }

    Child.prototype = new Father();
    let father = new Father();
    let child = new Child();
    console.log(child1);
    console.log(father.getName());
    console.log(child.getName());

总结:

child 并没有 getName 方法,通过 child 的 proto 去 Child.prototype 找,
发现 Child.prototype 是 Father 实例对象,在内部找,还是没有,通过实例对象 proto 去 Father.prototype 找
好耶找到了 child 调用了函数 this 绑定到 child 输出自己的 name 属性值。

缺点:

如果共享的原型属性中有引用类型属性,无论是哪个实例使该引用类型属性的值发生改变时,其他共享了该原型的实例中该值都会改变。

构造函数继承

直接上代码:

    function Father() {
        this.name = 'Father';
        this.getName = function () {
            return this.name;
        }
    }

    Father.prototype.a = '我去你大爷的我没了'

    function Child() {
        this.name = 'Child';
        Father.call(this);
    }


    let child = new Child();
    let child2 = new Child();

    console.log(child.getName());// Father
    console.log(child.a);// undefined
    
    console.log(child2.getName());// Father
    console.log(child2.a);// undefined
    
    console.log(child.getName === child2.getName);// false

缺点:

虽然方法被继承了下来而且是各自独立的,但遇到同名属性时会被覆盖(可以写在调用函数下面解决),而且无法继承父的 prototype 上的属性。

组合继承

上:

  function Father() {
        this.name = 'Father';
        this.getName = function () {
            return this.name;
        }
    }

    Father.prototype.a = '我tm回来了'

    function Child() {
        this.name = 'Child';
        Father.call(this);
    }

    Child.prototype = new Father();//只比上面多这一行

    let child = new Child();
    let child2 = new Child();

    console.log(child.getName());// Father
    console.log(child.a);// undefined
    
    console.log(child2.getName());// Father
    console.log(child2.a);// undefined
    
    console.log(child.getName === child2.getName);// false

总结:

是的,孩子们我回来了

缺点:

在Child.prototype 也添加了 getName 方法和 name 属性,造成了重复

寄生组合继承

上:

    function Father() {
        this.name = 'Father';
        this.getName = function () {
            return this.name;
        }
        this.getAge = function () {
            return this.age;
        }
    };

    Father.prototype.a = '我tm回来了'

    function Child() {
        this.age = 18;
        Father.call(this);
        this.name = 'Child';// 兄弟碰到属性相同 咱内部都写这下边不然全给覆盖了
    };

    Child.prototype.a = 'xdm造孽啊给我自己prototype干没了';
    // Object.create(Father.prototype) 创建一个新对象,把新对象的原型指向Father.prototype
    Child.prototype = Object.create(Father.prototype);
    // 修复指向
    Child.prototype.constructor = Child;

    let child = new Child();
    let child2 = new Child();
    let father = new Father();

    console.log(child.getName());
    console.log(child.getAge());
    console.log(child.a);

    console.log(child2.getName());
    console.log(child2.getAge());
    console.log(child2.a);

    console.log(child.getName === child2.getName);
    console.log(child.getAge === child2.getAge);

总结:

重复解决了,给 Child.prototype 干没了

各有各的好,视情况用

发表评论

电子邮件地址不会被公开。 必填项已用*标注