JavaScript | 函数
一、函数定义
JavaScript 使用关键字 function 定义函数。函数可以通过声明定义,也可以是一个表达式。
1. 普通函数
用function来定义
function functionName(parameters) { 执行的代码 }
分号是用来分隔可执行JavaScript语句。由于函数声明不是一个可执行语句,所以不以分号结束
function add(a,b){ return a+b; } add(1,2);
2. 函数表达式
JavaScript 函数可以通过一个表达式定义。函数存储在变量中,不需要函数名称,通常通过变量名来调用。
函数表达式可以存储在变量中:
var x = function (a, b) {return a * b};
在函数表达式存储在变量后,变量也可作为一个函数使用:
var x = function (a, b) {return a * b}; var z = x(4, 3);
以上函数实际上是一个 匿名函数 (函数没有名称)。上述函数以分号结尾,因为它是一个执行语句。
3. Function() 构造函数
在以上实例中,我们了解到函数通过关键字 function 定义。函数同样可以通过内置的 JavaScript 函数构造器(Function())定义。
var myFunction = new Function("a", "b", "return a * b"); var x = myFunction(4, 3);
实际上,你不必使用构造函数。上面实例可以用匿名函数写成:
var myFunction = function (a, b) {return a * b}; var x = myFunction(4, 3);
在 JavaScript 中,很多时候,你需要避免使用 new 关键字。
4. 自调用函数
函数表达式可以 "自调用"。如果表达式后面紧跟 () ,则会自动调用。
通过添加括号,来说明它是一个函数表达式:
(function () { var x = "Hello!!"; // 我将调用自己 })();
以上函数实际上是一个 匿名自我调用的函数 (没有函数名)。
例如:
<p id="demo"></p> <script> (function(){document.getElementById("demo").innerHTML = "hello kity";})() </script>
对于函数自调用,必须通过把函数表达式外面添加括号(来说明它是一个函数表达式)再调用,否则会报错,如下实例:
function () { document.write( "Hello! 我是自己调用的" );}(); // 报错:Uncaught SyntaxError: Unexpected token (
正确写法:
(function () { document.write( "Hello! 我是自己调用的" );})();
现在我们知道函数表达式后面紧跟 () 会自动调用,但是如果把函数表达式赋给一个变量则不需要添加括号也可以直接调用,下方的代码中,函数表达式的主体部分会执行,并且会返回一个字符串给 a:
var a = function () { document.write("Hello! 我是自己调用的" + "<br />"); return '返回的东西'; }(); // 因为函数自调了,变量a不再指向一个函数,而是“返回的东西”这个字符串 document.write(a); //输出字符串
补充一下 return 的知识(与后面的闭包一节有联系!):
var add = (function () { var counter = 0; return function () {return counter += 1;} //这里return了一个内嵌方法,即add指向一个方法 })();
上面的 return 为什么不是返回一个数值呢?不懂的请学习闭包一节。
x=x+5; return x;
可以简化为:
return x=x+5;
补充
函数提升(Hoisting)
提升(Hoisting)是 JavaScript 默认将当前作用域提升到前面去的的行为。 提升(Hoisting)应用在变量的声明与函数的声明。因此,函数可以在声明之前调用:
myFunction(5); function myFunction(y) { return y * y; }
使用表达式定义函数时无法提升。
JavaScript 函数作为一个值或表达式使用
JavaScript 函数作为一个值使用:
function myFunction(a, b) { return a * b; } var x = myFunction(4, 3);
完整示例
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>函数可作为一个值:</p> <p>x = myFunction(4,3) 或 x = 12</p> <p>两种情况下,x 的值都为 12。</p> <p id="demo"></p> <script> function myFunction(a, b) { return a * b; } var x = myFunction(4, 3); document.getElementById("demo").innerHTML = x; </script> </body>

JavaScript 函数可作为表达式使用:
function myFunction(a, b) { return a * b; } var x = myFunction(4, 3) * 2;
函数是对象
在 JavaScript 中使用 typeof 操作符判断函数类型将返回 "function" 。但是JavaScript 函数描述为一个对象更加准确。对象就会有自己的有 属性 和 方法。
arguments.length 属性返回函数调用过程接收到的参数个数:
function myFunction(a, b) { return arguments.length; }
toString() 方法将函数作为一个字符串返回:
function myFunction(a, b) { return a * b; } var txt = myFunction.toString();
完整代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p> toString() 将函数作为一个字符串返回:</p> <p id="demo"></p> <script> function myFunction(a, b) { return a * b; } document.getElementById("demo").innerHTML = myFunction.toString(); </script> </body> </html>

函数定义作为对象的属性,称之为对象方法。函数如果用于创建新的对象,称之为对象的构造函数。
箭头函数
ES6 新增了箭头函数。箭头函数表达式的语法比普通函数表达式更简洁。
(参数1, 参数2, …, 参数N) => { 函数声明 } (参数1, 参数2, …, 参数N) => 表达式(单一) // 相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }
当只有一个参数时,圆括号是可选的:
(单一参数) => {函数声明}
单一参数 => {函数声明}
没有参数的函数应该写成一对圆括号:
() => {函数声明}
实例
// ES5 var x = function(x, y) { return x * y; } // ES6 const x = (x, y) => x * y;
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <h2>JavaScript 箭头函数</h2> <p>箭头函数不需要使用 function、return 关键字及大括号 {}。</p> <p>IE11 及更早 IE 版本不支持箭头函数。</p> <p id="demo"></p> <script> const x = (x, y) => x * y; document.getElementById("demo").innerHTML = x(5, 5); </script>
有的箭头函数都没有自己的 this。 不适合顶一个 对象的方法。当我们使用箭头函数的时候,箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的。箭头函数是不能提升的,所以需要在使用之前定义。使用 const 比使用 var 更安全,因为函数表达式始终是一个常量。
如果函数部分只是一个语句,则可以省略 return 关键字和大括号 {},这样做是一个比较好的习惯:
const x = (x, y) => { return x * y };
注意:IE11 及更早 IE 版本不支持箭头函数。
二、JavaScript 函数参数
JavaScript 函数对参数的值没有进行任何的检查。
函数显式参数(Parameters)与隐式参数(Arguments)
- 函数显式参数在函数定义时列出。
- 函数隐式参数在函数调用时传递给函数真正的值。
参数规则
- JavaScript 函数定义显式参数时没有指定数据类型。
- JavaScript 函数对隐式参数没有进行类型检测。
- JavaScript 函数对隐式参数的个数没有进行检测。
默认参数
ES5 中如果函数在调用时未提供隐式参数,参数会默认设置为: undefined。有时这是可以接受的,但是建议最好为参数设置一个默认值:
function myFunction(x, y) { if (y === undefined) { y = 0; } }
完整代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>设置参数的默认值。</p> <p id="demo"></p> <script> function myFunction(x, y) { if (y === undefined) { y = 0; } return x * y; } document.getElementById("demo").innerHTML = myFunction(4); </script> </body> </html>
输出:0
或者,更简单的方式(ES5)
function myFunction(x, y) { y = y || 0; }
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>设置函数参数默认值。</p> <p id="demo"></p> <script> function myFunction(x, y) { y = y || 0; return x * y; } document.getElementById("demo").innerHTML = myFunction(4); </script> </body> </html>

如果函数调用时设置了过多的参数,参数将无法被引用,因为无法找到对应的参数名。 只能使用 arguments 对象来调用。
ES6 函数可以自带参数
ES6 支持函数带有默认参数,就判断 undefined 和 || 的操作:
实例(ES6)
function myFunction(x, y = 10) { // y is 10 if not passed or undefined return x + y; } myFunction(0, 2) // 输出 2 myFunction(5); // 输出 15, y 参数的默认值
完整代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>设置参数的默认值。</p> <p id="demo1"></p> <p id="demo2"></p> <script> function myFunction(x, y = 10) { // 如果不传入参数 y ,则其默认值为 10 return x + y; } // 输出 2 document.getElementById("demo1").innerHTML = myFunction(0, 2) ; // 输出 15, y 参数的默认值 document.getElementById("demo2").innerHTML = myFunction(5); </script> </body> </html>

arguments 对象
JavaScript 函数有个内置的对象 arguments 对象。argument 对象包含了函数调用的参数数组。
通过这种方式你可以很方便的找到最大的一个参数的值:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>查找最大的数。</p> <p id="demo"></p> <script> x = findMax(1, 123, 500, 115, 44, 88); function findMax() { var i, max = arguments[0]; if(arguments.length < 2) return max; for (i = 0; i < arguments.length; i++) { if (arguments[i] > max) { max = arguments[i]; } } return max; } document.getElementById("demo").innerHTML = x; </script> </body> </html>

创建一个函数用来统计所有数值的和:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>计算所有参数之和:</p> <p id="demo"></p> <script> function sumAll() { var i, sum = 0; for(i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } document.getElementById("demo").innerHTML = sumAll(1, 123, 500, 115, 44, 88); </script> </body> </html>

通过值传递参数
函数调用的参数是函数的隐式参数。JavaScript 隐式参数通过值来传递:函数仅仅只是获取值。如果函数修改参数的值,不会修改显式参数的初始值(在函数外定义)。隐式参数的改变在函数外是不可见的。
通过对象传递参数
在JavaScript中,可以引用对象的值。因此我们在函数内部修改对象的属性就会修改其初始的值。修改对象属性可作用于函数外部(全局变量)。修改对象属性在函数外是可见的。
三、JavaScript 函数调用
JavaScript 函数有 4 种调用方式。每种方式的不同在于 this 的初始化。
this 关键字
一般而言,在Javascript中,this指向函数执行时的当前对象。注意 this 是保留关键字,你不能修改 this 的值。
- this代表函数运行时,自动生成的一个内部对象,所以,只能在函数内部使用。
随着函数使用场合的不同,this 的值会发生变化。但是有一个总的原则,那就是this指的是,调用函数的那个对象。
this 的多种指向:
- 1、在对象方法中, this 指向调用它所在方法的对象。
- 2、单独使用 this,它指向全局(Global)对象。
- 3、函数使用中,this 指向函数的所属者。
- 4、严格模式下函数是没有绑定到 this 上,这时候 this 是 undefined。
- 5、在 HTML 事件句柄中,this 指向了接收事件的 HTML 元素。
- 6、apply 和 call 允许切换函数执行的上下文环境(context),即 this 绑定的对象,可以将 this 引用到任何对象。
方法中的 this
在对象方法中, this 指向调用它所在方法的对象。
var person = { firstName: "John", lastName : "Doe", id : 5566, fullName : function() { return this.firstName + " " + this.lastName; } };
在上面一个实例中,this 表示 person 对象。fullName 方法所属的对象就是 person。
单独使用 this
单独使用 this,则它指向全局(Global)对象。
在浏览器中,window 就是该全局对象为 [object Window]:
var x = this;
严格模式下,如果单独使用,this 也是指向全局(Global)对象。
"use strict"; var x = this;
函数中使用 this(默认)
在函数中,函数的所属者默认绑定到 this 上。在浏览器中,window 就是该全局对象为 [object Window]:
function myFunction() { return this; }
函数中使用 this(严格模式)
严格模式下函数是没有绑定到 this 上,这时候 this 是 undefined。
"use strict"; function myFunction() { return this; }
事件中的 this
在 HTML 事件句柄中,this 指向了接收事件的 HTML 元素:
<button onclick="this.style.display='none'">
点我后我就消失了
</button>
对象方法中绑定
下面实例中,this 是 person 对象,person 对象是函数的所有者:
var person = { firstName : "John", lastName : "Doe", id : 5566, myFunction : function() { return this; } };
var person = { firstName: "John", lastName : "Doe", id : 5566, fullName : function() { return this.firstName + " " + this.lastName; } };
显式函数绑定
在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。
在下面实例中,当我们使用 person2 作为参数来调用 person1.fullName 方法时, this 将指向 person2, 即便它是 person1 的方法:
var person1 = { fullName: function() { return this.firstName + " " + this.lastName; } } var person2 = { firstName:"John", lastName: "Doe", } person1.fullName.call(person2); // 返回 "John Doe"
1. 作为一个函数调用
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p> 全局函数 (myFunction) 返回参数参数相乘的结果: </p> <p id="demo"></p> <script> function myFunction(a, b) { return a * b; } document.getElementById("demo").innerHTML = myFunction(10, 2); </script> </body> </html>

以上函数不属于任何对象。但是在 JavaScript 中它始终是默认的全局对象。在 HTML 中默认的全局对象是 HTML 页面本身,所以函数是属于 HTML 页面。
在浏览器中的页面对象是浏览器窗口(window 对象)。以上函数会自动变为 window 对象的函数。
myFunction() 和 window.myFunction() 是一样的:
function myFunction(a, b) { return a * b; } myFunction(10, 2); // myFunction(10, 2) 返回 20
function myFunction(a, b) { return a * b; } window.myFunction(10, 2); // window.myFunction(10, 2) 返回 20
全局对象
当函数没有被自身的对象调用时 this 的值就会变成全局对象。在 web 浏览器中全局对象是浏览器窗口(window 对象)。
该实例返回 this 的值是 window 对象:
function myFunction() { return this; } myFunction(); // 返回 window 对象
完整代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>在 HTML 中 <b>this</b> 的值, 在全局函数是一个 window 对象。</p> <p id="demo"></p> <script> function myFunction() { return this; } document.getElementById("demo").innerHTML = myFunction(); </script> </body> </html>

函数作为全局对象调用,会使 this 的值成为全局对象。使用 window 对象作为一个变量容易造成程序崩溃
2. 函数作为方法调用
在 JavaScript 中你可以将函数定义为对象的方法。
以下实例创建了一个对象 (myObject), 对象有两个属性 (firstName 和 lastName), 及一个方法 (fullName):
var myObject = { firstName:"John", lastName: "Doe", fullName: function () { return this.firstName + " " + this.lastName; } } myObject.fullName(); // 返回 "John Doe"
fullName 方法是一个函数。函数属于对象。 myObject 是函数的所有者。
this对象,拥有 JavaScript 代码。实例中 this 的值为 myObject 对象。
完整代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>myObject.fullName() 返回 John Doe:</p> <p id="demo"></p> <script> var myObject = { firstName:"John", lastName: "Doe", fullName: function() { return this.firstName + " " + this.lastName; } } document.getElementById("demo").innerHTML = myObject.fullName(); </script>

测试以下!修改 fullName 方法并返回 this 值:
var myObject = { firstName:"John", lastName: "Doe", fullName: function () { return this; } } myObject.fullName(); // 返回 [object Object] (所有者对象)
函数作为对象方法调用,会使得 this 的值成为对象本身。
3. 使用构造函数调用函数
如果函数调用前使用了 new 关键字, 则是调用了构造函数。
这看起来就像创建了新的函数,但实际上 JavaScript 函数是重新创建的对象:
// 构造函数: function myFunction(arg1, arg2) { this.firstName = arg1; this.lastName = arg2; } // This creates a new object var x = new myFunction("John","Doe"); x.firstName; // 返回 "John"
构造函数的调用会创建一个新的对象。新对象会继承构造函数的属性和方法。
构造函数中 this 关键字没有任何的值。this 的值在函数调用实例化对象(new object)时创建。
4. 作为函数方法调用函数
在 JavaScript 中, 函数是对象。JavaScript 函数有它的属性和方法。
call() 和 apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身。
function myFunction(a, b) { return a * b; } myObject = myFunction.call(myObject, 10, 2); // 返回 20
完整代码
<!DOCTYPE html> <html> <body> <p id="demo"></p> <script> var myObject; function myFunction(a, b) { return a * b; } myObject = myFunction.call(myObject, 10, 2); // 返回 20 document.getElementById("demo").innerHTML = myObject; </script> </body> </html>

两个方法都使用了对象本身作为第一个参数。 两者的区别在于第二个参数: apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。
在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。
通过 call() 或 apply() 方法你可以设置 this 的值, 且作为已存在对象的新方法调用。
四、JavaScript 闭包
JavaScript 变量可以是局部变量或全局变量。私有变量可以用到闭包。
全局变量
函数可以访问由函数内部定义的变量,如:
function myFunction() { var a = 4; return a * a; }
函数也可以访问函数外部定义的变量,如:
var a = 4; function myFunction() { return a * a; }
在第一个实例中, a 是一个 局部 变量。后面一个实例中, a 是一个 全局 变量。
- 局部变量只能用于定义它函数内部。对于其他的函数或脚本代码是不可用的。
- 在web页面中全局变量属于 window 对象。全局变量可应用于页面上的所有脚本。
- 全局和局部变量即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。
- 变量声明时如果不使用 var 关键字,那么它就是一个全局变量,即便它在函数内定义。
变量生命周期
全局变量的作用域是全局性的,即在整个JavaScript程序中,全局变量处处都在。
而在函数内部声明的变量,只在函数内部起作用。这些变量是局部变量,作用域是局部性的;函数的参数也是局部性的,只在函数内部起作用。
计数器困境
设想下如果你想统计一些数值,且该计数器在所有函数中都是可用的。
你可以使用全局变量,函数设置计数器递增:
var counter = 0; function add() { return counter += 1; } add(); add(); add(); // 计数器现在为 3
完整代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>全局变量计数。</p> <button type="button" onclick="myFunction()">计数!</button> <p id="demo">0</p> <script> var counter = 0; function add() { return counter += 1; } function myFunction(){ document.getElementById("demo").innerHTML = add(); } </script>
最开始是0

每点击一次,就增加一个数,点击5次之后

计数器数值在执行 add() 函数时发生变化。但问题来了,页面上的任何脚本都能改变计数器,即便没有调用 add() 函数。
如果我在函数内声明计数器,如果没有调用函数将无法修改计数器的值:
function add() {
var counter = 0;
return counter += 1;
}
add();
add();
add();
// 本意是想输出 3, 但事与愿违,输出的都是 1 !
以上代码将无法正确输出,每次我调用 add() 函数,计数器都会设置为 1。
JavaScript 内嵌函数可以解决该问题。
JavaScript 内嵌函数
所有函数都能访问全局变量。
实际上,在 JavaScript 中,所有函数都能访问它们上一层的作用域。JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。
该实例中,内嵌函数 plus() 可以访问父函数的 counter 变量:
function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;
}
<html> <head> <meta charset="utf-8"> </head> <body> <p>局部变量计数。</p> <p id="demo">0</p> <script> document.getElementById("demo").innerHTML = add(); function add() { var counter = 0; function plus() {counter += 1;} plus(); return counter; } </script> </body> </html>

如果我们能在外部访问 plus() 函数,这样就能解决计数器的困境。我们同样需要确保 counter = 0 只执行一次。我们需要闭包。
JavaScript 闭包
闭包就是一个函数引用另一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会增加内存消耗。或者说闭包就是子函数可以使用父函数的局部变量,还有父函数的参数。
还记得函数自我调用吗?该函数会做什么?
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
add();
add();
add();
// 计数器为 3
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <p>局部变量计数。</p> <button type="button" onclick="myFunction()">计数!</button> <p id="demo">0</p> <script> var add = (function () { var counter = 0; return function () {return counter += 1;} })(); function myFunction(){ document.getElementById("demo").innerHTML = add(); } </script> </body> </html>
效果:每点击一次,就加1
实例解析
变量 add 指定了函数自我调用的返回值。自我调用函数只执行一次。设置计数器为 0。并返回函数表达式。add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。这个叫作 JavaScript 闭包。它使得函数拥有私有变量变成可能。计数器受匿名函数的作用域保护,只能通过 add 方法修改。
为什么上面这段代码没有直接写的 function add (){...} 而是把function赋值给了变量add呢?
我们通常会想当然的认为每次调用 add() 都会重走一遍add()中的代码块, 但其实不然。注意add方法中的return, 它return的并不是1,2,3这样的数值,而是return了一个方法,并且把这个方法赋值给了add变量。那么在这个function自运行一遍之后,其实最后赋值给add的是return counter += 1 这段代码。所以后面每次调用add() 其实都是在调用return counter += 1。再结合之前所说的, 闭包会持有父方法的局部变量并且不会随父方法销毁而销毁, 所以这个counter其实就是来自于第一次function执行时创建的变量。
var tempFunc;
var add = (function () {
var counter = 0;
tempFunc = function () { return counter += 1; }
return tempFunc
})();
function myFunction() {
console.log("add === tempFunc : " + (add === tempFunc))
document.getElementById("addOneS").innerHTML = add();
}
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。直观的说就是形成一个不销毁的栈环境。

浙公网安备 33010602011771号