Function()对象概要

函数是代码语句的容器,可以使用圆括号操作符()来调用。调用函数时,参数可以在圆括号内传递,以便函数中的语句可以访问这些特定值。

如下代码创建了两个版本的addNumbers函数对象,一个使用new操作符,另一个使用更常见的字面量模式。两者都需要两个参数。两种函数在调用时,都可以在圆括号操作符()中传递参数。

1
2
3
4
5
6
7
8
var addNumbersA = new Function('num1', 'num2', 'return num1 + num2');
console.log(addNumbersA(2, 3)); //output 5

// 也可以使用字面量模式,这种方式更常用
var addNumbersB = function(num1, num2){
return num1 + num2;
}
console.log(addNumbersA(2, 2)); //output 4

函数可用于返回值、构建对象,或者作为一种机制简单的运行代码。Javascript的函数有很多种用途,但从最基本的形式来说,函数只是可执行语句的唯一作用域。

Function()的参数

Function()构造函数采用不定数量的参数,但Function()构造函数的最后一个参数是一个包含具有函数体语句的字符串。在最后一个参数之前传递给构造函数的任何参数都可用于创建函数。也可以将多个参数放在一起以逗号分隔形成字符串进行传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var addFunction = new Function('num1', 'num2', 'return num1 + num2');

// 同样,所有参数放在一起用逗号隔开形成字符串作为第一个参数,第二个参数为函数体
var timesFunction = new Function('num1,num2', 'return num1 * num2');

console.log(addFunction(2, 2), timesFunction(2, 3));

// 与此同时,还可以使用表示形式初始化函数的方式,这种形式更常用
var addFunction2 = function(num1, num2){
return num1 + num2;
}

// 语句形式
function addFunction3(num1,num2){
return num1 + num2;
}

注意:

  • 不推荐或通常不直接利用Function()构造函数,因为Javascript将使用eval()来解析包含函数逻辑的字符串。很多人认为不必要使用eval(),因为如果使用它,很有可能出现代码设计缺陷。
  • 使用带new关键字的Function()构造函数创建函数对象,与仅使用构造函数创建函数对象具有先攻的效果,如new Function('x', 'return x')function('x', 'return x')
  • 直接使用Function()构造函数时没有创建闭包。

函数总有返回值

由于可以创建一个函数以简单的执行代码语句,因此函数返回一个值也是很常见的。

1
2
3
4
var sayHi = function(){
return 'Hi';
}
console.log(sayHi()); // output Hi

如果函数没有指定返回值,则会返回undefined。下面代码调用yelp函数,它将字符串'yelp'打印至控制台,切不会显示返回值。

1
2
3
4
5
6
7
8
9
10
11
12
var sayHi = function(){
return 'Hi';
}
console.log(sayHi()); // output Hi


var yelp = function(){
// 如果不声明返回值,则返回undefined
console.log('I am yelping!');
}
// output true
console.log(yelp() === undefined);

这里的重点是,即使没有显示地提供要返回的值,所有函数也都返回了一个值。如果不指定要返回的值,则返回的值是undefined

函数是一等公民——不仅语法,还有值

在Javascript中,函数是对象。这意味着函数可以存储在一个变量、数组或对象中。同时,函数还可以传递给函数,并由函数返回。函数拥有属性,因为他是一个对象。所有这些因素是函数在Javascript中成为了一等公民 —— first-class citizen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 函数可以保存在变量(funcA)、数组(funcB)和对象(funcC)中
var funcA = function(){}; // 调用方式: funcA()
var funcB = [function(){}]; // 调用方式: funcB[0]()
var funcC = { method: function(){ } }; // 调用方式: funcC.method() 或者 funcC['method']()

// 函数可以作为参数传递,也可以作为返回值返回
var funcD = function(func){
return func;
}

var runFuncPassedToFuncD = funcD(function(){
console.log('Hi');
});

runFuncPassedToFuncD();

// 函数是对象,也就意味着函数也拥有属性
var funcE = function(){ };
funcE.answer = 'yup'; // 实例化属性
console.log(funcE.answer); // output yup

至关重要的是要意识到函数是一个对象,因此函数也是一个值。它可以像Javascript中的其他表达式那样被传递或增加。

函数的参数传递

在调用函数时,参数是将值传递给函数作用域的工具。

1
2
3
4
5
6
var addFunction = function(number1, number2){
var sum = number1 + number2;
return sum;
}

console.log(addFunction(2, 6)); // output 8

注意

  • 与其他编程语言相比,在Javascript中省略参数是完全合法的,即使已定义了接收这些参数的函数。只是为省略的参数赋予了undefined值。当然,省略参数后函数有可能无法正常工作。
  • 如果向函数传递意想不到的参数(那些在创建函数时没有被定义的参数),则不会发生任何错误。而且可以从arguments对象访问这些参数,arguments对象对所有的函数都是可用的。

