JavaScript高级程序设计学习笔记第六章--面向对象程序设计

1.ECMAScript没有类的概念,ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”,有点类似于散列表

2.ECMAScript 中有两种属性:数据属性和访问器属性。

  • 数据属性:
    • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特
      性,或者能否把属性修改为访问器属性。
    • [[Enumerable]]:表示能否通过 for-in 循环返回属性。
    • [[Writable]]:表示能否修改属性的值。
    • [[Value]]:包含这个属性的数据值。

修改默认属性:

要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descriptor)对象的属性必须是: configurable、 enumerable、 writable 和 value。设置其中的一或多个值,可以修改对应的特性值。例如:

1 var person = {};
2 Object.defineProperty(person, "name", {
3 writable: false,
4 value: "Nicholas"
5 });
6 alert(person.name); //"Nicholas"
7 person.name = "Greg";
8 alert(person.name); //"Nicholas"

如果通过defineProperty将configurable设置为false,那么就不可以在更改其他设置了。

  • 访问器属性:不包含数据值;它们包含一对儿 getter 和 setter 函数
    • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特
      性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为
      true。
    • [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这
      个特性的默认值为 true。
    • [[Get]]:在读取属性时调用的函数。默认值为 undefined。
    • [[Set]]:在写入属性时调用的函数。默认值为 undefined。

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

 1 var book = {
 2 _year: 2004,
 3 edition: 1
 4 };
 5 Object.defineProperty(book, "year", {
 6 get: function(){
 7 return this._year;
 8 },
 9 set: function(newValue){
10 if (newValue > 2004) {
11 this._year = newValue;
12 this.edition += newValue - 2004;
13 }
14 }
15 });
16 book.year = 2005;
17 alert(book.edition); //2

3.为对象定义多个属性:用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;
}
}
}
});

p.s 需要注意的点

每个属性名后面是冒号

第二个参数的属性用花括号括起来,每个属性用逗号分隔

最后有一个分号

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

 1 var book = {};
 2 Object.defineProperties(book, {
 3 _year: {
 4 value: 2004
 5 },
 6 edition: {
 7 value: 1
 8 },
 9 year: {
10 get: function(){
11 return this._year;
12 },
13 set: function(newValue){
14 if (newValue > 2004) {
15 this._year = newValue;
16 this.edition += newValue - 2004;
17 }
18 }
19 }
20 });
21 var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
22 alert(descriptor.value); //2004
23 alert(descriptor.configurable); //false
24 alert(typeof descriptor.get); //"undefined"
25 var descriptor = Object.getOwnPropertyDescriptor(book, "year");
26 alert(descriptor.value); //undefined
27 alert(descriptor.enumerable); //false
28 alert(typeof descriptor.get); //"function"

5.创造对象模式:

工厂模式:在函数内部新创建一个对象Object,并在函数内部定义其属性与方法,最后返回这个对象Object。例如:

 1 function createPerson(name, age, job){
 2 var o = new Object();
 3 o.name = name;
 4 o.age = age;
 5 o.job = job;
 6 o.sayName = function(){
 7 alert(this.name);
 8 };
 9 return o;
10 }
11 var person1 = createPerson("Nicholas", 29, "Software Engineer");
12 var person2 = createPerson("Greg", 27, "Doctor");

优点:创建的对象都是相似的,用函数封装了具体创建的细节

缺点:不能确定创建的是怎样的对象类型

 

 

构造函数模式:

 1 function Person(name, age, job){
 2 this.name = name;
 3 this.age = age;
 4 this.job = job;
 5 this.sayName = function(){
 6 alert(this.name);
 7 };
 8 }
 9 var person1 = new Person("Nicholas", 29, "Software Engineer");
10 var person2 = new Person("Greg", 27, "Doctor");

与工厂模式相比,构造函数模式有以下几个不同:

  • 不用显示的创建对象
  • 不用返回对象
  • 将属性和方法直接交给this对象

优点:创建的函数可以当做普通函数用,也可以与new操作符结合当做构造函数使用,使用方法灵活

缺点:每个方法都要在每个实例上创建一遍,每个方法都是不同的实例。例如:alert(person1.sayName == person2.sayName); //false,person1的sayName与person2的sayName不是同一个实例。

 

 

原型模式:

每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。那么 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。
优点:可以让所有对象实例共享它所包含的属性和方法。

 

 1 function Person(){
 2 }
 3 Person.prototype.name = "Nicholas";
 4 Person.prototype.age = 29;
 5 Person.prototype.job = "Software Engineer";
 6 Person.prototype.sayName = function(){
 7 alert(this.name);
 8 };
 9 var person1 = new Person();
10 person1.sayName(); //"Nicholas"
11 var person2 = new Person();
12 person2.sayName(); //"Nicholas"
13 alert(person1.sayName == person2.sayName); //true

上述代码中,person1中的sayName与person2中的sayName是一个函数。

原型对象:

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

 

根据Person创建实例时(上述代码中创建了person1和person2),该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个指针叫[[Prototype]],存在于实例与构造函数的原型对象之间。

虽然在脚本中没有标准的方式访问[[Prototype]],但 Firefox、 Safari 和 Chrome 在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。

虽然person1与person2中没有对应的属性和方法,但是可以根据[[prototype]]属性找到Person Prototype,里面有相应的方法。

 

 

虽然在所有实现中都无法访问到[[Prototype]],但可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系。从本质上讲,如果[[Prototype]]指向调用 isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回 true。例如:

1 alert(Person.prototype.isPrototypeOf(person1)); //true
2 alert(Person.prototype.isPrototypeOf(person2)); //true

 

ECMAScript 5 增加了一个新方法,叫 Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值。例如:

