js高级程序设计-面向对象的程序设计-阅读笔记

ECMAScript 中又两种属性:数据属性(包含一个数据值的位置叫作数据属性)和访问器属性(getter()setter()就是访问器属性)chrome

  1. 数据属性而数据属性又有四个这样的特性:数组

    • [Configurable] 是否可配置,编辑,删除属性,默认true浏览器

    • [Enumberable]是否能够被枚举,便可被for-in,默认true安全

    • [Writable] 是否可写,默认true,写不表明删除,这里仅限修改函数

    • [Value] 属性的数据值,默认是undefinedthis

    var person = {};//设置了一个空对象
      //定义了对象的默认属性 name 的一些属性的特性
      Object.defineProperty(person,"name",{
          writable:false,// 不能够编辑
          value:"niconico" //默认的值就是 niconico
      });
    
      console.log(person.name);//返回niconico,由于默认值,不用设置也有值
      person.name = "gggg";//即便设置了name 新值,由于不可编辑,因此没变化
      console.log(person.name);//返回niconico

须要注意的是[Configurable]被改 false 以后没办法改回来 truespa

  1. 访问器属性(getter() 和 setter()),而他们又有这样的特性:firefox

    • [Configurable] 跟以前同样prototype

    • [Enumberable] 跟以前同样指针

    • [Get] 在读取属性时调用的函数,默认是 undefined

    • [Set] 在写入属性时调用的函数,默认是 undefined

    var book = {
          _year: 2004, //经常使用语法,表明只能经过对象方法访问的属性
          edition: 1
      };
    
      Object.defineProperty(book, "year", {
          get: function () { //定义一个 getter
              return this._year; //直接读取属性
          },
          //若是不设置 setter的话那么这个对象的属性就没办法修改
          set: function (newValue) { //定义一个 setter
              if (newValue > 2004) {
                  this._year = newValue; //若是注释掉,那么_ year 不会改变
                  this.edition += newValue - 2004;
              }
          }
      });
    
      book.year = 2005;
      //由于这个函数的 setter 里面也一块儿把_year修改了,因此可以看到被修改
      console.log(book.year); //返回2005
      console.log(book.edition);//返回2

数据属性和访问器属性的区分

var book = {};

    Object.defineProperties(book, { //这里用了defineProperties定义多个属性
        _year: { //数据属性
            value: 2004
        },
        edition: { //数据属性
            value: 1
        },
        year: {//访问器属性,判断的标准就是是否有 getter 或者 setter
            get: function () {
                return this._year;
            },
            set: function (newValue) {
                if (newValue > 2004) {
                    this._year = newValue;
                    this.edition += newValue - 2004;
                }
            }
        }
    })
    
    
    //获取属性的特性
    var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
    console.log(descriptor.value); //获取值这个特性
    console.log(descriptor.configurable); //获取 configurable 这个特性

建立对象

  • 工厂模式:用函数封装以特定的接口建立对象,无法建立特定类型的对象

  • 构造函数模式: 构造函数能够用来建立特定类型的对象,可是每一个成员没法复用

  • 原型模式:使用构造函数的 prototype 属性来指定那些应该共享的属性和方法

  • 组合继承: 使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法
    动态原型模式:能够在没必要预先定义构造函数的状况下实现继承,其本质是执行给指定对象的浅复制

  • 寄生构造函数模式:基于某个对象或某些信息建立一个对象,而后加强对象,最后返回对象

  • 稳妥构造函数模式:集寄生式继承和组合继承的优势


工厂模式

这种模式抽象了建立具体对象的过程,由于 ECMAScript 中没法建立类,因此用函数封装以特定的接口建立对象

function createPerson(name,age,job) {
        var o = new Object(); //代替建立对象
        o.name = name;//代替设置属性
        o.age = age;
        o.job = job;
        o.sayName = function () { // 代替设置方法
            console.log(this.name);
        };
        return o; //返回是一个对象
    }

    var person1 = createPerson("nico",29,"soft");
    var person2 = createPerson("gg",30,"dog");
    console.log(person1);//返回Object {name: "nico", age: 29, job: "soft"}
    console.log(person2);

优势:
1.建立对象的方式简单了
缺点:
1.没有解决对象类型识别的问题,由于都是直接new Object, 都是 Object,因此无法区分是具体哪个对象类型

构造函数模式