this和arguments适用于所有函数

在所有函数的作用域/函数体内,thisarguments值都是可用的。

arguments对象是一种类数组的对象,它包含所有传递给函数的参数。在下列代码中,即使在定义函数时不指定参数,在调用时如果还是发送参数,我们也可以依靠传递给函数的arguments数组来访问参数。

1
2
3
4
5
var add = function(){
return arguments[0] + arguments[1];
};

console.log(add(3, 5)); // output 8

传递给所有函数的this关键字都是对包含函数的对象的引用。如您所料,作为属性包含在对象内的函数(即方法),也是可以使用this来获得对“父”对象的引用。当函数在全局作用域中定义时,this值是全局对象。

1
2
3
4
5
6
7
8
9
10
var myObject1 = {
name: 'myObject1',
myMethod: function(){ console.log(this); }
};

myObject1.myMethod(); // output 'myObject1'

var myObject2 = function(){ console.log(this); }

myObject2(); // output 'window对象'

arguments.callee属性

arguments对象拥有名为callee的属性,它是对当前执行函数的引用。该属性可以用于从函数的作用域内引用函数(如arguments.callee)——自我引用。

1
2
3
4
var foo = function foo() {
console.log(arguments.callee); // output
// callee 可以用于对foo函数进行递归调用(例如,arguments.callee())
}();

当函数需要递归调用时,它非常有用。

函数实例的length属性和arguments.length

arguments对象有一个独特的length属性。大家可能会认为这个length属性将提供已定义参数的数量,实际上,它给出的是在调用时发送给函数的参数数量。

1
2
3
4
5
var myFunction = function(z, s, d){
return arguments.length;
};

console.log(myFunction()); // output 0 因为没有传入任何参数

通过使用Function()实例的length属性,实际上可以获得函数所需要的参数总数量。

1
2
3
4
5
var myFunction = function(z, s, d, e, r, m, q){
return myFunction.length;
};

console.log(myFunction()); // output 7

注意

自Javascript 1.4 开始,arguments.length属性就被废弃掉了,可以从函数对象的length属性获取发送给函数的参数数量。
因此,接下来可以通过利用callee属性获得长度值,首先获得对被调用函数的引用(即arguments.callee.length)。

重定义函数参数

函数的参数可以直接在函数内部或通过使用arguments数组进行重新定义。

1
2
3
4
5
6
7
8
9
10
var foo = false;
var bar = false;

var myFunction = function(foo, bar){
arguments[0] = true;
bar = true;
console.log(arguments[0], bar); // output true true
};

myFunction();

请注意,可以使用arguments索引或直接为参数再指定一个新值来重新定义bar参数的值。

代码执行完成前取消函数执行

可以通过返回有值或无值的return关键字在调用时随时取消函数执行。下面代码中如果参数是undefined或非数字,则取消执行add函数。

1
2
3
4
5
6
7
8
9
10
var add = function(x, y){
// 如果参数不是数字,返回错误
if(typeof x !== 'number' || typeof y !== 'number'){
return 'pass in numbers';
}
return x + y;
}

console.log(add(3, 4)); // output 7
console.log(add('2', '2')); // output 'pass in numbers'

这里的重点是,可以在函数执行的任意点上通过使用return关键字来取消函数的执行。

定义函数

函数的定义有三种不同的方式:函数构造函数函数语句函数表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 函数构造函数:最后一个参数是函数逻辑,之前的都是参数
var addConstructor = new Function('x', 'y', 'return x + y');

// 函数语句
function addStatement(x, y){
return x + y;
}

// 函数表达式
var addExpression = function(x, y){
return x + y;
}

console.log(addConstructor(2, 2), addStatement(2, 3), addExpression(2, 4));

注意:

  • 有些人说,第四种定义函数的方式被称为“命名函数表达式”。命名函数表达式只是一个包含名称的函数表达式(如 var add = function add(x, y){return x + y;})。

##调用函数
使用4中不同的场景或模式调用函数。

  • 作为函数
  • 作为方法
  • 作为构造函数
  • 使用apply()call()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 函数模式
var myFunction = function(){ return 'foo';}
console.log(myFunction()); // output 'foo'

// 方法模式
var myObject = { myFunction: function(){ return 'bar'; }}
console.log(myObject.myFunction()); // output 'bar'

// 构造函数模式
var Cody = function(){
this.living = true,
this.age = 33,
this.gender = 'male',
this.getGender = function(){ return this.gender };
}
var cody = new Cody(); // 通过Cody构造函数调用
console.log(cody); // output cody对象和属性

// apply() 和 call() 模式
var greet = {
runGreet: function(){
console.log(this.name, arguments[0], arguments[1]);
}
}

var cody = { name: 'cody' };
var lisa = { name: 'lisa' };

