JavaScript中的Function对象是函数,函数的用途分为3类:

  1. 作为普通逻辑代码容器;
  2. 作为对象方法;
  3. 作为构造函数。

1.作为普通逻辑代码容器


function multiply(x, y){
    return x*y;
}

函数multiply封装了两位数的乘法运算公式:


var product = multiply(128,128); // product = 16384

创建函数实例的方式有3种。第一种是声明式,即像声明变量一样,将通过function(){}标识符创建的匿名函数直接赋值给变量,以该变量作为调用时的函数名称:


var multiply = function(x, y){
    return x*y;
}

第二种是定义式,即以function关键字后跟函数名称及(){}来直接定义命名函数,前面第一个multiply函数就是通过定义式创建的。

第三种是构造函数式,即通过new运算符调用构造函数Function来创建函数。这种方式极不常用,因此就不作介绍了。

在创建函数的3种方式中,声明式和定义式还存在细微的差别。比如下列代码中的函数采用声明式:


var example = function(){
    return 1;
}
example();

var example = function(){
    return 2;
}
example();

执行结果如下:
1
2

而如果采用定义式,即:


function example(){
     return 1;
}
example();

function example(){
    return 2;
}
example();

那么会得到另一种结果:
2
2

即,在采用定义式创建同名函数时,后创建的函数会覆盖先创建的函数。这种差别是由于JavaScript解释引擎的工作机制所导致的。JavaScript解释引擎在执行任何函数调用之前,首先会在全局作用域中注册以定义式创建的函数,然后再依次执行函数调用。由于注册函数时,后定义的函数重写了先定义的函数,因此无论调用语句位于何处,执行的都是后定义的函数。相反,对于声明式创建的函数,JavaScript解释引擎会像对待任何声明的变量一样,等到执行调用该变量的代码时才会对变量求值。由于JavaScript代码是从上到下顺序执行的,因此当执行第一个example()调用时,example函数的代码就是首先定义代码;而当执行第二个example()调用时,example函数的代码又变成了后来定义的代码。

2.作为对象方法

JavaScript在解析代码时,会为声明或定义的函数指定调用对象。所谓调用对象,就是函数的执行环境。如果函数体内有以关键字this声明的变量,则this引用的就是调用对象。

事实上,在普通的函数中,也存在调用对象,只不过这个调用对象是默认的全局window对象而已。例如:


var product = window.multiply(128,128); // product = 16384

这说明,默认情况下,在全局作用域中定义或声明的函数的调用对象就是window。

在面向对象编程中,通常将作为对象成员的函数称为方法。例如:


var dog = {};
dog.name = "heibao";
dog.age = "3 months";
dog.shout = function(){
    return "Hello, My name is "+ this.name + " and I am " + this.age + " old!";
}
dog.shout(); // "Hello, My name is heibao and I am 3 months old!"

有意思的是,对象也可以借用其他对象的方法:


var cat = {};
cat.name = "xiaohua";
cat.age = "2 years";
cat.greet = dog.shout;
cat.greet(); // "Hello, My name is xiaohua and I am 2 years old!"

另外,使用函数对象的call和apply方法,还可以动态指定函数或方法的调用对象:


dog.shout.call(cat); // "Hello, My name is xiaohua and I am 2 years old!"

或者


dog.shout.apply(cat); // "Hello, My name is xiaohua and I am 2 years old!"

3.作为构造函数

JavaScript是通过构造函数来模拟面向对象语言中的类的。例如:


function Animal(sort, character){
    this.sort = sort;
    this.character = character;
}

以Animal作为构造函数,就可以像下面这样创建一个新对象:


var dog = new Animal("mammal","four legs");

创建dog的对象的过程如下:首先,new运算符创建一个空对象({}),然后以这个空对象为调用对象调用函数Animal,为这个空对象添加两个属性sort和character,接着,再将这个空对象的默认constructor属性修改为构造函数的名称(即Animal;空对象创建时默认的constructor属性值是Object),并且将空对象的__proto__属性设置为指向Animal.prototype——这就是所谓的对象初始化。最后,返回初始化完毕的对象。这里将返回的新对象赋值给了变量dog。


dog.sort; // mammal
dog.character; // four legs
dog.constructor; // Animal

聪明的读者结合前面介绍的内容,可能会认为使用new运算符调用构造函数创建对象的过程也可以像下面这样来实现:


var dog = {};
Animal.call(dog, "mammal","four legs");

表面上看,这两行代码与var dog = new Animal(“mammal”,”four legs”);是等价的,其实却不是。虽然通过指定函数的执行环境能够部分达到初始化对象的目的,例如空对象dog确实获得了sort和character这两个属性:


dog.sort; // mammal
dog.character; // four legs
dog.constructor; // Object —— 注意,没有修改dog对象默认的constructor属性

