JavaScript繼承

默認的繼承方法:經過原型來實現繼承關係鏈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;
            };
        }

 繼承的代碼:java

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

 對對象的prototype屬性進行徹底替換時(不一樣於向prototype指向的對象添加屬性,有可能會對對象的constructor屬性產生必定的反作用),數組

因此對這些對象的constructor屬性進行相應的重置:ide

        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()的方法。this

2.再去查看my.__proto__所指向的對象,該對象應該是在繼承關係構建中由new  ToDShape()所建立的實體prototype

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()函數來接收父對象,並返回一個以對象爲原型的新對象

相關文章
相關標籤/搜索