• 讲解关于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 
  • 相关阅读:
    【转】如何高效地阅读技术类书籍与博客
    测试站点大全
    【转】软件测试面试- 购物车功能测试用例设计
    div+css 定位浅析
    C# Enum,Int,String的互相转换
    sqlserver 空间数据类型
    系统学习sqlserver2012 一
    sql查询数据库中所有表的记录条数,以及占用磁盘空间大小。
    linux网站推荐
    匿名用户访问sharepoint2010中的列表
  • 原文地址:https://www.cnblogs.com/haohaoday/p/2914711.html
Copyright © 2020-2023  润新知