005 - Js中继承的实现方式和优化

在上边博文,简单讲了对象和函数中的几个属性,总结如下

prototypeJs中函数特有一个属性,指向这个函数的原型对象
_proto_Js中对象特有的一个属性,指向对象的原型对象
constructorJs对象特有的一个属性,指向这个对象的构造函数,每个对象都会有构造函数,只不过函数对象的构造函数是其自身

同时我们也了解,Javascript是通过new 构造函数创建对象

function Shape() {
  this.name = 'Shape';
  this.toString = function () {
    return this.name;
  };
}
var aShape = new Shape() ; 

有了上边几个概念,设计师就可以通过这些概念组合,形成Js基于原型的继承方式,接下来,由浅及深,从个人角度,去考虑,设计师如何考虑继承

原型链是通过 _proto_、 prototype 、constructor实现的 , 因此想要修改继承关系,通过修改构造函数的原型对象,就会让修改后的构造函数,new 创建的对象,满足继承关系

比如下边是三个构造函数 ,它们此时是没有任何关系的,接下来,我们通过修改 原型对象,让他们形成继承关系

  // 构造器函数
function Shape() {
  this.name = 'Shape';
  this.toString = function () {
    return this.name;
  };
}

function TwoDShape() {
  this.name = '2D shape';
}

function Triangle(side, height) {
  this.name = 'Triangle';
  this.side = side;
  this.height = height;
  this.getArea = function () {
    return this.side * this.height / 2;
  };
}
方式一 : 原型指向 “父亲的对象”
  • 让儿子的构造函数原型指向的是,用父亲构造函数 新创建的对象,这样的有点是,修改父亲的原型,不会影响子类。语法如下

    TwoDShape.prototype = new Shape();
    TwoDShape.prototype.constructor = TwoDShape
    

    具体代码如下

    TwoDShape.prototype = new Shape();
    TwoDShape.prototype.constructor = TwoDShape
    Triangle.prototype = new TwoDShape();
    Triangle.prototype.constructor = Triangle
    
    

    测试一下 , TwoDShape 创建的对象name属性是不是 自己,删除自己的name属性之后,能否获取到父亲的name属性,输出结果 证明了 两者存在父子关系

    var tds = new TwoDShape()
    console.log(tds.name) // 输出 2D shape
    delete tds.name
    console.log(tds.name)  // 输出 Shape
    

    在看一下 tds的 _proto_ 指向的是不是 创建的 New Shape()

     console.log(tds.__proto__)   
    //输出 :  Shape {name: "Shape", toString: ƒ, constructor: ƒ}
    // 说明 TwoDShape 确实 实现了 "继承" 这一功能
    
    // 测试 原型是否是新建对象的原型
    console.log(TwoDShape.prototype.isPrototypeOf(tds)) // true
    console.log(Shape.prototype.isPrototypeOf(tds)) // true 
    
  • 实现继承之后, 以调用toString()方法为例,看Js引擎如何查找一个对象的属性(方法)

    var my = new Triangle(5, 10);
    console.log(my.toString())   //Triangle
    
  1. 它会遍历 my 对象中的所有属性,但没有找到
  2. 去查看 my._proto_所指向的对象 (该对象应该是在继承关系构建过程中由 new TwoDShape()所创建的实体) , 但没有找到
  3. 继续检查该实体的_proto_属性。 (该_proto_属性所指向的实体是由 new Shape()所创建的) , 成功找到
  4. 调用方法(方法中this 绑定的是调用此方法的对象)
方式二 : 只继承原型
  • 方式一的优缺点

    继承的是父亲构造函数创建的一个对象,这样的设计,修改父亲的原型时,不会影响子类。

    var par = function (){this.name = 'par',this.pro = 1}
    var son = function(){this.age = 11;this.name = 'son'}
    son.prototype = new par()
    son.prototype.constructor = son
    var s1 = new son()
    console.log(s1.pro) ; //输出 1
    

    如果父亲的原型也有一个 pro属性,修改了父亲原型,发现并不会影响儿子

    par.prototype.pro = 2
    console.log(s1.pro) ; //输出 1
    

    当然 和接下来方式一笔,缺点就是 原型指向的是一个new的对象,一是消耗大,二是要找父亲原型中的属性,需要多找一层对象

  • 继承方式二 : 直接继承原型的方式

    TwoDShape.prototype = Shape.prototype;
    TwoDShape.prototype.constructor = TwoDShape;
    

    测试代码 :

    TwoDShape.prototype.name = '2D shape';
    // 测试代码
    var tds = new TwoDShape();
    tds.toString(); //输出 2D shape
    

    上边代码优势:
    执行 tds.toString() 时, 先去找 tds对象,没找到 ; 再去找该对象的原型属性(指向Shape的原型) ;因为都是引用传递,所以速度块。
    另一个方面,可以满足这么一种需求 , 子对象对原型做修改,同时父对象也会被修改

    Shape.prototype.ps = '111';
    TwoDShape.prototype = Shape.prototype // 本质上只有一个 原型对象
    TwoDShape.prototype.ps = '2D shape';
    var s = new Shape();
    console.log(s.ps) // 输出  2D shape
    
方式三: 创建临时构造器
  • 方式一和方式二的问题

    方式一 继承的是父亲构造函数创建的对象,方式二是直接继承父亲的对象。方式一效率低,方式二修改父亲的原型会影响子类,有副作用

  • 创建的临时构造器

    创建一个临时构造函数,让这构造函数指向父亲的原型,然后继承的是这个临时的构造函数

    var F = function() {};
    F.prototype = Shape.prototype;
    TwoDShape.prototype = new F();
    TwoDShape.prototype.constructor = TwoDShape
    
    var F = function(){};
    F.prototype = TwoDShape.prototype;
    Triangle.prototype = new F();
    Triangle.prototype.constructor = Triangle;
    
