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()函数来接收父对象,并返回一个以对象为原型的新对象

 

posted @ 2018-03-10 17:08  石shi  阅读(195)  评论(0编辑  收藏  举报