• 讲解关于javascript的继承


     接着下来,我们看一下继承的实现。上面就说过,只要prototype有什么东西,它的实例就有什么东西,不论这个属性是后来添加的,还是这整个prototype都是置换上去的。如果我们将这个prototype对象置换为另一个类的原型,那么它就轻而易举得到那个类的所有原型成员。

    function A(){}
    A.prototype = {
       aaa:1
    }
    function B(){}
    B.prototype = A.prototype;
    var b= new B;
    console.log(b.aaa);//1;
    A.prototype.bbb = 2;
    console.log(b.bbb);//2;

    但由于是引用着相同的一个对象,意味着,如果我们修改A类的原型,也等同于修改了B类的原型。因此我们不能把一个对象赋给两个类,这有两种办法。方法一是通过for in把父类的原型成员逐一赋给子类的原型。方法二是,子类的原型不是直接由父类获得,先将此父类的原型赋给一个函数,然后将这个函数的实例作为子类的原型。

    方法一,我们通常要实现mixin这样的方法,比如Prototype.js的extend方法:
    function extend(destination, source) {
      for(var property in source)
      destination[property] = source[property];
      return destination;
    }

    方法二,则是需要如下代码:
    A.prototype = {
      aa: function() {
        alert(1)
      }
    }

    function bridge() {};
    bridge.prototype = A.prototype;

    function B() {}
    B.prototype = new bridge();
    var a = new A;
    var b = new B;
    //false,说明成功分开它们的原型
    console.log(A.prototype == B.prototype);
    //true,子类共享父类的原型方法
    console.log(a.aa === b.aa);
    //为父类动态添加新的原型方法
    A.prototype.bb = function() {
      alert(2)
    }
    //true,孩子总会得到父亲的遗产
    console.log(a.bb === b.bb);
    B.prototype.cc = function() {
      alert(3)
    }
    //false,但父亲未必有机会看到孩子的新产业
    console.log(a.cc === b.cc);
    //并且它能正常通过JS自带验证机制————instanceof
    console.log(b instanceof A);//true
    console.log(b instanceof B);//true

    并且也只有方法二能通过instanceof 验证。不过现在es5就内置了这种方法来实现原型继续,它就是Object.create,如果不考虑第二个参数,它约等于下面代码:

    Object.create = function (o) {  
        function F() {}  
        F.prototype = o;  
        return new F();  
    }  

    上面方法,要求传入一个父类的原型作为参数,然后返回子类的原型。

    不过,这样我们还是遗漏了一点东西——子类不只是继承父类的遗产,还拥有自己的东西,此外,原型继承并没有让子类继承父类的类成员与特权成员。这些我们还得手动添加,比如类成员,我们可以通过上面的extend方法,特权成员我们可以在子类的构造器中,通过apply实现,即:

    function inherit(init, Parent, proto){
        function Son(){
            Parent.apply(this,argument);//先继承父类的特权成员
            init.apply(this,argument);//再执行自己的构造器
        }
        //由于Object.create可能是我们伪造的,因此避免使用第二个参数
        Son.prototype = Object.create(Parent.prototype,{});
        Son.prototype.toString = Parent.prototype.toString;//处理IE BUG
        Son.prototype.valueOf = Parent.prototype.valueOf;//处理IE BUG
        Son.prototype.constructor = Son;//确保构造器正常指向自身,而不是Object
        extend(Son.prototype, proto);//添加子类特有的原型成员
        extend(Son, Parent);//继承父类的类成员
        return Son;
    }

    下面我们做一组实验,测试一下实例的回溯机制。许多资料都说,但总是语焉不详——当我们访问对象的一个属性,那么它先找其特权成员,如果有同名的就返回,没有找原型,再没有找父类的原型……我们尝试把它的原型临时修改一下,看它的属性会变成哪一个!

    function A() {}
    A.prototype = {
      aa: 1
    }
    var a = new A;
    console.log( a.aa);//1
    //把它整个原型对象都换掉
    A.prototype = {
      aa: 2
    }
    console.log(a.aa);//1,不受影响
    //于是我们想到实例都有一个constructor方法,指向其构造器,
    //而构造器上面正好有我们的原型,javascript引擎是不是通过该路线回溯属性呢
    function B(){}
    B.prototype = {
      aa: 3
    }
    a.constructor = B;
    console.log( a.aa );//1 不受影响

    因此类的实例肯定通过另一条通道进行回溯,翻看esma规范可知每一个对象都有一个内部属性[[Prototype]],它保存着当我们new它时构造器所引用的prototype对象。除IE外的浏览器都暴露了一个叫__proto__属性来访问它。因此只要不动__proto__,上面的代码怎么动,a.aa始终坚定不移的返回1。

    我们再来看一下new操作时发生了什么事。
    创建一个空对象instance
    instance.__proto__ = instanceClass.prototype
    将构造器函数里面的this = instance
    执行构造器里面的代码
    判定有没有返回值,没有返回值默认为undefined,如果返回值为复合数据类型,则直接返回,否则返回this。

    于是有下面结果:

    function A() {
      console.log(this.__proto__.aa); //1
      this.aa = 2
    }
    A.prototype = {
      aa: 1
    }
    var a = new A;
    console.log(a.aa); //2
    a.__proto__ = {
      aa: 3
    }
    delete a.aa; //删掉特权属性,暴露原型链上的同名属性
    console.log(a.aa); //3

    有了__proto__,我们可以将原型继承设计得更简洁。我们还是拿上面的例子改一下进行实验


    function A() {}
    A.prototype = {
      aa: 1
    }

    function bridge() {};
    bridge.prototype = A.prototype;

    function B() {}
    B.prototype = new bridge();
    B.prototype.constructor = B;
    var b = new B;
    B.prototype.cc = function() {
      alert(3)
    }
    console.log(b.__proto__ == B.prototype);
    //true 这个大家应该都没有疑问
    console.log(b.__proto__.__proto__ === A.prototype);
    //true  样就得到父类的原型对象

    为什么呢?因为b.__proto__.constructor为B,而B的原型是从bridge中得来的,而bridge.prototype = A.prototype。反过来,我们在定义时,让B.prototype.__proto__ = A.prototype,就轻松实现两个类的继承。

    有了__proto__,我们可以将原型继承设计得更简洁。我们还是拿上面的例子改一下进行实验:
    function A() {}
    A.prototype = {
      aa: 1
    }
    function B() {}
    B.prototype.__proto__ = A.prototype;
    B.prototype.constructor = B;
    var b = new B;
    B.prototype.bb = 2;
    for(var i in B.prototype){
        console.log(i)
    }
    // aa, bb 
  • 相关阅读:
    octotree神器 For Github and GitLab 火狐插件
    实用篇如何使用github(本地、远程)满足基本需求
    PPA(Personal Package Archives)简介、兴起、使用
    Sourse Insight使用过程中的常使用功能简介
    Sourse Insight使用教程及常见的问题解决办法
    github 遇到Permanently added the RSA host key for IP address '192.30.252.128' to the list of known hosts问题解决
    二叉查找树的C语言实现(一)
    初识内核链表
    container_of 和 offsetof 宏详解
    用双向链表实现一个栈
  • 原文地址:https://www.cnblogs.com/haohaoday/p/2914711.html
Copyright © 2020-2023  润新知