优化 : 将继承的代码放到原型之中
  • 让我们能通过以下简单的调用来实现继承

    function extend(Child, Parent) {
      // 创建临时函数, 直接继承原型,儿子的原型通过new临时函数,
      var F = function(){};
      F.prototype = Parent.prototype;
      Child.prototype = new F();
      Child.prototype.constructor = Child;
      Child.uber = Parent.prototype;
    }
    

上边三种方式是通过修改儿子的原型,让儿子构造函数的原型对象能够连接到父亲,接下来还有另外的方式,直接copy属性

属性之间copy
  • 在构建可重用的继承代码时,我们也可以简单地将父对象的属性拷贝给子对象

  • 属性copy 实现代码复用

    function extend2(Child, Parent) {
      var p = Parent.prototype;
      var c = Child.prototype;
      for (var i in p) {
     		 c[i] = p[i];
      }
      // 子对象需要访问父对象的方法,我们可以通过设置 uber 属性来实现
      c.uber = p;
    }
    
    • 子对象需要访问父对象的方法,我们可以通过设置 uber 属性来实现
    var Shape = function(){};
    var TwoDShape = function(){};
    Shape.prototype.name = 'shape';
    Shape.prototype.toString = function(){
      return this.uber
        ? this.uber.toString() + ', ' + this.name
        : this.name;
    };
    
深copy
  • 深拷贝的实现方式

    与浅拷贝基本相同,也需要通过遍历对象的属性来进行拷贝操作。只是在遇到一个对象引用性的属性时,我们需要再次对其调用深拷贝函数

    function deepCopy(p, c) {
      c = c || {};
      for (vari in p) {
        if (p.hasOwnProperty(i)) {
          if (typeof p[i] === 'object') {
            c[i] = Array.isArray(p[i]) ? [] : {};
            deepCopy(p[i], c[i]);
          } else {
         		 c[i] = p[i];
          }
        }
      }
      return c;
    }
    
  • 在拷贝每个属性之前,建议使用 hasOwnProperty()来确认不会误拷贝不需要
    的继承属性

  • 由于区分 Array 对象和普通 Object 对象相当繁琐,所以 ES5 标准中实现了
    Array.isArray()函数

子类访问父类属性 : 类似 java 中的 super
  • 下边代码中 在构建原型继承关系时,指定一个自定义属性 uber 指向父的原型

  • 在调用方法内 (toString () )中 ,使用 uber属性

    function Shape(){}
    // augment prototype
    Shape.prototype.name = 'shape';
    Shape.prototype.toString = function(){
        var const = this.constructor;
        return const.uber 
          ? this.const.uber.toString() + ', ' + this.name
          : this.name;
    };
    function TwoDShape(){}
    // take care of inheritance
    var F = function(){};
    F.prototype = Shape.prototype;
    TwoDShape.prototype = new F();
    TwoDShape.prototype.constructor = TwoDShape;
    TwoDShape.uber = Shape.prototype;
    // augment prototype
    TwoDShape.prototype.name = '2D shape';
    function Triangle(side, height) {
      this.side = side;
      this.height = height;
    }
    
Object()
  • 可以用object()函数来接收父对象,并返回一个以该对象为原型的新对象
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}
  • 如果需要访问 uber属性,可以继续object() 函数
function object(o) {
  var n;
  function F() {}
  F.prototype = o;
  n = new F();
  n.uber = o;
  return n;
}
原型继承与属性copy混合应用

具体而言就是:

  • 使用原型继承的方式,将一个已有对象设置为新对象的原型。

  • 新建一个对象后,将另一个已有对象的所有属性拷贝过来

    // 其中对象 o 用于继承,而另一个对象 stuff 则用于拷贝方法与属性
    function objectPlus(o, stuff) {
        var n;
        function F() {}
        F.prototype = o;
        n = new F();
        n.uber = o;
        for (var i in stuff) {
        	n[i] = stuff[i];
        }
        return n;
    }
    
  • 测试代码


var shape = {
    name: 'shape',
    toString: function() {
    		return this.name;
    }
};
var twoDee = objectPlus(shape, {
  name: '2D shape',
  toString: function(){
  		return this.uber.toString() + ', ' + this.name;
  }
});

var triangle = objectPlus(twoDee, {
name: 'Triangle',
  getArea: function(){
 		 return this.side * this.height / 2;
  },
  side: 0,
  height: 0
});

var my = objectPlus(triangle, {
		side: 4, height: 4
});
my.getArea()); // 8
console.log(my.toString()); // "shape, 2D shape, Triangle, Triangle"
多重继承
  • 多重继承,通常指的是一个子对象中有不止一个父对象的继承模式

  • 注意的是, multi()中的循环是按照对象的输入顺序来进行遍历的。如果其中两个
    对象拥有相同的属性,前一个就会被后一个覆盖

function multi() {
  var n = [],
      stuff, j = 0,
      len = arguments.length;

  for (j = 0; j < len; j++) {
    stuff = arguments[i];
    for (var i in stuff) {
      n[i] = stuff[i];
    }
  }
  return n;
}
  • 用到了混合插入的理念

混合插入( mixins)的技术。我们可以将其看做一种为对象提供某些实用功能的技术,每当我们新建一个对象时,可以选择将其他对象的内容混合到我们的新对象中去

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值