但是,最关键的是新创建的dog对象失去了通过Animal.prototype属性继承其他对象的能力。只要与前面采用new运算符调用构造函数创建对象的过程对比一下,就会发现,new运算符在初始化新对象期间,除了为新对象添加显式声明的属性外,还会对新对象进行了一番“暗箱操作”——即将新对象的constructor属性重写为Animal,将新对象的__proto__属性设置为指向Animal.prototype。虽然手工“初始化对象”也可以将dog.constructor重写为Animal,但根据ECMA262规范,对象的__proto__属性对开发人员是只读的,对它的设置只能在通过new运算符创建对象时由JavaScript解释引擎替我们完成。
JavaScript是基于原型继承的,如果不能正确设置对象的__proto__属性,那么就意味着默认的继承机制会失效:


Animal.prototype.greet = "Hi, good lucky!";
dog.greet; // undefined

事实上,在Firefox中,__proto__属性也是可写的:


Animal.prototype.greet = "Hi, good lucky!";
dog.__proto__ = Animal.prototype;
dog.greet; // Hi, good lucky!

但这样做只能在Firefox中行得通。考虑到在兼容多浏览器,必须依赖于new运算符,才能实现基于原型的继承。



朋友们的留言

  1. seektan | 12月 26th, 2008 at 17:50

    不错,通俗易懂,一口气看完了;
    第一次知道dog.shout.call(cat)的用法,很强大

    Reply to this comment
  2. vampire | 12月 27th, 2008 at 16:27

    多谢 明白为什么非要用__proto__了

    Reply to this comment
  3. admin | 12月 28th, 2008 at 19:55

    不客气啊,呵呵

    Reply to this comment
  4. mickey | 03月 5th, 2009 at 16:14

    “创建dog的对象的过程如下:首先,new运算符创建一个空对象({}),然后以这个空对象为调用对象调用函数Animal,为这个空对象添加两个属性 sort和character,接着,再将这个空对象的默认constructor属性修改为构造函数的名称(即Animal;空对象创建时默认的 constructor属性值是Object),并且将空对象的__proto__属性设置为指向Animal.prototype——这就是所谓的对象初始化。最后,返回初始化完毕的对象。这里将返回的新对象赋值给了变量dog。

    能介绍一本类是讲解方式的js 书籍吗?

    Reply to this comment
  5. 为之漫笔 | 03月 5th, 2009 at 16:55

    提供两个链接,供参考:
    http://www.cnblogs.com/leadzen/archive/2008/02/25/1073404.html

    http://www.cnblogs.com/RicCC/archive/2008/02/15/javascript-object-model-execution-model.html

    Reply to this comment
  6. mickey | 03月 5th, 2009 at 18:55

    谢谢

    Reply to this comment
  7. lewis | 06月 1st, 2009 at 09:06

    给您提个建议。 页面的连接能不能“_blank”在另一个页面显示。这样不至于我总是按后退。呵呵

    Reply to this comment
  8. lewis | 06月 1st, 2009 at 09:24

    谢谢 很好

    Reply to this comment
  9. 为之漫笔 | 06月 1st, 2009 at 20:44

    OK

    Reply to this comment
  10. chirsjie | 07月 28th, 2009 at 13:46

    写的相当清楚,很不错 谢谢

    Reply to this comment
  11. walkingp | 10月 26th, 2009 at 22:35

    好网站一定要留言。

    Reply to this comment
  12. 明白的事情你没说明白 | 11月 18th, 2010 at 16:20

    感觉一个简单的事情,你把它说的云里雾里。你自己根本就没有明白吧

    Reply to this comment
  13. 同意楼上观点 | 02月 19th, 2011 at 08:40

    我作为一个js的初学者,说说我的看法:
    1.第一遍看时,觉得很神奇,很厉害.
    2.第二遍看时,觉得怪怪的,就是看的很模糊的感觉
    3.第三遍看时,恍然大悟,发觉你把有的没的概念都杂糅在一起说了,感觉好乱.有点误导初学者.
    感想:i>初学者看不懂,看了也白看
    ii>老鸟都懂了,无需再看
    iii>似乎这些章是写给自己看的,让自己偶尔回顾一下知识.
    4.希望,能够讲问题时由浅入深的讲,一个问题一个问题的讲,不要N个问题一起讲.
    另外,刚刚买了本您翻译的js高级程序设计,翻译的挺好,看得出里面的用词都是经过深思熟虑的,在此感谢下

    Reply to this comment
  14. idamag | 07月 19th, 2011 at 16:59

    在firebug控制台里运行:
    function example(){
    return 1;
    }
    example();

    function example(){
    return 2;
    }
    example();

    居然是1 , 2

    Reply to this comment

我来说两句儿

可以在留言中使用以下标签 :<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam Protection by WP-SpamFree