面向对象的程序设计

一、理解对象

  • 定义对象编程

    • 建立一个 Object 的实例数组

      var person = new Object();
      person.name = "Nicholas";
      person.age = 29;
      person.job = "Software Engineer";
      person.sayName = function(){
          alert(this.name);//"Nicholas"
      };
    • 使用对象字面量语法浏览器

      var person = {
          name: "Nicholas",
          age: 29,
          job: "Software Engineer",
          sayName: function(){
              alert(this.name);
          }
      };
  • 属性类型安全

    • 数据属性:包含一个数据值的位置,在这个位置能够读取和写入值app

      • [[Configurable]]:表示可否经过 delete 删除属性从而从新定义属性,可否修改属性的特性,或者可否把属性修改成访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true函数

      • [[Enumerable]]:表示可否经过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。测试

      • [[Writable]]:表示可否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 trueui

      • [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefinedthis

      • 使用Object.defineProperty()方法:接收三个参数:属性所在的对象、属性的名字和一个描述符对象;其中,描述符(descriptor)对象的属性必须是:configurable、enumerable、writable 和 valuegoogle

        var person = {};
        Object.defineProperty(person, "name", {
          writable: false,
          value: "Nicholas"
        });
        alert(person.name); //"Nicholas"
        person.name = "Greg";
        alert(person.name); //"Nicholas"
    • 访问器属性

      • [[Configurable]]:表示可否经过 delete 删除属性从而从新定义属性,可否修改属性的特性,或者可否把属性修改成数据属性。对于直接在对象上定义的属性,这个特性的默认值为true

      • [[Enumerable]]:表示可否经过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为 true

      • [[Get]]:在读取属性时调用的函数。默认值为 undefined

      • [[Set]]:在写入属性时调用的函数。默认值为 undefined

      • 使用Object.defineProperty()方法操做

        var book = {
          _year: 2004,
          edition: 1
        };
        Object.defineProperty(book, "year", {
          get: function(){
              return this._year;
          },
          set: function(newValue){
              if (newValue > 2004) {
                  this._year = newValue;
                  this.edition += newValue - 2004;
              }
          }
        });
        book.year = 2005;
        alert(book.edition); //2
      • 在不支持 Object.defineProperty() 方法的浏览器中使用两个非标准的方法defineGetter__()__defineSetter__()操做,可是不能修改 [[Configurable]] 和[[Enumerable]]。

        var book = {
          _year: 2004,
          edition: 1
        };
        //定义访问器的旧有方法
        book.__defineGetter__("year", function(){
          return this._year;
        });
        book.__defineSetter__("year", function(newValue){
          if (newValue > 2004) {
              this._year = newValue;
              this.edition += newValue - 2004;
          }
        });
        book.year = 2005;
        alert(book.edition); //2
  • 定义多个属性

    • Object.defineProperties()方法:经过描述符一次定义多个属性

    • 接收两个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应

      var book = {};
      Object.defineProperties(book, {
          _year: {
              value: 2004
          },
          edition: {
              value: 1
          },
          year: {
              get: function(){ 
                  return this._year;
              },
              set: function(newValue){
                  if (newValue > 2004) {
                      this._year = newValue;
                      this.edition += newValue - 2004;
                  }
              }
          }
      });
  • 读取属性的特性

    • Object.getOwnPropertyDescriptor()方法;接收两个参数,属性所在的对象和要读取其描述符的属性名称;

    • 返回值是一个对象,若是是访问器属性,这个对象的属性有 configurable、enumerable、get 和 set;若是是数据属性,这个对象的属性有 configurable、enumerable、writable 和 value

      var book = {};
      Object.defineProperties(book, {
          _year: {
              value: 2004
          },
          edition: {
              value: 1
          },
          year: {
              get: function(){
              return this._year;
          },
          set: function(newValue){
                  if (newValue > 2004) {
                      this._year = newValue;
                      this.edition += newValue - 2004;
                  }
              }
          }
      });
      
      var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
      alert(descriptor.value); //2004
      alert(descriptor.configurable); //false 
      alert(typeof descriptor.get); //"undefined"
      
      var descriptor = Object.getOwnPropertyDescriptor(book, "year");
      alert(descriptor.value); //undefined
      alert(descriptor.enumerable); //false
      alert(typeof descriptor.get); //"function"

二、建立对象

  • Object 构造函数或对象字面量能够建立单个对象,但使用同一个接口建立不少对象,会产生大量的重复代码

    var person = new Object();
    var person = {};
  • 工厂模式,解决了建立多个类似对象的问题,但却没有解决对象识别的问题(对象的类型)

    function createPerson(name, age, job){
      var o = new Object();
      o.name = name;
      o.age = age;
      o.job = job;
      o.sayName = function(){
          alert(this.name);
      };
      return o;
    }
    var person1 = createPerson("Nicholas", 29, "Software Engineer");
    var person2 = createPerson("Greg", 27, "Doctor");
  • 构造函数模式:

    • 语法:构造函数始终都应该以一个大写字母开头,建立实例必须使用 new 操做符

      function Person(name, age, job){
          this.name = name;
          this.age = age;
          this.job = job;
          this.sayName = function(){
              alert(this.name);
          };
      }
      var person1 = new Person("Nicholas", 29, "Software Engineer");
      var person2 = new Person("Greg", 27, "Doctor");
    • 与工厂模式的不一样之处

      • 没有显式地建立对象
      • 直接将属性和方法赋给了 this 对象
      • 没有 return 语句
    • 调用构造函数建立实例后台经历的步骤:

      • 建立一个新对象
      • 将构造函数的做用域赋给新对象(所以 this 就指向了这个新对象)
      • 执行构造函数中的代码(为这个新对象添加属性)
      • 返回新对象
    • 实例具备constructor(构造函数)属性:用来标识对象类型的

    • 使用instanceof操做符检测对象类型

      alert(person1 instanceof Object); //true
      alert(person1 instanceof Person); //true
      alert(person2 instanceof Object); //true
      alert(person2 instanceof Person); //true
    • 将构造函数看成函数

      // 看成构造函数使用
      var person = new Person("Nicholas", 29, "Software Engineer");
      person.sayName(); //"Nicholas"
      // 做为普通函数调用
      Person("Greg", 27, "Doctor"); // 添加到 window
      window.sayName(); //"Greg"
      // 在另外一个对象的做用域中调用
      var o = new Object();
      Person.call(o, "Kristen", 25, "Nurse");
      o.sayName(); //"Kristen"
    • 构造函数的问题:

      • 每一个方法都要在每一个实例上从新建立一遍

        function Person(name, age, job){
          this.name = name;
          this.age = age;
          this.job = job;
          this.sayName = new Function("alert(this.name)");//与声明函数在逻辑上是等的
        } 
        alert(person1.sayName == person2.sayName); //false
      • 经过把函数定义转移到构造函数外部来解决这个问题

        function Person(name, age, job){
          this.name = name;
          this.age = age;
          this.job = job;
          this.sayName = sayName;
        }
        function sayName(){
         alert(this.name);
        }
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
        //新问题1:在全局做用域中定义的函数实际上只能被某个对象调用,这让全局做用域有点名存实亡
        //新问题2:若是对象须要定义不少方法,那么就要定义不少个全局函数,没有封装性
  • 原型模式

    • prototype(原型)属性:每一个函数都有这个属性,是一个指针,指向一个包含能够由特定类型的全部实例共享的属性和方法的对象

      function Person(){};
      Person.prototype.name = "Nicholas";
      Person.prototype.age = 29;
      Person.prototype.job = "Software Engineer";
      Person.prototype.sayName = function(){
          alert(this.name);
      };
      var person1 = new Person();
      person1.sayName(); //"Nicholas"
      var person2 = new Person(); 
      person2.sayName(); //"Nicholas"
      alert(person1.sayName == person2.sayName); //true
    • 理解原型对象

      • 只要建立了一个新函数,就会根据一组特定的规则为该函数建立一个 prototype(__proto__)属性,这个属性指向函数的原型对象,原型对象都会自动得到一个 constructor(构造函数)属性

      • isPrototypeOf()方法:来肯定对象之间是否存在[[Prototype]]原型关系

        alert(Person.prototype.isPrototypeOf(person1)); //true
        alert(Person.prototype.isPrototypeOf(person2)); //true
      • Object.getPrototypeOf()方法:返回[[Prototype]]的值

        alert(Object.getPrototypeOf(person1) == Person.prototype); //true
        alert(Object.getPrototypeOf(person1).name); //"Nicholas"
      • 多个对象实例共享原型所保存的属性和方法的基本原理:经过层层搜索查找对象属性,首先从对象实例自己开始,找不到则继续搜索指针指向的原型对象,在原型对象中查找具备给定名字的属性

      • 能够经过对象实例访问保存在原型中的值,但不能经过对象实例重写原型中的值,当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性

      • 使用 delete 操做符则能够彻底删除实例属性,从而可以从新访问原型中的属性

        function Person(){}
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){
          alert(this.name);
        };
        var person1 = new Person();
        var person2 = new Person();
        person1.name = "Greg";
        alert(person1.name); //"Greg"——来自实例
        alert(person2.name); //"Nicholas"——来自原型
        delete person1.name;
        alert(person1.name); //"Nicholas"——来自原型
      • hasOwnProperty()方法:检测一个属性是存在于实例中,仍是存在于原型中

        function Person(){}
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){ 
            alert(this.name);
        };
        var person1 = new Person();
        var person2 = new Person();
        alert(person1.hasOwnProperty("name")); //false
        person1.name = "Greg";
        alert(person1.name); //"Greg"——来自实例
        alert(person1.hasOwnProperty("name")); //true
        alert(person2.name); //"Nicholas"——来自原型
        alert(person2.hasOwnProperty("name")); //false
        delete person1.name;
        alert(person1.name); //"Nicholas"——来自原型
        alert(person1.hasOwnProperty("name")); //false
    • 原型与 in 操做符

    • 单独使用in 操做符:在经过对象可以访问给定属性时返回 true,不管该属性存在于实例中仍是原型中

      function Person(){}
      Person.prototype.name = "Nicholas";
      Person.prototype.age = 29;
      Person.prototype.job = "Software Engineer";
      Person.prototype.sayName = function(){
        alert(this.name);
      };
      
      var person1 = new Person();
      var person2 = new Person();
      
      alert(person1.hasOwnProperty("name")); //false
      alert("name" in person1); //true
      
      person1.name = "Greg";
      alert(person1.name); //"Greg" ——来自实例
      alert(person1.hasOwnProperty("name")); //true
      alert("name" in person1); //true
      
      alert(person2.name); //"Nicholas" ——来自原型
      alert(person2.hasOwnProperty("name")); //false
      alert("name" in person2); //true
      
      delete person1.name;
      alert(person1.name); //"Nicholas" ——来自原型
      alert(person1.hasOwnProperty("name")); //false
      alert("name" in person1); //true
    • hasPrototypeProperty()方法:同时使用 hasOwnProperty()方法和 in操做符,就能够肯定该属性究竟是存在于对象中,仍是存在于原型中

      function hasPrototypeProperty(object, name){
        return !object.hasOwnProperty(name) && (name in object);
      } 
      
      function Person(){}
      Person.prototype.name = "Nicholas";
      Person.prototype.age = 29;
      Person.prototype.job = "Software Engineer";
      Person.prototype.sayName = function(){
        alert(this.name);
      };
      var person = new Person();
      alert(hasPrototypeProperty(person, "name")); //true
      person.name = "Greg";
      alert(hasPrototypeProperty(person, "name")); //false
    • 在for-in 循环里使用:返回的是全部可以经过对象访问的、可枚举的(enumerated)属性,其中
      既包括存在于实例中的属性,也包括存在于原型中的属性

      var o = {
        toString : function(){
            return "My Object";
        }
      };
      for (var prop in o){
        if (prop == "toString"){
        alert("Found toString"); //在 IE 中不会显示
       }
      }
    • Object.keys()方法:接收一个对象做为参数,返回一个包含全部可枚举属性的字符串数组

      function Person(){}
      Person.prototype.name = "Nicholas";
      Person.prototype.age = 29;
      Person.prototype.job = "Software Engineer";
      Person.prototype.sayName = function(){
        alert(this.name);
      };
      var keys = Object.keys(Person.prototype);
      alert(keys); //"name,age,job,sayName"
      var p1 = new Person();
      p1.name = "Rob";
      p1.age = 31;
      var p1keys = Object.keys(p1);
      alert(p1keys); //"name,age"
    • Object.getOwnPropertyNames()方法:返回全部实例属性,不管它是否可枚举

      var keys = Object.getOwnPropertyNames(Person.prototype);
      alert(keys); //"constructor,name,age,job,sayName"
    • 更简单的原型语法

    • 用一个包含全部属性和方法的对象字面量来重写整个原型对象,可是致使constructor 属性再也不指向 Person

      function Person(){}
      Person.prototype = {
        name : "Nicholas",
        age : 29,
        job: "Software Engineer",
        sayName : function () {
            alert(this.name);
        }
      }; 
      
      var friend = new Person();
      alert(friend instanceof Object); //true
      alert(friend instanceof Person); //true
      alert(friend.constructor == Person); //false
      alert(friend.constructor == Object); //true
    • 解决办法:从新设置个constructor 属性的指向,可是致使它的[[Enumerable]]特性被设置为 true,默认状况下,原生的 constructor 属性是不可枚举的

      function Person(){}
      Person.prototype = {
        constructor : Person,
        name : "Nicholas",
        age : 29,
        job: "Software Engineer",
        sayName : function () {
            alert(this.name);
        }
      };
    • 解决办法:使用Object.defineProperty()方法重设构造函数

      function Person(){}
      Person.prototype = {
        name : "Nicholas",
        age : 29,
        job : "Software Engineer",
        sayName : function () {
            alert(this.name);
        }
      }; 
      //重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
      Object.defineProperty(Person.prototype, "constructor", {
        enumerable: false,
        value: Person
      });
    • 原型的动态性

      • 对原型对象所作的任何修改都可以当即从实例上反映出来

        var friend = new Person();
        Person.prototype.sayHi = function(){
          alert("hi");
        };
        friend.sayHi(); //"hi"(没有问题!)
      • 实例中的指针仅指向原型,而不指向构造函数

        function Person(){}
        var friend = new Person();
        Person.prototype = {
          constructor: Person,
          name : "Nicholas",
          age : 29,
          job : "Software Engineer",
          sayName : function () {
              alert(this.name);
          }
        };
        friend.sayName(); //error 
        //调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,
        //而把原型修改成另一个对象就等于切断了构造函数与最初原型之间的联系
    • 原生对象的原型

      • 全部原生引用类型(Object、Array、String,等等)都在其构造函数的原型上定义了方法

        alert(typeof Array.prototype.sort); //"function"
        alert(typeof String.prototype.substring); //"function"
      • 经过原生对象的原型,不只能够取得全部默认方法的引用,并且也能够定义新方法

        String.prototype.startsWith = function (text) {
          return this.indexOf(text) == 0;
        };
        var msg = "Hello world!";
        alert(msg.startsWith("Hello")); //true
    • 原型对象的问题

      • 全部实例在默认状况下都将取得相同的属性值

      • 原型中全部属性是被不少实例共享的,这种共享对于函数很是合适,对于包含引用类型值的属性来讲会出现问题

        function Person(){}
        Person.prototype = {
          constructor: Person,
          name : "Nicholas",
          age : 29,
          job : "Software Engineer",
          friends : ["Shelby", "Court"],
          sayName : function () {
              alert(this.name);
          }
        };
        var person1 = new Person();
        var person2 = new Person();
        person1.friends.push("Van");
        alert(person1.friends); //"Shelby,Court,Van"
        alert(person2.friends); //"Shelby,Court,Van"
        alert(person1.friends === person2.friends); //true
  • 组合使用构造函数模式和原型模式

    • 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
    • 每一个实例都会有本身的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存
    • 这种混成模式还支持向构造函数传递参数
    function Person(name, age, job){
      this.name = name;
      this.age = age;
      this.job = job;
      this.friends = ["Shelby", "Court"];
    }
    Person.prototype = {
      constructor : Person,
      sayName : function(){
          alert(this.name);
      }
    }
    var person1 = new Person("Nicholas", 29, "Software Engineer");
    var person2 = new Person("Greg", 27, "Doctor");
    person1.friends.push("Van");
    alert(person1.friends); //"Shelby,Count,Van"
    alert(person2.friends); //"Shelby,Count"
    alert(person1.friends === person2.friends); //false
    alert(person1.sayName === person2.sayName); //true
  • 动态原型模式

    • 经过检查某个应该存在的方法是否有效,来决定是否须要初始化原型

      function Person(name, age, job){
       //属性
          this.name = name;
          this.age = age;
          this.job = job; //方法
          if (typeof this.sayName != "function"){
              Person.prototype.sayName = function(){
                  alert(this.name);
              };
          }
      } 
      var friend = new Person("Nicholas", 29, "Software Engineer");
      friend.sayName();
    • 使用动态原型模式时,不能使用对象字面量重写原型,若是在已经建立了实例的状况下重写原型,那么就会切断现有实例与新原型之间的联系

  • 寄生构造函数模式

    • 基本思想:建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建立的对象;但
      从表面上看,这个函数又很像是典型的构造函数。构造函数在不返回值的状况下,默认会返回新对象实例。而经过在构造函数的末尾添加一个 return 语句,能够重写调用构造函数时返回的值

      function Person(name, age, job){
       var o = new Object();
       o.name = name;
       o.age = age;
       o.job = job;
       o.sayName = function(){
          alert(this.name);
       };
       return o;
      }
      var friend = new Person("Nicholas", 29, "Software Engineer");
      friend.sayName(); //"Nicholas"
    • 这个模式能够在特殊的状况下用来为对象建立构造函数。假设咱们想建立一个具备额外方法的特殊数组。因为不能直接修改 Array 构造函数,所以可使用这个模式。

      function SpecialArray(){
          //建立数组
          var values = new Array();
          //添加值
          values.push.apply(values, arguments);
          //添加方法
           values.toPipedString = function(){
               return this.join("|");
          };
      
          //返回数组
          return values;
      }
      var colors = new SpecialArray("red", "blue", "green");
      alert(colors.toPipedString()); //"red|blue|green"
    • 返回的对象与构造函数或者与构造函数的原型属性之间没有关系,不能依赖instanceof操做符来肯定对象类型

  • 稳妥构造函数模式

    • 稳妥对象,指的是没有公共属性,并且其方法也不引用 this 的对象;适合在一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其余应用程序(如 Mashup程序)改动时使用。

    • 稳妥构造函数遵循与寄生构造函数相似的模式,但有两点不一样:

      • 一是新建立对象的实例方法不引用 this
      • 二是不使用 new 操做符调用构造函数
      function Person(name, age, job){
          //建立要返回的对象
          var o = new Object();
          //能够在这里定义私有变量和函数
          //添加方法
          o.sayName = function(){
              alert(name);
          };
          //返回对象
          return o;
      } 
      var friend = Person("Nicholas", 29, "Software Engineer");
      friend.sayName(); //"Nicholas
    • 稳妥构造函数模式提供的这种安全性,使得它很是适合在某些安全执行环境——例如,ADsafe(www.adsafe.org)和 Caja(http://code.google.com/p/google-caja/)提供的环境——下使用

    • 使用稳妥构造函数模式建立的对象与构造函数之间也没有什么关系,所以 instanceof 操做符对这种对象也没有意义

三、继承

  • 原型链

    • 基本概念

      • 原型链是实现继承的主要方法:利用原型让一个引用类型继承另外一个引用类型的属性和方法
      • 构造函数、原型和实例的关系:每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针
      • 让原型对象等于另外一个类型的实例,此时的原型对象将包含一个指向另外一个原型的指针,相应地,另外一个原型中也包含着一个指向另外一个构造函数的指针。假如另外一个原型又是另外一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条
    • 基本模式

      function SuperType(){
          this.property = true;
      }
      SuperType.prototype.getSuperValue = function(){
          return this.property;
      };
      function SubType(){
          this.subproperty = false;
      }
      //继承了 SuperType
      SubType.prototype = new SuperType();
      SubType.prototype.getSubValue = function (){
          return this.subproperty;
      };
      var instance = new SubType();
      alert(instance.getSuperValue()); //true
    • 注意的地方:

      • 别忘记默认的原型:全部函数的默认原型都是 Object 的实例,所以默认原型都会包含一个内部指针,指向Object.prototype。这也正是全部自定义类型都会继承 toString()valueOf()等默认方法的根本缘由

      • 肯定原型和实例的关系:instanceof操做符和isPrototypeOf()方法

        //使用instanceof操做符,测试实例与原型链中出现过的构造函数,结果就会返回 true
        alert(instance instanceof Object); //true
        alert(instance instanceof SuperType); //true
        alert(instance instanceof SubType); //true 
        
        //isPrototypeOf()方法,原型链中出现过的原型,都是该原型链所派生的实例的原型,返回 true
        alert(Object.prototype.isPrototypeOf(instance)); //true
        alert(SuperType.prototype.isPrototypeOf(instance)); //true
        alert(SubType.prototype.isPrototypeOf(instance)); //true
      • 谨慎地定义方法:

        //1.给原型添加方法的代码必定要放在替换原型的语句以后
        function SuperType(){
          this.property = true;
        }
        SuperType.prototype.getSuperValue = function(){
          return this.property;
        };
        function SubType(){
           this.subproperty = false;
        }
        //继承了 SuperType
        SubType.prototype = new SuperType();
        //添加新方法
        SubType.prototype.getSubValue = function (){
          return this.subproperty;
        };
        //重写超类型中的方法
        SubType.prototype.getSuperValue = function (){
          return false;
        };
        var instance = new SubType();
        alert(instance.getSuperValue()); //false 
        
        //2.在经过原型链实现继承时,不能使用对象字面量建立原型方法,这样作就会重写原型链
        function SuperType(){
          this.property = true;
        }
        SuperType.prototype.getSuperValue = function(){
          return this.property;
        };
        function SubType(){
          this.subproperty = false;
        }
        //继承了 SuperType
        SubType.prototype = new SuperType();
        //使用字面量添加新方法,会致使上一行代码无效
        SubType.prototype = {
          getSubValue : function (){
              return this.subproperty;
          },
          someOtherMethod : function (){
               return false;
          }
        };
        var instance = new SubType();
        alert(instance.getSuperValue()); //error!
    • 原型链的问题

    • 最主要的问题来自包含引用类型值的原型,在经过原型来实现继承时,原型实际上会变成另外一个类型的实例,因而,原先的实例属性也就瓜熟蒂落地变成了如今的原型属性了

      function SuperType(){
        this.colors = ["red", "blue", "green"];
      }
      function SubType(){}
      //继承了 SuperType
      SubType.prototype = new SuperType();
      var instance1 = new SubType();
      instance1.colors.push("black");
      alert(instance1.colors); //"red,blue,green,black"
      var instance2 = new SubType();
      alert(instance2.colors); //"red,blue,green,black"
    • 在建立子类型的实例时,不能向超类型的构造函数中传递参数

  • 借用构造函数

    • 基本思想:在子类型构造函数的内部调用超类型构造函数

      function SuperType(){
          this.colors = ["red", "blue", "green"];
      }
      function SubType(){
          //继承了 SuperType
          SuperType.call(this);
      }
      var instance1 = new SubType();
      instance1.colors.push("black");
      alert(instance1.colors); //"red,blue,green,black"
      var instance2 = new SubType();
      alert(instance2.colors); //"red,blue,green"
    • 传递参数:能够在子类型构造函数中向超类型构造函数传递参数

      function SuperType(name){
          this.name = name;
      }
      function SubType(){
          //继承了 SuperType,同时还传递了参数
          SuperType.call(this, "Nicholas");
      
          //实例属性
          this.age = 29;
      }
      var instance = new SubType();
      alert(instance.name); //"Nicholas";
      alert(instance.age); //29
    • 借用构造函数的问题:方法都在构造函数中定义,所以函数复用就无从谈起了。并且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果全部类型都只能使用构造函数模式

  • 组合继承:

    • 基本思想:使用原型链实现对原型属性和方法的继承,而经过借用构造函数来实现对实例属性的继承

      function SuperType(name){
          this.name = name;
          this.colors = ["red", "blue", "green"];
      }
      SuperType.prototype.sayName = function(){
          alert(this.name); 
      };
      function SubType(name, age){
          //继承属性
          SuperType.call(this, name);
          this.age = age;
      }
      //继承方法
      SubType.prototype = new SuperType();
      SubType.prototype.constructor = SubType;
      SubType.prototype.sayAge = function(){
          alert(this.age);
      };
      var instance1 = new SubType("Nicholas", 29);
      instance1.colors.push("black");
      alert(instance1.colors); //"red,blue,green,black"
      instance1.sayName(); //"Nicholas";
      instance1.sayAge(); //29
      var instance2 = new SubType("Greg", 27);
      alert(instance2.colors); //"red,blue,green"
      instance2.sayName(); //"Greg";
      instance2.sayAge(); //27
    • 组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优势,成为 JavaScript 中最经常使用的继承模式。并且,instanceofisPrototypeOf()也可以用于识别基于组合继承建立的对象

  • 原型式继承

    • 思路:借助原型能够基于已有的对象建立新对象,同时还没必要所以建立自定义类型

      function object(o){
          function F(){}
          F.prototype = o;
          return new F();
      } 
      var person = {
          name: "Nicholas",
          friends: ["Shelby", "Court", "Van"]
      };
      var anotherPerson = object(person);
      anotherPerson.name = "Greg";
      anotherPerson.friends.push("Rob");
      var yetAnotherPerson = object(person);
      yetAnotherPerson.name = "Linda";
      yetAnotherPerson.friends.push("Barbie");
      alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
    • Object.create()方法:规范化了原型式继承,这个方法接收两个参数,一个用做新对象原型的对象和(可选的)一个为新对象定义额外属性的对象

      //在传入一个参数的状况下,Object.create()与 object()方法的行为相同
      var person = {
          name: "Nicholas",
          friends: ["Shelby", "Court", "Van"]
      };
      var anotherPerson = Object.create(person);
      anotherPerson.name = "Greg";
      anotherPerson.friends.push("Rob");
      
      var yetAnotherPerson = Object.create(person);
      yetAnotherPerson.name = "Linda";
      yetAnotherPerson.friends.push("Barbie");
      alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
      
      //第二个参数与Object.defineProperties()方法的第二个参数格式相同:
      //每一个属性都是经过本身的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性
      var person = {
           name: "Nicholas",
           friends: ["Shelby", "Court", "Van"]
      }; 
      var anotherPerson = Object.create(person, {
          name: {
              value: "Greg"
          }
      });
      
      alert(anotherPerson.name); //"Greg"
  • 寄生式继承

    • 思路:与寄生构造函数和工厂模式相似,即建立一个仅用于封装继承过程的函数,该函数在内部以某种方式来加强对象,最后再像真地是它作了全部工做同样返回对象

      function createAnother(original){
          var clone = object(original); //经过调用函数建立一个新对象
          clone.sayHi = function(){ //以某种方式来加强这个对象
              alert("hi");
          };
          return clone; //返回这个对象
      }
      var person = {
          name: "Nicholas",
          friends: ["Shelby", "Court", "Van"]
      };
      var anotherPerson = createAnother(person);
      anotherPerson.sayHi(); //"hi"
    • 在主要考虑对象而不是自定义类型和构造函数的状况下,寄生式继承也是一种有用的模式。使用寄生式继承来为对象添加函数,会因为不能作到函数复用而下降效率;这一点与构造函数模式相似

  • 寄生组合继承

    • 组合继承最大的问题:就是不管什么状况下,都会调用两次超类型构造函数,一次是在建立子类型原型的时候,另外一次是在子类型构造函数内部

      //子类型最终会包含超类型对象的所有实例属性,但咱们不得不在调用子类型构造函数时重写这些属性
      function SuperType(name){ 
          this.name = name;
          this.colors = ["red", "blue", "green"];
      }
      SuperType.prototype.sayName = function(){
          alert(this.name);
      };
      function SubType(name, age){
          SuperType.call(this, name); //第二次调用 SuperType()
          this.age = age;
      }
      SubType.prototype = new SuperType(); //第一次调用 SuperType()
      SubType.prototype.constructor = SubType;
      SubType.prototype.sayAge = function(){
          alert(this.age);
      };
    • 寄生组合式继承,即经过借用构造函数来继承属性,经过原型链的混成形式来继承方法;其背后的基本思路是:没必要为了指定子类型的原型而调用超类型的构造函数,咱们所须要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,而后再将结果指定给子类型的原型

      function inheritPrototype(subType, superType){
          var prototype = object(superType.prototype); //建立对象
          prototype.constructor = subType; //加强对象
          subType.prototype = prototype; //指定对象
      }
      function SuperType(name){
          this.name = name;
          this.colors = ["red", "blue", "green"];
      }
      SuperType.prototype.sayName = function(){
          alert(this.name);
      };
      function SubType(name, age){
          SuperType.call(this, name);
          this.age = age;
      }
      inheritPrototype(SubType, SuperType);
      SubType.prototype.sayAge = function(){
          alert(this.age);
      };
    • YUI 的 YAHOO.lang.extend()方法采用了寄生组合继承,从而让这种模式首次出如今了一个应用很是普遍的 JavaScript 库中,要了解有关 YUI 的更多信息,请访问http://developer. yahoo.com/yui/

四、总结

ECMAScript支持面向对象(OO)编程,但不使用类或者接口。对象能够在代码执行过程当中建立和加强,所以具备动态性而非严格定义的实体。在没有类的状况下,能够采用下列模式建立对象

  • 工厂模式,使用简单的函数建立对象,为对象添加属性和方法,而后返回对象。这个模式后来被构造函数模式所取代
  • 构造函数模式,能够建立自定义引用类型,能够像建立内置对象实例同样使用 new 操做符。不过,构造函数模式也有缺点,即它的每一个成员都没法获得复用,包括函数。因为函数能够不局限于任何对象(即与对象具备松散耦合的特色),所以没有理由不在多个对象间共享函数
  • 原型模式,使用构造函数的 prototype 属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法

JavaScript 主要经过原型链实现继承。原型链的构建是经过将一个类型的实例赋值给另外一个构造函数的原型实现的。这样,子类型就可以访问超类型的全部属性和方法,这一点与基于类的继承很类似。原型链的问题是对象实例共享全部继承的属性和方法,所以不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就能够作到每一个实例都具备本身的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而经过借用构造函数继承实例属性。此外,还存在下列可供选择的继承模式:

  • 原型式继承,能够在没必要预先定义构造函数的状况下实现继承,其本质是执行对给定对象的浅复制。而复制获得的副本还能够获得进一步改造
  • 寄生式继承,与原型式继承很是类似,也是基于某个对象或某些信息建立一个对象,而后加强对象,最后返回对象。为了解决组合继承模式因为屡次调用超类型构造函数而致使的低效率问题,能够将这个模式与组合继承一块儿使用
  • 寄生组合式继承,集寄生式继承和组合继承的优势与一身,是实现基于类型继承的最有效方式
相关文章
相关标签/搜索