// 在 cody 对象上调用 runGreet 函数
greet.runGreet.call(cody, 'foo', 'bar'); // output 'cody foo bar'

// 在 lisa 对象上调用 runGreet 函数
greet.runGreet.apply(lisa, ['foo', 'bar']); // output 'lisa foo bar'

/* 注意 call() 和 apply() 之间的区别是函数调用时,参数传递的不同,
* 前者传递多个分开的参数,后者传递多个参数组成的数组
*/

要理解这4中调用模式,因为以后遇到的代码可能会用到其中一种模式。

匿名函数

匿名函数是一种没有给出标示符的函数。匿名函数主要用于将函数作为参数传递给另一个函数。

1
2
3
4
5
6
7
8
9
10
11
// function(){console.log('hi');}; // 匿名函数,但无法调用

// 创建一个函数来执行匿名函数
var sayHi = function(f){
f();
}

// 将匿名函数作为参数传递
sayHi(function(){
console.log('hi'); // output 'hi'
});

自调用的函数表达式

通过圆括号操作符可以在定义函数表达式后立即调用该函数表达式(除了由Function()构造函数创建的函数)。下面创建了一个sayWord()函数表达式,然后立即调用该函数。这就是自调用函数。

1
2
// output 'hello world.'
var sayWord = function () { console.log('hello world.'); }();

自调用的匿名函数语句

可以创建一个自调用的匿名函数语句,这叫做自调用匿名函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 最经常使用的匿名函数
(function (msg) {
console.log(msg);
} )('Hi');

// 看起来有点不一样,但效果一样
(function(msg){
console.log(msg);
}('Hi 1'));

// 更简短的方式
!function(msg){ console.log(msg); }('Hi 2');

// 注意,下面的代码报错:Uncaught SyntaxError: Unexpected token )
function sayHi(){ console.log('Hi');}()

注意:

  • 根据ECMAScript标准,如果要立即调用函数,需要使用函数外面的圆括号(或任何将函数转化为表达式的符号)。

函数可以嵌套

函数可以无限地嵌套在其他函数内部。在如下代码中,在bar函数内部封装了goo函数,同时bar函数位于foo函数内部。

1
2
3
4
5
6
7
var foo = function(){
var bar = function(){
var goo = function(){
console.log(this); // ouput window 对象
}();
}();
}();

这里的要点是,函数是可以嵌套的,并且嵌套的深度是没有限制的。

注意:

  • 请记住,嵌套函数的this值在ECMAScript3、Javascript 1.5中是head对象(如在web浏览器中是window对象)。

给函数传递函数,从函数返回函数

如上所述,函数在Javascript世界中是“一等公民”。由于函数是一个值,并且可以将任何类型的值传递给函数,因此函数可以被传递给函数。函数可以接收/返回其他函数。它们有时被称为“高阶函数”。

如下代码,将一个匿名函数传递给foo函数,它是稍后立即从foo函数返回的函数。这个匿名函数是bar变量指向的函数,foo函数先接收,然后返回该匿名函数。

1
2
3
4
5
6
7
8
// 函数可以传入,也可以传出
var foo = function(f){
return f;
}

var bar = foo(function(){ console.log('Hi'); });

bar(); // outpu 'Hi'

因此,当bar被调用时它调用传递给foo()匿名函数,然后从foo()函数传回,并从bar变量引用。所有这些都是在展示,函数可以像任何其他的值一样被传递。

函数定义之前调用(函数提升)

在真正定义函数语句之前,可以在执行时调用该函数语句。这有点奇怪,但你应该了解它,这样你就可以利用它,或者至少知道遇到它时会发生什么事情。

1
2
3
4
5
6
7
8
9
10
// 示例 1
var speak = function(){
sayYo(); // sayYo() 还没有被定义,但依然可以执行,并输出 'yo'
function sayYo(){ console.log('yo'); }
}(); // invoke


// 示例 2
console.log(sum(2, 3)); // sum() 没定义之前,被调用,依然可以执行
function sum(x, y){ return x + y; }

上述情况发生的原因是,在运行代码之前,函数语句其实已经被编译器解释,并添加至执行堆栈/上下文。在使用函数语句时要确保自己理解这一点。

注意

被定义为“函数表达式”的函数没有被提升,只有“函数语句”被提升。

函数可以调用自身(递归)

函数调用自身完全是合法的。事实上,这是非常有名的编码模式。在如下代码中,

1
2
3
4
5
6
7
8
9
var coundDownFrom = function coundDownFrom(num){
console.log(num);
num--; //改变参数值
if(num < 0){ return false; } // 如果 num < 0 ,停止递归
// 如果是匿名函数的话,也可以使用 arguments.callee(num) 进行递归
coundDownFrom(num);
};

coundDownFrom(5); // 调用函数,输出 5,4,3,2,1,0