前言
函数作为 JavaScript 的一等公民,这篇文章主要梳理下 JavaScript 中有多少种函数
在 JavaScript 中,每个函数其实都是一个Function
对象
构造函数
使用 new Function 创建一个函数
var multiply = new Function("x", "y", "y", "return x * y");
console.log(multiply(2,3,4))
函数声明
function getName() {
console.log("gavin");
}
getName()
//输出
> "gavin"
函数声明具有变量提升的特性,变量提升是变量和函数声明会被提升到它们所在作用域的顶部,但只有声明会被提升,而赋值不会。
所以 getName()
可以在声明之前调用,也可以放在声明之后
函数表达式
具名函数表达式
与函数声明类似,我们把函数赋值给一个变量,这个就是函数表达式
var showName = function getName() {
console.log("gavin");
};
console.log(showName.name)
// 输出
> getName
具名函数表达式可以用于在函数内部代指其本身,如递归调用自己,或者在调试器堆栈跟踪中识别该函数
匿名函数表达式
var showName = function() {
console.log("gavin");
};
showName()
console.log(showName.name)
//输出
> gavin
> showName
JavaScript 中的函数表达式没有提升,不像函数声明会提升到作用域最前边,所以不能这样去用
showName()
var showName = function() {
console.log("gavin");
};
//输出
> TypeError: showName is not a function
被函数表达式赋值的那个变量会有一个 name 属性,如果你把这个变量赋值给另一个变量的话,这个 name 属性的值也不会改变。
如果函数是一个匿名函数,那 name 属性的值就是被赋值的变量的名称(隐藏值)。
如果函数不是匿名的话,那 name 属性的值就是这个函数的名称(显性值)。
立即执行函数
IIFE(Immediately Invoked Function Expression),这种定义的函数允许在函数定义的时候立即执行
(function () {
console.log("gavin");
})()
// 输出
> gavin
函数分为两块,
- 第一部分是里面的匿名函数(也可以是具名函数),并且用
()
运算符闭合起来,将函数声明变成一个表达式 - 第二部分是外面的
()
,作用是创建一个立即执行函数表达式,通过它,JavaScript 引擎将立即执行该函数。
立即执行函数为什么会存在?
创建私有作用域
因为我们的程序可能包括很多来自不同源文件的函数和全局变量,因此限制全局变量的数量非常重要。如果我们有一些不再使用的初始化代码,我们可以使用 IIFE 模式
例如,立即执行函数外面,我们定义了一个全局变量,匿名函数里面我们定义的变量不会干扰到外部变量
var a = 3;
(function () {
var a = 2
console.log("gavin");
})()
console.log(a)
// 输出
> gavin
> 3
防止变量提升
下面这段代码在 ES6 之前,是一个比较常见的问题,当点击按钮是,打印的都是 3,原因就是 var
声明的变量 i
在全局作用域或函数作用域中是共享的,当点击按钮的时候使用还是全局变量 i
,此时变量 i
已经变成了 3
for (var i = 0; i < 3; i++) {
const button = document.createElement("button");
button.innerText = `Button ${i}`;
button.onclick = function () {
console.log(i);
};
document.body.appendChild(button);
}
console.log(i);
// 输出
> 3
怎么解决这个问题呢,ES6 之后直接使用 let 定义变量 i ,这样每次迭代都会创建一个新的变量,当然我们也可以使用立即执行函数来解决这个问题
for (var i = 0; i < 3; i++) {
const button = document.createElement("button");
button.innerText = `Button ${i}`;
// IIFE 写法
(function (ii){
button.onclick = function() {
console.log(ii)
}
})(i)
document.body.appendChild(button);
}
console.log(i);
可以看到里面的这段 IIFE 函数,这样每一个按钮都有自己的作用域,ii
参数捕获了当前迭代的 i
值,避免了共享变量导致的问题
尽管 IIFE 在特定情况下很有用,但在现代 JavaScript 中,由于引入了
let
和const
关键字以及模块化系统,使用 IIFE 的需求已经减少。尤其是在 ES6 及以后的版本中,块级作用域的引入减少了对 IIFE 的依赖。
函数表达式的形式
如果你遇到下面这些写法,现在能区分它们是什么类型的了
// 函数声明
function foo() {}
// 函数表达式
(function bar() {});
// 函数表达式
x = function hello() {};
if (x) {
// 函数表达式
function world() {}
}
// 函数声明
function a() {
// 函数声明
function b() {}
if (0) {
//函数表达式
function c() {}
}
}
方法 or 函数?
当一个函数是一个对象的属性时,称之为方法
从 ECMAScript 2015 开始,在对象初始器中引入了一种更简短定义方法的语法,这是一种把方法名直接赋给函数的简写方式
var obj = {
getName: function () {
return "gavin"
},
setName: function () {
/* code */
},
};
console.log(obj.getName());
// 输出
> "gavin"
上面这种使用匿名函数,也可以用具名函数,跟上面的函数表达式是一样的用法
var obj = {
getName: function showName () {
return "gavin"
},
setName: function () {
/* code */
},
};
console.log(obj.getName.name);
console.log(obj.setName.name);
// 输出
> "showName"
> "setName"
也可以被简写如下
var obj = {
getName() {
return "gavin"
},
setName() {
/* code */
},
};
这种情况下是不能使用匿名函数的,必须是具名函数
以上就是本节的全部内容,主要介绍了几种常见的函数形式,希望后面在实际程序代码中遇到这样的写法不会迷茫