• javascript 的原型与原型链的理解


    javascript中一切皆对象,但是由于没有Class类的概念,所以就无法很好的表达对象与对象之间的关系了。

    比如对象A与对象B之间,它们两个是相对独立的个体,互不干扰,对象A修改自身的属性不会影响到对象B。

    虽然这很好,但是有一个问题,如果对象A与对象B都有一个方法 run() ,并且代码也一样,那对象A与对象B各自都独立拥有一份 run() 方法的完整代码,这是需要资源去保存的。

    一旦我们程序中应用的对象过多,那这种资源消耗会是巨大的。那有没有一种方法可以让对象A与对象B拥有一些公共的属性和方法,让它们之前有某种联系?

    我们设想一下,会不会存在一个 common对象(公共对象),common对象上保存着公共的属性和方法,而对象A与对象B里面有一个prototype属性指向这个 common对象,

    当然我们调用对象A或对象B的属性和方法时,如果在自身对象中没有找到,就去prototype这个属性指向的对象上面去找。

    而common对象本身也有一个prototype属性指向更上一级的common对象,然后一直往上找啊找,直到为null,就停止。

    这种不断的从下往上找的这种路径,就像链条一样,我们称它为 原型链,而那个common对象,我们称它为 原型对象。

    我们来看一个构造函数

    function Base(name) {
        this.name = name;
    }
    
    let A = new Base('A');
    let B = new Base('B');
    
    //每一个函数都有一个prototype属性,指向该函数的原型对象
    console.log(Base.prototype);
    
    //当然原型对象也是一个对象,它也有一个constructor,指向构造函数
    console.log(Base.prototype.constructor === Base);
    
    //每一个实例对象的constructor都指向创建它们的构造函数
    console.log(A.constructor === Base);
    console.log(B.constructor === Base);
    
    //每一个实例对象都有一个__proto__属性,该属性指向构造函数的原型对象
    console.log(A.__proto__ === Base.prototype);
    console.log(B.__proto__ === Base.prototype);
    

    1、每一个函数都有一个prototype属性,它指向该函数的原型对象。

    2、原型对象也是对象,它也有自已的constructor,它指向构造函数Base()。换句话说,其实原型对象也是构造函数Base()的一个实例。只不过比较特殊,用来存放公共属性和方法的。

    3、每一个通过构造函数Base()创建的实例对象,都有一个constructor,指向创建它们的构造函数。

    4、每一个对象,都有一个 __proto__ 属性,指向构造函数Base()的 原型对象。换句话说,__proto__ 是将 原型 串联起来形成链条的关键。不然对象A与对象B都无法找到原型对象上的公共属性和方法。

    function Base(name) {
        this.name = name;
    }
    //我们在原型对象上添加公共属性
    Base.prototype.status = '开始';
    //我们在原型对象上添加公共方法
    Base.prototype.run = function() {
        console.log(this.name + ' run ...');
    };
    
    let A = new Base('A');
    let B = new Base('B');
    
    A.run();
    B.run();
    
    console.log(A.status);
    console.log(B.status);
    
    //修改原型上的属性,则实例对象也会跟着改变
    Base.prototype.status = '停止';
    
    console.log(A.status);
    console.log(B.status);
    

    通过原型与原型链,让对象与对象之间有了关联关系。

    那如何通过原型与原型链,让一个构造函数继承于另一个构造函数?

    比如,我们要让构造函数Child 继承于 构造函数Base,只需要让 Child 的 prototype 指向 Base的 原型对象,不就可以了?

    function Base(name) {
    
    }
    
    Base.prototype.name = 'Base';
    Base.prototype.run = function () {
        console.log(this.name + ' run ...');
    };
    
    function Child() {
    
    }
    
    Child.prototype = Base.prototype;
    //注意这个时候,Child.prototype对象的constructor属性指向了Base
    //这就导致通过构造函数Child创建的实例对象,对象的constructor属性会指向Base,而不是Child,这会导致混乱。
    //所以我们重新设置Child.prototype.constructor指向Child
    Child.prototype.constructor = Child;
    
    let c = new Child();
    
    console.log(c.name);
    c.run();
    

    这样有一个问题,Child.prototype 与 Base.prototype 指向同一个原型对象,任何对 Child.prototype 的修改都会反应到 Base.prototype 上面。

    这时,Base.prototype.constructor 指向了 Child,这显然是有问题。

    我们只能通过一个中间的空构造函数,来完成原型的指向。

    function Base(name) {
    
    }
    
    Base.prototype.name = 'Base';
    Base.prototype.run = function () {
        console.log(this.name + ' run ...');
    };
    
    function Child() {
    
    }
    
    //创建一个中间的空构造函数
    function Mid() {
    
    }
    
    //让该空构造函数的prototype指向Base的原型对象
    Mid.prototype = Base.prototype;
    //再让Child的prototype指向该空构造函数的一个实例
    Child.prototype = new Mid();
    //这样,当修改Child.prototype.constructor时,Base.prototype就不会受影响了
    Child.prototype.constructor = Child;
    
    let c = new Child();
    
    console.log(c.name);
    c.run();
    
    //Base.prototype的constructor仍然指向Base,没有受到影响
    console.log(Base.prototype.constructor);
    

      

    那怎么通过原型与原型链,让你一对象继承于另一个对象呢?

    比如,我们要让对象B继承于对象A,无非就是想要拿到对象A的属性和方法,这么一想,那通过把对象B的 __proto__  指向 对象A,不就可以实现了?

    let A = {
        name: 'A',
        run() {
            console.log(this.name + ' run ...');
        }
    };
    console.log(A.name);
    A.run();
    
    let B = {};
    //让对象B的__proto__指向对象A
    B.__proto__ = A;
    //当对象B调用run()方法时会在自身上找,如果没找到,则通过__proto__向上找
    //由于__proto__指向对象A,所以最终会在对象A中找到run()方法
    B.run();
    
    B.__proto__.name = 'B';
    console.log(A.name);
    console.log(B.name);
    

    这样有一个问题,当修改 B.__proto__.name = 'B'; 时,对象A也会受到影响。

    我们可以通过ES5提供的 Object.create() 来解决此问题,Object.create()可以通过指定的 原型对象 创建一个新对象。

    let A = {
        name: 'A',
        run() {
            console.log(this.name + ' run ...');
        }
    };
    console.log(A.name);
    A.run();
    
    let B = {};
    //通过Object.create()创建一个以对象A为原型对象的新对象
    //让对象B的__proto__指向该新对象
    //这样再操作B.__proto__中的属性就与对象A无关了。
    B.__proto__ = Object.create(A);
    
    B.run();
    
    B.__proto__.name = 'B';
    console.log(A.name);
    console.log(B.name);
    
  • 相关阅读:
    欧拉代码005
    欧拉计划003
    欧拉计划004
    欧拉计划006
    欧拉计划002
    LINUXS3C2440SJA1000驱动程序笔记
    WPF实现窗体内容分割
    InotifyPropertyChanged接口实现简单数据绑定
    C#的6种常用集合类大比拼
    WPF获取窗体或控件句柄
  • 原文地址:https://www.cnblogs.com/jkko123/p/10212072.html
Copyright © 2020-2023  润新知