JavaScript 笔记(六):函数
函数是一种引用数据类型(对象类型),可以存储在一个变量中,基本格式如下:
function funcName(parameterList) {
// statement
// ...
// return
}
函数的形参与返回值可以有,也可以没有,如果函数没有返回值,那么默认返回 undefined;return 可以立即结束函数的执行;调用函数时,实参的个数与形参的个数可以不同,实参默认与形参以从左至右的顺序一一对应,如果实参个数小于形参个数,那么没有相应实参的形参的值在函数作用域中为 undefined,如果实参个数大于形参个数,那么多余的实参将被丢弃;下面是一些使用函数的示例:
let sum = function(a, b) {
return a + b;
}
let num = 5;
console.log(sum(num, 10)); // 15
function sayHello(name) {
console.log("Hello, " + name);
}
sayHello("Reyn"); // Hello, Reyn
在 JavaScript 中,由于函数为一种引用类型,可以存储在变量中,因此也可以作为函数的参数与返回值,示例如下:
function getFunc() {
let sayHello = function () {
console.log("Hello World");
}
return sayHello;
}
function callFunc(fnc) {
fnc();
}
callFunc(getFunc()); // Hello World
在 JavaScript 中,可以在函数内定义函数,称为函数的嵌套定义
arguments
每一个函数内部默认都有一个名为 arguments 的伪数组,可以保存所有传递给函数的实参,示例如下:
function getSum() {
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
console.log(getSum(1, 2, 3, 4, 5)); // 15
使用 ES6 新增的扩展运算符同样可以实现此功能,扩展运算符将所有传递给函数的实现打包到一个数组中,示例如下:
function getSum(...args) {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
console.log(getSum(1, 2, 3, 4, 5)); // 15
值得注意的是,函数中的扩展运算符与赋值运算符左侧的扩展运算符相同,扩展运算符所在的参数必须为最后一个
默认值
在 ES6 之前使用逻辑运算符实现为参数指定默认值,示例如下:
function getFullName(firstName, lastName) {
firstName = firstName || "Steven";
lastName = lastName || "Jobs";
return firstName + ' ' + lastName;
}
console.log(getFullName("Reyn", "Morales")); // Reyn Morales
console.log(getFullName()); // Steven Jobs
在 ES6 之后可以使用如下形式实现为参数指定默认值:
function getFullName(firstName = "Steven", lastName = "Jobs") {
return firstName + ' ' + lastName;
}
console.log(getFullName("Reyn", "Morales")); // Reyn Morales
console.log(getFullName()); // Steven Jobs
值得注意的是,在 ES6 之后,默认值也可以从其它函数中获取,示例如下:
let celebrityLastName = function () {
return "Jobs";
}
function getFullName(firstName = "Steven", lastName = celebrityLastName()) {
return firstName + ' ' + lastName;
}
console.log(getFullName("Reyn", "Morales")); // Reyn Morales
console.log(getFullName()); // Steven Jobs
匿名函数
匿名函数即为没有名称的函数,不可以只定义不使用,示例如下:
(function (function () { // 将匿名函数作为参数
return function () { // 将匿名函数作为返回值
console.log("Hello World"); // Hello World
};
}){
arguments[0]();
})(); // 调用一个匿名函数
以上写法是错误的,旨在一次性展示匿名函数的所有用法,值得注意的是,如果在定义匿名函数时立即调用,那么必须将定义置于小括号中,并在使用小括号置于其后,表明调用函数,格式如下:
(function () {
console.log("Hello World");
})();
箭头函数
在 ES6 中新增的一种定义函数的方式,目的在于简化代码,格式如下:
let functionName = (parametersList) => {
// statement
}
示例如下:
let say = function(name) {
console.log("Hello, " + name);
};
say("Reyn Morales"); // Hello, Reyn Morales
let hello = (name) => {
console.log("Hello, " + name);
};
hello("Reyn Morales"); // Hello, Reyn Morales
let sayHello = name => console.log("Hello, " + name);
sayHello("Reyn Morales"); // Hello, Reyn Morales
使用箭头函数时,如果只有一个形参,那么可以省略
()
,如果函数体中只有一条语句,那么{}
也可以省略
递归函数
递归函数即为函数调用自己本身,示例如下:
let fibonacci = num => {
if (num == 1)
return 1;
return num * fibonacci(num - 1);
}
console.log(fibonacci(5)); // 120
作用域
在 JavaScript 中,位于 {}
之外区域称为全局作用域,位于函数之后的 {}
内部的区域称为局部作用域,位于其它语句(循环、分支等)之后的 {}
称为块级作用域
使用 var 定义变量时,如果在局部作用域内部,那么此变量为局部变量,如果在块级作用域内部,那么此变量为全局变量;此外,不论是在局部作用域还是块级作用域,如果省略了定义函数时的 var 或 let,那么此变量是一个全局变量
关键字 | 全局作用域 | 局部作用域 | 块级作用域 |
var | 全局变量 | 局部变量 | 全局变量 |
let | 全局变量 | 局部变量 | 局部变量 |
使用 let 定义变量时,在不同的作用域中定义的变量不是同一个变量,即使它们的标识符相同,示例如下:
{
let name = "Reyn";
{
let name = "Jobs";
console.log(name); // Jobs
}
console.log(name); // Reyn
}
在同一个作用域中,如果出现了使用 let 定义的一个变量,那么不能再使用相同的标识符定义一个变量,即使使用 var,不论顺序如何,均会报错,示例如下:
let name = "Reyn";
var name = "Jobs"; // 报错
var num = 520;
let num = 1024; // 报错
作用域链
ES6 之前
在研究 ES6 之前的作用域之前,明确如下:
- ES6 之前使用关键字 var 定义变量
- ES6 之前只有全局作用域和局部作用域,没有块级作用域
- ES6 之前函数大括号之外的均为全局作用域
- ES6 之前函数大括号之内的均为局部作用域
在 ES6 之前,全局作用域又称为 0 级作用域,如果定义了函数,那么就会开启 1、2、3、… 级作用域,JavaScript 将作用域链接在一起,形成一个作用域链:
0 -> 1 -> 2 -> 3 -> ...
除了 0 级作用域之外,其它作用域级别等于上一级作用域级别加一,示例如下:
// 0 级作用域(全局作用域)
function fnc() {
// 1 级作用域
function subFnc() {
// 2 级作用域
function subSubFnc() {
// 3 级作用域
function subSubSubFnc() {
// ...
}
}
}
}
当在某一个作用域中使用某个变量时,将先在当前作用域中查找此变量,如果没有找到,那么将在上一级作用域中查找,如果依然没有找到,那么将在上上一级作用域中查找,依次类推,直到 0 级作用域,如果在 0 级作用域任然没有找到,那么将会报错
ES6 之后
在研究 ES6 之后的作用域之前,明确如下:
- 在 ES6 之后使用 let 定义变量
- 在 ES6 之后除了全局作用域和局部作用域之外,还新增了块级作用域
- 在 ES6 之后虽然新增了块级作用域,但对于 let 定义的变量来说,在局部作用域和块级作用域中并没有区别,均为局部变量
在 ES6 之后的作用域链与 ES6 之后的作用域类似,不同的是在使用代码块时也会开启作用域,示例如下:
// 0 级作用域(全局作用域)
{
// 1 级作用域
function subFnc() {
// 2 级作用域
if (true) {
// 3 级作用域
while (false) {
// ...
}
}
}
}
当在某一个作用域中使用某个变量时,将先在当前作用域中查找此变量,如果没有找到,那么将在上一级作用域中查找,如果依然没有找到,那么将在上上一级作用域中查找,依次类推,直到 0 级作用域,如果在 0 级作用域任然没有找到,那么将会报错
预解析
浏览器在执行 JavaScript 代码之前,存在预解析的步骤,即先将代码加工处理之后再解释执行,预解析时,先将变量声明和函数声明提升到当前作用域的最前面,然后将剩余的代码以原来的顺序依次后置,函数的预解析示例如下:
使用 let 定义的变量不会被提升(预解析)
/* 格式一 */
say();
function say() {
console.log("Hello World");
}
/* 格式二 */
say(); // 报错
let say = function() {
console.log("Hello World");
}
/* 格式三 */
say(); // 报错
let say = () => console.log("Hello World");
在 JavaScript 中,除了函数头之外,函数体也为函数声明的一部分,因此格式一正确,在使用函数时,函数确实已经被定义了,而通过变量使用函数,将变量声明提升之后,在使用变量时,此变量还没有被赋予函数内容,因此格式二和格式三错误
在高级浏览器中,在 {}
之中的函数不会被预解析,只有在低级浏览器中才会以正常方式解析,此外,如果在同一级作用域下,如果变量名称与函数名称相同,那么函数的优先级高于变量,示例如下:
console.log(value);
var value = 520;
function value() {
console.log("fnc value");
}
console.log(value);
/* 预解析 */
function value() {
console.log("fnc value");
}
console.log(value); // function value() { console.log("fn value"); }
var value;
value = 520;
console.log(value); // 520