标签:
(1)function命令
函数就是使用function命令命名的代码区块,便于反复调用。这种声明方式叫做函数的声明(Function Declaration)。
function print(){ // ... }
(2)函数表达式
除了用function命令声明函数,还可以采用变量赋值的写法。
var print = function (){ // ... };
这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(Function Expression),因为赋值语句的等号右侧只能放表达式。采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名将成为函数内部的一个局部变量,只在函数体内部有效,在函数体外部无效。
var print = function x(){ console.log(typeof x); }; x // ReferenceError: x is not defined print() // function
(3)Function构造函数
还有第三种声明函数的方式:通过Function构造函数声明。如果只有一个参数,该参数就是函数体。
var add = new Function("x","y","return (x+y)"); // 相当于定义了如下函数 // function add(x, y) { // return (x+y); // }
JavaScript引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return语句所带的那个表达式,就是函数的返回值。return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined。
JavaScript的函数与其他数据类型处于同等地位,可以使用其他数据类型的地方就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。这表示函数与其他数据类型的地方是平等,所以又称函数为第一等公民。
function add(x,y){ return x+y; } // 将函数赋值给一个变量 var operator = add; // 将函数作为参数和返回值 function a(op){ return op; } a(add)(1,1) // 2
JavaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会被提升到代码头部。所以,下面的代码不会报错:
f(); function f(){}
表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript就会报错:
f(); var f = function (){}; // TypeError: undefined is not a function
上面的代码等同于:
var f; f(); f = function (){};
当调用f的时候,f只是被声明,还没有被赋值,等于undefined,所以会报错。因此,如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。
var f = function() { console.log (‘1‘); } function f() { console.log(‘2‘); } f() // 1
函数声明语句并非真正的语句,ECMAScript规范只是允许他们作为顶级语句。它们可以出现在全局代码里,或者内嵌在其他函数里,但它们不能出现在循环、条件判断、或者try/catch/finally以及with语句中。
大多数JavaScript引擎支持非标准的name属性。该属性返回紧跟在function关键字之后的那个函数名。
function f1() {} f1.name // ‘f1‘ var f2 = function () {}; f2.name // ‘‘ var f3 = function myName() {}; f3.name // ‘myName‘
上面代码中,函数的name属性总是返回紧跟在function关键字之后的那个函数名。对于f2来说,返回空字符串,对于f3来说,返回函数表达式的名字(真正的函数名还是f3,myName这个名字只在函数体内部可用)。
所有函数都有一个length属性,返回函数定义中参数的个数。length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的”方法重载“(overload)。
function f(a,b) {} f.length // 2
上面代码定义了空函数f,它的length属性就是定义时参数的个数。不管调用时输入了多少个参数,length属性始终等于2。
参数不是必需的,Javascript语言允许省略参数。
function f(a,b){ return a; } f(1,2,3) // 1 f(1) // 1 f() // undefined f.length // 2
上面代码的函数f定义了两个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript都不会报错。被省略的参数的值就变为undefined。需要注意的是,函数的length属性与实际传入的参数个数无关,只反映定义时的参数个数。
但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。
JavaScript的函数参数传递方式是传值传递(passes by value),这意味着,在函数体内修改参数值,不会影响到函数外部。
// 修改原始类型的参数值 var p = 2; function f(p){ p = 3; } f(p); p // 2 // 修改复合类型的参数值 var o = [1,2,3]; function f(o){ o = [2,3,4]; } f(o); o // [1, 2, 3]
上面代码分成两段,分别修改原始类型的参数值和复合类型的参数值。两种情况下,函数内部修改参数值,都不会影响到函数外部。
需要十分注意的是,虽然参数本身是传值传递,但是对于复合类型的变量来说,属性值是传址传递(pass by reference),也就是说,属性值是通过地址读取的。所以在函数体内修改复合类型变量的属性值,会影响到函数外部。
// 修改对象的属性值 var o = { p:1 }; function f(obj){ obj.p = 2; } f(o); o.p // 2 // 修改数组的属性值 var a = [1,2,3]; function f(a){ a[0]=4; } f(a); a // [4,2,3]
上面代码在函数体内,分别修改对象和数组的属性值,结果都影响到了函数外部,这证明复合类型变量的属性值是传址传递。
某些情况下,如果需要对某个变量达到传址传递的效果,可以将它写成全局对象的属性。
var a = 1; function f(p){ window[p]=2; } f(‘a‘); //变量a本来是传值传递,但是写成window对象的属性,就达到了传址传递的效果。 a // 2
ECMAScript函数不介意传递进来多少个参数,也不在乎传进来的参数是什么类型的,原因是在ECMAScript中的参数是用一个数组表示的,这个数字就是arguments。arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,依次类推。这个对象只有在函数体内部,才可以使用。
var f = function(one) { console.log(arguments[0]); console.log(arguments[1]); console.log(arguments[2]); } f(1, 2, 3) // 1 // 2 // 3
arguments对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)。可以通过arguments对象的length属性,判断函数调用时到底带几个参数。
(1)与数组的关系
需要注意的是,虽然arguments很像数组,但它是一个对象。某些用于数组的方法(比如slice和forEach方法),不能在arguments对象上使用。但是,有时arguments可以像数组一样,用在某些只用于数组的方法。比如,用在apply方法中,或使用concat方法完成数组合并。
// 用于apply方法 myfunction.apply(obj, arguments). // 使用与另一个数组合并 Array.prototype.concat.apply([1,2,3], arguments)
(2)caller和callee
arguments对象带有一个callee属性,返回它所对应的原函数。
函数的内部属性arguments和this
标签:
原文地址:http://www.cnblogs.com/borage/p/4419454.html