函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值
声明函数的 5 种方式
-
具名函数 (function 命令)
function f(x, y){ return x + y; } f.name // 'f'
-
函数表达式
var f f = function(x, y){ return x + y; } f.name // 'f'
-
具名函数赋值
var f f = function f2(x, y){ return x + y; } f.name // 'f2' console.log(f2) // undefined
-
window.Function
var f = new Function('x', 'y', 'return x + y;') f.name // 'anonymous'
-
箭头函数
var f = (x, y) => { return x + y; } var sum = (x, y) => x + y var n2 = n => n*n
函数的特性
-
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明
-
函数内部的
return
语句表示返回,JavaScript 引擎遇到return
语句,就直接返回return
后面的哪个表达式的值,即时后面还有语句也不会得到执行。return
语句不是必须的,如果没有,函数默认返回undefined
-
在 JavaScript 中函数可以看成一个种值,与其他值(数字,字符串,布尔值等)地位相同,称为第一等公民,函数可以当作惭怍传递给另外一个函数。
-
函数名的提升
JavaScript 引擎将函数名视同变量名,所以采用
function
命令声明函数时,整个函数会像变量声明一样,被提升到代码头部,所以,下面的代码不会出错。f(); function f() {} // 表面上,代码好像在声明之前就调用了函数f。 // 但是实际上,由于“变量提升”,函数 f 被提升到了代码头部,也就是在调用之前已经声明了
如果使用函数表达式的方式,声明前就调用,下面的代码就会出错
f(); var f = function (){} // TypeError: f is not a function // 等价于 var f; f(); f = function(){}
函数作用域
作用域(scope)指的是变量存在的范围,在 ES5 的规范中只有全局作用域和函数作用域,ES6 新增了块级作用域。
全局作用域:变量在整个程序中一直存在,所有地方都可以读取
函数作用域:变量只在函数内部存在,外部无法访问函数内部的变量,除了使用闭包。
-
函数内部可以读取全局变量
var v = 1; function f() { console.log(v); } f() // 1
-
在函数内部定义的变量,外部无法读取,称为“局部变量”
function f(){ var v = 1; } v // ReferenceError: v is not defined
-
函数内部定义的变量,会在该作用域内覆盖同名全局变量,就近原则
var v = 1; function f(){ var v = 2; console.log(v); } f() // 2 v // 1
-
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。
var
命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部function foo(x) { if (x > 100) { var tmp = x - 100; } } // 等同于 function foo(x) { var tmp; if (x > 100) { tmp = x - 100; }; }
-
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。参考本文中的 题目二
闭包
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
函数 f2
和变量 n
的组合就形成了一个闭包。
「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包
闭包的作用:
闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」,让函数执行形成的私有作用域,保护里面的变量不受外界干扰的机制。
var api = (function() {
var lives = 50
var 奖励一条命 = function() {
lives += 1
console.log('当前剩余' + lives)
}
var 死一条命 = function() {
lives -= 1
console.log('当前剩余' + lives)
}
return {
a: 奖励一条命,
b: 死一条命
}
}())
api.a() // 当前剩余51
api.b() // 当前剩余50
api.b() // 当前剩余49
参考阅读 JS 中的闭包是什么
立即执行函数
(function(){
/* code */
}());
// 或者
(function(){
/* code */
})();
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。
它的目的有两个:
一是不必为函数命名,避免了污染全局变量;
二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
练习题
题目一
var a = 1
function f1(){
alert(a) // 是多少
var a = 2
}
f1.call()
// 遇到代码题,请先提升声明!
// 遇到代码题,请先提升声明!
// 遇到代码题,请先提升声明!
var a = 1
function f1(){
var a
alert(a)
a = 2
}
f1.call()
// 答案: alert 一个 undefined
题目二
var a = 1
function f1(){
var a = 2
f2.call()
}
function f2(){
console.log(a) // 是多少
}
f1.call()
// 函数本身也是一个值,也有自己的作用域。
// 它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
// 答案:输出 1
题目三
var liTags = document.querySelectorAll('li')
for(var i = 0; i<liTags.length; i++){
liTags[i].onclick = function(){
console.log(i) // 点击第3个 li 时,打印 2 还是打印 6?
}
}
// 答案 6
// 如何解决?
// 1. 使用 let 声明变量 i
// 2. 使用立即执行函数
var liTags = document.querySelectorAll('li')
for (var i = 0; i < liTags.length; i++) {
(function(item) {
liTags[item].onclick = function() {
console.log(item)
}
})(i);
}