实际上构造函数经历了如下过程:

  1. 建立一个新对象

  2. 将构造函数的做用域赋给新对象(所以this指向了这个新对象)

  3. 执行构造函数中的代码(为这个新对象添加属性)

  4. 返回新对象

function Person(name,age,job) {  //标准写法,构造函数名第一个大写
        this.name = name; 
        this.age = age;
        this.job = job;
        this.sayName = function () {
            console.log(this.name);
        }
        //不须要return,由于会自动返回新对象,若是使用了 return 就会改变了返回的内容
    }

        var person1 = new Person("nico",29,"soft");//用new
        var person2 = new Person("gg",30,"dog");
        console.log(person1);//返回Person {name: "nico", age: 29, job: "soft"}
        console.log(person2);
        
        
    //这些实例都是Object 对象,也是Person 对象
    console.log(person1 instanceof Object);//返回 true
    console.log(person1 instanceof Person);//返回 true
    //person1和 person2分别保存着Person 一个不一样的实例,这两个实例都有一个constructor(构造函数)属性,都指向Person, 说明他们都是同一个构造函数建立的
    console.log(person1.constructor == Person);//返回 true
    console.log(person2.constructor == Person);//返回

构造函数与其余函数的惟一区别就在于调用他们的方式不一样,任何函数,只要经过 new 来调用,那他就能够做为构造函数,而任何函数,若是不经过 new 来调用,那他就跟普通函数也不会有两样.

构造函数也能够做为普通函数使用

function Person(name, age, job) {
        this.name = name; //直接赋值给 this, 即直接设置当前对象的属性
        this.age = age;
        this.job = job;
        this.sayName = function () {
            console.log(this.name);
        }
        //不须要return, 也不须要返回对象
    }
    // 做为构造函数调用
    var person = new Person("pp", 10, "kk");
    person.sayName();//返回 pp
    //做为普通函数调用
    Person("yy", 20, "gg");//这里添加到 window 对象了,由于默认全局做用域
    window.sayName();//返回 yy
    //在另一个对象的做用域中调用
    var o = new Object();
    Person.call(o, "aa", 25, "bb"); //由于是被 o 调用,因此 this 指向 o
    o.sayName();//返回 aa

优势:
1.能够知道对象实例是是哪一个对象类型,即构造函数是谁(经过 instanceOf() 或者 constructor 来验证)
缺点:
1.每一个方法都要在每一个实例上从新建立一遍,会致使不一样的做用域链和标示符解析问题,例如两个实例之间的方法并不能用== 来判断,例如 person1.sayName == person2.sayName 是返回 false 的,由于都是新建立的实例,都是独立的

原型模式

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

  • 换句话说,没必要再构造函数中定义对象实例的信息,而是能够将这些信息直接添加到原型对象中

function Person() {} //初始化一个空对象

    Person.prototype.name = "nico"; //直接将属性写到原型里面
    Person.prototype.sayName = function () {//直接将方法写到原型里面
        console.log(this.name);
    };

    //原型的全部属性和方法被全部实例共享
    var person1 = new Person();
    person1.sayName();//返回 nico

    var person2 = new Person();
    person2.sayName();//返回 nico

    //他们其实都指向同一个原型的同一个方法,因此 true
    console.log(person1.sayName() == person2.sayName());//返回true

优势:
1.可让全部对象实例共享它所包含的属性和方法
缺点:
1.实例都须要有只属于本身的属性,而原型对象是彻底共享的,因此不多有人单独使用原型模式

理解原型对象

  • 在脚本中没有标准的方式访问[prototype],可是firefox,safari,chrome在每一个对象上都支持一个_proto_

  • 建立了自定义的构造函数以后,其原型对象默认只会取得constructor属性,当调用构造函数建立一个新实例后,该实例的内部将包含一个指针指向构造函数的运行对象.

