• JavaScript继承


    默认的继承方法:通过原型来实现继承关系链

      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();
            Triangle.prototype = new TwoDShape();
    

     对对象的prototype属性进行完全替换时(不同于向prototype指向的对象添加属性,有可能会对对象的constructor属性产生一定的副作用),

    所以对这些对象的constructor属性进行相应的重置:

            TwoDShape.prototype.constructor = TwoDShape;
            Triangle.prototype.constructor = Triangle;
    

     测试一下实现的内容:

      var my = new Triangle(5, 10);
            my.getArea();
    --25
    
       my.toString();
         --"Triangle"
    

     在JavaScript引擎在my.toString()被调用时发生的事情。

    1.会遍历my对象中的所有属性,没找到一个叫toString()的方法。

    2.再去查看my.__proto__所指向的对象,该对象应该是在继承关系构建中由new  ToDShape()所创建的实体

    3.JS在遍历ToDShape实体的过程中依然不会找到toString方法,然后又继续检查该实体的__proto__属性,该__proto__属性所指向的实体由new Shape()所创建

    4.在new  Shape()所创建的实体中找到了toString()方法

    5.该方法就会在my对象中被调用,并且其this指向了my

     my.constructor === Triangle;
    --true
    

     通过instanceof,可以验证my对象同时是上述三个构造器的实例:

           my instanceof Shape;
            --true
            my instanceof TwoDShape;
           --true
            my instanceof Triangle;
          --true
    
      my instanceof Array;
      --false
    

     以my参数调用这些构造器原型的isPropertypeOf()方法时,结果也是如此:

     Shape.prototype.isPrototypeOf(my);
      --true
    TwoDShape.prototype.isPrototypeOf(my);
    --true
    Triangle.prototype.isPrototypeOf(my);
    --true
    
      String.prototype.isPrototypeOf(my);
    --false
    

     用其他两个构造器来创建对象,用new TwoDShape()所创建的对象也可以获得继承自Shape()的toString()的方法。

           var td = new TwoDShape();
            td.constructor === TwoDShape;
           --true
    
     td.toString();
    "2D Shape"
    
           var s = new Shape();
            s.constructor === Shape;
            --true
    

     2.将共享属性迁移到原型中去:

    用某一个构造器创建对象时,其属性就会被添加到this中去,被添加的属性实际上不会随着实体改变,这种做法没有什么效率。

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

     用new Shape()创建的每个实体都会拥有一个全新的name属性,并在内存中拥有自己的独立存储空间,可以将name属性添加到原型上去,所有实体就可以共享这个属性

       function Shape() { }
            Shape.prototype.name = 'Shape';
    

     将所有的方法和符合条件的属性添加到原型对象中,Shape()和TwoDShape()而言,所有东西都是可以共享的

     function Shape() {
    
            }
                Shape.prototype.name = 'Shape';
                Shape.prototype.toString = function () {
                    return this.name;
                };
    
                function TwoDShape() { }
                TwoDShape.prototype = new Shape();
                TwoDShape.prototype.constructor = TwoDShape;
    
                TwoDShape.prototype.name = '2D shape';
          
                function Triangle(side, height) {
                    this.side = side;
                    this.height = height;
                }
                Triangle.prototype = new TwoDShape();
                Triangle.prototype.constructor = Triangle;
    
                Triangle.prototype.name = 'Triangle';
                Triangle.prototype.getArea = function () {
                    return this.side * this.height / 2;
                }
    
     var my = new Triangle(5, 10);
    
      my.getArea();
    --25
      my.toString();
    --"Triangle"
    

     也可以通过hasOwnPrototype()属性来明确对象的自身属性和原型属性

      my.hasOwnProperty('side');
    --true
    
     my.hasOwnProperty('name');
    false
    
    TwoDShape.prototype.isPrototypeOf(my);
    --true
    
      my instanceof Shape;
    --true
    

     3.只继承与原型:

    1.不要单独为继承关系创建新对象

    2.尽量减少运行时的方法搜索

     function Shape() { }
                Shape.prototype.name = 'shape';
                Shape.prototype.toString = function () {
                    return this.name;
                };
                function TwoDShape() { }
                    TwoDShape.prototype = Shape.prototype;
                    TwoDShape.prototype.constructor = TwoDShape;
                    TwoDShape.prototype.name = '2D Shape';
    
                    function Triangle(side, height) {
                        this.side = side;
                        this.height = height;
                    }
                    Triangle.prototype = TwoDShape.prototype;
                    Triangle.prototype.constructor = Triangle;
    
                    Triangle.prototype.name = 'Triangle';
                    Triangle.prototype.getArea = function () {
                        return this.side * this.height / 2;
                    }
    
                    var my = new Triangle(5, 10);
    
      my.getArea();
      - -25
    
      my.toString();
    --"Triangle"
    

     以上代码采用了引用传递而不是值传递。

    简单的拷贝原型在效率上来说固然好一些,但有他的副作用,子对象和父对象指向同一个对象,一旦子对象对其原型就行修改,父对象也会随即被改变,如:

     Triangle.prototype.name = 'Triangle';
                    var s = new Shape();
                    s.name;
    --"Triangle"

     效率高,应用场景中并不适用

    二:临时构造器——new F()

    解决上述问题就必须利用中介来打破这种连锁关系,可以用一个临时构造器函数来充当中介,创建一个空函数F(),将其原型设置为父级构造器。

      function Shape() {}
    
                    Shape.prototype.name = 'Shape';
                    Shape.prototype.toString = function () {
                        return this.name;
                    };
                    function TwoDShape() { }
                    var F=function(){};
                    F.prototype = Shape.prototype;
                    TwoDShape.prototype = new F();
                    TwoDShape.prototype.constructor = TwoDShape;
                    TwoDShape.prototype.name = '2D shape';
    
                    function Triangle(side, height) {
                        this.side = side;
                        this.height = height;
                    }
    
                    var F = function () { }
                    F.prototype = TwoDShape.prototype;
                    Triangle.prototype = new F();
                    Triangle.prototype.constructor = Triangle;
    
                    Triangle.prototype.name = 'Triangle';
                    Triangle.prototype.getArea = function () {
                        return this.side * this.height / 2;
                    }
    
     var my = new Triangle(5, 10);
                    my.getArea();
    --25
    
      my.toString();
    --"Triangle"
    

     通过这种方法,我们就可以保持住原型链:

      my.__proto__ === Triangle.prototype;
       --true
     my.__proto__.constructor == Triangle;
      --true
     my.__proto__.__proto__ === TwoDShape.prototype;
    --true
      my.__proto__.__proto__.__proto__.constructor === Shape;
    --true
    

     并且父对象的属性不会被子对象所覆盖:

     var s = new Shape();
                    s.name;
    --"Shape"
    
    "I am a " + new TwoDShape();
    --"I am a 2D shape"
    

     将所有要共享的属性与方法添加到原型中,然后只围绕原型构建继承关系。

    3.uber--子对象访问父对象的方式(指向父级原型对象)

     function Shape() { }
                    Shape.prototype.name = 'shape';
                    Shape.prototype.toString = function () {
                        //var const1 = this.constructor;
                        return this.constructor.uber
                            ? this.constructor.uber.toString() + ', ' + this.name
                            : this.name;
                    };
    
                    function TwoDShape() { }
                    var F = function () { };
                    F.prototype = Shape.prototype;
                    TwoDShape.prototype = new F();
                    TwoDShape.prototype.constructor = TwoDShape;
                    TwoDShape.uber = Shape.prototype;
                    TwoDShape.prototype.name = '2D shape';
    
                    function Triangle(side, height) {
                        this.side = side;
                        this.height = height;
                    }
    
                    var F = function () { };
                    F.prototype = TwoDShape.prototype;
                    Triangle.prototype = new F();
                    Triangle.prototype.constructor = Triangle;
                    Triangle.uber = TwoDShape.prototype;
                    Triangle.prototype.name = 'Triangle';
                    Triangle.prototype.getArea = function () {
                        return thi.side * this.height / 2;
                    };
            
                    var my = new Triangle(5, 10);
                    my.toString();
    --"shape, 2D shape, Triangle"
    

    增加以下内容:

    1.将uber属性设置成指向其父级原型的引用

    2.对toString()方法进行了更新

    检查对象中是否存在this.constructor.uber属性,如果存在,就先调用该属性的toString方法,由于this.constructor本身是一个函数,而this.constructor.uber则是指向当前对象父级原型的引用。

    当调用Triangle实体的toString()方法时,其原型链上所用的toString()都会被调用。

    4.将继承部分封装成函数:

      function extend(Child, Perent) {
                      var F = function () { };
                      F.prototype = Perent.prototype;
                      Child.prototype = new F();
                      Child.prototype.constructor = Child;
                      Child.uber = Perent.prototype;
                  }
    
                  function Shape() { };
                  Shape.prototype.name = 'Shape';
                  Shape.prototype.toString = function () {
                      return this.constructor.uber
                          ? this.constructor.uber.toString() + ', ' + this.name
                          : this.name;
                  };
                  function TwoDShape() { };
                  extend(TwoDShape, Shape);
                  TwoDShape.prototype.name = '2D shape';
    
                  function Triangle(side, height) {
                      this.side = side;
                      this.height = height;
                  }
                  extend(Triangle, TwoDShape);
                  Triangle.prototype.name = 'Triangle';
                  Triangle.prototype.getArea = function () {
                      return this.side * this.height / 2;
                  }
    
                  new Triangle().toString();
    --"Shape, 2D shape, Triangle"
    

     6.属性拷贝

    将父对象的属性拷贝给子对象,

       function extend2(Child, Parent) {
                      var p = Parent.prototype;
                      var c = Child.prototype;
                      for (var i in p) {
                          c[i] = p[i];
                      }
                      c.uber = p;
                  }
    

    这种方法仅适用于只包含基本数据类型的对象,所有的对象类型(包括函数与数组)都是不可复制的,他们只支持引用传递。

    Shape的原型中包含了一个基本类型属性name,和一个非基本类型属性---toString()方法

     var Shape = function () { };
                  var TwoDShape = function () { };
                  Shape.prototype.name = 'shape';
                  Shape.prototype.toString = function () {
                      return this.uber
                      ? this.uber.toString() + ', ' + this.name
                          : this.name;
                  };
    

     通过extend()方法实现继承,name属性既不会是TwoDShape()实例的属性,也不会成为其原型对象的属性,但是子对象依然可以通过继承方式来访问该属性

        extend(TwoDShape, Shape);
                  var td = new TwoDShape();
                  td.name;
    --"shape"
     TwoDShape.prototype.name;
    
    --"shape"
    
     td.__proto__.name;
    --"shape"
    
    td.hasOwnProperty('name');
    --false
    
     td.__proto__.hasOwnProperty('name');
    --false
    

     继承通过extend2()方法来实现,TwoDShape()的原型中就会拷贝获得属于自己的name属性,同样也会拷贝toString()方法,但这只是一个函数引用,函数本身并没有被再次创建

      extend2(TwoDShape, Shape);
                  var td = new TwoDShape();
                  td.__proto__.hasOwnProperty('name');
    --true
     td.__proto__.hasOwnProperty('toString');
    --true
    
    td.__proto__.toString === Shape.prototype.toString;
    --true
    

     extend2()方法的效率要低于extend()方法,主要是前者对部分原型属性进行了重建

     td.toString();
    --"shape, shape"
    

     TwoDShape并没有重新定义name属性,所以打印了两个Shape,可以在任何时候重新定义name属性,

     TwoDShape.prototype.name = "2D shape";
                  td.toString();
    --"shape, 2D shape"
    

    6.小心处理引用拷贝

    对象类型(包括函数与数组)通常都是以引用形式来进行拷贝的,会导致一些预期不同的结果:

                  function Papa() { }
                  function Wee() { }
                  Papa.prototype.name = 'Bear';
                  Papa.prototype.owns = ["porridge", "chair", "bed"];
    

    让Wee继承Papa(通过extend()或extend2()来实现):

     extend2(Wee, Papa);
    

     即Wee的原型继承了Papa的原型属性,并将其变成了自身属性

      Wee.prototype.hasOwnProperty('name');
    ---true
     Wee.prototype.hasOwnProperty('owns');
    ---true
    

     name属于基本类型属性,创建的是一份全新的拷贝,owns属性是一个数组对象,它执行的引用拷贝。

    Wee.prototype.owns;
    -- ["porridge", "chair", "bed"]
    Wee.prototype.owns === Papa.prototype.owns;
    --true
    

     改变Wee中的name属性,不会对Papa产生影响:

     Wee.prototype.name += ', Little Bear';
    --"Bear, Little Bear"
    
    Papa.prototype.name;
    --"Bear"
    

     如果改变的是Wee的owns属性,Papa就会受到影响,这两个属性在内存中引用的是同一个数组:

    --pop() 方法用于删除并返回数组的最后一个元素。

    Wee.prototype.owns.pop();
    --"bed"
    
     Papa.prototype.owns;
    --["porridge", "chair"]
    

     用另一个对象对Wee的owns属性进行完全重写(不是修改现有属性),这种情况下,Papa的owns属性将会继续引用原有对象,而Wee的owns属性指向了新对象。

     Wee.prototype.owns = ["empty bowl", "broken chair"];
                  Papa.prototype.owns.push('bed');
                  Papa.prototype.owns;
    -- ["porridge", "chair", "bed"]
    

    7.对象之间的继承

    在对象之间进行直接属性拷贝

    用var o={}语句创建一个没有任何私有属性的“空”对象作为画板,逐步为其添加属性,将现有对象的属性全部拷贝过来

    function extendCopy(p) {
                      var c = {};
                      for (var  i in p ) {
                          c[i] = p[i];
                      }
     var twoDee = extendCopy(shape);
                  twoDee.name = '2D shape';
                  twoDee.toString = function () {
                      return this.uber.toString() + ', ' + this.name;
                  };
    
    c.uber = p; return c; }

     创建一个基本对象:

    var shape = {
                      name: 'Shape',
                      toString: function () {
                          return this.name;
                      }
                  };
    

     根据就对象来创建一个新对象,调用extendCopy()函数,返回一个新对象,继续对这个新对象进行扩展,添加额外的功能

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

     让triangle对象继承一个2D图形对象。

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

     使用triangle:

                  triangle.side = 5;
                  triangle.height = 10;
                  triangle.getArea();
    --25
    triangle.toString();
    ---"Shape, 2D shape, Triangle"

    8.深拷贝

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

    当对象被拷贝时,实际上拷贝的只是该对象在内存中的位置指针----浅拷贝(如果我们修改了拷贝对象,就等于修改了原对象)

     function deepCopy(p, c) {
                      c = c || {};
                      for (var i 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;
                  }
    

          //创建一个对象,包含数组和子对象

     var parent = {
                      numbers: [1, 2, 3],
                      letters: ['a', 'b', 'c'],
                      obj: {
                          prop: 1
                      },
                      bool: true
                  };
    

     在深拷贝中,对拷贝对象的numbers属性进行更改不会对原对象产生影响

     var mydeep = deepCopy(parent);
                  var myshallow = extendCopy(parent);
                  mydeep.numbers.push(4, 5, 6);
    
    mydeep.numbers;
    ---- [1, 2, 3, 4, 5, 6]
    
     parent.numbers;
    ---- [1, 2, 3]
    
     myshallow.numbers.push(10);
    --4
    
    myshallow.numbers;
    -- [1, 2, 3, 10]
    
    parent.numbers;
    -- [1, 2, 3, 10]
    
    mydeep.numbers;
    --- [1, 2, 3, 4, 5, 6]
    

    push:方法将一个或多个元素添加到数组的末尾,并返回新数组的长度 

    使用deepCopy()函数注意的地方:

    1.在拷贝每个属性之前,使用hasOwnProperty()来确认不会误拷贝不需要的继承属性

    2.区分Array对象和普通Object对象相当繁琐,ES5实现了Array.isArray()函数。

                  if (Array.isArray != "function") {
                      Array.isArray = function (candidate) {
                          return Object.prototype.toString.call(candidate) === '[Object Array]';
                      };
                  }
    

     9.Object()

    基于这种在对象之间直接构建构建继承关系的理念,即可以用Object()函数来接收父对象,并返回一个以对象为原型的新对象

  • 相关阅读:
    利用for循环 修改精灵图背景位置
    添加列表项 避免浏览器反复渲染 Fragment
    向元素添加属性名和属性值
    分割文本节点
    查询、回显 基本功能
    获取注释
    合并文本节点
    Node(节点)的4个操作方法
    setTimeout与setInterval
    javascript循环
  • 原文地址:https://www.cnblogs.com/sunliyuan/p/8540723.html
Copyright © 2020-2023  润新知