四、JavaScript函数的了解与使用
Lyk 2022/6/27 JS基础局部变量全局变量函数立即执行函数函数声明函数表达式arguments递归斐波那契数列实现回调函数高阶函数匿名函数立即执行函数debugger调试技巧
# 1、认识函数
什么是函数呢?
目前, 我们已经接触过几个函数了
- alert函数: 浏览器弹出一个弹窗
- prompt函数: 在浏览器弹窗中接收用户的输入
- console.log函数: 在控制台输入内容
- String/Number/Boolean函数等
当我们在谈函数时, 到底在谈些什么?
- 函数其实就是某段代码的封装,这段代码帮助我们完成某一个功能;
- 默认情况下JavaScript引擎或者浏览器会给我们提供一些已经实现好的函数;
- 我们也可以编写属于自己的函数;
# 2、函数使用的步骤
函数的使用包含两个步骤:
- 声明函数 —— 封装 独立的功能
- 调用函数 —— 享受 封装 的成果
声明函数,在JavaScript中也可以称为定义函数:
- 声明函数的过程是对某些功能的封装过程;
- 在之后的开发中,我们会根据自己的需求定义很多自己的函数;
调用函数,也可以称为函数调用:
- 调用函数是让已存在的函数为我们所用;
- 这些函数可以是刚刚自己封装好的某个功能函数;
- 当然, 我们也可以去使用默认提供的或者其他三方库定义好的函数;
函数的作用:在开发程序时,使用函数可以提高编写的效率以及代码的重用;
# 3、声明和调用函数
- 声明函数使用function关键字:这种写法称之为函数的定义
function 函数名() {
函数封装的代码
...
}
1
2
3
4
2
3
4
注意:
- 函数名的命名规则和前面变量名的命名规则是相同的;
- 函数要尽量做到见名知意(并且函数通常是一些行为(action),所以使用动词会更多一些);
- 函数定义完后里面的代码是不会执行的,函数必须调用才会执行;
调用函数通过函数名()即可:比如test()
// - 函数的练习:
// 练习一:定义一个函数,打印一个人的个人信息
function person() {
var name = 'lyk'
var age = 23
console.log(`我是${name},今年${age}岁。`)
}
person()
// 练习二:定义一个函数,函数中计算10和20数字的和,并且打印出结果
function num() {
console.log(`我们的和是:${10+20}`)
}
num()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4、函数的参数(形参/实参)
函数的参数:
- 函数,把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用
- 函数的参数,增加函数的 通用性,针对 相同的数据处理逻辑,能够 适应更多的数据
- 在函数 内部,把参数当做 变量 使用,进行需要的数据处理
- 函数调用时,按照函数定义的参数顺序,把 希望在函数内部处理的数据,通过参数 传递
形参和实参
- 形参(参数 parameter):定义 函数时,小括号中的参数,是用来接收参数用的,在函数内部 作为变量使用
- 实参(参数 argument):调用 函数时,小括号中的参数,是用来把数据传递到 函数内部 用的
function num(n,m) {//n,m是形参
console.log(`我们的和是:${n+m}`)
}
num(20,30)//20,30是实参
1
2
3
4
2
3
4
- 有参数的函数练习
// - 函数的练习:
// 练习一:定义一个函数,打印自定义的个人信息
function person(name,age) {
console.log(`我是${name},今年${age}岁。`)
}
person('lyk',23)
// 练习二:定义一个函数,计算传入的两数字之和,并且打印出结果
function num(n,m) {
console.log(`我们的和是:${n+m}`)
}
num(20,30)
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 5、函数的返回值
- 回想我们之前使用的prompt函数,函数需要接受参数,并且会返回用户的输入:
- 所以说, 函数不仅仅可以有参数, 也可以有返回值:
- 使用return关键字来返回结果;
- 一旦在函数中执行return操作,那么当前函数会终止;
- 如果函数中没有使用 return语句 ,那么函数有默认的返回值:undefined;
- 如果函数使用 return语句,但是return后面没有任何值,那么函数的返回值也是:undefined;
function num(n,m) {
return n+m
}
var total = num(20,30)
console.log(total)
1
2
3
4
5
2
3
4
5
- 函数的练习
//案例:定义一个函数,传入n(n为正整数),计算1~n数字的和
//实战函数练习:传入一个数字,可以根据数字转化成显示为 亿、万文字显示的文本;
1
2
3
2
3
# 6、arguments参数(JS高级再学习)
事实上在函数有一个特别的对象:arguments对象(类数组对象:不可用数组中的方法)
- 默认情况下,arguments对象是所有(非箭头)函数中都可用的局部变量;
- 该对象中存放着所有的调用者传入的参数,从0位置开始,依次存放;
- arguments变量的类型是一个object类型( array-like ),不是一个数组,但是和数组的用法看起来很相似;
- 如果调用者传入的参数多余函数接收的参数,可以通过arguments去获取所有的参数;
因为这里涉及到数组、对象等概念,目前大家了解有这么一个参数即可。
- 后续我们会对其专门进行学习,包括和数组之间的转化;
- Array.from()可以将类数组对象转化成数组,这样它就可以用数组中的方法了
# 7、函数中调用函数
- 在开发中,函数内部是可以调用另外一个函数的。
function foo() {
console.log('foo函数被调用了')
}
function bar() {
console.log('bar函数被调用了')
foo()
}
bar()
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 既然函数中可以调用另外一个函数,那么函数是否可以调用自己呢?
- 当然是可以的;
- 但是函数调用自己必须有结束条件,否则会产生无限调用,造成报错;
var count = 0
function bar() {
console.log(count++)
bar()
}
bar()
//报错:Uncaught RangeError: Maximum call stack size exceeded(未捕获范围错误:超过最大调用堆栈大小)
1
2
3
4
5
6
7
2
3
4
5
6
7
# 8、函数的递归
事实上,函数调用自己还有一个专业的名词,叫做递归(Recursion)
在语言学方面,我们也可以描述为递归:
- 从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?
- 递归读取上面的话;
递归是一种重要的编程思想:
- 将一个复杂的任务,转化成可以重复执行的相同任务;
//案例:实现一个自己的幂函数pow(pow单词可以表示指数的意思)
//- 我们可以先用for循环来实现;
function pow(a,n) {
var result = 1
for(var i=0;i<n;i++) {
result *= a
}
return result
}
console.log(pow(2,10))//1024
//下面第9点;是另一种实现思路:递归实现幂函数
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 9、递归的实现思路
另一种实现思路是递归实现:
- 这是因为在数学上:x的n次方 = x * x的n-1次方
- 那么对于函数的调用,我们也可以进行划分:
- 这里需要有一个结束的条件,就是当n已经等于1的时候就不需要拆分了;
所以最终的代码如下:
function pow(x,n) {
if(n === 1) return x
return x * pow(x,n-1)
}
console.log(pow(2,10))//1024
1
2
3
4
5
2
3
4
5
- 递归的代码第一次接触会有点绕,对于初次接触函数的同学,可以先跳过去。
- 后续我们讲解数据结构与算法时,会使用递归来解决一些算法问题;
# 10、for循环/递归实现斐波那契数列
//斐波那契数列规律:1 1 2 3 5 8 13 21 34 55
// n: 1 2 3 4 5 6 7 8 9 10
//1、for循环实现斐波那契数列
function fibonacciFor(n) {
if(n ===1 || n===2) return 1
var n1 = 1
var n2 = 1
var result = 0
for(var i=3;i<=n;i++) {
result = n1 + n2
n1 = n2
n2 = result
}
return result
}
console.log(fibonacciFor(10))//55
//2、递归实现斐波那契数列
function fibonacciRecursion(n) {
if(n === 1 || n === 2) return 1
return fibonacciRecursion(n-1) + fibonacciRecursion(n-2)
}
console.log(fibonacciRecursion(10))//55
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 11、局部变量和外部变量
在JavaScript(ES5之前)中没有块级作用域的概念,但是函数可以定义自己的作用域。
- 作用域(Scope)表示一些标识符的作用有效范围(所以也有被翻译为有效范围的);
- 函数的作用域表示在函数内部定义的变量,只有在函数内部可以被访问到;
外部变量和局部变量的概念:
- 定义在函数内部的变量,被称之为局部变量(Local Variables)。
- 定义在函数外部的变量,被称之为外部变量(Outer Variables)。
什么是全局变量?
- 在函数之外声明的变量(在script中声明的),称之为全局变量。
- 全局变量在任何函数中都是可见的。
- 通过var声明的全局变量会在window对象上添加一个属性(了解);
在函数中**,**访问变量的顺序是什么呢?
- 优先访问自己函数中的变量,没有找到时,在外部中访问。
关于块级作用域、作用域链、变量提升、AO、VO、GO等概念我们后续将进行学习。
# 12、函数表达式(Function Expressions)
- 在JavaScript中,函数并不是一种神奇的语法结构,而是一种特殊的值。
- 前面定义函数的方式,我们称之为函数的声明(Function Declaration)
//函数声明
function num(n,m) {
var result = n +m
return result
}
1
2
3
4
5
2
3
4
5
- 还有另外一种写法是函数表达式(Function Expressions)
- 注意,function 关键字后面没有函数名
- 函数表达式允许省略函数名。
//函数表达式
var num = function(n,m) {
var result = n + m
return result
}
1
2
3
4
5
2
3
4
5
- 无论函数是如何创建的,函数都是一个值(这个值的类型是一个对象,对象的概念后面会讲到)。
- 在JavaScript开发中,我们可以将函数作为头等公民
# 13、函数声明 vs 函数表达式
在开发中,函数的声明和函数表达式有什么区别,以及如何选择呢?
首先,语法不同:
- 函数声明:在主代码流中声明为单独的语句的函数。
- 函数表达式:在一个表达式中或另一个语法结构中创建的函数。
其次,JavaScript创建函数的时机是不同的:
- 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。
- 在函数声明被定义之前,它就可以被调用。
- 这是内部算法的原故;
- 当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数;
开发中如何选择呢?
- 当我们需要声明一个函数时,首先考虑函数声明语法。
- 它能够为组织代码提供更多的灵活性,因为我们可以在声明这些函数之前调用这些函数。
//函数声明(定义之前可用)
console.log(num1) //function num1(n,m) { var result = n +m return result }
function num1(n,m) {
var result = n +m
return result
}
//函数表达式 (创建之前不可用)
console.log(num2) //undefined
var num2 = function(n,m) {
var result = n + m
return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 14、JavaScript头等函数
头等函数(first-class function;第一级函数)是指在程序设计语言中,函数被当作头等公民。
- 这意味着,函数可以作为别的函数的 参数、函数的返回值,赋值给变量或存储在数据结构中;
- 有人主张也应包括支持匿名函数(下面会讲到);
通常我们对函数作为头等公民的编程方式,称之为函数式编程
- JavaScript就是符合函数式编程的语言,这个也是JavaScript的一大特点;
比如:函数可以在变量和变量之间相互进行赋值;
function foo() {
console.log('foo函数执行')
}
var bar = foo
bar()
1
2
3
4
5
2
3
4
5
# 15、回调函数(Callback Function)、高阶函数、匿名函数
- 既然函数可以作为一个值相互赋值,那么也可以传递给另外一个函数。
function foo(fn) {
// 通过fn去调用bar函数的过程, 称之为函数的回调
fn()
}
function bar() {
console.log('我是bar函数,被调用了!')
}
foo(bar)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
// 2.函数回调的案例
function request(url, callback) {
console.log("根据URL向服务器发送网络请求")
console.log("需要花费比较长的时间拿到对应的结果")
var list = ["javascript", "javascript学习", "JavaScript高级编程"]
callback(list)
}
function handleResult(res) {
console.log("在handleResult中拿到结果:", res)
}
request("url", handleResult)
// 3.函数回调的案例重构
function request(url, callback) {
console.log("根据URL向服务器发送网络请求")
console.log("需要花费比较长的时间拿到对应的结果")
var list = ["javascript", "javascript学习", "JavaScript高级编程"]
callback(list)
}
// 传入的函数是没有名字, 匿名函数
request("url", function(res) {
console.log("在handleResult中拿到结果:", res)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
foo这种函数我们也可以称之为高阶函数(Higher-order function);
高阶函数必须至少满足两个条件之一:
- 接受一个或多个函数作为输入;
- 输出一个函数;
匿名(anonymous)函数的理解:
- 如果在传入一个函数时,我们没有指定这个函数的名词或者通过函数表达式指定函数对应的变量,那么这个函数称之为匿名函数
//函数表达式:将一个匿名函数赋值给变量的过程
var foo = function() {//等号右边的部分就叫做匿名函数
console.log('我是foo')
};
//第一个()里面的函数,是一个匿名函数
(function() {
console.log('我是立即执行函数')
})()
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 16、立即执行函数
- 什么是立即执行函数?
- 专业名字:Immediately-Invoked Function Expression(IIFE 立即调用函数表达式)
- 表达的含义是一个函数定义完后被立即执行;
- 第一部分是定义了一个匿名函数,这个函数有自己独立的作用域。
- 第二部分是后面的(),表示这个函数被执行了
(function(){
console.log('我是一个立即执行函数!')
})()
1
2
3
2
3
- 这个东西有什么用?
- 会创建一个独立的执行上下文环境,可以避免外界访问或修改内部的变量,也避免了对内部变量的修改
// 立即执行函数应用场景一: 防止全局变量的命名冲突
// 立即执行函数和普通的代码有什么区别?
// 在立即执行函数中定义的变量是有自己的作用域的
(function() {
var message = "Hello World nihao"
console.log(message)//Hello World nihao
})()
console.log(message)//undefined
var message = "Hello World lyk"
console.log(message)//Hello World lyk
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<!-- 立即执行函数应用场景二: -->
<body>
<button class='btn'>按钮1</button>
<button class='btn'>按钮2</button>
<button class='btn'>按钮3</button>
<button class='btn'>按钮4</button>
<script>
//1、没有使用立即执行函数
var btnEls = document.querySelectorAll(".btn")
for (var i = 0; i < btnEls.length; i++) {
var btn = btnEls[i];
btn.onclick = function() {
console.log(`按钮${i+1}发生了点击`)//不管点击哪个按钮都打印的是:按钮5发生了点击
}
}
//2、使用立即执行函数
var btns = document.querySelectorAll('.btn')
for(var i=0;i<btns.length;i++) {
(function(m){
btns[i].onclick = function() {
console.log(`第${m+1}个按钮被点击了`)
}
})(i)
}
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 17、立即执行函数的其他写法
- 立即执行函数必须是一个表达式(整体),不能是函数声明(了解即可):
- 下面的这种写法会报错,因为是一个函数声明,不是一个函数表达式;
- 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。
function foo() {
console.log('立即执行函数')
}()
1
2
3
2
3
- 当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明。
(function foo() {
console.log('立即执行函数')
})()
1
2
3
2
3
- 下面是一个函数表达式,所以可以执行; 案例可看该文章 立即执行函数写法参考(了解) (opens new window)
+function foo() {
console.log('立即执行函数')
}()
1
2
3
2
3
(function foo() {
console.log('立即执行函数')
}())
1
2
3
2
3