图片描述

  1. Person 是构造函数,Person.prototype是原型对象,person1 和 person2 是实例

  2. Person.prototype的constructor指向Person,由于原型对象是构造函数建立的,因此 constructor 指向Person

  3. Person的prototype 指向了原型对象,而又由于默认状况下,全部的原型对象的 constructor 都是在被建立的时候指向构造函数

  4. person1和person2 有一个内部属性[prototype],指向Person.prototype,实例的prototype 指向原型对象很正常

  5. 经过isPrototypeOf()来肯定对象之间是否存在实例和原型对象的关联关系

    //若是[prototype]指向调用isPrototypeOf方法的对象的话,那么就会返回 true
    console.log(Person.prototype.isPrototypeOf(person1)); //返回 true
    console.log(Person.prototype.isPrototypeOf(person2)); //返回 true
  6. 经过 getPrototypeOf 方法来获取原型的属性

    //getPrototypeOf返回的对象是原型对象
    console.log(Object.getPrototypeOf(person1) == Person.prototype);//返回 true
    console.log(Object.getPrototypeOf(person1).name); //即便这个实例没有设置属性 name, 也能够获取原型对象的属性 name
  7. 用 hasOwnProperty() 方法检测一个属性是否存在实例中(返回 true),仍是存在与原型中(返回 false)

function Person() {} //初始化一个空对象
    Person.prototype.name = "nico";
    var person1 = new Person();
    var person2 = new Person();
    //没有这个属性也会返回 false
    console.log(person1.hasOwnProperty("name"));//返回 false

    person1.name="bbb";//设置 person1的name
    console.log(person1.name); //返回 bbb
    console.log(person1.hasOwnProperty("name"));//返回true

    //没有设置,使用的是原型的 name,即便不存在实例中的时候
    console.log(person2.name);//返回 nico
    console.log(person2.hasOwnProperty("name"));//返回 false

每当代码读取某个对象的某个属性的时候,都会执行一次搜搜,搜索搜索对象实例自己,若是没有,就去搜索原型对象

  1. 同时使用 in 和hasOwnProperty就能肯定该属性是存在对象中仍是存在原型中

  2. 只能肯定是否存在实例中,但区分不了是对象仍是原型,hasOwnProperty只能确认是否存在实例中,因此二者结合能够实现判断

function hasPrototypeProperty(object,name) {
        //属性不存在于实例中 而且属性存在于对象中就返回 true    
        return !object.hasOwnProperty(name) && (name in object);
    }
  1. 在 for-in 循环时,返回的是全部可以经过对象访问的,可枚举的属性,其中包括实例中的属性和原型中的属性

  2. 用 Object.keys() 方法返回全部可枚举的属性, Object.getOwnPropertyNames能够返回全部属性,包括不可枚举的属性

function Person() {
    }
    Person.age = 19;
    Person.prototype.name = "nico";
    var keys1 = Object.keys(Person);//Person 的属性
    console.log(keys1); //返回["age"],数组
    var keys2 = Object.keys(Person.prototype);//Person的原型对象属性
    console.log(keys2);//返回["name"],数组
    
     //getOwnPropertyNames能够返回全部属性,包括不可枚举的属性,例如constructor
    var keys3 = Object.getOwnPropertyNames(Person);
    console.log(keys3); //返回["length", "name", "arguments", "caller", "prototype", "age"]
    var keys4 = Object.getOwnPropertyNames(Person.prototype);
    console.log(keys4); //返回["constructor", "name"]

Object.keys()和Object.getOwnPropertyNames()均可不一样程度的代替for-in, 不过须要比较新的浏览器

  • 更简单的原型语法,封装原型

function Person() {
    }
//字面量建立对象语法
    Person.prototype = {
        constructor: Person, 
        name: "nico",
        age: 18,
        sayName: function () {
            console.log(this.name);
        }
    }

须要注意的是这种写法的话, constructor属性再也不指向Person,由于没建立一个函数,就会同时建立他的 prototype 对象,这个对象自动得到 constructor 属性,而字面量语法会重写这个 prototype 对象,所以 constructor 属性也就变成了新的对象的 constructor 属性(指向 Object),因此须要另外指定一个 constructor

原型的动态性

因为在原型中查找值的过程是一次搜索,所以咱们队原型对象所作的任何修改都可以当即从实例上反映出来

function Person1() {};
    var friend = new Person1(); //先与修改原型前建立了实例,但也能使用这个原型方法
    Person1.prototype.sayHi = function () { 
        console.log("hi");
    };
    //先找本身,而后找原型
    friend.sayHi();//返回 hi

缘由:实例与原型之间的松散连接关系
当咱们调用 friend.sayHi( )时,首先会在实例中搜索名为 sayHi 的属性,没找到以后会继续搜索原型,由于实例与原型之间的连接只不过是一个指针,而非一个副本,所以就能够在原型中找到新的 sayHi 属性并返回保存在那里的函数

  • 重写原型切断了现有原型与任何以前已经存在的对象实例之间的联系
    调用构造函数时会为实例添加一个指向最初原型的[prototype]指针,而把原型修改成另一个对象就等于切断了构造函数与最初原型之间的联系

