美文网首页JavaScript < ES5、ES6、ES7、… >
JavaScript 高级程序设计(第6章 面向对象程序设计)

JavaScript 高级程序设计(第6章 面向对象程序设计)

作者: yinxmm | 来源:发表于2018-08-07 16:20 被阅读34次

    第6章 面向对象程序设计

    1.理解对象

    (1) 建自定义对象的方式
    *创建一个object实例,然后添加属性和方法。

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

    (2) 属性类型
    ECMAScript 中有两种属性:数据属性访问器属性
    * 数据属性:
    数据属性包含一个数据值的位置。在这个位置可以读取和写入值。
    数据属性有4 个描述其行为的特性。

    1. [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的
      这个特性默认值为 true。
    2. [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
    3. [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
    4. [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined。

    1. Object.defineProperty()方法 :修改属性默认的特性,这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。
    2. 在调用 Object.defineProperty()方法时,如果不指定,configurable、enumerable 和 writable 特性的默认值都是 false。

    var person = {};
    Object.defineProperty(person, "name", {
        writable: false,
        value: "Nicholas"
    });
    alert(person.name); //"Nicholas"
     person.name = "Greg";  //在严格模式下会导致错误。
    alert(person.name); //"Nicholas"
    

    3. 一旦把属性定义为不可配置的, 就不能再把它变回可配置了。

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

    * 访问器属性
    访问器属性不包含数据值,它们包含一对儿 getter 和 setter 函数。
    问器属性有如下 4 个特性:

    1. [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特 性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为 true。
    2. [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这 5 个特性的默认值为 true。
    3. [[Get]]:在读取属性时调用的函数。默认值为 undefined。
    4. [[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
    
    
    ===========================
    
    //定义访问器的旧有方法 
    book.__defineGetter__("year", function(){
        return this._year;
    });
    book.__defineSetter__("year", function(newValue){
        if (newValue > 2004) {
            this._year = newValue;
            this.edition += newValue - 2004;
        }
    });
    

    (3) 定义多个属性
    Object.defineProperties()方法:可以通过描述符一次定义多个属性。

    这个方法接收两个对象参数:

    1. 第一 个对象是要添加和修改其属性的对象。
    2. 第二个对象的属性与第一个对象中要添加或修改的属性一一对应。
    //book 对象上定义了两个数据属性(_year 和 edition)和一个访问器属性(year)。
    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;
                    }
                 }
             }
    });
    

    (3) 读取属性的特性
    bject.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。

    1. 这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。
    2. 返回值是一个对象,如果是访问器属性,这个对象的属性有 configurable、enumerable、get 和 set;如果是数据属性,这 个对象的属性有 configurable、enumerable、writable 和 value。
    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"
    

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

    (2) 构造函数模式
    1. 创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
    2. 造函数始终都应该以一个 大写字母开头,而非构造函数则应该以一个小写字母开头。
    3. 要创建 Person 的新实例,必须使用 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");
    

    4. 创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。
    * 将构造函数当作函数

    任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数。

    // 当作构造函数使用
    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"
    

    * 构造函数的问题

    使用构造函数的主要问题,就是每个方法都要在每个 实例上重新创建一遍。

    (3) 原型模式

    1. 每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
    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
    

    *理解原型对象
    1.只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。
    2. 在默认情况下,所有原型对象都会自动获得一个 constructor (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。Person.prototype. constructor 指向 Person。通过这个构造函数,我们还可继续为原型对象 添加其他属性和方法。

    1. 调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部 属性),指向构造函数的原型对象。ECMA-262 第 5 版中管这个指针叫[[Prototype]]
    2. 连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
    Person 构造函数、Person 的原型属性以及 Person 现有的两个实例之间的关系

    Person.prototype 指向了原型对象,
    Person.prototype.constructor 又指回了 Person。
    原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。
    Person 的每个实例 person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype。

    isPrototypeOf() 方法

    1. 所有实现中都无法访问到[[Prototype]],但可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系。
    2. 如果[[Prototype]]指向调用 isPrototypeOf()方法的对象 (Person.prototype),那么这个方法就返回 true。
    //person1 和 person2它们内部都 有一个指向 Person.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"
    

    3. 每当代码读取某个对象的某个属性时,先后执行两次搜索,首先 从对象实例本身开始搜索,再续搜索指针指向的原型对象。
    4.通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。

    如果我们 在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该 属性将会屏蔽原型中的那个属性。

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

    将这个属性设置为 null,也 只会在实例中设置这个属性,而不会恢复其指向原型的连接。使用 delete 操作符则可以完全删 除实例属性,从而让我们能够重新访问原型中的属性。

    person1.name = "Greg";
    alert(person1.name);//"Greg"——来自实例
    alert(person2.name); //"Nicholas"——来自原型
    delete person1.name;
    alert(person1.name);//"Nicholas"——来自原型
    

    hasOwnProperty()方法

    可以检测一个属性是存在于实例中,还是存在于原型中。
    在给定属性存在于对象实例中时,才会返回 true

    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 操作符:单独使用和 在for-in 循环中使用。

    1. 在单独使用时,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();
    
    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
    
    • 同时使用 hasOwnProperty()方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中
    function hasPrototypeProperty(object, name){
            return !object.hasOwnProperty(name) && (name in object);
    }
    
    1. 在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将 [[Enumerable]]标记为 false 的属性)的``实例属性也会在 for-in 循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的。
    var o = {
        toString : function(){
            return "My Object";
        }
    };
    for (var prop in o){
        if (prop == "toString"){
            alert("Found toString");//在 IE 中不会显示
    } }
    

    ECMAScript 5 也将 constructor 和 prototype 属性的[[Enumerable]]特性设置为 false,但并不是所有浏览器都照此实现。

    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"
    

    * 更简单的原型语法

    用一个包含所有属性和方法的对象字面量来重写整个原型对象。

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

    constructor 属性不再指向 Person ,我们在这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新 对象的 constructor 属性(指向 Object 构造函数),不再指向 Person 函数。
    尽管 instanceof 操作符还能返回正确的结果,但通过 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 属性会导致它的[[Enumerable]]特性被设置为 true。默认 情况下,原生的 constructor 属性是不可枚举的

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

    解决:

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

    * 4. 原型的动态性

    1. 我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此。
      当我们调用 person.sayHi() 时,首先会在实例中搜索名为 sayHi 的属性,在没找到的情况下,会继续搜索原型。
    var friend = new Person();
    Person.prototype.sayHi = function(){
        alert("hi");
    };
    friend.sayHi(); //"hi"(没有问题!)
    
    1. 调用构造函数时会为实例添加一个指向最初原型的 [[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
    
    重写原型对象

    * 原生对象的原型

    1. 原生引用类型(Object、Array、String,等等)都在其构造函数的原型上定义了方法。
    2. 通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法。

    * 原型对象的问题

    1. 略了为构造函数传递初始化参数这一环节,结果所有实例在 默认情况下都将取得相同的属性值。
    2. 通过在实例上添加一个同名属性,可以隐藏原型中的对应属 性。然而,对于包含引用类型值的属性来说,问题就比较突出了。

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

    1. 构造函数模式用于定义实例属性。
    2. 原型模式用于定义方法和共享的属性。
      每个实例都会有自己的一份实例属性的副本, 但同时又共享着对方法的引用,最大限度地节省了内存。
    function Person(name, age, job){
    this.name = name; 3 this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
    2
    }
    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
    

    (5) 动态原型模式

    把所有信息都封装在了构造函数中,而通过在构造函数 中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。

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

    (6) 寄生构造函数模式

    基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。

    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"
    

    构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个 return 语句,可以重写调用构造函数时返回的值。

    假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改 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"
    

    寄生构造函数模式说明:

    1. 返回的对象与构造函数或者与构造函数的原型属 性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。
    2. 不能依赖 instanceof 操作符来确定对象类型。

    (7) 稳妥构造函数模式

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

    稳妥构造函数遵循与寄生构造函数不同点:一是新创建对象的 实例方法不引用 this;二是不使用 new 操作符调用构造函数。

    function Person(name, age, job){
    //创建要返回的对象
    var o = new Object();
    //可以在这里定义私有变量和函数
    //添加方法
    o.sayName = function(){
    alert(name);
    };
    //返回对象
    return o; 
    }
    //以这种模式创建的对象中,除了使用 sayName()方法之外,
    //没有其他办法访问 name 的值。
    
    var friend = Person("Nicholas", 29, "Software Engineer");
    friend.sayName();  //"Nicholas"
    

    3. 继承

    (1) 原型链

    1. ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。
    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;
    };
    var instance = new SubType();
    alert(instance.getSuperValue());//true
    

    实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于 SuperType 的实例中的所有属性和方法,现在也存在于 SubType.prototype 中了。`

    实例以及构造函数和原型之间的关系
    1. 要注意instance.constructor现在指向的 是 SuperType,这是因为原来 SubType.prototype 中的 constructor 被重写了的缘故。(实际上,不是 SubType 的原型的 constructor 属性被重写了,而是 SubType 的原型指向了另一个对象—— SuperType 的原型,而这个原型对象的 constructor 属性指向的是 SuperType。)
    2. 原型搜索机制:三个搜索步骤:
      1)搜索实例;
      2)搜索 SubType.prototype;
      3)搜索 SuperType.prototype,最后一步才会找到该方法。

    * 默认的原型
    1. 所有引用类型默认都继承了 Object,而 这个继承也是通过原型链实现的。
    2. 所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype。

    完整的原型链

    * 确定原型和实例的关系
    instanceof 操作符

    只要用 这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回 true。

    alert(instance instanceof Object);//true
    alert(instance instanceof SuperType);//true
    alert(instance instanceof SubType);//true
    

    isPrototypeOf()方法

    只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此 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!
    

    * 原型链的问题
    1. 最主要的问题来自包含引 用类型值的原型。

    包含引用类型值的原型属性会被所有实例共享;而 这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原 型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

    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"
    

    2. 在创建子类型的实例时,不能向超类型的构造函数中传递参数。

    (2) 借用构造函数
    基本思想:子类型构造函数的内部调用超类型构造函数。

    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
    

    * 借用构造函数的问题

    方法都在构造函数中定 义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

    (3) 组合继承
    基本思想: 使用原型链实现对原型属性和方 法的继承,而通过借用构造函数来实现对实例属性的继承。

    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
    

    (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()方法 :范化了原型式继承

    接收两个参数:

    1. 用作新对象原型的对象
    2. (可选的)一个为新对象定义额外属性的对象

    传入一个参数的情况下:

    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"
    

    第二个参数:

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

    (6) 寄生式继承
    基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

    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"
    //基于 person 返回了一个新对象——anotherPerson。新对象不仅具有 person
    // 的所有属性和方法,而且还有自己的 sayHi()方法。
    

    (6) 寄生组合式继承
    组合继承问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是 在子类型构造函数内部。

    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);
    };
    
    1. 在第一次调用 SuperType 构造函数时, SubType.prototype 会得到两个属性:name 和 colors;它们都是 SuperType 的实例属性,只不过 现在位于 SubType 的原型中。
    2. 当调用 SubType 构造函数时,又会调用一次 SuperType 构造函数,这 一次又在新对象上创建了实例属性 name 和 colors。于是,这两个属性就屏蔽了原型中的两个同名属 性。

    寄生组合式继承的基本模式:

    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);
    };
    
    
    寄生组合式继承

    相关文章

      网友评论

        本文标题:JavaScript 高级程序设计(第6章 面向对象程序设计)

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