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.寄生组合式继承

时间: 2024-07-30 13:44:08

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

C++ Primer学习总结 第15章 面向对象程序设计

第15章 面向对象程序设计 1.    构造基类和派生类. 其中A类是基类,B类是派生类.派生类的构造函数必须重新写过,不能继承.(因为毕竟两个类的类名都不一样,不可能构造函数继承)只继承其他的成员函数和成员变量. 派生类可以覆盖基类的虚函数,但是也可以选择不覆盖(即直接使用父类的函数版本)比例A类的print_1()虚函数就没有被覆盖. 基类的静态成员:如果基类有一个静态成员,那么基类和所有派生类都共同拥有这仅有的一个静态成员. 2.    基类的虚函数默认实参最好与派生类的虚函数默认实参一致

JavaScript高级程序设计 第六章 面向对象程序设计

面向对象程序设计 ECMA-262将对象定义为:“无序属性的集合,其属性可以包含基本值.对象或者函数.”严格来讲,这就相当于说对象是一组没有特定顺序的值.对象的每个属性和方法都有一个名字,而每个名字都映射到一个值. 6.1理解对象 创建对象的最简单方式就是创造一个Object实例,然后再为它添加属性和方法,如下所示: var person = new Object(); person.name = 'yyg'; person.age = 23; person.job = 'student; pe

Android学习笔记—第六章 Asynctask异步加载

第六章 Asynctask 异步加载 1.好处:不需要创建线程就可管理线程 缺点:步骤多 2.步骤: (1)创建一个类继承Asynctask<xxx,xxx,xxx>; 三个泛型参数: 第一个:决定了execute()方法的传入值类型,决定了doInBackground()方法的传入值类型 第二个:决定了publishProgress()方法的传入值类型,决定了onProgressUpdate()方法的传入值类型 第三个:决定了doInBackground()方法的返回值类型,决定了onPos

Java学习笔记—第六章 流程控制语句

第六章  熟悉Java的流程控制语句 Java的程序流程控制分为顺序结构.选择结构.循环结构和跳转语句. 顺序结构:按照程序代码自上而下执行,直到程序结束,中间没有任何判断和跳转. 选择结构(分支结构):判断给定的条件,根据判断结果控制程序的流程.包括if语句和switch语句. 2.1 if语句:通过判断给定表达式的值来决定程序的流程.常见if语句的形式有三种: (1)if(expression){ statement: } (2)if(expression){ statement; }els

C#学习笔记(六)&mdash;&mdash;面向对象编程简介

一.面向对象编程的含义 *   是一种模块化编程方法,使代码的重用性大大的增加. *   oop技术使得项目的设计阶段需要的精力大大的增加,但是一旦对某种类型的数据表达方式达成一致,这种表达方式就可以沿用下去,直到这款应用的生命周期结束. (一)对象的含义 1.对象就是OPP应用程序的一个组成部分,这个组成部件封装了部分应用程序,这部分程序可以是一个过程.一些数据或一些更抽象的实体.对象的类型在OOP中有个特殊的名称叫做类.可以使用类来定义一个对象. PS:术语"类"和"对象

javascript高级程序设计 学习笔记 第五章 上

第五章 引用类型的值(对象)是引用类型的一个实例.在 ECMAScript 中,引用类型是一种数据结构, 用于将数据和功能组织在一起.它也常被称为类,但这种称呼并不妥当.尽管 ECMAScript 从技术上讲是一门面向对象的语言,但它不具备传统的面向对象语言所支持的类和接口等基本结构.引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法. 对象是某个特定引用类型的实例.新对象是使用 new 操作符后跟一个构造函数来创建的. 构造函数本身就是一个函数,只不过该函数是出于创建新

JavaScript高级程序设计学习笔记第三章--基本概念

一.标识符: 1.区分大小写 2.命名规则: 第一个字符必须是一个字母.下划线(_)或一个美元符号($) 其他字符可以是字母.下划线.美元符号或数字 标识符中的字母也可以包含扩展的 ASCII 或 Unicode 字母字符(如 À 和 Æ) ,但不推荐这样做. 不能把关键字.保留字.true.false和null用作标识符 3.书写方式:最好按照驼峰大小写格式书写,就是第一个字母小写,剩下的每个单词的首字母大写,但不强制这么做二.注释(两种方式) 单行注释:// 多行注释:/*……*/ 三.严格

JavaScript高级程序设计学习笔记第五章--引用类型(函数部分)

四.Function类型: 1.函数定义的方法: 函数声明:function sum (num1, num2) {return num1 + num2;} 函数表达式:var sum = function(num1, num2){return num1 + num2;};//注意有个分号 构造函数的方式:var sum = new Function("num1", "num2", "return num1 + num2");// 2.函数的重复声

JavaScript高级程序设计学习笔记第四章--变量、作用域和内存问题

1.变量可能包含两种不同数据类型的值:基本类型值和引用类型值. 基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象. 2.变量复制 如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,两个变量可以参与任何操作而不会相互影响. 当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中.不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象.复制操作结束