function Person() {
    }
    //重写原型以前
    var friend = new Person();

    Person.prototype.sayName = function () {
        console.log("hi");
    };
     friend.sayName();//返回 hi,
    //重写原型以后(注释了)
//    Person.prototype = {
//        constructor: Person,
//        name: "nico",
//        age: 18,
//        sayName: function () {
//            console.log(this.name);
//        }
//    };

    friend.sayName();//直接报错

图片描述

1.字面量写法修改原型对象会重写这个原型对象
2.实例中的指针仅指向原型,而不指向构造函数
3.由于他会建立一个新的原型对象,原有的实例会继续指向原来的原型,可是全部的属性和方法都存在于新的原型对象里面,因此没办法使用这些属性和方法
4.并不推荐在产品化得程序中修改原生对象的原型,可能会影响了其余使用原生对象的原型的代码

组合使用构造函数模式和原型模式(经常使用)

  • 构造函数用于定义实例属性,原型模式用于定义方法和共享的属性.

  • 每一个实例都会有本身的一份实例属性的副本,但同时又共享着对方法的引用,这种模式还支持向构造函数传递参数

function Person(name, age, job) {
        //实例属性在构造函数中定义
        this.name = name;
        this.age = age;
        this.job = job;
        this.friends = ["tom", "jim"];
    }

    Person.prototype = {
        //共享的方法在原型中定义
        constructor: Person,
        sayName: function () {
            console.log(this.name);
        }
    };

    var person1 = new Person("Nico", 29, "software eng");
    var person2 = new Person("Greg", 30, "doctor");

    person1.friends.push("Vivi");//单独添加 person1实例的数组数据
    console.log(person1.friends);//返回["tom", "jim", "Vivi"]
    console.log(person2.friends);//返回["tom", "jim"]
    console.log(person1.friends === person2.friends); //返回 false,没共享 friends 数组
    console.log(person1.sayName === person2.sayName); //返回 true ,共享了其余方法

动态原型模式

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

  • 把全部信息都封装在构造函数,经过在构造函数中初始化原型

function Person(name, age, job) {
        //实例属性在构造函数中定义
        this.name = name;
        this.age = age;
        this.job = job;
        //只在sayName方法不存在的时候才添加原型中
        if (typeof this.sayName != "function") {
            Person.prototype.sayName = function () {
                console.log(this.name);
            }
        }
    }

    var friend = new Person("jack", 29, "soft ware eng");
    friend.sayName();
  1. 对原型修改的话,不能使用字面量重写,由于会断开跟原型的关联

寄生构造函数模式(parasitic)(不建议使用)

  • 建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建立的对象.

  • 这个代码几乎跟工厂模式同样,惟一区别是如何调用,工厂模式没有 new, 这个有 new

function Person(name, age, job) {
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function () {
            console.log(this.name);
        };
        //在构造函数里面添加一个 return 会重写调用构造函数时返回的值
        //不写 return 的话,默认会返回新对象实例
        return o;
    }
    //用 new 方式来调用
    var friend = new Person("jack", 29, "soft ware eng"); 
    friend.sayName(); //返回的实例就是 Person 函数里面新建立的那个指定实例,因此有这个实例的全部属性和方法
  1. 返回的对象与构造函数或者构造函数原型属性之间没有关系,因此不能使用 instanceof 来肯定对象类型,不建议使用这种模式

稳妥构造函数模式(较少使用)

  • 稳妥对象durable object指的是没有公共属性,并且其方法也不引用 this 的对象,主要用在一些安全的环境中,禁止使用this 和 new 之类的,或者在防止数据被其余应用程序改动时使用

function Person(name, age, job) {
        //建立要返回的对象
        var o = new Object(); // 这个就是一个稳妥对象,由于单独独立
        //能够在这里定义私有变量和函数
        //添加方法
        o.sayName = function () {
            console.log(name);
        };
        //返回对象
        return o;
    }

    var friend = Person("nico", 29, "software eng");
    friend.sayName(); //返回 nico

