Add New Notes

This commit is contained in:
geekard
2012-08-08 14:26:04 +08:00
commit 5ef7c20052
2374 changed files with 276187 additions and 0 deletions

View 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 输出101get中+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设置如 writableconfigurableenumerable 等这类的属性配置。
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);
callapply 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用委托实现了这一机制。其实这就是PrototypePerson是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.

View 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]); }
未完,请继续阅读这个系列的第二部分《构造函数的继承》和第三部分《非构造函数的继承》。

View 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。
(完)

View 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
自从有了 JSONJavaScript 注定要成为伟大的语言。

View 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."
(完)

View 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库使用的就是这种继承方法。
(完)

View 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); // 动物
未完,请继续阅读第三部分《非构造函数的继承》。
(完)