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

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

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

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

    • [Enumberable]是否可以被枚举,即可被for-in,默认true

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

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

        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 之后没办法改回来 true

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

    • [Configurable] 跟之前一样

    • [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

你可能感兴趣的:(javascript)