1 alert(Object.getPrototypeOf(person1) == Person.prototype); //true
2 alert(Object.getPrototypeOf(person1).name); //"Nicholas"

 

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。

 

虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。例如:

 1 function Person(){
 2 }
 3 Person.prototype.name = "Nicholas";
 4 Person.prototype.age = 29;
 5 Person.prototype.job = "Software Engineer";
 6 Person.prototype.sayName = function(){
 7 alert(this.name);
 8 };
 9 var person1 = new Person();
10 var person2 = new Person();
11 person1.name = "Greg";
12 alert(person1.name); //"Greg"—— 来自实例
13 alert(person2.name); //"Nicholas"—— 来自原型

上述代码中重新定义了person1的name属性,这样就不会去访问原型中的属性。如果想要重新访问原型中的属性,就可以先delete(person1.name);,将person1的属性删除掉,在访问person1.name属性就是原型中的name属性。

 

使用 hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。属性在实例对象中返回true,在原型中返回false。

原型与in操作符:

使用in操作符的两种方式:第一种,单独使用。只要是能通过实例访问到的属性时,无论该属性是在对象中还是在实例中,总是返回true。通过hasOwnProperty()方法与in方法连个使用可以确定该属性是在原型中还是实例中。

第二种,for-in循环中使用,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,无论该属性是在原型中还是实例中。

 

要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的 Object.keys()方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

要得到所有实例属性,无论它是否可枚举,都可以使用 Object.getOwnPropertyNames()方法。

如果将原型函数添加属性的代码改写成字面量形式。如下所示:

 1 function Person(){
 2 }
 3 Person.prototype = {
 4 name : "Nicholas",
 5 age : 29,
 6 job: "Software Engineer",
 7 sayName : function () {
 8 alert(this.name);
 9 }
10 };
11 PrototypePatternExample07.ht

结果相同,但是constructor 属性不再指向 Person ,而是指向了Object。

解决方法:

如果显示的将constructor指向Person,例如:

 1 function Person(){
 2 }
 3 Person.prototype = {
 4 constructor : Person,
 5 name : "Nicholas",
 6 age : 29,
 7 job: "Software Engineer",
 8 sayName : function () {
 9 alert(this.name);
10 }
11 };

那么constructor 就会成为可枚举的,原生constructor 确实不可枚举的。

 

在兼容 ECMAScript 5 的 JavaScript 引擎,可以试一试 Object.defineProperty()。如下所示:

1 Object.defineProperty(Person.prototype, "constructor", {
2 enumerable: false,
3 value: Person
4 });

 

可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,因为原型与实例之间是指针关系,不是副本。但是如果先创建实例,在重写整个原型对象的话,就会出错,因为该实例指向的仍然是原先的原型对象,无法访问到新的原型对象的属性与方法。

原型模式的缺点:

  • 首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
  • 原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,通过在实例上添加一个同名属性,可以隐藏原型中的对应属
    性。对于包含引用类型的状况,比如包含一个数组的时候,就会出现不能够包含特殊属性,都会成为共享属性。

4.构造函数模式与原型模式的组合:将公用的属性与方法添加到原型中,将有区别的属性添加到构造函数中。例如:

 1 function Person(name, age, job){
 2 this.name = name;
 3 this.age = age;
 4 this.job = job;
 5 this.friends = ["Shelby", "Court"];
 6 }
 7 Person.prototype = {
 8 constructor : Person,
 9 sayName : function(){
10 alert(this.name);
11 }
12 }
13 var person1 = new Person("Nicholas", 29, "Software Engineer");
14 var person2 = new Person("Greg", 27, "Doctor");
15 person1.friends.push("Van");
16 alert(person1.friends); //"Shelby,Count,Van"
17 alert(person2.friends); //"Shelby,Count"
18 alert(person1.friends === person2.friends); //false
19 alert(person1.sayName === person2.sayName); //true

 

5.动态原型模式

6.寄生构造函数模式

7.稳妥构造函数模式

 

8.继承:将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
将构造函数指向原型的指针,将指向的原型变成另一个类型的实例。其本质是重写了原型对象,依次类推,就会出现原型链。

 

使用原型链也是扩展了原型搜索机制的范围。

继承大概过程实现:

 1 function SuperType(){
 2 this.property = true;
 3 }
 4 SuperType.prototype.getSuperValue = function(){
 5 return this.property;
 6 };
 7 function SubType(){
 8 this.subproperty = false;
 9 }
10 //继承了 SuperType
11 SubType.prototype = new SuperType();
12 SubType.prototype.getSubValue = function (){
13 return this.subproperty;
14 };
15 var instance = new SubType();
16 alert(instance.getSuperValue()); //true

 

确定原型与实例的关系:

有两种方法:第一种,使用 instanceof 操作符。只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回 true。例如:

1 alert(instance instanceof Object); //true
2 alert(instance instanceof SuperType); //true
3 alert(instance instanceof SubType); //true

第二种,使用 isPrototypeOf()方法。只要是原型链上出现的原型,都可以说是该原型链所派生的实例的原型,因此 isPrototypeOf()方法也会返回 true。

1 alert(Object.prototype.isPrototypeOf(instance)); //true
2 alert(SuperType.prototype.isPrototypeOf(instance)); //true
3 alert(SubType.prototype.isPrototypeOf(instance)); //true

 

在通过原型链实现继承时,不能使用对象字面量创建原型方法,会是的继承无效。

原型链实现继承的问题:

  • 在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

  • 在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

9.借用构造函数,解决传递参数的问题

10.组合继承:将借用构造函数与原型链进行组合的一种构造方式。

11.原型式继承

12.寄生式继承

13.寄生组合式继承

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



 

posted on 2016-02-07 15:00  莫尤公子  阅读(162)  评论(0编辑  收藏  举报

导航