JavaScript高级程序设计(第3版)学习笔记 第6章

第6章 面向对象的程序设计

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

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,特性默认值为 true。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性,默认值为 true。
  • [[Writable]]:表示能否修改属性的值,默认值为 true。
  • [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置,默认值为 undefined。

使用 ECMAScript 5 的 Object.defineProperty()方法修改属性默认的特性。

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

访问器属性:包含一对儿 getter 和 setter 函数,在读取访问器属性时,调用 getter 函数,负责返回有效的值;在写入访问器属性时,调用 setter 函数并传入新值,负责决定如何处理数据,有如下 4 个特性:

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性,默认值为 true。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性,默认值为 true。
  • [[Get]]:在读取属性时调用的函数,默认值为 undefined。
  • [[Set]]:在写入属性时调用的函数,默认值为 undefined。

访问器属性不能直接定义,必须使用 Object.defineProperty()来定义。

  • var book = {
        _year: 2004,
        edition: 1 
    };
    Object.defineProperty(book, "year", {
        get: function(){
            return this._year;
        },
        set: function(newValue){
            if (newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            } 
        }
    });
    book.year = 2005; 
    alert(book.edition); //2

Object.defineProperties()方法定义多个属性,接收两个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应。

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

使用 ECMAScript 5 的 Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符,接收两个参数:属性所在的对象和要读取其描述符的属性名称,返回一个对象。如果是访问器属性,这个对象的属性有 configurable、enumerable、get 和 set;如果是数据属性,这 个对象的属性有 configurable、enumerable、writable 和 value。

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

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)构造函数模式

  • /* 没有显式地创建对象;
    直接将属性和方法赋给了 this 对象; 
    没有 return 语句。*/
    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 个步骤: 

  • 创建一个新对象;
  • 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
  • 执行构造函数中的代码(为这个新对象添加属性);
  • 返回新对象。

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

(3)原型模式

  • 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 (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。

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

使用 ECMAScript 5 的 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"

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

 

  • function Person(){
    }
    Person.prototype = {
        constructor : Person,//本质上完全重写了默认的 prototype 对象,因此 constructor 属性不再指向 Person 函数,特意将它设置回适当的值。
        name : "Nicholas",
        age : 29,
        job: "Software Engineer",
        sayName : function () {
            alert(this.name);
        }
    };

原型模式的最大问题是由其共享的本性所导致的,对于包含引用类型值的属性比如数组,一个对象的修改会在另一个使用该原型创建的对象中反应出来。

(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,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"

不能依赖 instanceof 操作符来确定对象类型建议在可以使用其他模式的情 况下,不要使用这种模式。

(7)稳妥构造函数模式:没有公共属性,其方法也不引用 this 的对象,最适合在 一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup 程序)改动时使用。

 

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

3.继承

(1)原型链:利用原型让一个引用类型继承另一个引用类型的属性和方法。

  • /通过创建 SuperType 的实例,并将该实例赋给 SubType.prototype 
    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

本质是重写原型对象,代之以一个新类型的实例。

使用 isPrototypeOf()方法和instanceof 操作符确定原型和实例的关系。

  • //instanceof 操作符测试实例与原型链中出现过的构造函数,返回 true。
    alert(instance instanceof Object);//true
    alert(instance instanceof SuperType);//true
    alert(instance instanceof SubType);//true
    
    //只要是原型链中出现过的原型,isPrototypeOf()方法返回 true
    alert(Object.prototype.isPrototypeOf(instance));//true
    alert(SuperType.prototype.isPrototypeOf(instance));//true
    alert(SubType.prototype.isPrototypeOf(instance));//true
    

原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。

(2)借用构造函数:在子类型构造函数的内部调用超类型构造函数。

//通过使用 call()方法(或 apply()方法),实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。
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)组合继承:使用原型链实现对原型属性和方 法的继承,而通过借用构造函数来实现对实例属性的继承,成为 JavaScript 中最常用的继承模式。

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)原型式继承

/*在 object()函数内部,先创建了一个临时性的构造函数
然后将传入的对象作为这个构造函数的原型
最后返回了这个临时类型的一个新实例。
本质上讲,object()对传入其中的对象执行了一次浅复制*/
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"

ECMAScript 5的 Object.create()方法规范化了原型式继承,接收两个参数:一 个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。

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

(5)寄生式继承:创建一个仅用于封装继承过程的函数,函数内部以某种方式来增强对象,最后返回对象。

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

(6)寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。引用类型最理想的继承范式。

  • //接收两 个参数:子类型构造函数和超类型构造函数
    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);
    };

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值