1 函数
每个函数都是 Function 类型的实例,且都与其他引用类型一样具有属性和方法。由于函数式对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。
1.1 函数概述
1.1.1 函数定义
函数通常都是使用函数声明语法定义的,如下:
- 使用函数声明语法定义(必须有函数名):
function sum(num1, num2) {return num1 + num2;}
- 使用函数表达式定义(可以没有函数名):
var sum = function(num1, num2) {return num1 + num2;};
- 使用 Function 构造函数定义:
Function 构造函数可以接收任意数量的参数, 但后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。
var sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐
这种方式可以很直观理解“函数是对象,函数名是指针”。
从技术上角度讲,这是一个函数表达式。但这种语法会导致解析两次代码(解析常规 ECMAScript 代码,解析传入构造函数中的字符串),影响性能。因此,不推荐使用这种方法定义。由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字。使用带圆括号的函数名是调用函数,使用不带圆括号的函数名是访问函数指针,而非调用函数。
1.1.2 函数调用——functionName(arg0, arg1,…,argN);
sayHi("Nicholas", "how are you today?");输出结果:弹出 "Hello Nicholas,how are you today?"
1.1.3 函数返回值
任何函数、任何时候都可以通过 return 语句后跟要返回的值来是实现返回值。函数会在执行完 return 语句后停止并立即退出。因此,return 之后的任何代码都永远不会执行。
function sum(num1, num2) {
return num1 + num2;
}
调用:var result = sum(5, 10); // result 为15
注:return 可以不带人和返回值。则函数将返回 undefined 值。一般用在需要提前停止函数执行,又不需要返回值的情况下。推荐:要么让函数始终都有返回值,要么就永远都不要有返回值,否则会给代码调试带来不便。严格模式下的限制: 发生以下情况,会导致语法错误,代码无法执行。不能把函数/参数命名为eval 或arguments;不能出现两个命名参数同名的情况。
1.1.4 理解参数
ECMAScript 函数不介意传递的参数的个数及类型,即使个数与定义的个数不同,也不会报错。因为参数在内部使用一个数组来表示的。函数体内部可以通过 arguments 对象来访问这个参数数组,从而获取传递过来的每一个参数。
function sayHi(name, message) { alert("Hello " + name + "," + message);}
可以像下面这样重写
function sayHi() { alert("Hello " + arguments[0] + "," + arguments[1]);}
ECMAScript 函数的重要特点:
命名的参数只提供便利,但不是必需的;
在调用时,对应参数名字不一定要一致;
aruments 对象可以与命名参数一起使用;
arguments 的值永远与对应命名参数的值保持同步;
arguments 对象的长度由传入函数的参数决定,而非定义函数时的命名参数个数;
没有传递值的命名参数自动被赋予 undefined 值,类似于定义了变量但未初始化。
下面这个函数会在每次被调用时,输出传入其中的参数个数:
function howManyArgs() {
alert(arguments.length);
}
howManyArgs("string", 45); //2
howManyArgs(); //0
howManyArgs(12); //1
严格模式下:严格模式对如何使用 arguments 对象做出了一些限制。
首先,像前面例子中那样的赋值会变得无效。也就是说,即使把 arguments[1] 设置为 10,num2 的值仍然还是 undefined。其次,重写 arguments 的值会导致语法错误(代码将不会执行)。
1.2 没有重载(深入理解)
重载函数
重载函数是函数的一种特殊情况,在同一个作用域中,如果有多个函数的名字相同,但形参列表不同(参数类型不同或参数个数不同),返回值类型可同也可不同,我们称之为重载函数。
ECMAScript 函数不能重载
ECMAScript 函数不能像传统意义上那样实现重载。
如果在ECMAScript 中定义了两个名字相同的函数,则该名字只属于后定义的函数,后面的会覆盖前面的。通过检查传入函数中参数的类型和数量并作出不同的反应,可以模仿方法的重载。
1.3 函数声明与函数表达式
函数声明:率先读取函数声明,并使其在执行任何代码前可用(可访问);解析器会在代码开始执行之前,通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript 引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript 引擎也能把函数声明提升到顶部。
使用函数声明语法定义函数(必须有函数名):
alert(sum(10,10));
function sum(num1, num2) {
return num1 + num2;
}
函数表达式:等到解析器执行到它所在的代码行,才会真正被解释执行。使用函数表达式定义函数(可以没有函数名):
alert(sum(10,10));
var sum = function(num1, num2) {
return num1 + num2;
};
使用函数表达式定义函数,如上,若在定义前通过变量访问函数,会导致报错。因为在执行到函数所在的语句之前,变量 sum 中不会保存有对函数的引用;而且,第一行代码就会导致“unexpected identifier”(意外标识符)错误,代码并不会执行到下一行。
1.4 作为值的函数
因为 ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。即不仅可以像传递参数一样把一个函数传递给另一个函数,也可以将一个函数作为另一个函数的结果返回。如下:
//接受两个参数,第一个参数是一个函数,第二个是要传递给该函数的一个值
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
function add10(num){
return num + 10;
}
var result1 = callSomeFunction(add10, 10);
alert(result1); //20
function getGreeting(name){
return "Hello, " + name;
}
var result2 = callSomeFunction(getGreeting, "Nicholas");
alert(result2); //"Hello, Nicholas"
1.5 函数内部属性
函数内部有两个特殊的对象:arguments 和 this。
1.5.1 arguments
arguments 是一个类数组对象,包含着传入函数中的所有参数。arguments 有一个 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。下面是非常经典的阶乘函数:
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
} }
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //0
alert(factorial(5)); //0
alert(factorial(5)); //0
如上,若这个函数的执行与函数名 factorial 紧紧耦合在了一起,当函数名 factorial 重写后,会对函数执行有所影响,为了消除这种紧密耦合的现象,可以像下面这样使用 arguments.callee。
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //120
alert(factorial(5)); //0
alert(factorial(5)); //0
1.5.2 this
this 引用的是函数据以执行的环境对象。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red" 全局作用域调用,this 引用全局对象 window,this.color => window.color
o.sayColor = sayColor; o.sayColor(); //"blue" 函数赋给了对象 o,this 引用的是对象 o,this.color => o.color
注:函数名仅仅是一个包含指针的变量而已。因此,即使是在不同环境中执行,全局的 sayColor() 函数与 o.sayColor() 指向的仍是同一个函数。
1.5.3 caller
caller 属性中保存着调用当前函数的函数的引用。
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
以上代码,会导致警告框中显示 outer() 函数的源代码。因为 outer()调用了 inter(),所以 inner.caller 就指向 outer()。如果是在全局作用域中调用当前函数,它的值为 null。为了实现更松散的耦合,也可以通过 arguments.callee.caller 来访问相同的信息。
function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller);
}
outer();
注:严格模式下,访问 arguments.callee 会导致错误,不能为函数的 caller 属性赋值,否则也会导致错误。
1.6 函数属性和方法
ECMAScript 中的函数都是对象,因此函数也有属性和方法。每个函数都包含两个属性:length 和 prototype。
length:函数希望接收的命名参数的个数;
prototype:保存所有实例方法的真正所在。
1.6.1 length
function sayName(name){
alert(name);
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
alert("hi");
}
alert(sayName.length); //1
alert(sum.length); //2
alert(sayHi.length); //0
1.6.2 prototype
对于 ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。每个函数都包含两个非继承而来的方法:apply() 和 call()。
这两个方法的用途都是在特定的作用域中调用函数,实际上相当于设置函数体内 this 对象的值。
1.6.2.1 apply
apply() 方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是 arguments 对象。
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments); // 传入 arguments 对象 }
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]); // 传入数组
}
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
注:严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window。除非明确把函数添加到某个对象或者调用 apply() 或 call(),否则 this 值将是 undefined。
1.6.2.2 call
call() 方法与 apply() 方法作用相同,区别仅在于接收参数的方式不同。对于 call() 而言,第一个参数是 this 值,剩下的参数都直接传递给函数。即使用 call() 方法时,传递给函数的参数必须逐个列举出来。如下:
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20
使用 apply() 还是 call() ,完全取决于你才去哪种给函数传递参数的方式最方便。不传参数,则哪种方法都无所谓。事实上,传递参数并非 apply() 和 call() 真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red this=>window
sayColor.call(this); //red this=>window
sayColor.call(window); //red this=>window
sayColor.call(o); //blue this=>o
使用 call() 或 apply() 来扩充作用域的最大好处:对象不需要与方法有任何耦合关系。不需要像前面那样把要用的函数放到对象中,调用。
1.6.2.3 bind
这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind() 函数的值。如:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
这里,sayColor()调用 bind()并传入对象 o,创建了 objectSayColor()函数。
object- SayColor()函数的 this 值等于 o,因此即使是在全局作用域中调用这个函数,也会看到”blue”。