继承

  • 实现继承:表示一个类型派生于一个基类型,拥有该基类型的全部成员字段和函数。

  • 接口继承:表示一个类型只继承了函数的签名,没有继承任何实现代码。

  • 一个函数由这么几部分组成,函数名、参数个数、参数类型、返回值,函数签名由参数个数与其类型组成。函数在重载时,利用函数签名的不一样(即参数个数与类型的不一样)来区别调用者到底调用的是那个方法!函数签名由函数的名称和它的每个形参(按从左到右的顺序)的类型和种类(值、引用或输出)组成。

  • 由于 ECMAScript 中函数没有签名,因此没法实现接口继承

  • ECMAScript 只支持实现继承,并且其实现继承主要是依靠原型链来实现.

原型链

  • 实现继承主要是利用原型让一个引用类型继承另外一个引用类型的属性和方法

  • 构造函数,原型和实例的关系:

    • 每一个构造函数都有一个原型对象

    • 原型对象都包含一个指向构造函数的指针

    • 实例都包含一个指向原型对象的内部指针

  • 假如咱们让原型对象等于另一个类型的实例,此时,原型对象将包含一个指向另外一个原型的指针,相应地,另外一个原型中也包含着一个指向另一个构造函数的指针,如此类推

(我把 SuperType 的 prototype 属性换了一个名字testprototype,方便理解)

  1. instance 指向 SubType 的原型, SubType 的原型又指向了 SuperType 的原型, getSuperValue 方法仍然还在 SuperType.prototype 中,可是property(testprototype) 则位于 SubType.prototype 中,这是由于 prototype(testprototype)是一个实例属性,而 getSuperValue() 则是一个原型方法.既然 SubType.prototype 如今是 SuperType 的实例,那么 property(testprototype)就位于该实例中.

  2. instance.constructor 如今指向SuperType, 这是由于 SubType 的原型指向了另一个对象-- SuperType 的原型,而这个原型对象的 constructor 属性指向 SuperType

//假设这个类型要被继承方
    function SuperType() {
        //属性
        this.testSuperprototype = true;
    }
    //原型的方法
    SuperType.prototype.getSuperValue = function () {
        return this.testSuperprototype;
    };
    //假设这个类型是继承方
    function SubType() {
        //属性
        this.subproperty = false;
    }

    //SubType继承于SuperType,将实例赋给SubType.prototype(Subtype的原型),
    //实现的本质就是重写了SubType的原型对象
    SubType.prototype = new SuperType();
    //集成以后,设置SubType的原型的方法
    SubType.prototype.getSubValue = function () {
        return this.subproperty;//获取subproperty属性,若是没有继承的话,那么这里是 false
                                //继承以后就改变了
    };
    var instance = new SubType();
    console.log(instance.getSuperValue()); //返回 true
  1. 继承经过建立 SuperType 的实例,而后赋给 Subtype.prototype 原型实现的,原来存在于 SuperType 的实例的全部属性和方法,如今也存在于 SubType.prototype 中了

  2. 确立继承关系以后,咱们给 Subtype.prototype 添加了一个方法,这样就在继承了 SuperType 的属性和方法的基础上有添加了一个新方法

这是完整的原型链图,由于还要包含 Object, 不过总的来讲基本差很少,例如,若是调用 instance的 toString()方法,其实就是调用 Object 的 toString()

肯定原型和实例的关系

//由于原型链的关系, instance都是Object 或者SuperType 或者SubType 任何一个类型的实例
    console.log(instance instanceof Object);//true
    console.log(instance instanceof SuperType);//true
    console.log(instance instanceof SubType);//true
    //只要在原型链出现过的原型,均可以说是该原型链所派生的实例的原型
    console.log(Object.prototype.isPrototypeOf(instance));//true
    console.log(SuperType.prototype.isPrototypeOf(instance));//true
    console.log(SubType.prototype.isPrototypeOf(instance));//true

谨慎地定义方法

给原型添加方法的代码必定要放在替换原型的语句以后,否则就会覆盖了超类中的方法了.

//假设这个类型要被继承方
        function SuperType() {
            //属性
            this.testSuperprototype = true;
        }
        //原型的方法
        SuperType.prototype.getSuperValue = function () {
            return this.testSuperprototype;
        };
        //假设这个类型是继承方
        function SubType() {
            //属性
            this.subproperty = false;
        }

        //SubType继承于SuperType,将实例赋给SubType.prototype(Subtype的原型)
        SubType.prototype = new SuperType();
        //继承以后,设置SubType的原型的方法
        SubType.prototype.getSubValue = function () {
            return this.subproperty;//获取subproperty属性,若是没有继承的话,那么这里是 false
                                    //继承以后就改变了
        };
        //重写超类型(被继承的类型)中的方法
        SubType.prototype.getSuperValue = function () {
          return false;   //返回的是这个,而不是 true(被继承的类型中是 true)
        };
        var instance = new SubType();
        console.log(instance.getSuperValue()); //返回 false

