JavaScript的函数详解
函数,在C语言之类的过程式语言中,是顶级的实体,而在Java/C++之类的面向对象的语言中,则被对象包装起来,一般成为对象的方法.在JavaScript中,函数本身与其他任何的内置对象在地位上是没有任何区别的,也就是说函数本身也是对象.总的来说,函数在JavaScript中可以:
被赋值给一个变量;
被赋值为对象的属性;
作为参数被传入别的函数;
作为函数的结果被返回;
用字面量来创建.
一.创建函数
创建JavaScript函数的一种不常见的方式是通过new操作符来作用于Function"构造器".
var add=new Function("x","y","return(x+y)"); print(add(2,4)); 将会打印结果: 6
参数列表可以有任意的参数,然后紧跟着是函数体,,如果函数体比较复杂,那拼接String需要花费很大的力气,所以JavaScript提供了一种语法糖,即通过字面量来创建函数.
function add(x,y){ return x+y; }或: var add=function(x,y){ return x+y; }
function关键字会调用Function来new一个对象,并将参数表和函数体准确地传递给Function的构造器.通常来说,在全局作用域内声明一个对象,只不过是对一个属性赋值而已,比如上例中的add函数,事实上只是为全局对象加了一个属性,属性名为add,而属性的值是一个对象,即function(x,y){return x+y}
函数跟其他的对象一样,都是作为一个独立的对象存在于JavaScript的运行系统,例如:
function p(){ print("invoke p by ()"); } p.id="func"; p.type="function"; print(p); print(p.id+":"+p.type); print(P()); 运行结果如下: function (){ print("invoke p by ()"); } func:function invoke p by () //p函数引用了一个匿名函数(对象),但是同时又可以拥有属性,这些属性不会影响函数本身的功能,完全跟其他对象一样,
二.函数作用域
JavaScript中的变量作用域为函数体内有效,而无块作用域.我们在Java函数方法中使用两个for循环时可以使用相同的变量名(如i),而 JavaScript就不可以.JavaScript的函数是在局部作用域内运行的,在局部作用域内运行的函数体可以访问其外层(可能是全局作用域)的变量和函数. JavaScript的作用域为词法作用域,其作用域是在定义时就确定下来的,而非在执行时确定,例如:
var str="global"; function scopeTest(){ print(str); var str="local"; print(str); } scopeTest(); 运行结果为: undefined local
为什么函数这个时候不去访问外部的str变量呢?因为在词法分析结束后,构造作用域链的时候会将函数内定义的var 变量放入该链,因此在整个函数内部是可见的(从函数的第一行到最后一行),由于str变量本身是未定义的,程序顺序进行,到第一行就会返回未定义.
三.调用对象
在JavaScript中,在所有函数之外声明的变量为全局变量,而在函数内部声明的变量(通过var关键字)为局部变量.在执行一个函数时,函数的参数和其局部变量会作为调用对象的属性进行存储.同时,解释器会为函数创建一个执行器上下文.与上下文对应的是一个作用域链.作用域链是关于作用域的链,通常实现为一个链表,链表的每个项都是一个对象,即全集对象.对应的,在一个函数中,作用域上会有两个对象,第一个(首先被访问到的)为调用对象,第二个为全局对象.
当函数需要用到某个变量时,解释器会遍历作用域链,例如:
var str="global"; function scopeTest(){ print(str); var str="local"; print(str); }
当解释器进入scopeTest函数的时候,一个调用对象就被创建了,其中包含了str变量作为其中的一个属性并被初始化为underfined,当执行到第一个print(str)时,解释器会在作用域链中查找str,找到之后,打印其值为underfined,然后执行赋值语句,此时调用对象的属性str会被赋值为local,因此第二个print(str)语句会打印local.
四. call和apply
call和apply通常用来修该函数的上下文,函数中的this指针将被替换为call和apply的第一个参数,例如:
//定义一个人,名字为zhaoyanzhi var zhaoyanzhi={ name : "zhaoyanzhi", age : 23, } //定义另一个人,名字为zhayanwen var zhaoyanwen={ name : "zhaoyanwen", age : 17 } //定义一个全局的函数对象 function printName(){ return this.name; } //设置printName的上下文为zhaoyanzhi,此时的this为zhaoyanzhi print(printName.call(zhaoyanzhi)); //设置printName的上下文为zhaoyanwen,此时的this为zhayanwen print(printName.call(zhayanwen)); print(printName.apply(zhaoyanzhi)); print(printName.apply(zhayanwen)); 只有一个参数的时候call和apply的使用方法是一样的,如果有多个参数: setName.apply(zhaoyanzhi,["yanzhi"]) print(printName.apply(zhaoyanzhi)); setName.call(zhaoyanwen,"yanwen") 得到的结果为: yanzhi yanwen apply的第二个参数为一个函数需要的参数组成的一个数组,而call则需要跟若干个参数,参数之间以逗号 (,)隔开
五.使用函数
(1)赋值给一个变量
function add(x,y){ return x+y; } var a=0; a=add; //将函数赋值给一个变量,a的值是一个对象 var b=a(2,3); //调用这个新的函数a print(b); //打印的结果为5
(2)赋值为对象的属性
var obj={ id:"obj1" } obj.func=add; //赋值为obj对象的属性 obj.func(2,3); //返回5
(3)作为参数传递
function adPrint2(str handler){ print(handler(str)); } //将字符串转换为大写形式,并返回 function up(str){ return str.toUpperCase(); } //将字符串转换为小写的形式,并返回 function low(str){ return str.toLowerCase(); } adPrint2("hello, world", up); adPrint2("hello,world",low); 运行结果为: HELLO, WORLD hello,world
(4)作为函数的返回值
function currying(){ return function(){ print("curring"); } }
函数currying返回一个匿名函数,这个匿名函数会打印"curring",简单地调用currying()会得到下面的结果.
function(){ print("curring"); }
这个结果本身就是一个匿名函数,如果要掉用currying返回的这个匿名函数,需要这样:
currying()();
第一个括号操作,表示调用currying本身,此时返回值为函数,第二个括号操作符调用这个返回值,结果为:
currying