第6章 面向对象的程序设计 JS与传统的面向对象(Object-Oriented)的语言不同,它没有类的概念。
可以把JS的对象看作是散列表:无非就是一组名值对,其中值可以是数据 或函数 。
每个对象 都是基于一个引用类型 创建的,这个引用类型可以是原生引用类型(Object、Array、Date、RegExp、Function、基本包装类型(Boolean、Number、String)、Math),也可以是用户自定义的类型。
两种简单的创建自定义对象 的方法:
Object构造函数法
1
2
3
4
5
6
7
var king = new Object ();
king.name = "DaShuaiBi" ;
king.job = "playAndPlay" ;
king.age = 27 ;
king.say = function ( ) {
alert("I\'m the king." );
};
对象字面量法
1
2
3
4
5
6
7
8
9
var king = {
name : "DaShuaiBi" ,
job : "playAndPlay" ,
age : 27 ,
say : function ( ) {
alert("I'm the king." );
}
}
6.1 对象的属性 JS中对象的属性(property)有两种类型:数据属性 和访问器属性 ,这两种属性都有自己的特性 (attribute)。可以设置、更改、读取这些特性。
数据属性 4个特性:[[Configurable]](表示能否通过delete 删除属性)、[[Enumerable]](表示能否通过for-in循环返回属性)、[[Writable]](表示能否修改属性的值 )、[[Value]]
在对象中定义了某种属性,那么这种属性就是数据属性了??
访问器属性 4个特性:[[Configurable]]、[[Enumerable]]、[[Get]]、[[Set]]
访问器属性不能直接定义,必须使用Object.defineProperty()来定义。
三个方法
Object.defineProperty() 定义、修改属性 三个参数:属性所在对象、属性名、描述符对象(即属性特性的描述,属性特性名:xx)
1
2
3
4
5
6
7
8
9
10
11
var person = {};
Object .defineProperty(person, "name" , {
writable: false ,
value : "DaShuaiBi"
});
alert(person.name);
person.name = "HeiHeiHei" ;
alert(person.name);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 ;
}
}
});
alert(book._year);
alert(book.edition);
book.year = 2005 ;
alert(book._year);
alert(book.edition);
注意 ,在调用Object.defineProperty()方法时,如果不指定,configurable、enumerable和writable特性的默认值都是false;而在之前使用Object构造函数和对象字面量方法创建的对象,其属性的这几个特性都默认为true。(上边第二段代码中的book的_year和edition就是通过对象字面量定义的属性)
Object.defineProperties() 定义多个属性 两个参数:要添加或修改其属性的对象、一个对象(与第一个参数对象要添加或修改的属性一一对应,即{属性名1: {属性特姓名1: xx}, 属性名2: {属性特性名: xx}, 属性名3: { {属性特性名1: xx}, {属性特姓名2: xx}})
注意,这是上边注意 的一个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 ;
}
}
}
});
alert(book._year);
alert(book.edition);
book.year = 2005 ;
alert(book._year);
alert(book.edition);
Object.getOwnPropertyDescriptor() 读取属性的特性 两个参数:属性所在对象、要读取其描述符的特性名称;返回一个对象,包含相应的属性特性。
6.2 创建对象 《JS高级程序设计》中可以说一共提供了9种创建对象 的方法。为什么会有这么多的方法?要让JS更“面向对象”,就要满足一些要求。开始的方法不能很好的满足一定的要求,就会有新的方法出现。
总结这些要求大概有:
1.代码复用
2.封装性、继承性??、多态性??
3.对象识别,即可以方便的知道实例是什么自定义类型 xxx instanceof XXX
4.实例间要有不同 的基本值的属性
5.实例间要有不同 的引用类型值的属性 ,除Function之外
6.实例间要有相同(共享) 的函数(方法),空间复用?
Object构造函数法、对象字面量法 本篇最开始就介绍了这两种方法。 缺点:不满足第1点要求,创建对个实例时会产生大量重复代码。
→→ 工厂模式 ←← 1
2
3
4
5
6
7
8
9
10
11
12
13
function createPerson (name, age, job ) {
var o = new Object ();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function ( ) {
alert(o.name);
};
return o;
}
var person1 = createPerson("DaShuaiBi" , 27 , "playAndPlay" );
var person2 = createPerson("HeiHeiHei" , 18 , "heixiuheixiu" );
优点:解决了Object构造函数法和对象字面量方法的不足之处,代码可以复用。
缺点:不满足第3点要求(对象识别),不能知道person1和person2是什么样的自定义类型;不满足第6点,实例间的函数是不同的,(函数也是对象),占用空间。
→→ 构造函数模式 ←← 1
2
3
4
5
6
7
8
9
10
11
function Person (name, age, job ) {
this .name = name;
this .age = age;
this .job = job;
this .sayName = function ( ) {
alert(this .name);
}
}
var person1 = new Person("DaShuaiBi" , 27 , "playAndPlay" );
var person2 = new Person("HeiHeiHei" , 18 , "heixiuheixiu" );
注意:Person的首字母是大写(惯例)
优点:解决了工厂模式的不足之一,可以进行对象识别。1
2
3
4
alert(person1 instanceof Object );
alert(person1 instanceof Person);
alert(person2 instanceof Object );
alert(person2 instanceof Person);
缺点:不满足第6点,实例间的函数是不同的,(函数也是对象),占用空间。 1
2
3
person1.sayName == person2.sayName;
person1.sayName() == person2.sayName();
构造函数模式的改进 1
2
3
4
5
6
7
8
9
10
11
12
13
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("DaShuaiBi" , 27 , "playAndPlay" );
var person2 = new Person("HeiHeiHei" , 18 , "heixiuheixiu" );
优点:解决了构造函数模式的缺点,实例间的函数是相同的。1
person1.sayName == person2.sayName;
缺点:应是实例的方法sayName现在成了全局函数,而且要是自定义类型的方法很多时,就得定义很多个全局函数。不满足第2点中的封装性。
→→ 原型模式 ←← 每个函数 都有一个prototype属性 ,这个属性是个指针,指向一个对象,这个对象包含可以由特定类型的所有实例共享 的属性和方法。
prototype是通过调用构造函数 而创建的那个对象实例 的原型对象 。(prototype是对象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person ( ) {
}
Person.prototype.name = "DaShuaiBi" ;
Person.prototype.age = 27 ;
Person.prototype.job = "playAndPlay" ;
Person.prototype.sayName = function ( ) {
alert(this .name);
};
var person1 = new Person();
var person2 = new Person();
person2.name = "HeiHeiHei" ;
person2.age = 18 ;
person2.job = "heixiuheixiu" ;
优点:解决了构造函数模式的不足,实例的函数是同一个函数。1
person1.sayName == person2.sayName; // true
缺点:Person.prototype.代码重复太多;不满足第4、5点要求。由于原型对象上所有属性都是实例对象所共享的,结果默认情况下实例的基本值的属性和引用类型值的属性都一样。虽然可以通过在实例上添加同名属性来屏蔽 原型对象上的同名属性,但当只是简单的修改实例上的引用类型值的属性 时,就比较难受了。
更简单的原型模式 1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person ( ) {
}
Person.prototype = {
constructor : Person,
name : "DaShuaiBi" ,
age : 27 ,
job : "playAndPlay" ,
sayName : function ( ) {
alert(this .name);
}
};
优点:解决了Person.prototype代码复用问题
缺点:原型模式的真正缺点还没有解决。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Person ( ) {
}
Person.prototype = {
constructor : Person,
name : "DaShuaiBi" ,
age : 27 ,
job : "playAndPlay" ,
friends: ["ErShuai" , "SanShuai" ],
sayName : function ( ) {
alert(this .name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("SiShuai" );
alert(person1.friends);
alert(person2.friends);
alert(person1.friends == person2.friends);
♥♥♥ →→ 组合使用构造函数模式和原型模式 ←← ♥♥♥ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person (name, age, job ) {
this .name = name;
this .age = age;
this .job = job;
this .friends = ["ErShuai" , "SanShuai" ];
}
Person.prototype = {
constructor : Person,
sayName : function ( ) {
alert(this .name);
}
}
var person1 = new Person("DaShuaiBi" , 27 , "playAndPlay" );
var person2 = new Person("HeiHeiHei" , 18 , "heixiuheixiu" );
优点:结合了构造函数模式和原型模式的优点。每个实例都有自己的一份实例属性的副本 ,但同时又共享着对方法的引用 ,最大限度地节省了内存。 解决了原型模式的不足。(使用最广泛、认同度最高)1
2
3
4
5
6
7
person1.friends.push("SiShuai" );
alert(person1.friends);
alert(person2.friends);
alert(person1.friends == person2.friends);
缺点:实例的不同属性(构造函数)、共享方法(原型对象)合起来才算是创建对象的方法。缺一点点封装性。
♥♥♥ →→ 动态原型模式 ←← ♥♥♥ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 person1 = new Person("DaShuaiBi" , 27 , "playAndPlay" );
var person2 = new Person("HeiHeiHei" , 18 , "heixiuheixiu" );
优点:if(){}代码块只会在初次调用构造函数时(生成person1时)才会执行。在生成person2时就不用执行了!JS中创建对象非常完美 的方法。
→→ 寄生构造函数模式 ←← 不太理解。函数中有return一个对象,创建对象时还使用new关键字???
→→ 稳妥构造函数模式 ←← 不太理解。虽说是xx构造函数模式,但函数中有return一个对象,创建对象时没有使用new关键字,更像是xx工厂模式???
6.3 继承 这部分和上边创建对象有很大联系。一些模式之间的代码结构很相似。
创建对象的方法:Object构造函数、对象字面量、工厂模式、构造函数模式、(构造函数模式的改进)、原型模式、(更简单的形式原型模式)、组合模式(构造函数+原型模式)、动态原型模式、寄生构造函数模式、稳妥构造函数模式。
继承方法:原型链继承、借用构造函数继承、组合继承(构造函数+原型链)、原型式继承、寄生式继承(原型式plus)、寄生组合式继承(寄生式+构造函数+原型链)
→→ 原型链继承 ←← 关键语句 SubType.prototype = new SuperType();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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());
SuperType.prototype.constructor;
SubType.prototype.constructor;
instance.constructor;
问题1:与使用原型模式创建对象 时的问题类似 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function SuperType ( ) {
this .colors = ["red" , "blue" , "green" ];
}
function SubType ( ) {
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black" );
alert(instance1.colors);
var instance2 = new SubType();
alert(instance2.colors);
问题2:在创建子类型的实例时,不能向超类型的构造函数中传递参数。
→→ 借用构造函数继承 ←← 关键语句 SuperType.call(this);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function SuperType ( ) {
this .property = true ;
this .colors = ["red" , "blue" , "green" ];
this .sayHi = function ( ) {
alert("hi" );
};
}
SuperType.prototype.name = "siguoyi" ;
SuperType.prototype.getSuperValue = function ( ) {
return this .property;
};
function SubType ( ) {
SuperType.call(this );
this .subproperty = false ;
}
SubType.prototype.getSubValue = function ( ) {
return this .subproperty;
};
var instance = new SubType();
alert(instance.subproperty);
alert(instance.property);
instance.sayHi();
alert(instance.getSubValue());
instance.getSuperValue();
alert(instance.name);
SuperType.prototype.constructor;
SubType.prototype.constructor;
instance.constructor;
优点1:解决了原型链继承的问题1。子类型实例有各自的属性副本。1
2
3
4
5
6
7
var instance1 = new SubType();
instance1.colors.push("black" );
alert(instance1.colors);
var instance2 = new SubType();
alert(instance2.colors);
优点2:解决原型链继承的问题2 可以个超类型的构造函数传递参数。
缺点:与使用构造函数模式创建对象 的缺点类似。函数不能复用。1
2
instance1.sayHi == instance2.sayHi
instance1.getSubValue == instance2.getSubValue
♥♥♥ →→ 组合继承 ←← ♥♥♥ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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("DaShuaiBi" , 27 );
SuperType.prototype.constructor;
SubType.prototype.constructor;
instance1.constructor;
优点:结合了原型链继承和借用构造函数继承的优点,消除了两者的缺点。JS中最常用的继承模式 。1
2
3
4
5
6
7
8
9
10
11
12
instance1.colors.push("black" );
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instacne2 = new SubType("HeiHeiHei" , 18 );
alert(instacne2.colors);
instacne2.sayName();
instacne2.sayAge();
instance1.sayName == instance2.sayName;
instance1.sayAge == instance2.sayAge;
缺点:虽然最常用,也是有缺点。会调用超类型(SubType)构造函数两次。第一次:SubType.prototype = new SuperType(); 第二次:SuperType.call(this, name); (更准确的说应该是在实例化一个SubType时(调用SubType的构造函数时,因为这一行代码就在SubType的构造函数里边)) 。
→→ 原型式继承 ←← 有两种方式。
第一种:ECMAScript5增加的方法Object.create() (其中属性描述符与前边的 一样)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var person = {
name : "DaShuaiBi" ,
friends : ["ErShuai" , "SanShuai" ]
};
var anotherPerson = Object .create(person, {
name: {
value : "HeiHeiHei"
}
});
alert(anotherPerson.name);
var person = {
name : "DaShuaiBi" ,
friends : ["ErShuai" , "SanShuai" ]
};
var anotherPerson = Object .create(person);
anotherPerson.name = "HeiHeiHei" ;
第二种 先自己定义一个函数object()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function object (o ) {
function F ( ) {}
F.prototype = o;
return new F();
}
var person = {
name : "DaShuaiBi" ,
friends : ["ErShuai" , "SanShuai" ]
};
var anotherPerson = object(person);
anotherPerson.name = "HeiHeiHei" ;
person.prototype.constructor;
anotherPerson.prototype.constructor;
person.constructor;
anotherPerson.constructor;
person.__proto__;
Object .isPrototypeOf(person);
Object .prototype.isPrototypeOf(person);
anotherPerson.__proto__;
anotherPerson.__proto__ == person;
person.isPrototypeOf(anotherPerson);
Object .prototype.isPrototypeOf(anotherPerson);
优点: 不必兴师动众地创建构造函数
缺点:当然没有构造函数,不能知道实例是什么“类型”,只是知道实例跟哪个对象很像;与使用原型模式创建对象 及使用原型链继承 时的问题一样。1
2
3
4
5
6
7
8
anotherPerson.friends.push("yoyoyo" );
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "bang" ;
yetAnotherPerson.friends.push("zero" );
alert(person.friedns);
alert(person.name);
原型式继承中,object()函数实际上是对传入其中的对象进行了一次浅复制 ,浅复制是针对引用类型的值而言的。上边的例子中anotherPerson对person进行了浅复制,所以改变anotherPerson上的值,person上的值也会改变。可是name是基本类型值,所以是真的复制了一个副本出来,而friends是引用类型值,准确的说是浅复制了引用类型(对象)上的引用类型(数组Array)值。
→→ 寄生式继承 ←← 寄生式继承感觉是在原型式继承的基础上,又套了一层函数用来对新实例增加一些自己的东西。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function object (o ) {
function F ( ) {}
F.prototype = o;
return new F();
}
function createAnother (original ) {
var clone = object(original);
clone.sayHi = function ( ) {
alert("hi" );
};
return clone;
}
var person = {
name : "DaShuaiBi" ,
friends : ["ErShuai" , "SanShuai" ]
};
var anotherPerson = createAnother(person);
person.constructor;
anotherPerson.constructor;
person.isPrototypeOf(anotherPerson);
anotherPerson.__proto__;
anotherPerson.__proto__ == person;
anotherPerson.sayHi();
person.sayHi();
var yetAnotherPerson = createAnother(person);
anotherPerson.sayHi == yetAnotherPerson.sayHi;
优点:貌似和原型式继承差不多?
缺点:貌似和原型式继承差不多?
书中还写说寄生式继承为对象添加函数时不能复用,就像这里所示;原型式继承也有这个问题啊!!!但没有写。是不是因为一般使用原型式继承时只是简单的复制一下某个对象,而使用寄生式继承时除了复制一下某个对象外还稍微加点方法、属性什么的???
♥♥♥ →→ 寄生组合式继承 ←← ♥♥♥ 这个的感觉就和使用动态原型模式创建对象 的地位一样。
寄生组合式继承是引用类型最理想的继承范式
原型式继承+寄生式继承+借用构造函数继承+原型链继承 = 寄生组合式继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function object (o ) {
function F ( ) {}
F.prototype = o;
return new F();
}
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);
}
var instance1 = new SubType("DaShuaiBi" , 27 );
SuperType.prototype.constructor;
SubType.prototype;
SubType.prototype.constructor;
SubType.prototype == SuperType
SubType.prototype == SuperType.prototype
SuperType.isPrototypeOf(SubType.prototype)
SuperType.prototype.isPrototypeOf(SubType.prototype)
instance1.constructor;
instance1.__proto__;
instance1.__proto__ == SuperType.prototype;
SuperType.prototype.isPrototypeOf(instance1);
instance1.colors.push("black" );
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instacne2 = new SubType("HeiHeiHei" , 18 );
alert(instacne2.colors);
instacne2.sayName();
instacne2.sayAge();
优点:解决了组合继承的问题 ,父类型构造函数只调用了一次。
17-03-08