在经过原型链实现继承时,不能使用对象字面量建立原型方法,由于这样会重写原型链的

借用构造函数constructor stealing(不多用)

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

function SuperType() {
        this.colors = ["red", "blue", "green"];
    }
    function SubType() {
        ///call 的方式以SubType的身份来调用SuperType的构造函数,
        //这么作能够将SuperType的构造函数的属性传到SubType上,
        SuperType.call(this); 
    }
    var instance1 = new SubType();
    instance1.colors.push("black");
    console.log(instance1.colors);//返回["red", "blue", "green", "black"]

    var instance2 = new SubType();
    console.log(instance2.colors);//返回["red", "blue", "green"]

优势:
1.能实现继承
缺点:
1.由于使用 call 的方式即便能够调用超类来实现继承,可是超类的原型属性和方法都不能使用,由于 call 只是改变 this, 没有改变 constructor 指向

组合继承combination inheritance(经常使用)

  • 将原型链和借用构造函数技术组合到一块儿

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

  • 既经过原型上定义方法实现了函数复用,又可以保证每一个实例都有它本身的属性

//设置一个超类,即SuperType的构造函数,里面有2个属性
    function SuperType(name) {
        this.name = name;
        this.colors = ["red", "blue"];
    }
    //设置一个超类,即SuperType的原型方法 sayName()
    SuperType.prototype.sayName = function () {
        console.log(this.name);
    };
    //设置一个子类,即SubType的构造函数
    function SubType(name, age) {
        //call 的方式以SubType的身份来调用SuperType的构造函数,
        //这么作能够将SuperType的构造函数的属性传到SubType上,可是由于call 只能改变 this 指向,改变不了constructor, 因此没办法得到超类的原型方法
        //这样的话就将超类的属性放到子类里面,因此在实例化子类以后,即便改变了其中一个子类实例的属性,也不会影响其余的子类实例
        SuperType.call(this, name);////第二次调用超类SuperType
        this.age = age; //也设置了本身自己的属性(方便区分)
    }
    //将超类SuperType实例化,并赋值给SubType的原型
    //SubType的原型被改写了,如今就是SuperType实例了,这样就能够获取到SuperType的原型方法了
    SubType.prototype = new SuperType();//第一次调用超类SuperType
    //定义一个本身的原型方法(方便区分)
    //这个须要在原型被改写完成后才能作,否则的话会被覆盖
    SubType.prototype.sayAge = function () {
        console.log(this.age);
    };

    var instance1 = new SubType("nico", 20); 
    instance1.colors.push("black"); //instance1改变了,不过 instance2不会改变
    console.log(instance1.colors); //返回["red", "blue", "black"]
    instance1.sayName();//返回 nico,这是超类的原型方法,拿到子类用
    instance1.sayAge();//返回20,这是子类自定义的原型方法,同样能够用

    var instance2 = new SubType("greg", 29);
    console.log(instance2.colors);//返回["red", "blue"]
    instance2.sayName();//返回 greg
    instance2.sayAge();//返回29

备注:

  1. 须要理解原型链的知识

  2. 须要理解构造函数的执行过程

  3. 使用这种方式实现继承, 子类可以调用超类的方法和属性,由于超类的原型也赋值给子类了,真正实现了复用和继承,并且也可以保证各自实例的属性互不干涉,由于属性都在new 构建的时候生成,每一个实例都单独生成

  4. 第一次调用超类会获得两个属性 name 和 colors,他们是超类 SuperType 的属性,不过如今位于子类 SubType 的原型中,第二次调用超类会建立新的两个属性 name 和 colors, 他们会覆盖掉子类 SubType原型中的两个同名属性

缺点:
1.会调用两次超类型构造函数
2.不得不在调用子类型构造函数时重写属性

原型式继承

  • 必须有一个对象做为另一个对象的基础

  • 在不必建立构造函数,只想让一个对象与另一个对象保持相似的状况下,可使用这个方式,须要注意的就是共享的问题

