JavaScript面向对象(3)

全局对象

我们知道在对象方法内部可以使用this来表示对象本身。

var hero = {
    name:'Rafaelo',
    sayName:function(){
        return this.name;
    }
}

>>> hero.sayName();
>>> "Rafaelo"

接下来我们通过构造器函数来创建对象

function Hero(){
    this.occupation = 'Ninja';
}

>>> var hero = new Hero();
>>> hero.occupation;
>>> "Ninja"

我们可以看出构造器的函数来创建对象有两个比较好的特点: 1. 可以在创建对象的时候传参数。 2. 在使用的时候才真正创建和执行。

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操作符

>>> var h = Hero('xiaohong');
>>> typeof h
>>> "undefined"

没有使用new操作符,因此我们不是在创建一个新对象,这里Hero和一个普通函数没什么区别,所以h就是这个Hero函数的返回值。

那么在这种情况下,Hero函数内这个this引用的是什么呢?

我们知道有全局变量,程序的宿主环境一般会为其提供一个全局对象,而全局变量只不过是全局对象的一个属性罢了。

>>> var a = 1;
//也可以window['a']或者window.a来访问

在Web浏览器中,window就是全局对象。这里的this其实指向了这个window对象。所以此时this.name的name成了全局变量。这就是为什么函数中没有声明var的变量是全局变量。

instanceof操作符

通过instanceof操作符,我们可以测试一个对象是不是由某个指定的构造器函数所创建的。

>>> 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中所有对象的父级对象,这意味着我们创建的所有对象都继承自它。

下面两行执行结果是等价的:

var o = {}
var o = new Object();

实际上空对象没有什么实质作用,Object对象包含了一些方法和属性,比如toString(),valueOf()方法。

所有字面量创建的对象(非 function 声明)都直接连接到 Object.prototype

Function对象

我们知道函数是对象,函数对象内建构造器是Function(),我们可以将它作为创建函数的另一种备用方法。

下面三种定义函数的方式是等效的:

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方法将父对象的构造函数绑定在子对象上来实现继承。

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只是在传参形式上有所不同,下面代码是等效的

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,那么该函数在调用的时候就会返回自身引用。

function f(){
    return arguments.callee;
}

我们可以通过arguments.callee属性来实现匿名函数递归调用。

(
    function(count){
        if(count<5){
            alert(count);
            arguments.callee(++count);
        }
    }
)(1)

异常

和 Java 类似, JavaScript 也提供了一套异常处理机制,例如:

var add = function(a, b){
    if(typeof a !== 'number' || typeof b !== 'number'){
        throw{
            name: 'TypeError',
            message: 'add needs numbers'
        };
    }
    return a + b;
}

throw 语句会中断函数的执行,它抛出一个 exception 对象,该对象包含异常类型和异常描述,当然也可以添加其他属性。

try{
    add(1, 'a');
}catch(e){
    document.writeln(e.name + ':' + e.message);
}

给类型增加方法

JavaScript 允许给基本类型增加方法,前面我们已经知道可以通过给 Object.prototype 增加方法, 同样的我们可以给 Object 增加方法:

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 如下:

Object.beget = function (o){
    var F = function(){
        this.nickname = 'HHH';
    };
    F.prototype = o;
    return new F();
}

结果输出 HHH ,上面过程充分说明了 JavaScript 的原型委托,首先会从原对象 F 对象中查找 nickname, 如果找不到,则会委托给 prototype 指向的 stooge 对象查找。

这里提出一个问题,如果将上面的代码修改如下,也就是去掉 this 关键字, 输出会如何变化:

Object.beget = function (o){
    var F = function(){
-       this.nickname = 'HHH';
+       nickname = 'HHH';
    };
    F.prototype = o;
    return new F();
}

结果发现输出了 OOO, 实际上这里的 nickname 此时是一个全局变量了,总结一下:如果定义对象的属性就要用 this,如果作为局部变量就要用 var, 否则是全局变量。

同样的我们可以给 Function.prototype 增加方法来让对所有函数可用:

Function.prototype.method = function(name, func){
    this.prototype[name] = func;
    return this;
}