mirror of
https://github.com/beyondx/Notes.git
synced 2026-02-03 18:33:26 +08:00
Add New Notes
This commit is contained in:
914
Zim/Programme/js/Javascript_面向对象编程.txt
Normal file
914
Zim/Programme/js/Javascript_面向对象编程.txt
Normal file
@@ -0,0 +1,914 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-03-06T20:57:09+08:00
|
||||
|
||||
====== Javascript 面向对象编程 ======
|
||||
Created Tuesday 06 March 2012
|
||||
http://coolshell.cn/articles/6441.html
|
||||
|
||||
|
||||
Javascript是一个类C的语言,他的面向对象的东西相对于C++/Java比较奇怪,但是其的确相当的强大,在 Todd 同学的“对象的消息模型”一文中我们已经可以看到一些端倪了。这两天有个前同事总在问我Javascript面向对象的东西,所以,索性写篇文章让他看去吧,这里这篇文章主要想从一个整体的角度来说明一下Javascript的面向对象的编程。(成文比较仓促,应该有不准确或是有误的地方,请大家批评指正)
|
||||
|
||||
另,这篇文章主要基于 ECMAScript 5, 旨在介绍新技术。关于兼容性的东西,请看最后一节。
|
||||
初探
|
||||
|
||||
我们知道Javascript中的变量定义基本如下:
|
||||
|
||||
|
||||
var name = 'Chen Hao';;
|
||||
var email = 'haoel(@)hotmail.com';
|
||||
var website = 'http://coolshell.cn';
|
||||
|
||||
如果要用对象来写的话,就是下面这个样子:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
|
||||
var chenhao = {
|
||||
name :'Chen Hao',
|
||||
email : 'haoel(@)hotmail.com',
|
||||
website : 'http://coolshell.cn'
|
||||
};
|
||||
|
||||
于是,我就可以这样访问:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
|
||||
//以成员的方式
|
||||
chenhao.name;
|
||||
chenhao.email;
|
||||
chenhao.website;
|
||||
|
||||
//以hash map的方式
|
||||
chenhao["name"];
|
||||
chenhao["email"];
|
||||
chenhao["website"];
|
||||
|
||||
关于函数,我们知道Javascript的函数是这样的:
|
||||
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
var doSomething = function(){
|
||||
alert('Hello World.');
|
||||
};
|
||||
|
||||
于是,我们可以这么干:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
|
||||
var sayHello = function(){
|
||||
var hello = "Hello, I'm "+ this.name
|
||||
+ ", my email is: " + this.email
|
||||
+ ", my website is: " + this.website;
|
||||
alert(hello);
|
||||
};
|
||||
|
||||
//直接赋值,这里很像C/C++的函数指针
|
||||
chenhao.Hello = sayHello;
|
||||
|
||||
chenhao.Hello();
|
||||
|
||||
相信这些东西都比较简单,大家都明白了。 可以看到javascript对象函数是直接声明,直接赋值,直接就用了。runtime的动态语言。
|
||||
|
||||
还有一种比较规范的写法是:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
|
||||
//我们可以看到, 其用function来做class。
|
||||
var Person = function(name, email, website){
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.website = website;
|
||||
|
||||
this.sayHello = function(){
|
||||
var hello = "Hello, I'm "+ this.name + ", \n" +
|
||||
"my email is: " + this.email + ", \n" +
|
||||
"my website is: " + this.website;
|
||||
alert(hello);
|
||||
};
|
||||
};
|
||||
|
||||
var chenhao = new Person("Chen Hao", "haoel@hotmail.com",
|
||||
"http://coolshell.cn");
|
||||
chenhao.sayHello();
|
||||
|
||||
顺便说一下,要删除对象的属性,很简单:
|
||||
1
|
||||
|
||||
delete chenhao['email']
|
||||
|
||||
上面的这些例子,我们可以看到这样几点:
|
||||
|
||||
Javascript的数据和成员封装很简单。没有类完全是对象操作。纯动态!
|
||||
Javascript function中的this指针很关键,如果没有的话,那就是局部变量或局部函数。
|
||||
Javascript对象成员函数可以在使用时临时声明,并把一个全局函数直接赋过去就好了。
|
||||
Javascript的成员函数可以在实例上进行修改,也就是说不同实例相同函数名的行为不一定一样。
|
||||
|
||||
属性配置 – Object.defineProperty
|
||||
|
||||
先看下面的代码:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
|
||||
//创建对象
|
||||
var chenhao = Object.create(null);
|
||||
|
||||
//设置一个属性
|
||||
Object.defineProperty( chenhao,
|
||||
'name', { value: 'Chen Hao',
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: true });
|
||||
|
||||
//设置多个属性
|
||||
Object.defineProperties( chenhao,
|
||||
{
|
||||
'email' : { value: 'haoel@hotmail.com',
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: true },
|
||||
'website': { value: 'http://coolshell.cn',
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: true }
|
||||
}
|
||||
);
|
||||
|
||||
下面就说说这些属性配置是什么意思。
|
||||
|
||||
writable:这个属性的值是否可以改。
|
||||
configurable:这个属性的配置是否可以改。
|
||||
enumerable:这个属性是否能在for…in循环中遍历出来或在Object.keys中列举出来。
|
||||
value:属性值。
|
||||
get()/set(_value):get和set访问器。
|
||||
|
||||
Get/Set 访问器
|
||||
|
||||
关于get/set访问器,它的意思就是用get/set来取代value(其不能和value一起使用),示例如下:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
|
||||
var age = 0;
|
||||
Object.defineProperty( chenhao,
|
||||
'age', {
|
||||
get: function() {return age+1;},
|
||||
set: function(value) {age = value;}
|
||||
enumerable : true,
|
||||
configurable : true
|
||||
}
|
||||
);
|
||||
chenhao.age = 100; //调用set
|
||||
alert(chenhao.age); //调用get 输出101(get中+1了);
|
||||
|
||||
我们再看一个更为实用的例子——利用已有的属性(age)通过get和set构造新的属性(birth_year):
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
|
||||
Object.defineProperty( chenhao,
|
||||
'birth_year',
|
||||
{
|
||||
get: function() {
|
||||
var d = new Date();
|
||||
var y = d.getFullYear();
|
||||
return ( y - this.age );
|
||||
},
|
||||
set: function(year) {
|
||||
var d = new Date();
|
||||
var y = d.getFullYear();
|
||||
this.age = y - year;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
alert(chenhao.birth_year);
|
||||
chenhao.birth_year = 2000;
|
||||
alert(chenhao.age);
|
||||
|
||||
这样做好像有点麻烦,你说,我为什么不写成下面这个样子:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
var chenhao = {
|
||||
name: "Chen Hao",
|
||||
email: "haoel@hotmail.com",
|
||||
website: "http://coolshell.cn",
|
||||
age: 100,
|
||||
get birth_year() {
|
||||
var d = new Date();
|
||||
var y = d.getFullYear();
|
||||
return ( y - this.age );
|
||||
},
|
||||
set birth_year(year) {
|
||||
var d = new Date();
|
||||
var y = d.getFullYear();
|
||||
this.age = y - year;
|
||||
}
|
||||
|
||||
};
|
||||
alert(chenhao.birth_year);
|
||||
chenhao.birth_year = 2000;
|
||||
alert(chenhao.age);
|
||||
|
||||
是的,你的确可以这样的,不过通过defineProperty()你可以干这些事:
|
||||
1)设置如 writable,configurable,enumerable 等这类的属性配置。
|
||||
2)动态地为一个对象加属性。比如:一些HTML的DOM对像。
|
||||
查看对象属性配置
|
||||
|
||||
如果查看并管理对象的这些配置,下面有个程序可以输出对象的属性和配置等东西:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
//列出对象的属性.
|
||||
function listProperties(obj)
|
||||
{
|
||||
var newLine = "<br />";
|
||||
var names = Object.getOwnPropertyNames(obj);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var prop = names[i];
|
||||
document.write(prop + newLine);
|
||||
|
||||
// 列出对象的属性配置(descriptor)动用getOwnPropertyDescriptor函数。
|
||||
var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
|
||||
for (var attr in descriptor) {
|
||||
document.write("..." + attr + ': ' + descriptor[attr]);
|
||||
document.write(newLine);
|
||||
}
|
||||
document.write(newLine);
|
||||
}
|
||||
}
|
||||
|
||||
listProperties(chenhao);
|
||||
call,apply, bind 和 this
|
||||
|
||||
关于Javascript的this指针,和C++/Java很类似。 我们来看个示例:(这个示例很简单了,我就不多说了)
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
|
||||
function print(text){
|
||||
document.write(this.value + ' - ' + text+ '<br>');
|
||||
}
|
||||
|
||||
var a = {value: 10, print : print};
|
||||
var b = {value: 20, print : print};
|
||||
|
||||
print('hello');// this => global, output "undefined - hello"
|
||||
|
||||
a.print('a');// this => a, output "10 - a"
|
||||
b.print('b'); // this => b, output "20 - b"
|
||||
|
||||
a['print']('a'); // this => a, output "10 - a"
|
||||
|
||||
我们再来看看call 和 apply,这两个函数的差别就是参数的样子不一样,另一个就是性能不一样,apply的性能要差很多。(关于性能,可到 JSPerf 上去跑跑看看)
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
|
||||
print.call(a, 'a'); // this => a, output "10 - a"
|
||||
print.call(b, 'b'); // this => b, output "20 - b"
|
||||
|
||||
print.apply(a, ['a']); // this => a, output "10 - a"
|
||||
print.apply(b, ['b']); // this => b, output "20 - b"
|
||||
|
||||
但是在bind后,this指针,可能会有不一样,但是因为Javascript是动态的。如下面的示例
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
|
||||
var p = print.bind(a);
|
||||
p('a'); // this => a, output "10 - a"
|
||||
p.call(b, 'b'); // this => a, output "10 - b"
|
||||
p.apply(b, ['b']); // this => a, output "10 - b"
|
||||
继承 和 重载
|
||||
|
||||
通过上面的那些示例,我们可以通过Object.create()来实际继承,请看下面的代码,Student继承于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
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42
|
||||
43
|
||||
|
||||
var Person = Object.create(null);
|
||||
|
||||
Object.defineProperties
|
||||
(
|
||||
Person,
|
||||
{
|
||||
'name' : { value: 'Chen Hao'},
|
||||
'email' : { value : 'haoel@hotmail.com'},
|
||||
'website': { value: 'http://coolshell.cn'}
|
||||
}
|
||||
);
|
||||
|
||||
Person.sayHello = function () {
|
||||
var hello = "<p>Hello, I am "+ this.name + ", <br>" +
|
||||
"my email is: " + this.email + ", <br>" +
|
||||
"my website is: " + this.website;
|
||||
document.write(hello + "<br>");
|
||||
}
|
||||
|
||||
var Student = Object.create(Person);
|
||||
Student.no = "1234567"; //学号
|
||||
Student.dept = "Computer Science"; //系
|
||||
|
||||
//使用Person的属性
|
||||
document.write(Student.name + ' ' + Student.email + ' ' + Student.website +'<br>');
|
||||
|
||||
//使用Person的方法
|
||||
Student.sayHello();
|
||||
|
||||
//重载SayHello方法
|
||||
Student.sayHello = function (person) {
|
||||
var hello = "<p>Hello, I am "+ this.name + ", <br>" +
|
||||
"my email is: " + this.email + ", <br>" +
|
||||
"my website is: " + this.website + ", <br>" +
|
||||
"my student no is: " + this. no + ", <br>" +
|
||||
"my departent is: " + this. dept;
|
||||
document.write(hello + '<br>');
|
||||
}
|
||||
//再次调用
|
||||
Student.sayHello();
|
||||
|
||||
//查看Student的属性(只有 no 、 dept 和 重载了的sayHello)
|
||||
document.write('<p>' + Object.keys(Student) + '<br>');
|
||||
|
||||
通用上面这个示例,我们可以看到,Person里的属性并没有被真正复制到了Student中来,但是我们可以去存取。这是因为Javascript用委托实现了这一机制。其实,这就是Prototype,Person是Student的Prototype。
|
||||
|
||||
当我们的代码需要一个属性的时候,Javascript的引擎会先看当前的这个对象中是否有这个属性,如果没有的话,就会查找他的Prototype对象是否有这个属性,一直继续下去,直到找到或是直到没有Prototype对象。
|
||||
|
||||
为了证明这个事,我们可以使用Object.getPrototypeOf()来检验一下:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
|
||||
Student.name = 'aaa';
|
||||
|
||||
//输出 aaa
|
||||
document.write('<p>' + Student.name + '</p>');
|
||||
|
||||
//输出 Chen Hao
|
||||
document.write('<p>' +Object.getPrototypeOf(Student).name + '</p>');
|
||||
|
||||
于是,你还可以在子对象的函数里调用父对象的函数,就好像C++里的 Base::func() 一样。于是,我们重载hello的方法就可以使用父类的代码了,如下所示:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
|
||||
//新版的重载SayHello方法
|
||||
Student.sayHello = function (person) {
|
||||
Object.getPrototypeOf(this).sayHello.call(this);
|
||||
var hello = "my student no is: " + this. no + ", <br>" +
|
||||
"my departent is: " + this. dept;
|
||||
document.write(hello + '<br>');
|
||||
}
|
||||
|
||||
这个很强大吧。
|
||||
组合
|
||||
|
||||
上面的那个东西还不能满足我们的要求,我们可能希望这些对象能真正的组合起来。为什么要组合?因为我们都知道是这是OO设计的最重要的东西。不过,这对于Javascript来并没有支持得特别好,不好我们依然可以搞定个事。
|
||||
|
||||
首先,我们需要定义一个Composition的函数:(target是作用于是对象,source是源对象),下面这个代码还是很简单的,就是把source里的属性一个一个拿出来然后定义到target中。
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
|
||||
function Composition(target, source)
|
||||
{
|
||||
var desc = Object.getOwnPropertyDescriptor;
|
||||
var prop = Object.getOwnPropertyNames;
|
||||
var def_prop = Object.defineProperty;
|
||||
|
||||
prop(source).forEach(
|
||||
function(key) {
|
||||
def_prop(target, key, desc(source, key))
|
||||
}
|
||||
)
|
||||
return target;
|
||||
}
|
||||
|
||||
有了这个函数以后,我们就可以这来玩了:
|
||||
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
|
||||
|
||||
//艺术家
|
||||
var Artist = Object.create(null);
|
||||
Artist.sing = function() {
|
||||
return this.name + ' starts singing...';
|
||||
}
|
||||
Artist.paint = function() {
|
||||
return this.name + ' starts painting...';
|
||||
}
|
||||
|
||||
//运动员
|
||||
var Sporter = Object.create(null);
|
||||
Sporter.run = function() {
|
||||
return this.name + ' starts running...';
|
||||
}
|
||||
Sporter.swim = function() {
|
||||
return this.name + ' starts swimming...';
|
||||
}
|
||||
|
||||
Composition(Person, Artist);
|
||||
document.write(Person.sing() + '<br>');
|
||||
document.write(Person.paint() + '<br>');
|
||||
|
||||
Composition(Person, Sporter);
|
||||
document.write(Person.run() + '<br>');
|
||||
document.write(Person.swim() + '<br>');
|
||||
|
||||
//看看 Person中有什么?(输出:sayHello,sing,paint,swim,run)
|
||||
document.write('<p>' + Object.keys(Person) + '<br>');
|
||||
Prototype 和 继承
|
||||
|
||||
我们先来说说Prototype。我们先看下面的例程,这个例程不需要解释吧,很像C语言里的函数指针,在C语言里这样的东西见得多了。
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
|
||||
var plus = function(x,y){
|
||||
document.write( x + ' + ' + y + ' = ' + (x+y) + '<br>');
|
||||
return x + y;
|
||||
};
|
||||
|
||||
var minus = function(x,y){
|
||||
document.write(x + ' - ' + y + ' = ' + (x-y) + '<br>');
|
||||
return x - y;
|
||||
};
|
||||
|
||||
var operations = {
|
||||
'+': plus,
|
||||
'-': minus
|
||||
};
|
||||
|
||||
var calculate = function(x, y, operation){
|
||||
return operations[operation](x, y);
|
||||
};
|
||||
|
||||
calculate(12, 4, '+');
|
||||
calculate(24, 3, '-');
|
||||
|
||||
那么,我们能不能把这些东西封装起来呢,我们需要使用prototype。看下面的示例:
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
|
||||
var Cal = function(x, y){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
Cal.prototype.operations = {
|
||||
'+': function(x, y) { return x+y;},
|
||||
'-': function(x, y) { return x-y;}
|
||||
};
|
||||
|
||||
Cal.prototype.calculate = function(operation){
|
||||
return this.operations[operation](this.x, this.y);
|
||||
};
|
||||
|
||||
var c = new Cal(4, 5);
|
||||
|
||||
c.calculate('+');
|
||||
c.calculate('-');
|
||||
|
||||
这就是prototype的用法,prototype 是javascript这个语言中最重要的内容。网上有太多的文章介始这个东西了。说白了,prototype就是对一对象进行扩展,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的(当然,这里没有真正的复制,实际只是委托)。上面的这个例子中,我们扩展了实例Cal,让其有了一个operations的属性和一个calculate的方法。
|
||||
|
||||
这样,我们可以通过这一特性来实现继承。还记得我们最最前面的那个Person吧, 下面的示例是创建一个Student来继承Person。
|
||||
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
|
||||
|
||||
function Person(name, email, website){
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.website = website;
|
||||
};
|
||||
|
||||
Person.prototype.sayHello = function(){
|
||||
var hello = "Hello, I am "+ this.name + ", <br>" +
|
||||
"my email is: " + this.email + ", <br>" +
|
||||
"my website is: " + this.website;
|
||||
return hello;
|
||||
};
|
||||
|
||||
function Student(name, email, website, no, dept){
|
||||
var proto = Object.getPrototypeOf;
|
||||
proto(Student.prototype).constructor.call(this, name, email, website);
|
||||
this.no = no;
|
||||
this.dept = dept;
|
||||
}
|
||||
|
||||
// 继承prototype
|
||||
Student.prototype = Object.create(Person.prototype);
|
||||
|
||||
//重置构造函数
|
||||
Student.prototype.constructor = Student;
|
||||
|
||||
//重载sayHello()
|
||||
Student.prototype.sayHello = function(){
|
||||
var proto = Object.getPrototypeOf;
|
||||
var hello = proto(Student.prototype).sayHello.call(this) + '<br>';
|
||||
hello += "my student no is: " + this. no + ", <br>" +
|
||||
"my departent is: " + this. dept;
|
||||
return hello;
|
||||
};
|
||||
|
||||
var me = new Student(
|
||||
"Chen Hao",
|
||||
"haoel@hotmail.com",
|
||||
"http://coolshell.cn",
|
||||
"12345678",
|
||||
"Computer Science"
|
||||
);
|
||||
document.write(me.sayHello());
|
||||
兼容性
|
||||
|
||||
上面的这些代码并不一定能在所有的浏览器下都能运行,因为上面这些代码遵循 ECMAScript 5 的规范,关于ECMAScript 5 的浏览器兼容列表,你可以看这里“ES5浏览器兼容表”。
|
||||
|
||||
本文中的所有代码都在Chrome最新版中测试过了。
|
||||
|
||||
下面是一些函数,可以用在不兼容ES5的浏览器中:
|
||||
Object.create()函数
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
|
||||
function clone(proto) {
|
||||
function Dummy() { }
|
||||
|
||||
Dummy.prototype = proto;
|
||||
Dummy.prototype.constructor = Dummy;
|
||||
|
||||
return new Dummy(); //等价于Object.create(Person);
|
||||
}
|
||||
|
||||
var me = clone(Person);
|
||||
defineProperty()函数
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
|
||||
function defineProperty(target, key, descriptor) {
|
||||
if (descriptor.value){
|
||||
target[key] = descriptor.value;
|
||||
}else {
|
||||
descriptor.get && target.__defineGetter__(key, descriptor.get);
|
||||
descriptor.set && target.__defineSetter__(key, descriptor.set);
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
keys()函数
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
|
||||
function keys(object) { var result, key
|
||||
result = [];
|
||||
for (key in object){
|
||||
if (object.hasOwnProperty(key)) result.push(key)
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Object.getPrototypeOf() 函数
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
|
||||
function proto(object) {
|
||||
return !object? null
|
||||
: '__proto__' in object? object.__proto__
|
||||
: /* not exposed? */ object.constructor.prototype
|
||||
}
|
||||
bind 函数
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
|
||||
var slice = [].slice
|
||||
|
||||
function bind(fn, bound_this) { var bound_args
|
||||
bound_args = slice.call(arguments, 2)
|
||||
return function() { var args
|
||||
args = bound_args.concat(slice.call(arguments))
|
||||
return fn.apply(bound_this, args) }
|
||||
}
|
||||
参考
|
||||
|
||||
W3CSchool
|
||||
MDN (Mozilla Developer Network)
|
||||
MSDN (Microsoft Software Development Network)
|
||||
Understanding Javascript OOP.
|
||||
193
Zim/Programme/js/Javascript_面向对象编程(一):封装.txt
Normal file
193
Zim/Programme/js/Javascript_面向对象编程(一):封装.txt
Normal file
@@ -0,0 +1,193 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-03-06T21:08:26+08:00
|
||||
|
||||
====== Javascript 面向对象编程(一):封装 ======
|
||||
Created Tuesday 06 March 2012
|
||||
|
||||
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
|
||||
|
||||
作者: 阮一峰
|
||||
日期: 2010年5月17日
|
||||
|
||||
学习Javascript,最难的地方是什么?
|
||||
|
||||
我觉得,**Object(对象)**最难。因为Javascript的Object模型很独特,和其他语言都不一样,初学者不容易掌握。
|
||||
|
||||
下面就是我的学习笔记,希望对大家学习这个部分有所帮助。我主要参考了以下两本书籍:
|
||||
|
||||
* 《面向对象的Javascript》(Object-Oriented JavaScript)
|
||||
* 《Javascript高级程序设计(第二版)》(Professional JavaScript for Web Developers, 2nd Edition)
|
||||
|
||||
它们都是非常优秀的Javascript读物,推荐阅读。
|
||||
|
||||
笔记分成三部分。今天的第一部分是讨论"封装"(Encapsulation),后面的第二部分和第三部分讨论"继承"(Inheritance)。
|
||||
|
||||
============================
|
||||
|
||||
Javascript 面向对象编程(一):封装
|
||||
|
||||
作者:阮一峰
|
||||
|
||||
Javascript是一种基于对象(object-based)的语言,__你遇到的所有东西几乎都是对象__。但是,它__又不是一种真正的面向对象编程(OOP)语言__,因为它的语法中没有class(类)。
|
||||
|
||||
那么,如果我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要__从原型对象生成一个实例对象__,我们应该怎么做呢?
|
||||
|
||||
===== 1. 生成对象的原始模式 =====
|
||||
|
||||
假定我们把猫看成一个对象,它有"名字"和"颜色"两个**属性**。
|
||||
|
||||
var Cat = {
|
||||
|
||||
name : '',
|
||||
|
||||
color : ''
|
||||
|
||||
}
|
||||
|
||||
现在,我们需要__根据这个原型对象,生成两个实例对象__。
|
||||
|
||||
var cat1 = {}; // 创建一个空对象
|
||||
|
||||
cat1.name = "大毛"; // 按照原型对象的属性赋值
|
||||
|
||||
cat1.color = "黄色";
|
||||
|
||||
var cat2 = {};
|
||||
|
||||
cat2.name = "二毛";
|
||||
|
||||
cat2.color = "黑色";
|
||||
|
||||
好了,这就是最简单的封装了。但是,这样的写法有两个缺点,一是如果多生成几个实例,写起来就非常麻烦;二是__实例与原型之间,没有任何办法,可以看出有什么联系__。
|
||||
|
||||
===== 2. 原始模式的改进 =====
|
||||
|
||||
我们可以写一个函数,解决代码重复的问题。
|
||||
|
||||
function Cat(name,color){
|
||||
return {
|
||||
** name:name,**
|
||||
** color:color**
|
||||
}
|
||||
}
|
||||
|
||||
然后**生成实例对象,就等于是在调用函数**:
|
||||
|
||||
var cat1 = Cat("大毛","黄色");
|
||||
|
||||
var cat2 = Cat("二毛","黑色");
|
||||
|
||||
这种方法的问题依然是,__cat1和cat2之间没有内在的联系,不能反映出它们是同一个原型对象的实例__。
|
||||
|
||||
===== 3. 构造函数模式 =====
|
||||
|
||||
为了解决**从原型对象生成实例的问题**,Javascript提供了__一个构造函数(Constructor)模式__。
|
||||
|
||||
所谓"构造函数",其实就是一个普通函数,但是__内部使用了this变量__。对构造函数使用__new运算符__,就能生成实例,并且__this变量会绑定在实例对象上__。
|
||||
|
||||
比如,猫的原型对象现在可以这样写,
|
||||
|
||||
function Cat(name,color){
|
||||
this.name=name;
|
||||
this.color=color;
|
||||
}
|
||||
|
||||
我们现在就可以生成实例对象了。
|
||||
|
||||
var cat1 = new Cat("大毛","黄色");
|
||||
var cat2 = new Cat("二毛","黑色");
|
||||
|
||||
alert(cat1.name); // 大毛
|
||||
alert(cat1.color); // 黄色
|
||||
|
||||
这时cat1和cat2会__自动含有一个constructor属性,指向它们的构造函数__。
|
||||
|
||||
alert(cat1.constructor == Cat); //true
|
||||
alert(cat2.constructor == Cat); //true
|
||||
|
||||
Javascript还提供了一个__instanceof运算符__,验证原型对象与实例对象之间的关系。
|
||||
|
||||
alert(cat1 instanceof Cat); //true
|
||||
|
||||
alert(cat2 instanceof Cat); //true
|
||||
|
||||
===== 4. 构造函数模式的问题 =====
|
||||
|
||||
构造函数方法很好用,但是存在一个**浪费内存**的问题。
|
||||
|
||||
请看,我们现在为Cat对象添加一个不变的属性"type"(种类),再添加一个方法eat(吃老鼠)。那么,原型对象Cat就变成了下面这样:
|
||||
|
||||
function Cat(name,color){
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
this.type = "猫科动物";
|
||||
this.eat = function(){alert("吃老鼠");};
|
||||
}
|
||||
|
||||
还是采用同样的方法,生成实例:
|
||||
|
||||
var cat1 = new Cat("大毛","黄色");
|
||||
var cat2 = new Cat ("二毛","黑色");
|
||||
alert(cat1.type); // 猫科动物
|
||||
cat1.eat(); // 吃老鼠
|
||||
|
||||
表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。那就是对于每一个实例对象,type属性和eat()方法都是__一模一样的内容__,每一次生成一个实例,都必须为重复的内容,多占用一些内存。这样既不环保,也缺乏效率。
|
||||
|
||||
alert(**cat1.eat == cat2.ea**t); //false
|
||||
|
||||
能不能让type属性和eat()方法在__内存中只生成一次__,然后所有实例都指向那个内存地址呢?回答是可以的。
|
||||
|
||||
===== 5. Prototype模式 =====
|
||||
|
||||
Javascript规定,__每一个构造函数都有一个prototype属性__,指向另一个对象。**这个对象的所有属性和方法,都会被构造函数的实例继承**。
|
||||
|
||||
这意味着,我们可以__把那些不变的属性和方法,直接定义在prototype对象上__。
|
||||
|
||||
function Cat(name,color){
|
||||
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
|
||||
}
|
||||
|
||||
Cat.prototype.type = "猫科动物";
|
||||
Cat.prototype.eat = function(){alert("吃老鼠")};
|
||||
|
||||
然后,生成实例。
|
||||
|
||||
var cat1 = new Cat("大毛","黄色");
|
||||
var cat2 = new Cat("二毛","黑色");
|
||||
alert(cat1.type); // 猫科动物
|
||||
cat1.eat(); // 吃老鼠
|
||||
|
||||
这时所有实例的type属性和eat()方法,其实都是__同一个内存地址__,指向prototype对象,因此就提高了运行效率。
|
||||
|
||||
alert(**cat1.eat == cat2.eat**); //true
|
||||
|
||||
===== 6. Prototype模式的验证方法 =====
|
||||
|
||||
==== 6.1 isPrototypeOf() ====
|
||||
这个方法用来判断,某个**proptotype对象和某个实例之间**的关系。
|
||||
alert(Cat.prototype.isPrototypeOf(cat1)); //true
|
||||
alert(Cat.prototype.isPrototypeOf(cat2)); //true
|
||||
|
||||
==== 6.2 hasOwnProperty() ====
|
||||
|
||||
每个实例对象都有一个hasOwnProperty()方法,用来__判断某一个属性到底是本地属性,还是继承自prototype对象的属性__。
|
||||
|
||||
alert(cat1.hasOwnProperty("name")); // true
|
||||
alert(cat1.hasOwnProperty("type")); // false
|
||||
|
||||
==== 6.3 in运算符 ====
|
||||
|
||||
in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
|
||||
|
||||
alert("name" in cat1); // true
|
||||
alert("type" in cat1); // true
|
||||
|
||||
in运算符还可以用来**遍历某个对象的所有属性**。
|
||||
|
||||
for(**var prop** in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }
|
||||
|
||||
未完,请继续阅读这个系列的第二部分《构造函数的继承》和第三部分《非构造函数的继承》。
|
||||
137
Zim/Programme/js/Javascript的this用法.txt
Normal file
137
Zim/Programme/js/Javascript的this用法.txt
Normal file
@@ -0,0 +1,137 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-03-06T21:37:56+08:00
|
||||
|
||||
====== Javascript的this用法 ======
|
||||
Created Tuesday 06 March 2012
|
||||
http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html
|
||||
|
||||
作者: 阮一峰
|
||||
日期: 2010年4月30日
|
||||
|
||||
this是Javascript语言的一个关键字。
|
||||
|
||||
__它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用__。比如,
|
||||
|
||||
function test(){
|
||||
this.x = 1;
|
||||
}
|
||||
|
||||
__随着函数使用场合的不同,this的值会发生变化__。但是有一个总的原则,__那就是this指的是,调用函数的那个对象__。
|
||||
|
||||
下面分四种情况,详细讨论this的用法。
|
||||
|
||||
===== 情况一:纯粹的函数调用 =====
|
||||
|
||||
这是函数的最通常用法,属于**全局性调用**,因此this就代表__全局对象__Global。
|
||||
|
||||
请看下面这段代码,它的运行结果是1。
|
||||
|
||||
function test(){
|
||||
|
||||
this.x = 1;
|
||||
|
||||
alert(this.x);
|
||||
|
||||
}
|
||||
|
||||
test(); // 1
|
||||
|
||||
为了证明this就是全局对象,我对代码做一些改变:
|
||||
|
||||
var x = 1;
|
||||
|
||||
function test(){
|
||||
|
||||
alert(this.x);
|
||||
|
||||
}
|
||||
|
||||
test(); // 1
|
||||
|
||||
运行结果还是1。再变一下:
|
||||
|
||||
var x = 1;
|
||||
|
||||
function test(){
|
||||
|
||||
this.x = 0;
|
||||
|
||||
}
|
||||
|
||||
test();
|
||||
|
||||
alert(x); //0
|
||||
|
||||
===== 情况二:作为对象方法的调用 =====
|
||||
|
||||
函数还可以作为某个对象的方法调用,这时//this就指这个上级对象//。
|
||||
|
||||
function test(){
|
||||
|
||||
alert(this.x);
|
||||
|
||||
}
|
||||
|
||||
var o = {};
|
||||
|
||||
o.x = 1;
|
||||
|
||||
o.m = test;
|
||||
|
||||
o.m(); // 1
|
||||
|
||||
===== 情况三 作为构造函数调用 =====
|
||||
|
||||
所谓构造函数,就是**通过这个函数生成一个新对象(object)**。这时,__this就指这个新对象__。
|
||||
|
||||
function test(){
|
||||
|
||||
this.x = 1;
|
||||
|
||||
}
|
||||
|
||||
var o = new test();
|
||||
|
||||
alert(o.x); // 1
|
||||
|
||||
运行结果为1。为了表明这时this不是全局对象,我对代码做一些改变:
|
||||
|
||||
var x = 2;
|
||||
|
||||
function test(){
|
||||
|
||||
this.x = 1;
|
||||
|
||||
}
|
||||
|
||||
var o = new test();
|
||||
|
||||
alert(x); //2
|
||||
|
||||
运行结果为2,表明全局变量x的值根本没变。
|
||||
|
||||
===== 情况四 apply调用 =====
|
||||
|
||||
**apply()是函数对象**的一个方法,它的作用是__改变函数的调用对象__,它的第一个参数就表示改变后的调用这个函数的对象。因此,this指的就是这第一个参数。
|
||||
|
||||
var x = 0;
|
||||
|
||||
function test(){
|
||||
alert(this.x);
|
||||
}
|
||||
|
||||
var o={};
|
||||
o.x = 1;
|
||||
o.m = test;
|
||||
o.m.apply(); //0
|
||||
|
||||
**apply()的参数为空时,默认调用全局对象**。因此,这时的运行结果为0,证明this指的是全局对象。
|
||||
|
||||
如果把最后一行代码修改为
|
||||
|
||||
o.m.apply(o); //1
|
||||
|
||||
运行结果就变成了1,证明了这时this代表的是对象o。
|
||||
|
||||
(完)
|
||||
154
Zim/Programme/js/Javascript继承机制的设计思想.txt
Normal file
154
Zim/Programme/js/Javascript继承机制的设计思想.txt
Normal file
@@ -0,0 +1,154 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-03-06T21:01:07+08:00
|
||||
|
||||
====== Javascript继承机制的设计思想 ======
|
||||
Created Tuesday 06 March 2012
|
||||
|
||||
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
|
||||
|
||||
我一直很难理解Javascript语言的继承机制。
|
||||
|
||||
它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的__"原型链"__(prototype chain)模式,来实现继承。
|
||||
|
||||
我花了很多时间,学习这个部分,还做了很多笔记。但是都属于强行记忆,无法从根本上理解。
|
||||
|
||||
直到昨天,我读到法国程序员Vjeux的解释,才恍然大悟,__完全明白了Javascript为什么这样设计__。
|
||||
|
||||
下面,我尝试用自己的语言,来解释它的设计思想。彻底说明白prototype对象到底是怎么回事。其实根本就没那么复杂,真相非常简单。
|
||||
|
||||
===== 一、从古代说起 =====
|
||||
|
||||
要理解Javascript的设计思想,必须从它的诞生说起。
|
||||
|
||||
1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。这是历史上第一个**比较成熟**的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,__不具备与访问者互动的能力__。比如,如果网页上有一栏"用户名"要求填写,浏览器就无法判断访问者是否真的填写了,只有**让服务器端判断**。如果没有填写,服务器端就返回错误,要求用户重新填写,这太浪费时间和服务器资源了。
|
||||
|
||||
因此,网景公司急需一种网页脚本语言,使得__浏览器可以与网页互动__。工程师Brendan Eich负责开发这种新语言。他觉得,__没必要设计得很复杂__,这种语言只要能够完成一些简单操作就够了,比如判断用户有没有填写表单。
|
||||
|
||||
1994年正是面向对象编程(object-oriented programming)最兴盛的时期,C++是当时最流行的语言,而Java语言的1.0版即将于第二年推出,Sun公司正在大肆造势。
|
||||
|
||||
Brendan Eich无疑受到了影响,__Javascript里面所有的数据类型都是对象(object)__,这一点与Java非常相似。但是,他随即就遇到了一个难题,到底要不要设计"继承"机制呢?
|
||||
|
||||
===== 二、Brendan Eich的选择 =====
|
||||
|
||||
如果真的是一种简易的脚本语言,其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,__将所有对象联系起来__。所以,Brendan Eich最后还是设计了"继承"。
|
||||
|
||||
但是,他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。
|
||||
|
||||
他考虑到,C++和Java语言都使用new命令,生成实例。
|
||||
|
||||
C++的写法是:
|
||||
|
||||
ClassName *object = new ClassName(param);
|
||||
|
||||
Java的写法是:
|
||||
|
||||
Foo foo = new Foo();
|
||||
|
||||
因此,他就把new命令引入了Javascript,__用来从原型对象生成一个实例对象__。但是,Javascript没有"类",怎么来表示原型对象呢?
|
||||
|
||||
这时,他想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,__在Javascript语言中,new命令后面跟的不是类,而是构造函数__。
|
||||
|
||||
举例来说,现在有一个叫做DOG的构造函数,表示狗对象的原型。
|
||||
|
||||
function DOG(name){
|
||||
|
||||
this.name = name;
|
||||
|
||||
}
|
||||
|
||||
对这个构造函数使用new,就会生成一个狗对象的实例。
|
||||
|
||||
var dogA = new DOG('大毛');
|
||||
|
||||
alert(dogA.name); // 大毛
|
||||
|
||||
注意构造函数中的**this关键字**,它就__代表了新创建的实例对象__。
|
||||
|
||||
===== 三、new运算符的缺点 =====
|
||||
|
||||
用构造函数生成实例对象,有一个缺点,那就是__无法共享属性和方法__。
|
||||
|
||||
比如,在DOG对象的构造函数中,设置一个实例对象的共有属性species。
|
||||
|
||||
function DOG(name){
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.species = '犬科';
|
||||
|
||||
}
|
||||
|
||||
然后,生成两个实例对象:
|
||||
|
||||
var dogA = new DOG('大毛');
|
||||
|
||||
var dogB = new DOG('二毛');
|
||||
|
||||
这两个对象的species属性是__独立__的,修改其中一个,不会影响到另一个。
|
||||
|
||||
dogA.species = '猫科';
|
||||
|
||||
alert(dogB.species); // 显示"犬科",不受dogA的影响
|
||||
|
||||
每一个实例对象,都有自己的属性和方法的__副本__。这不仅__无法做到数据共享__,也是极大的资源浪费。
|
||||
|
||||
===== 四、prototype属性的引入 =====
|
||||
|
||||
考虑到这一点,Brendan Eich决定__为构造函数设置一个prototype属性__。
|
||||
|
||||
这个属性包含一个对象(以下简称"prototype对象"),__所有实例对象需要共享的属性和方法,都放在这个对象里面__;那些不需要共享的属性和方法,就放在构造函数里面。
|
||||
|
||||
实例对象一旦创建,将**自动引用**prototype对象的属性和方法。也就是说,__实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的__。
|
||||
|
||||
还是以DOG构造函数为例,现在用prototype属性进行改写:
|
||||
|
||||
function DOG(name){
|
||||
|
||||
this.name = name;
|
||||
|
||||
}
|
||||
|
||||
DOG.prototype = { species : '犬科' };
|
||||
|
||||
|
||||
var dogA = new DOG('大毛');
|
||||
|
||||
var dogB = new DOG('二毛');
|
||||
|
||||
|
||||
alert(dogA.species); // 犬科
|
||||
|
||||
alert(dogB.species); // 犬科
|
||||
|
||||
现在,species属性放在prototype对象里,__是两个实例对象共享的__。只要修改了prototype对象,就会同时影响到两个实例对象。
|
||||
|
||||
DOG.prototype.species = '猫科';
|
||||
|
||||
|
||||
alert(dogA.species); // 猫科
|
||||
|
||||
alert(dogB.species); // 猫科
|
||||
|
||||
===== 五、总结 =====
|
||||
|
||||
由于**所有的实例对象共享同一个prototype对象**,那么从外界看起来,__prototype对象就好像是实例对象的原型__,而实例对象则好像"继承"了prototype对象一样。
|
||||
|
||||
这就是Javascript继承机制的设计思想。不知道我说清楚了没有,继承机制的具体应用方法,可以参考我写的系列文章:
|
||||
|
||||
* 《Javascript面向对象编程(一):封装》
|
||||
* 《Javascript面向对象编程(二):构造函数的继承》
|
||||
* 《Javascript面向对象编程(三):非构造函数的继承》
|
||||
|
||||
(完)
|
||||
----------------------
|
||||
|
||||
说起来prototype也是一种经典的面向对象的实现方式了. 是里面所介绍的模式之一.
|
||||
普通OO语言是使用class实现继承, prototype使用__对象链__实现继承.
|
||||
我认为javascript的__prototype__和__函数式编程风格__, 这两个特性让javascript简洁而不简单, 让javascript成为一个伟大的语言.
|
||||
这种简洁而不简单的设计, 使得它不仅能担当"检测用户名是否已经输入"这样的简单活计, 还能在html5,nodejs,mongodb等新领域发挥更大的威力.
|
||||
|
||||
---------------------
|
||||
JavaScript = C + Lisp
|
||||
|
||||
自从有了 JSON,JavaScript 注定要成为伟大的语言。
|
||||
94
Zim/Programme/js/Javascript诞生记.txt
Normal file
94
Zim/Programme/js/Javascript诞生记.txt
Normal file
@@ -0,0 +1,94 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-03-06T21:41:15+08:00
|
||||
|
||||
====== Javascript诞生记 ======
|
||||
Created Tuesday 06 March 2012
|
||||
|
||||
http://www.ruanyifeng.com/blog/2011/06/birth_of_javascript.html
|
||||
|
||||
作者: 阮一峰
|
||||
日期: 2011年6月24日
|
||||
|
||||
===== 1. =====
|
||||
|
||||
二周前,我谈了一点Javascript的历史。
|
||||
|
||||
今天把这部分补全,__从历史的角度,说明Javascript到底是如何设计出来的__。
|
||||
|
||||
只有了解这段历史,才能明白Javascript为什么是现在的样子。我依据的资料,主要是Brendan Eich的自述。
|
||||
|
||||
===== 2. =====
|
||||
|
||||
上一篇文章写道:
|
||||
|
||||
"1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。这是历史上第一个比较成熟的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。......网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。"
|
||||
|
||||
**网页脚本语言到底是什么语言**?网景公司当时有两个选择:一个是采用现有的语言,比如Perl、Python、Tcl、Scheme等等,允许它们直接嵌入网页;另一个是发明一种全新的语言。
|
||||
|
||||
这两个选择各有利弊。第一个选择,有利于充分利用现有代码和程序员资源,推广起来比较容易;第二个选择,有利于开发出完全适用的语言,实现起来比较容易。
|
||||
|
||||
到底采用哪一个选择,网景公司内部争执不下,管理层一时难以下定决心。
|
||||
|
||||
===== 3. =====
|
||||
|
||||
就在这时,发生了另外一件大事:1995年**Sun公司将Oak语言改名为Java**,正式向市场推出。
|
||||
|
||||
Sun公司大肆宣传,许诺这种语言可以"__一次编写,到处运行__"(Write Once, Run Anywhere),它看上去很可能成为未来的主宰。
|
||||
|
||||
网景公司动了心,决定与Sun公司结成联盟。它不仅__允许Java程序以applet(小程序)的形式,直接在浏览器中运行__;甚至还考虑直接将Java作为脚本语言嵌入网页,只是因为这样会使HTML网页过于复杂,后来才不得不放弃。
|
||||
|
||||
总之,当时的形势就是,网景公司的整个管理层,都是Java语言的信徒,**Sun公司完全介入网页脚本语言的决策**。因此,__Javascript后来就是网景和Sun两家公司一起携手推向市场的__,这种语言被命名为"Java+script"并不是偶然的。
|
||||
|
||||
===== 4. =====
|
||||
|
||||
此时,34岁的**系统程序员Brendan Eich**登场了。1995年4月,网景公司录用了他。
|
||||
|
||||
Brendan Eich的主要方向和兴趣是__函数式编程__,网景公司招聘他的目的,是研究将Scheme语言作为网页脚本语言的可能性。Brendan Eich本人也是这样想的,以为进入新公司后,会主要与Scheme语言打交道。
|
||||
|
||||
仅仅一个月之后,1995年5月,网景公司做出决策,未来的网页脚本语言必须__"看上去与Java足够相似",但是比Java简单,使得非专业的网页作者也能很快上手__。这个决策实际上将Perl、Python、Tcl、Scheme等非面向对象编程的语言都排除在外了。
|
||||
|
||||
Brendan Eich被指定为这种"简化版Java语言"的设计师。
|
||||
|
||||
===== 5. =====
|
||||
|
||||
但是,他对Java一点兴趣也没有。为了应付公司安排的任务,他只用__10天__时间就把Javascript设计出来了。
|
||||
|
||||
由于设计时间太短,语言的一些细节考虑得不够严谨,导致后来很长一段时间,Javascript写出来的程序混乱不堪。如果Brendan Eich预见到,未来这种语言会成为互联网第一大语言,全世界有几百万学习者,他会不会多花一点时间呢?
|
||||
|
||||
总的来说,他的设计思路是这样的:
|
||||
|
||||
(1)借鉴C语言的基本语法;
|
||||
(2)借鉴Java语言的数据类型和内存管理;
|
||||
(3)借鉴Scheme语言,__将函数提升到"第一等公民"(first class)的地位__;
|
||||
(4)借鉴Self语言,使用__基于原型(prototype)的继承机制__。
|
||||
|
||||
所以,Javascript语言实际上是两种语言风格的混合产物----__(简化的)函数式编程+(简化的)面向对象编程__。这是由Brendan Eich(函数式编程)与网景公司(面向对象编程)共同决定的。
|
||||
|
||||
===== 6. =====
|
||||
|
||||
多年以后,Brendan Eich还是看不起Java。
|
||||
|
||||
他说:
|
||||
|
||||
"Java(对Javascript)的影响,主要是**把数据分成基本类型(primitive)和对象类型(object)两种**,比如字符串和字符串对象,以及引入了Y2K问题。这真是不幸啊。"
|
||||
|
||||
把基本数据类型包装成对象,这样做是否可取,这里暂且不论。Y2K问题则是直接与Java有关。根据设想,Date.getYear()返回的应该是年份的最后两位:
|
||||
|
||||
var date1 = new Date(1999,0,1);
|
||||
var year1 = date1.getYear();
|
||||
alert(year1); // 99
|
||||
|
||||
但是实际上,对于2000年,它返回的是100!
|
||||
|
||||
var date2 = new Date(2000,0,1);
|
||||
var year2 = date2.getYear();
|
||||
alert(year2); // 100
|
||||
|
||||
如果用这个函数生成年份,某些网页可能出现"19100"这样的结果。这个问题完全来源于Java,因为Javascript的日期类直接采用了java.util.Date函数库。Brendan Eich显然很不满意这个结果,这导致后来不得不添加了一个返回四位数年份的Date.getFullYear()函数。
|
||||
|
||||
__如果不是公司的决策,Brendan Eich绝不可能把Java作为Javascript设计的原型__。作为设计者,他一点也不喜欢自己的这个作品:
|
||||
|
||||
"与其说我爱Javascript,不如说我恨它。它是C语言和Self语言一夜情的产物。十八世纪英国文学家约翰逊博士说得好:'它的优秀之处并非原创,它的原创之处并不优秀。'(the part that is good is not original, and the part that is original is not good.)"
|
||||
|
||||
(完)
|
||||
136
Zim/Programme/js/Javascript面向对象编程(三):非构造函数的继承.txt
Normal file
136
Zim/Programme/js/Javascript面向对象编程(三):非构造函数的继承.txt
Normal file
@@ -0,0 +1,136 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-03-06T21:30:33+08:00
|
||||
|
||||
====== Javascript面向对象编程(三):非构造函数的继承 ======
|
||||
Created Tuesday 06 March 2012
|
||||
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html
|
||||
|
||||
作者: 阮一峰 日期: 2010年5月24日
|
||||
|
||||
这个系列的第一部分介绍了"封装",第二部分介绍了使用构造函数实现"继承"。
|
||||
|
||||
今天是最后一个部分,介绍不使用构造函数实现"继承"。
|
||||
|
||||
===== 一、什么是"非构造函数"的继承? =====
|
||||
|
||||
比如,现在有一个对象,叫做"中国人"。
|
||||
|
||||
var Chinese = {
|
||||
nation:'中国'
|
||||
};
|
||||
|
||||
还有一个对象,叫做"医生"。
|
||||
|
||||
var Doctor ={
|
||||
career:'医生'
|
||||
}
|
||||
|
||||
请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个"中国医生"的对象?
|
||||
|
||||
这里要注意,__这两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现"继承"__。
|
||||
|
||||
===== 二、object()方法 =====
|
||||
|
||||
__json格式__的发明人Douglas Crockford,提出了一个object()函数,可以做到这一点。
|
||||
|
||||
function object(o) {
|
||||
function F() {}
|
||||
F.prototype = o;
|
||||
return new F();
|
||||
|
||||
}
|
||||
|
||||
这个object()函数,其实只做一件事,就是__把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起__。
|
||||
|
||||
使用的时候,第一步先在父对象的基础上,生成子对象:
|
||||
|
||||
var Doctor = object(Chinese);
|
||||
|
||||
然后,再加上子对象本身的属性:
|
||||
|
||||
Doctor.career = '医生';
|
||||
|
||||
这时,子对象已经继承了父对象的属性了。
|
||||
|
||||
alert(Doctor.nation); //中国
|
||||
|
||||
===== 三、浅拷贝 =====
|
||||
|
||||
除了使用"prototype链"以外,还有另一种思路:**把父对象的属性,全部拷贝给子对象,也能实现继承**。
|
||||
|
||||
下面这个函数,就是在做拷贝:
|
||||
|
||||
function extendCopy(p) {
|
||||
|
||||
var c = {};
|
||||
for (var i in p) {
|
||||
c[i] = p[i];
|
||||
}
|
||||
c.uber = p;
|
||||
return c;
|
||||
}
|
||||
|
||||
使用的时候,这样写:
|
||||
|
||||
var Doctor = extendCopy(Chinese);
|
||||
Doctor.career = '医生';
|
||||
|
||||
alert(Doctor.nation); // 中国
|
||||
|
||||
但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的__只是一个内存地址,而不是真正拷贝__,因此存在父对象被篡改的可能。
|
||||
|
||||
请看,现在给Chinese添加一个"出生地"属性,它的值是一个数组。
|
||||
|
||||
Chinese.birthPlaces = ['北京','上海','香港'];
|
||||
|
||||
通过extendCopy()函数,Doctor继承了Chinese。
|
||||
|
||||
var Doctor = extendCopy(Chinese);
|
||||
|
||||
然后,我们为Doctor的"出生地"添加一个城市:
|
||||
|
||||
Doctor.birthPlaces.push('厦门');
|
||||
|
||||
发生了什么事?Chinese的"出生地"也被改掉了!
|
||||
|
||||
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
|
||||
alert(Chinese.birthPlaces); //北京, 上海, 香港, 厦门
|
||||
|
||||
所以,extendCopy()只是拷贝基本类型的数据,我们把这种拷贝叫做__"浅拷贝"__。这是早期jQuery实现继承的方式。
|
||||
|
||||
===== 四、深拷贝 =====
|
||||
|
||||
所谓"深拷贝",就是能够**实现真正意义上的**__数组和对象__**的拷贝**。它的实现并不难,只要__递归调用"浅拷贝"__就行了。
|
||||
|
||||
function deepCopy(p, c) {
|
||||
|
||||
var c = c || {};
|
||||
for (var i in p) {
|
||||
if (typeof p[i] === 'object') {
|
||||
c[i] = (p[i].constructor === Array) ? [] : {};
|
||||
deepCopy(p[i], c[i]);
|
||||
} else {
|
||||
c[i] = p[i];
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
使用的时候这样写:
|
||||
|
||||
var Doctor = deepCopy(Chinese);
|
||||
|
||||
现在,给父对象加一个属性,值为数组。然后,在子对象上修改这个属性:
|
||||
|
||||
Chinese.birthPlaces = ['北京','上海','香港'];
|
||||
Doctor.birthPlaces.push('厦门');
|
||||
|
||||
这时,父对象就不会受到影响了。
|
||||
|
||||
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
|
||||
alert(Chinese.birthPlaces); //北京, 上海, 香港
|
||||
|
||||
目前,jQuery库使用的就是这种继承方法。
|
||||
(完)
|
||||
173
Zim/Programme/js/Javascript面向对象编程(二):构造函数的继承.txt
Normal file
173
Zim/Programme/js/Javascript面向对象编程(二):构造函数的继承.txt
Normal file
@@ -0,0 +1,173 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-03-06T21:17:32+08:00
|
||||
|
||||
====== Javascript面向对象编程(二):构造函数的继承 ======
|
||||
Created Tuesday 06 March 2012
|
||||
|
||||
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html
|
||||
|
||||
作者: 阮一峰 日期: 2010年5月23日
|
||||
|
||||
这个系列的第一部分,主要介绍了**如何"封装"数据和方法**,以及如何**从原型对象生成实例**。
|
||||
|
||||
今天要介绍的是,如何**生成一个"继承"多个对象的实例**。
|
||||
|
||||
比如,现在有一个"动物"对象的构造函数,
|
||||
|
||||
|
||||
function Animal(){
|
||||
this.species = "动物";
|
||||
}
|
||||
|
||||
还有一个"猫"对象的构造函数,
|
||||
|
||||
|
||||
function Cat(name,color){
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
怎样才能使"猫"继承"动物"呢?
|
||||
|
||||
===== 1. 构造函数绑定 =====
|
||||
|
||||
最简单的方法,大概就是使用__call或apply方法__,**将父对象的构造函数绑定在子对象上**,也就是在子对象构造函数中加一行:
|
||||
|
||||
function Cat(name,color){
|
||||
|
||||
Animal.apply(this, arguments);
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
var cat1 = new Cat("大毛","黄色");
|
||||
alert(cat1.species); // 动物
|
||||
|
||||
===== 2. prototype模式 =====
|
||||
|
||||
__更常见的做法,则是使用prototype属性__。
|
||||
|
||||
如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。
|
||||
|
||||
Cat.prototype = new Animal();
|
||||
__Cat.prototype.constructor = Cat;__
|
||||
var cat1 = new Cat("大毛","黄色");
|
||||
alert(cat1.species); // 动物
|
||||
|
||||
代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。
|
||||
|
||||
Cat.prototype = new Animal();
|
||||
|
||||
它相当于__完全删除了prototype 对象原先的值,然后赋予一个新值__。但是,第二行又是什么意思呢?
|
||||
|
||||
Cat.prototype.constructor = Cat;
|
||||
|
||||
原来,__任何一个prototype对象都有一个constructor属性,指向它的构造函数__。也就是说,Cat.prototype 这个对象的constructor属性,是指向Cat的。
|
||||
|
||||
我们在前一步已经删除了这个prototype对象原来的值,所以__新的prototype对象没有constructor属性,所以我们必须手动加上去__,否则后面的"__继承链__"会出问题。这就是第二行的意思。
|
||||
|
||||
总之,这是很重要的一点,__编程时务必要遵守__。下文都遵循这一点,即如果替换了prototype对象,
|
||||
|
||||
o.prototype = {};
|
||||
|
||||
那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
|
||||
|
||||
o.prototype.constructor = o;
|
||||
|
||||
===== 3. 直接继承prototype =====
|
||||
|
||||
由于Animal对象中,__不变的属性都可以直接写入Animal.prototype__。所以,我们也可以**让Cat()跳过 Animal(),直接继承Animal.prototype**。
|
||||
|
||||
现在,我们先将Animal对象改写:
|
||||
|
||||
function Animal(){ }
|
||||
Animal.prototype.species = "动物";
|
||||
|
||||
然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。
|
||||
|
||||
Cat.prototype = Animal.prototype;
|
||||
Cat.prototype.constructor = Cat;
|
||||
var cat1 = new Cat("大毛","黄色");
|
||||
alert(cat1.species); // 动物
|
||||
|
||||
与前一种方法相比,这样做的优点是__效率比较高__(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
|
||||
|
||||
所以,上面这一段代码其实是有问题的。请看第二行
|
||||
|
||||
Cat.prototype.constructor = Cat;
|
||||
|
||||
这一句**实际上把Animal.prototype对象的constructor属性也改掉了**!
|
||||
|
||||
alert(Animal.prototype.constructor); // Cat
|
||||
|
||||
===== 4. 利用空对象作为中介 =====
|
||||
|
||||
由于"直接继承prototype"存在上述的缺点,所以可以利用一个__空对象作为中介__。
|
||||
|
||||
var F = function(){};
|
||||
F.prototype = Animal.prototype;
|
||||
Cat.prototype = new F();
|
||||
Cat.prototype.constructor = Cat;
|
||||
|
||||
F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。
|
||||
|
||||
alert(Animal.prototype.constructor); // Animal
|
||||
|
||||
===== 5. prototype模式的封装函数 =====
|
||||
|
||||
我们将上面的方法,封装成一个函数,便于使用。
|
||||
|
||||
function extend(Child, Parent) {
|
||||
var F = function(){};
|
||||
F.prototype = Parent.prototype;
|
||||
Child.prototype = new F();
|
||||
Child.prototype.constructor = Child;
|
||||
Child.uber = Parent.prototype;
|
||||
}
|
||||
|
||||
使用的时候,方法如下
|
||||
|
||||
extend(Cat,Animal);
|
||||
var cat1 = new Cat("大毛","黄色");
|
||||
alert(cat1.species); // 动物
|
||||
|
||||
这个extend函数,就是__YUI库__如何实现继承的方法。
|
||||
|
||||
另外,说明一点。函数体最后一行
|
||||
|
||||
Child.uber = Parent.prototype;
|
||||
|
||||
意思是**为子对象设一个uber属性**,这个属性直接指向父对象的prototype属性。这等于__是在子对象上打开一条通道,可以直接调用父对象的方法__。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
|
||||
|
||||
===== 6. 拷贝继承 =====
|
||||
|
||||
上面是采用prototype对象,实现继承。我们也可以换一种思路,__纯粹采用"拷贝"方法实现继承__。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?
|
||||
|
||||
首先,还是把Animal的所有不变属性,都放到它的prototype对象上。
|
||||
|
||||
function Animal(){}
|
||||
Animal.prototype.species = "动物";
|
||||
|
||||
然后,再写一个函数,实现属性拷贝的目的。
|
||||
|
||||
function extend2(Child, Parent) {
|
||||
var p = Parent.prototype;
|
||||
var c = Child.prototype;
|
||||
for (var i in p) {
|
||||
c[i] = p[i];
|
||||
}
|
||||
c.uber = p;
|
||||
}
|
||||
|
||||
这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。
|
||||
|
||||
使用的时候,这样写:
|
||||
|
||||
extend2(Cat, Animal);
|
||||
var cat1 = new Cat("大毛","黄色");
|
||||
alert(cat1.species); // 动物
|
||||
|
||||
未完,请继续阅读第三部分《非构造函数的继承》。
|
||||
|
||||
(完)
|
||||
Reference in New Issue
Block a user