全局对象
我们知道在对象方法内部可以使用this来表示对象本身。
1
2
3
4
5
6
7
8
9
| var hero = {
name:'Rafaelo',
sayName:function(){
return this.name;
}
}
>>> hero.sayName();
>>> "Rafaelo"
|
接下来我们通过构造器函数来创建对象
1
2
3
4
5
6
7
| function Hero(){
this.occupation = 'Ninja';
}
>>> var hero = new Hero();
>>> hero.occupation;
>>> "Ninja"
|
我们可以看出构造器的函数来创建对象有两个比较好的特点:
- 可以在创建对象的时候传参数。
- 在使用的时候才真正创建和执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function Hero(name){
this.name = name;
this.occupation = "Ninja";
this.whoAreYou = function(){
return "I'm " + this.name + " and I'm a " + this.occupation;
}
}
//创建不同的对象
>>> var h1 = new Hero("xiaohong");
>>> var h2 = new Hero("xiaoming");
>>> h1.whoAreYou();
>>> "I'm xiaohong and I'm a Ninja";
>>> h2.whoAreYou();
>>> "I'm xiaoming and I'm a Ninja";
|
假如我们在调用构造函数的时候忽略了new操作符
1
2
3
| >>> var h = Hero('xiaohong');
>>> typeof h
>>> "undefined"
|
没有使用new操作符,因此我们不是在创建一个新对象,这里Hero和一个普通函数没什么区别,所以h就是这个Hero函数的返回值。
那么在这种情况下,Hero函数内这个this引用的是什么呢?
我们知道有全局变量,程序的宿主环境一般会为其提供一个全局对象,而全局变量只不过是全局对象的一个属性罢了。
1
2
| >>> var a = 1;
//也可以window['a']或者window.a来访问
|
在Web浏览器中,window就是全局对象。这里的this其实指向了这个window对象。所以此时this.name的name成了全局变量。这就是为什么函数中没有声明var的变量是全局变量。
instanceof操作符
通过instanceof操作符,我们可以测试一个对象是不是由某个指定的构造器函数所创建的。
1
2
3
4
5
6
7
8
9
| >>> function Hero(){}
>>> var h = new Hero();
>>> var o = {};
>>> h instanceof Hero;
>>> true
>>> h instanceof Object;
>>> true
>>> o instanceof Object;
>>> true
|
注意,这里不是调用函数,所以instanceof后面跟函数名。不要这样用(h instanceof Hero())
内建构造函数
内建构造器函数大致上可分为三个组:
- 数据封装类对象:包括Object、Array、Boolean、Number和String以及undefined和null.
- 工具类对象:包括Math、Date、RegExp等。
- 错误类对象:异常发生时的特殊错误类对象。
Object对象
Object对象是JavaScript中所有对象的父级对象,这意味着我们创建的所有对象都继承自它。
下面两行执行结果是等价的:
1
2
| var o = {}
var o = new Object();
|
实际上空对象没有什么实质作用,Object对象包含了一些方法和属性,比如toString(),valueOf()方法。
所有字面量创建的对象(非 function 声明)都直接连接到 Object.prototype
Function对象
我们知道函数是对象,函数对象内建构造器是Function(),我们可以将它作为创建函数的另一种备用方法。
下面三种定义函数的方式是等效的:
1
2
3
4
5
6
7
8
9
10
11
12
| function sum(a, b){
return a + b;
}
>>> sum(1, 2);
var sum = function(a, b){
return a + b;
}
>>> sum(1, 2);
var sum = new Function('a', 'b', 'return a + b;');
>>> sum(1, 2);
|
之前我们使用call或者apply方法将父对象的构造函数绑定在子对象上来实现继承。
1
2
3
4
5
6
7
8
9
10
11
12
| function Animal(){
this.species="动物"
}
function Cat(name, color){
Animal.apply(this, arguments);
this.name=name;
this.color=color;
}
var cat1 = new Cat("大黄", "yellow");
alert(cat1.species);
|
其实call()和apply()是Function对象的内置方法,call和apply只是在传参形式上有所不同,下面代码是等效的
1
2
| Animal.apply(this, ['a', 'b', 'c']);
Animal.call(this, 'a', 'b', 'c');
|
这样实现继承的原理是,Cat方法调用的时候,Animal中的this就会被自动设置为Cat对象的引用。
arguments对象
上面我们看到我们可以直接使用arguments对象来传递函数参数,尽管arguments看上去是一个数组,但实际上它是一个类似于数组的对象。arguments是一个单纯的数组,没有sort(),slice()等这样的方法。另外,arguments中有一个callee属性,该属性引用的是当前被调用的函数对象,如果我们所创建的函数返回值是arguments.callee,那么该函数在调用的时候就会返回自身引用。
1
2
3
| function f(){
return arguments.callee;
}
|
我们可以通过arguments.callee属性来实现匿名函数递归调用。
1
2
3
4
5
6
7
8
| (
function(count){
if(count<5){
alert(count);
arguments.callee(++count);
}
}
)(1)
|
异常
和 Java 类似, JavaScript 也提供了一套异常处理机制,例如:
1
2
3
4
5
6
7
8
9
| var add = function(a, b){
if(typeof a !== 'number' || typeof b !== 'number'){
throw{
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
}
|
throw 语句会中断函数的执行,它抛出一个 exception 对象,该对象包含异常类型和异常描述,当然也可以添加其他属性。
1
2
3
4
5
| try{
add(1, 'a');
}catch(e){
document.writeln(e.name + ':' + e.message);
}
|
给类型增加方法
JavaScript 允许给基本类型增加方法,前面我们已经知道可以通过给 Object.prototype
增加方法, 同样的我们可以给 Object
增加方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| if(typeof Object.beget != 'function'){
Object.beget = function (o){
var F = function(){
this.nickname = 'HHH';
//nickname = 'HHH';
};
F.prototype = o;
return new F();
}
}
var stooge = {
"first-name": "Jerome",
"last-name": "Howard",
'nickname': 'OOO'
}
var another_stooge = Object.beget(stooge);
another_stooge['first-name'] = 'Harry';
another_stooge['last-name'] = 'Moses';
//another_stooge.nickname = 'Moe';
//document.writeln(another_stooge['first-name']);
document.writeln(another_stooge.nickname);
|
上面我们给 Object 增加了一个 beget 方法, 这个中间使用 原对象(F)作为原型(stooge)的新对象,你会发现一个有趣的现象,最后一行访问到了 stooge
的 nickname。
为了探索这个问题,我们在 F 函数中添加一个 nickname 如下:
1
2
3
4
5
6
7
| Object.beget = function (o){
var F = function(){
this.nickname = 'HHH';
};
F.prototype = o;
return new F();
}
|
结果输出 HHH ,上面过程充分说明了 JavaScript 的原型委托,首先会从原对象 F 对象中查找 nickname, 如果找不到,则会委托给 prototype 指向的 stooge 对象查找。
这里提出一个问题,如果将上面的代码修改如下,也就是去掉 this 关键字, 输出会如何变化:
1
2
3
4
5
6
7
8
| Object.beget = function (o){
var F = function(){
- this.nickname = 'HHH';
+ nickname = 'HHH';
};
F.prototype = o;
return new F();
}
|
结果发现输出了 OOO, 实际上这里的 nickname 此时是一个全局变量了,总结一下:如果定义对象的属性就要用 this,如果作为局部变量就要用 var, 否则是全局变量。
同样的我们可以给 Function.prototype
增加方法来让对所有函数可用:
1
2
3
4
| Function.prototype.method = function(name, func){
this.prototype[name] = func;
return this;
}
|