以前我们总认为函数是一段代码,是程序部分,从来认为它们应该和if else等那样存储在程序代码中。没有考虑过它也可以像存储数据的方式那样,作为Object的子类,来存储。
Javascript中什么都是对象,给了一个保存数据的统一模板。发现函数也可以套用到这个模板中,来进行保存和调用
所以JavaScript中的函数不但有函数的可重复调用的一个完成特定功能的工具,同时它也有对象的可以赋给变量,可以作为变量等对象的功能
图 函数是对象的子类
函数是一段代码,它只定义一次,但可以被执行或调用任意次。
在 JavaScript 里,函数即对象,程序可以随意操控它们。
比如,可以把函数赋值给变量,
或者作为参数传递给其他函数,
也可以给它们设置属性,
甚至调用它们的方法。
如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。
如果函数嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量。
函数定义
在 JavaScript 中,函数实际上是对象,每个函数都是 Function 构造函数的实例,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常有以下3中定义方式。例如:
// 写法一:函数声明(推荐写法)
function sum (num1, num2) {
return num1 + num2;
}
// 写法二:函数表达式(推荐写法)
var sum = function(num1, num2){
return num1 + num2;
};
// 写法三:Function 构造函数(不推荐写法)
var sum = new Function("num1", "num2", "return num1 + num2");
由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字。例如:
function sum(num1, num2){
return num1 + num2;
}
console.log(sum(10,10)); // 20
var anotherSum = sum;
console.log(anotherSum(10,10)); // 20
sum = null;
console.log(anotherSum(10,10)); // 20
没有重载
将函数名想象为指针,也有助于理解为什么 JavaScript 中没有函数重载的概念。
function addSomeNumber(num){
return num + 100;
}
function addSomeNumber(num) {
return num + 200;
}
var result = addSomeNumber(100); // 300
显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。以上代码实际上与下面的代码没有什么区别。
函数声明与表达式
解析器在向执行环境中加载数据时,对「函数声明」和「函数表达式」并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
alert(sum(10,10)); // 20,不会报错
function sum(num1, num2){
return num1 + num2;
}
alert(sum1(12,12)); // 会报错,uncaught TypeError:sum1 is not function
var sum1=function(num1, num2){
return num1 + num2;
}
</script>
</head>
<body>
</body>
</html>
**说明:**第一段代码正常执行,因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码的后面,JavaScript引擎也能把函数声明提升到顶部。
第二段代码出错,原因在于函数位于一个初始化语句中,而不是一个函数声明。也就是说,在执行到函数所在的语句之前,sum1并不会保存有对函数的引用。
作为值的函数
因为 JavaScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。来看一看下面的函数。
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
这个函数接受两个参数。第一个参数应该是一个函数,第二个参数应该是要传递给该函数的一个值。然后,就可以像下面的例子一样传递函数了。
function add10(num){
return num + 10;
}
var result1 = callSomeFunction(add10, 10);
console.log(result1); // 20
function getGreeting(name){
return "Hello, " + name;
}
var result2 = callSomeFunction(getGreeting, "Nicholas");
console.log(result2); // "Hello, Nicholas"
这里的 callSomeFunction() 函数是通用的,即无论第一个参数中传递进来的是什么函数,它都会返回执行第一个参数后的结果。要访问函数的指针而不执行函数的话,必须去掉函数名后面的那对圆括号。因此上面例子中传递给 callSomeFunction() 的是 add10 和 getGreeting,而不是执行它们之后的结果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
function add10(num){
return num + 10;
}
var result1 = callSomeFunction(add10, 10);
console.log(result1); // 20
function getGreeting(name){
return "Hello, " + name;
}
var result2 = callSomeFunction(getGreeting, "Nicholas");
console.log(result2); // "Hello, Nicholas"
</script>
</head>
<body>
</body>
</html>
调试F9一步一步调试演示。
作为返回值的函数,例子:(获取一个根据指定属性名排序的函数)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function getComparisonFunction(propertyName){
return function(object1,object2){
var value1 = object1[propertyName];//使用[]可根据变量访问属性
var value2 = object2[propertyName];
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
}
var data = [{name:"zhangsan",age:25},{name:"lisi",age:26}];
data.sort(getComparisonFunction("name"));//根据name排序,lisi,zhangsan
console.log(data);
data.sort(getComparisonFunction("age"));//根据age排序,zhangsan,lisi
console.log(data);
</script>
</head>
<body>
</body>
</html>
函数内部属性
函数内部有两个特殊的对象,arguments(类数组对象)和this:arguments包含着传入函数中的所有参数。虽然arguments对象的主要用途是保存函数参数,但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。
经典的阶乘函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function factorial(num){
if (num <= 1) {
return 1;
} else {
return num * factorial(num-1)
}
}
//使用callee改造
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}
var trueFactorial = factorial;//让trueFactorial指向阶乘函数
factorial = function(){//解除factorial与阶乘函数的关联
return 0;
};
console.log(trueFactorial(5)); // 120
console.log(factorial(5)); // 0
</script>
</head>
<body>
</body>
</html>
this引用的是函数据以执行的环境对象,当在全局作用域中调用函数时,this对象引用的就是window
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
window.color = "red";
var o = { color: "blue" };
function sayColor(){
console.log(this.color);
}
sayColor(); // "red"
o.sayColor = sayColor;
o.sayColor(); // "blue"
</script>
</head>
<body>
</body>
</html>
ECMAScript5中也规范化了另一个函数对象的属性:caller。这个属性中保存着调用当前函数的函数的引用(如果在全局作用域中调用,它的值为null)
函数属性和方法
- length:表示函数希望接收的命名参数的个数
- prototype:对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。toString()和valueOf()等方法都保存在prototype名下,只不过是通过各自对象的实例访问罢了。prototype是不可枚举的,无法使用for-in发现。
每个函数都包含两个非继承而来的方法:apply()和call()。用途就是在特定的作用域中调用函数,相当于设置函数体内this对象的值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function sayName(name){
console.log(name);
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
console.log("hi");
}
console.log(sayName.length); // 1
console.log(sum.length); // 2
console.log(sayHi.length); // 0
</script>
</head>
<body>
</body>
</html>
- apply():接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组(可以是Array的实例,也可以是arguments对象),如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
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]); // 传入数组
}
console.log(callSum1(10,10)); // 20
console.log(callSum2(10,10)); // 20
</script>
</head>
<body>
</body>
</html>
- call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。call()方法接收参数时必须逐个列出来(明确传入的每个参数)
使用apply()与call()方法扩充函数赖以运行的作用域。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
window.color = "red";
var o = { color: "blue" };
function sayColor(){
console.log(this.color);
}
sayColor(); // red
sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(o); // blue
</script>
</head>
<body>
</body>
</html>
注意:使用call()或apply()来扩充作用域的好处,就是对象与方法不需要有任何耦合
- bind():创建函数的实例,this指向传给bind()函数的值。