function object(o) {
        function F() { //建立一个临时性的构造函数
        }
        F.prototype = o;//将传入的对象做为这个构造函数的原型
        return new F();//返回这个临时构造函数的新实例
    }

    var person = {
        name: "nico",
        friends: ["a", "b"]
    };
    //传入 person 对象,返回一个新的实例,这个实例也是传入的 person 对象做为原型的
    //因此可使用它的属性和方法
    var anotherPerson = object(person);
    anotherPerson.name = "gg";
    anotherPerson.friends.push("rr");

    //由于是使用同一个对象做为原型,因此跟原型链差很少,会共享这个原型对象的东西
    var yetAnotherPerson = object(person);
    yetAnotherPerson.name = "ll";
    yetAnotherPerson.friends.push("kk");

    console.log(person.friends);//返回["a", "b", "rr", "kk"]
    console.log(person.name);//返回nico, 由于基本类型值是不会变化的

在 ECMAScript 5下有一个 Object.create 方法跟他差很少

var person = {
            name: "nico",
            friends: ["a", "b"]
        };
        
        var anotherPerson = Object.create(person);
        anotherPerson.name = "gg";
        anotherPerson.friends.push("rr");
    
        var yetAnotherPerson = Object.create(person);
        yetAnotherPerson.name = "ll";
        yetAnotherPerson.friends.push("kk");
        //结果同样
        console.log(person.friends);//返回["a", "b", "rr", "kk"]

另外Object.create 支持第二个参数,能够指定任何属性覆盖原型对象的同名属性

var person = {
        name: "nico",
        friends: ["a", "b"]
    };

    var anotherPerson = Object.create(person, {
        name: { //以传入一个对象的方式, key 就是属性名
            value: "lala"
        }
    });
    
    console.log(anotherPerson.name);//返回 lala

寄生式继承

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

  • 任何可以返回新对象的函数都适用于此模式

  • 跟构造函数模式相似,不能作到函数复用.

function object(o) {
        function F() { //建立一个临时性的构造函数
        }

        F.prototype = o;//将传入的对象做为这个构造函数的原型
        return new F();//返回这个临时构造函数的新实例
    }
    //至关于扔了两次,第一次扔给一个临时的构造函数,生成一个实例
    //第二次再扔给一个固定变量,而后在这里去给予属性和方法
    function createAnother(original) {
        var clone = object(original);
        clone.sayHi = function () { //能够本身添加方法
            console.log("hi");
        };
        return clone;
    }

    var person = {
        name: "nico",
        friends: ["a", "b"]
    };

    var anotherPerson = createAnother(person);
    anotherPerson.sayHi();//返回 hi

寄生组合式继承

  • 经过借用构造函数来继承属性,经过原型链的混成形式来继承方法

  • 基本思路,使用寄生式继承来继承超类型的原型,而后再将结果指定给予子类型的原型

function object(o) {
        function F() { //建立一个临时性的构造函数
        }

        F.prototype = o;//将传入的对象做为这个构造函数的原型
        return new F();//返回这个临时构造函数的新实例
    }
    //两个参数,一个是子类型函数,一个是超类型构造函数
    function inheritPrototype(subType, superType) {
        //建立一个超类型原型的一个副本
        var prototype = object(superType.prototype);//建立对象
        //为建立的副本添加 constructor 属性,弥补因重写原型而失去默认的 constructor 属性
        prototype.constructor = subType;//加强对象
        //将新建立的对象赋值给子类型的原型,这样子类型就完成了继承了
        subType.prototype = prototype;//指定对象
    }


    function SuperType(name) {
        this.name = name;
        this.colors = ["red", "blue"];
    }
    SuperType.prototype.sayName = function () {
        console.log(this.name);
    };
    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    //能够看到,这里少调用了一次超类的构造函数
    inheritPrototype(SubType, SuperType);
    SubType.prototype.sayAge = function () {
        console.log(this.age);
    };

    var test = new SubType("aa", 100);
    test.colors.push("white");
    console.log(test.colors); //["red", "blue", "white"]
    test.sayName();//aa
    test.sayAge();//100

    var test1 = new SubType("pp", 1);
    test1.colors.push("black");
    console.log(test1.colors);//["red", "blue", "black"]
    test1.sayName();//pp
    test1.sayAge();//1
相关文章
相关标签/搜索