第06章 - 面向对象的程序设计

作者: 勤劳的悄悄 | 来源:发表于2016-01-19 11:26 被阅读37次

    对象就是键值对

    6.1 理解对象

    可以用 new 创建一个对象

    var person = new Object();
    
    person.name = "Nicholas";
    person.age = 29;
    person.job = "Software Engineer";
    
    person.sayName = function(){
        alert(this.name);
    };
    
    

    现在更流行用字面量创建对象

    var person = {
        name: "Nicholas",
        age: 29,
        job: "Software Engineer",
    
        sayName: function(){
            alert(this.name);
        }
    };
    
    

    6.1.1 属性的特性

    属性特性只供 Javascript 解释器内部使用,用户无法直接访问。有两种特性:数据特性和访问器特性

    数据特性

    • [[Configurable]] - 能否通过 delete 删除或者改变成访问器特性,默认值为 True
    • [[Enumerable]] - 是否可以使用 for - in 循环,默认值为 True
    • [[Writable]] - 能否修改属性的值,默认值为 True
    • [[Value]] - 保存属性的值,默认值为 undefined

    可以用 Object.defineProperty 方法来设置这几个特征

    var person = {};
    
    Object.defineProperty(person, "name", {
        writable: false, //设置为只读属性
        value: "Nicholas"
    });
    
    alert(person.name); //"Nicholas"
    
    //写入新值,没反应
    person.name = "Greg";
    alert(person.name); //"Nicholas"
    
    

    一个对象一旦被设置为不可配置,就不能再做任何修改

    var person = {};
    
    Object.defineProperty(person, "name", {
        configurable: false,//设置为不可配置
        value: "Nicholas"
    });
    
    alert(person.name); //"Nicholas"
    
    //想删除该属性,但是不起作用
    delete person.name;
    alert(person.name); //"Nicholas"
    
    
    //也无法改回成可配置,会抛出错误
    Object.defineProperty(person, "name", {
        configurable: true,
        value: "Nicholas"
    });
    

    访问器特性

    • [[Configurable]] - 能否通过 delete 删除或者改变成访问器特性,默认值为 True
    • [[Enumerable]] - 是否可以使用 for - in 循环,默认值为 True
    • [[Get]] - 读取时调用的函数,默认值为 undefined
    • [[Set]] - 写入时调用的函数,默认值为 undefined

    访问器特性也用 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
    
    

    6.1.2 定义多个属性特性

    可以一下定义多个属性

    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;
                }
            }
        }
    });
    
    

    6.1.3 读取特性值

    属性的特性也可以读取出来

    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;
                }
            }
        }
    });
    
    //读取 "_year" 的特性
    var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
    alert(descriptor.value); //2004
    alert(descriptor.configurable); //false
    alert(typeof descriptor.get); //"undefined"
    
    //读取 year 的特性
    var descriptor = Object.getOwnPropertyDescriptor(book, "year");
    alert(descriptor.value); //undefined
    alert(descriptor.enumerable); //false
    alert(typeof descriptor.get); //"function"
    

    6.2 创建对象

    6.2.1 工厂模式

    工厂模式的缺点是:创建的对象无法判断类型

    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");
    

    6.2.2 构造函数模式

    用 new 关键字创建对象,创建的对象会被绑定到构造函数中的 this 上

    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");
    

    用构造函数创建的对象可以检测类型

    alert(person1.constructor == Person); //true
    alert(person2.constructor == Person); //true
    
    alert(person1 instanceof Object); //true
    alert(person1 instanceof Person); //true
    alert(person2 instanceof Object); //true
    alert(person2 instanceof Person); //true
    

    使用普通函数创建对象

    构造函数就是一个普通函数,使用 new 关键字就变成了构造函数。使用普通函数也可以创建对象,但是需要绑定 this 指针

    //使用构造函数
    var person = new Person("Nicholas", 29, "Software Engineer");
    person.sayName(); //"Nicholas"
    
    //使用普通函数创建对象,this 被绑定到 window 上
    Person("Greg", 27, "Doctor"); //adds to window
    window.sayName(); //"Greg"
    
    //使用普通函数创建对象,自己绑定 this 
    var o = new Object();
    Person.call(o, "Kristen", 25, "Nurse");
    o.sayName(); //"Kristen"
    

    使用构造函数的问题

    正常情况下,同一类型的所有实例共享相同的方法代码。但是在 Javascript 中函数是一种对象,因此每个对象都会创建自己的成员方法实例。

    
    function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = new Function("alert(this.name)"); //这种写法更能看清楚,每次创建 Person 对象,同时也会创建一个新的 sayName 实例
    }
    

    可以写一个全局方法,然后设置到对象内部,这样所有对象共享同样的方法,但是会破坏了封装性。

    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");
    

    6.2.2 原型模式

    当定义函数时,系统在后台自动为其创建一个原型对象。可以通过函数的 prototype 属性访问原型对象;此外,可以通过 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
    

    访问和检测原型对象

    • 每个原型对象都有一个 constructor 属性,它反过来指向函数
    • 每个实例都有一个 proto 属性,它也指向原型对象
    • 利用 Object.getPrototypeOf 方法也可以从实例得到原型对象,这种方法比 proto 属性的兼容性更好
    alert(Object.getPrototypeOf(person1) == Person.prototype); //true
    alert(Object.getPrototypeOf(person1).name); //"Nicholas"
    
    • 可以用 Person.prototype.isPrototypeOf 方法检测一个类型是不是某个对象的原型
    alert(Person.prototype.isPrototypeOf(person1)); //true
    alert(Person.prototype.isPrototypeOf(person2)); //true
    

    原型对象的工作方式

    访问一个对象的属性或方法时,系统会先从对象本身查找,如果找不到再从原型对象中查找。

    例如,虽然对象中不包括 constructor 属性,但是原型对象中包括 constructor 属性,因此当我们直接用对象访问这个属性时,也可以访问到他。

    如果对象的属性和原型对象中的属性名字相同,则使用对象的属性。

    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" - 来自原型
    

    如果将实例中的同名属性删除,则又可以使用原型中的属性了

    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 方法

    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
    

    如果 in 操作符返回 true ,而 hasOwnProperty 方法返回 false ,则名字在原型中

    
    //这个函数检测名字是不是在原型中
    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 语法枚举出所有可枚举到的名字,但是 [[Enumerable]] 设置为 false 的名字枚举不出来

    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.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"
    

    返回所有的名字

    getOwnPropertyNames 方法可以返回所有的名字,不管它是在实例中还是在原型中,也不管它是否可枚举

    var keys = Object.getOwnPropertyNames(Person.prototype);
    alert(keys); //"constructor,name,age,job,sayName"
    

    更简单的原型语法

    之前的原型语法会出现很多的 prototype 属性名字,可以用字面量来重写整个原型对象

    function Person(){
    }
    
    Person.prototype = {
        name : "Nicholas",
        age : 29,
        job : "Software Engineer",
    
        sayName : function () {
            alert(this.name);
        }
    };
    

    这样的写法会导致一个问题,prototype 中不再有 constructor 属性了

    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 属性

    function Person(){
    }
    
    Person.prototype = {
    
        constructor: Person,
    
        name : "Nicholas",
        age : 29,
        job : "Software Engineer",
    
        sayName : function () {
            alert(this.name);
        }
    };
    

    这样做会导致另外一个矛盾,标准中定义的 constructor 属性是不可枚举的,上面的写法确是可枚举的。

    对于现代浏览器,可以用 Object.defineProperty 方法解决这个问题

    function Person(){
    }
    
    Person.prototype = {
        name : "Nicholas",
        age : 29,
        job : "Software Engineer",
    
        sayName : function () {
            alert(this.name);
        }
    };
    
    //ECMAScript 5 only – 定义一个 constructor 属性,将其设置为不可枚举
    Object.defineProperty(Person.prototype, "constructor", {
        enumerable: false,
        value: Person
    });
    

    原型的动态性

    创建一个对象之后,也可以修改原型对象,对象可以访问到修改后的属性和方法。

    var friend= new Person();
    
    Person.prototype.sayHi = function(){
        alert("hi");
    };
    
    friend.sayHi(); //"hi" - works!
    

    但是,如果替换掉整个原型对象对象,则之前已经创建的对象不能访问新原型对象中的属性和方法,因为对象的 [[prototype]] 仍然指向原来的原型对象

    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
    

    原生对象的原型

    原生对象的属性和方法也是在原型中定义的

    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
    

    6.2.4 组合使用构造函数模式和原型模式

    用构造函数来设置属性,用原型对象来设置方法,克服两种模式各自的缺点

    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,Court,Van"
    alert(person2.friends); //"Shelby,Court"
    alert(person1.friends === person2.friends); //false
    alert(person1.sayName === person2.sayName); //true
    

    6.2.5 动态原型模式

    同样是用构造函数来设置属性,用原型对象来设置方法,但是设置方法的代码不是放置在全局部分,而是也放在构造函数中。

    为了防止每次构造对象的时候都设置方法,这里增加了一个检测方法是否已经存在的条件语句,这样定义方法着部分代码只是在创建第一个对象的时候执行

    function Person(name, age, job){
    
        //properties
        this.name = name;
        this.age = age;
        this.job = job;
    
        //methods
        if (typeof this.sayName != "function"){
            Person.prototype.sayName = function(){
                alert(this.name);
            };
        }
    }
    var friend = new Person("Nicholas", 29, "Software Engineer");
    friend.sayName();
    

    6.2.5 寄生构造函数模式

    正常情况下,构造函数(即用 new 关键字调用的函数)不显式写明返回值,系统默认返回一个新对象。

    可以改变这种默认情况,在构造函数内创建一个对象,然后返回,作为 new 获得的对象。

    缺点是,通过这种模式获得的对象和构造函数以及构造函数的原型都没什么关系,因此 instanceof 关键字无法正常使用。

    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"
    

    利用这种模式可以改写一些内置对象

    function SpecialArray(){
        //create the array
        var values = new Array();
    
        //add the values
        values.push.apply(values, arguments);
    
        //assign the method
        values.toPipedString = function(){
            return this.join("|");
        };
    
        //return it
        return values;
    }
    
    var colors = new SpecialArray("red", "blue", "green");
    alert(colors.toPipedString()); //"red|blue|green"
    

    6.2.7 稳妥构造函数模式

    寄生构造函数模式的简化安全版,构造函数中创建的对象不设置任何属性,只有方法,同时不引用 this 指针,因此更安全。缺点和寄生构造模式也一样。

    function Person(name, age, job){
    
        //这里可以定义其他私有变量
    
        //创建对象
        var o = new Object();
    
        //添加方法
        o.sayName = function(){
            alert(name);
        };
    
        //返回对象
        return o;
    }
    

    6.3 继承

    6.3.1 原型链

    正常情况下,构造函数都有自己的原型对象,new 实例也包含一个指针指向这个原型对象,在对象中找不到属性和方法,会到原型对象中去查找。

    如果将构造函数的默认原型对象,用某个手动创建的对象替换,则构造函数的类型继承了手动创建对象中的所有方法和属性。

    查找属性和方法的流程如下:

    1. 先在自身实例中查找
    2. 再到原型对象(手动创建的某个对象)中查找
    3. 再到原型对象的原型对象中查找

    上面这个流程即构成了原型链

    
    //这里是父类
    function SuperType(){
        this.property = true;
    }
    
    //向父类的原型对象中添加方法
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    };
    
    //--------------------
    
    //这里是子类
    function SubType(){
        this.subproperty = false;
    }
    
    //创建一个父类的实例替换子类的原型对象
    SubType.prototype = new SuperType();
    
    //向子类的原型对象中添加方法
    SubType.prototype.getSubValue = function (){
    return this.subproperty;
    };
    
    //子类的实例可以访问父类的方法
    var instance = new SubType();
    alert(instance.getSuperValue()); //true
    

    需要注意的是,子类的原型对象中现在某有 constructor 这个属性,访问这个属性将从原型对象的原型对象中获得,即指向 SuperType

    别忘记默认的原型

    上面的原型链还少讲述了一个环节,那就是基类 Object,所有新建类型的默认原型对象就是一个 Object 对象。而 Object 也有自己的原型对象,Object 中的方法比如 toString ,都是在他自己的原型对象中定义的。一个对象如果使用 toString 方法,查找流程如下:

    1. 先在自身实例中查找
    2. 再到父类(手动创建的原型对象)中查找
    3. 再到父类的父类中查找
    4. 继续....
    5. 最后到 Object 的原型对象中查找

    确定原型对象与实例的关系

    instanceof 和 Object.prototype.isPrototypeOf 都可以确定原型对象与实例的关系

    alert(instance instanceof Object); //true
    alert(instance instanceof SuperType); //true
    alert(instance instanceof SubType); //true
    
    alert(Object.prototype.isPrototypeOf(instance)); //true
    alert(SuperType.prototype.isPrototypeOf(instance)); //true
    alert(SubType.prototype.isPrototypeOf(instance)); //true
    

    在子类中定义方法要小心

    在子类中重写父类的方法或者定义一个新方法,给原型添加方法的代码一定要放在替换原型的语句之后,否则方法将添加到默认原型对象上去

    function SuperType(){
        this.property = true;
    }
    
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    };
    
    //----------------------------------------------------
    
    function SubType(){
        this.subproperty = false;
    }
    
    //inherit from SuperType
    SubType.prototype = new SuperType();
    
    //new method
    SubType.prototype.getSubValue = function (){
        return this.subproperty;
    };
    
    //override existing method
    SubType.prototype.getSuperValue = function (){
        return false;
    };
    
    var instance = new SubType();
    alert(instance.getSuperValue()); //false
    

    另外,不要用字面量方式定义新方法,这样会创建一个新的原型对象

    function SuperType(){
        this.property = true;
    }
    
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    };
    
    //---------------------------------------------
    
    function SubType(){
        this.subproperty = false;
    }
    
    //inherit from SuperType
    SubType.prototype = new SuperType();
    
    //try to add new methods - this nullifies the previous line
    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(){
    }
    
    //inherit from 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”
    

    6.3.2 借用构造函数

    在子类构造函数中用 call 方法调用超类构造函数,这样超类中的 this 将指向子类实例,超类中在 this 上定义的属性和方法都变成了子类的属性和方法。这样解决了原型链中引用类型属性的问题

    function SuperType(){
        this.colors = [“red”, “blue”, “green”];
    }
    
    function SubType(){
        //inherit from 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(){
        //inherit from SuperType passing in an argument
        SuperType.call(this, “Nicholas”);
        //instance property
        this.age = 29;
    }
    
    var instance = new SubType();
    alert(instance.name); //”Nicholas”;
    alert(instance.age); //29
    

    借用构造函数的问题

    借用构造函数模式创建的子类的实例,只能使用在父类中构造函数中定义的属性和方法,但是不能使用父类原型对象中的属性和方法,复用性不高

    6.3.3 组合继承

    结合原型链继承和借用构造函数继承两种模式的优点,即

    • 本来通过原型链模式就可以访问全部的父类属性和方法,但是会存在引用属性的缺点
    • 在此基础上,再在子类构造函数中用 call 方法调用超类构造函数,让每个子类实例独立的拥有父类在构造函数中定义的属性和方法
    • 本质:从父类构造函数继承的属性和方法也同时存在于从原型继承的属性和方法中,父类构造函数的属性和方法会覆盖原型的属性和方法
    • 缺点:存在一部分无用的属性和方法
    
    function SuperType(name){
        this.name = name;
        this.colors = [“red”, “blue”, “green”];
    }
    
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };
    
    function SubType(name, age){
        //inherit properties
        SuperType.call(this, name);
        this.age = age;
    }
    
    //inherit methods
    SubType.prototype = new SuperType();
    
    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
    

    6.3.4 克隆式继承

    方法:将一个已存在的对象作为一个空对象的原型对象,并返回这个空对象。

    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 方法

    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.create 方法有第二个参数,传入一个键值对,可以在原型的基础上添加自己的属性和方法

    var person = {
        name: “Nicholas”,
        friends: [“Shelby”, “Court”, “Van”]
    };
    
    var anotherPerson = Object.create(person, {
        name: {
            value: “Greg”
        }
    });
    
    alert(anotherPerson.name); //”Greg”
    

    有时候没有必要写一个构造函数,利用原型式继承是一个好方法

    6.3.5 寄生式继承

    利用别的函数来创建对象(大多属性框是克隆已有对象),然后在这个对象上添加另外一些方法和属性

    function createAnother(original){
        var clone = object(original); //create a new object by calling a function
    
        clone.sayHi = function(){ //augment the object in some way
            alert(“hi”);
        };
    
        return clone; //return the object
    }
    
    
    //______________________________________________________
    
    var person = {
        name: “Nicholas”,
        friends: [“Shelby”, “Court”, “Van”]
    };
    
    var anotherPerson = createAnother(person);
    anotherPerson.sayHi(); //”hi”
    

    6.3.6 寄生组合式继承

    组合式继承的缺点前面已经说过,原型中会存在一些被覆盖的无用属性和方法

    //父类中定义的属性
    function SuperType(name){
        this.name = name;
        this.colors = [“red”, “blue”, “green”];
    }
    //父类中定义的方法
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };
    
    //通过 call 方法继承父类中的属性,这是第二次构造一个父类实例,会覆盖原型中的同名属性
    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);
    };
    

    如果不是将子类的原型直接指向一个父类实例,而是指向父类原型对象的克隆对象,这样子类通过原型继承的仅仅是父类原型对象中的方法,而不会继承父类中的属性,克服了组合模式的缺点。

    
    //通过这个方法处理,子类会继承父类中的方法,但是不会继承父类中的属性
    function inheritPrototype(subType, superType){
        //克隆父类的原型对象
        var prototype = object(superType.prototype); //create object
        prototype.constructor = subType; //augment object
    
        //通过将子类的原型指向这个克隆对象,继承其中的方法,而不会继承其中的属性
        subType.prototype = prototype; //assign object
    }
    
    
    //-------------------------------
    
    //定义父类
    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);
    };
    

    这种模式是最常用的模式

    相关文章

      网友评论

        本文标题:第06章 - 面向对象的程序设计

        本文链接:https://www.haomeiwen.com/subject/sowkkttx.html