函数
-
在 JS 里面,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用。
-
虽然 for循环语句也能实现一些简单的重复操作,但是比较具有局限性,此时我们就可以使用 JS 中的函数。
函数的概念
-
函数:就是封装了一段可被重复调用执行的代码块。
-
目的:就是让大量代码被重复使用。
-
在想要让这段代码执行的时候,直接执行这个代码块里面的代码就行
-
例如:
// 这个是一段代码 for (var i = 0; i < 10; i++) { console.log(i) } // 函数,这个 {} 就是那个代码块 function fn() { // 这个函数我们之前写的代码 for (var i = 0; i < 10; i++) { console.log(i) } } //fn 函数名不加括号代表整个函数 //fn() 函数名加括号代表函数的调用
函数的两个阶段(重点)
- 函数在使用时分为两步:声明函数和调用函数。
声明函数阶段
-
定义阶段就是把代码放进 代码块
-
有两种定义方式 声明式 和 赋值式
声明式
-
使用
function
这个关键字来声明一个函数 -
因为有名字,所以也被称为命名函数
-
调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后
-
语法:
//声明函数 function fn() { // 函数体代码 } // function: 声明函数的关键字(必须小写),表示接下来是一个函数了 // fn: 函数的名字,我们自己定义的(遵循变量名的命名规则和命名规范) // (): 必须写,是用来放参数的位置 // {}: 就是我们用来放一段代码的位置(代码块)
- 由于函数一般是为了实现某个功能才定义的, 所以通常我们将函数名命名为动词,比如 getSum
##### 赋值式
- 其实就是和我们使用 `var` 关键字是一个道理了
- 首先使用 `var` 定义一个变量,把一个函数当作值直接赋值给这个变量就可以了
- 因为函数没有名字,所以也被称为匿名函数
- 这个fn 里面存储的是一个函数
- 函数表达式方式原理跟声明变量方式是一致的
- 函数调用的代码必须写到函数体后
- 语法:
```javascript
var fn = function () {
// 一段代码
}
// 不需要在 function 后面书写函数的名字了,因为在前面已经有了
函数调用阶段
- 就是让 代码块 里的代码执行一下
- 让函数执行
- 两种定义函数的方式不同,但是调用函数的方式都以一样的
- 函数不调用,自己不执行
调用一个函数
-
函数调用就是直接写
函数名()
就可以了// 声明式函数 function fn() { console.log('我是 fn 函数') } // 调用函数 fn() //通过调用函数名来执行函数体代码 // 赋值式函数 var fn2 = function () { console.log('我是 fn2 函数') } // 调用函数 fn()
- 注意: 声明函数本身并不会执行代码,只有调用函数时才会执行函数体代码
调用上的区别
-
虽然两种定义方式的调用都是一样的,但是还是有一些区别的
-
声明式函数: 调用可以在 定义之前或者定义之后
// 可以调用 fn() // 声明式函数 function fn() { console.log('我是 fn 函数') } // 可以调用 fn()
-
赋值式函数: 调用只能在 定义之后
// 会报错 fn() // 赋值式函数 var fn = function () { console.log('我是 fn 函数') } // 可以调用 fn()
例子🌰: 利用函数计算1-100之间的累加和
// 1. 声明函数
function getSum() {
var sum = 0
for (var i = 1; i <= 100; i++) {
sum += i
}
console.log(sum)
}
// 2. 调用函数
getSum()
函数的参数(重点)
-
在声明函数时,可以在函数名称后面的小括号
()
中添加一些参数,这些参数被称为形参,而在调用该函数时, 同样也需要传递相应的参数,这些参数被称为实参。 -
参数的作用 : 在函数内部某些值不能固定,我们可以通过参数在调用函数时传递不同的值进去。
参数 说明 形参 形式上的参数,函数定义的时候 传递的参数 当前并不知道是什么 实参 实际上的参数,函数调用的时候传递的参数 实参时传递给形参的 -
参数分为两种 形参 和 实参
// 声明式 function fn(参) { // 一段代码 } fn(实参) // 赋值式函数 var fn = function (行参) { // 一段代码 } fn(实参)
行参和实参的作用
-
行参
-
在声明函数时,可以在函数名称后面的小括号中添加一些参数,这些参数被称为形参。
-
就是在函数内部可以使用的变量,在函数外部不能使用
-
每写一个单词,就相当于在函数内部定义了一个可以使用的变量(遵循变量名的命名规则和命名规范)
-
多个单词之间以
,
分隔// 带参数的函数声明 function 函数名(形参1, 形参2 , 形参3...) { // 可以定义任意多的参数,用逗号分隔 // 函数体 } // 带参数的函数调用 函数名(实参1, 实参2, 实参3...)
-
如果只有行参的话,那么在函数内部使用的值个变量是没有值的,也就是
undefined
-
行参的值是在函数调用的时候由实参决定的
-
-
实参
-
在调用该函数时, 需要传递相应的参数,这些参数被称为实参。
-
也就是说调用的时候,实参值是传递给形参的;即,在调用的时候是给形参一个实际的内容的
function fn(num) { // 函数内部可以使用 num } // 这个函数的本次调用,书写的实参是 100 // 那么本次调用的时候函数内部的 num 就是 100 fn(100)
-
函数内部的行参的值,由函数调用的时候传递的实参决定
-
多个参数的时候,是按照顺序一一对应的
function fn(num1, num2) { // 函数内部可以使用 num1 和 num2 } // 函数本次调用的时候,书写的参数是 100 和 200 // 那么本次调用的时候,函数内部的 num1 就是 100,num2 就是 200 fn(100, 200)
-
参数个数的关系
参数的个数 | 说明 |
---|---|
实参个数等于形参个数 | 输出结果 |
实参个数多余形参个数 | 只取到形参的个数 |
实参个数小于形参个数 | 多的形参定义为undefined,结果为NaN |
-
行参比实参少
-
因为是按照顺序一一对应的
-
行参少就会拿不到实参给的值,所以在函数内部就没有办法用到这个值
function fn(num1, num2) { // 函数内部可以使用 num1 和 num2 } fn(100, 200) // 形参和实参个数相等,输出正确结果 // 100 对应了 num1,200 对应了 num2,300 没有对应的变量 fn(100, 200, 300) // 实参个数多于形参,只取到形参的个数
-
-
行参比实参多
-
因为是按照顺序一一对应的
-
在JavaScript中,形参的默认值就是
undefined
。function fn(num1, num2, num3) { // 函数内部可以使用 num1 num2 和 num3 } // 100 对应了 num1, num2没有相对应的实参 fn(200) // 实参个数少于形参,多的形参定义为undefined,结果为NaN
-
函数的return(重点)
-
return
返回的意思,其实就是给函数一个 返回值 和 终断函数 -
语法:
// 声明函数 function 函数名(){ ... return 需要返回的值 或者 中断函数的位置; } // 调用函数 函数名() // 此时调用函数就可以得到函数体内return 后面的值
终断函数
-
当函数开始执行以后,函数内部的代码就会从上到下的依次执行,直到函数内的代码执行完毕
-
而
return
关键字就是可以在函数中间的位置停掉,让后面的代码不在继续执行function fn() { console.log(1) console.log(2) console.log(3) // 写了 return 以后,后面的 4 和 5 就不会继续执行了 return console.log(4) console.log(5) } // 函数调用 fn()
返回值
-
我们函数只是实现某种功能,最终的结果需要返回给函数的调用者函数名() 通过return 实现的
-
只要函数遇到return 就把后面的结果 返回给函数的调用者 函数名() = return后面的结果
-
函数调用本身也是一个表达式,表达式就应该有一个值出现
-
如果不
return
一个值,那么在函数执行完毕之后,是不会有结果出现// 比如 1 + 2 是一个表达式,那么 这个表达式的结果就是 3 console.log(1 + 2) // 3 function fn() { // 执行代码 } // fn() 也是一个表达式,这个表达式就没有结果出现 console.log(fn()) // undefined
-
return
关键字就是可以给函数执行完毕一个结果function fn() { // 执行代码 return 100 } // 此时,fn() 这个表达式执行完毕之后就有结果出现了 console.log(fn()) // 此时打印100 , 因为 return 语句会把自身后面的值返回给调用者,此时调用者fn
-
我们可以在函数内部使用
return
关键把任何内容当作这个函数运行后的结果 -
在使用 return 语句时,函数会停止执行,并返回指定的值
-
函数的优点
- 函数就是对一段代码的封装,在我们想调用的时候调用
- 函数的几个优点
- 封装代码,使代码更加简洁
- 复用,在重复功能的时候直接调用就好
- 代码执行时机,随时可以在我们想要执行的时候执行
argument的使用
-
当不确定有多少个参数传递的时候,可以用 arguments 来获取。
-
在 JavaScript 中,arguments 实际上 它是当前函数的一个内置对象。
-
所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的 所有实参。
-
arguments展示形式是一个伪数组,因此可以进行遍历。
-
伪数组(不是真正意义上的数组)具有以下特点:
- 具有 length 属性
- 按索引方式储存数据
- 不具有数组的 push , pop 等方法
function fn() { // console.log(arguments); // 里面存储了所有传递过来的实参 arguments = [1,2,3] //可以用arguments.length来判断函数中实参的个数(也就是数组的长度) // console.log(arguments.length); // console.log(arguments[2]); // 可以按照数组的方式遍历arguments for (var i = 0; i < arguments.length; i++) { console.log(arguments[i]); } } fn(1, 2, 3); fn(1, 2, 3, 4, 5);
作用域(重点)
-
作用域指一个变量的作用的范围 。通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
-
作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
全局作用域
-
全局作用域是最大的作用域
-
**全局作用域的概念:**直接写在 script 标签中的 JavaScript 代码都是全局作用域。
-
当页面打开运行时全局作用域就会自动创建,而当页面关闭时就会销毁。
-
在全局作用域中有一个全局的
window
对象可以使用,而所有全局作用域对象都会作为window
对象的属性来使用。// 变量都是存在在全局作用域下面的,都是可以在任意地方使用的 var num = 10 var age = 20
局部作用域(函数作用域)
-
局部作用域:由于跟函数有关,所以也称为函数作用域。
-
局部作用域:是在调用函数时才会被创建,函数执行完毕后就自动销毁。
-
而且,每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的。
-
在
JS
中只有函数能生成一个局部作用域,别的都不行 -
每一个函数,都是一个局部作用域
// 这个 num 是一个全局作用域下的变量 在任何地方都可以使用 var num = 100 function fn() { // 下面这个变量就是一个 fn 局部作用域内部的变量 // 只能在 fn 函数内部使用 var num2 = 200 } fn()
自动全局作用域
-
如果您为尚未声明的变量赋值,此变量会自动成为全局变量。
function fn() { //此时这个num 没有声明 所以它自动成为全局变量 num = 10 console.log(num) } fn() console.log(num)
-
注意:如果在函数中定义变量时没有指定 var 关键字,那么这个变量会自动提升为全局作用域的变量。
从执行效率来看全局变量
- 全局变量只有浏览器关闭的时候才会销毁,比较占内存资源
- 局部变量当我们程序执行完毕就会销毁, 比较节约内存资源
作用域链(重点)
-
作用域链就是执行环境中变量对象中变量和函数访问的顺序。
-
作用域的集合就是作用域链(子集可以访问父集,父集不能访问子集)
-
在局部作用域中可以访问全局作用域,而在全局作用域中不能访问局部作用域。
-
当在局部作用域中使用一个变量时,它会先在自身作用域内查找,如果找到就直接使用,如果没有找到则会向上一级作用域查找,直到找到全局作用域为止。如果都没有找到则会报错。
-
内部函数访问外部函数的变量,采取的是链式查找的方式来决定取那个值 这种结构我们称为作用域链 就近原则
var num = 10 function fn() { // 外部函数 var num = 20 function fun() { // 内部函数 console.log(num)//站在目标层 一层一层往外查找 先在自身函数里寻找对应的变量,没找到 //然后跳出自身函数取上一层找,找到了,使用,这时num=20 //但是如果这时候在外一层还没有找到就会继续向上找 } fun()//因为调用了fun函数是打印20 } fn() //20
一个小理解:
- 全局的变量就是window的属性
- 全局的函数就是window的方法
块级作用域(了解,es6新增)
- 块级作用域可通过新增命令 let 和 const 声明,所声明的变量在指定块的作用域外无法被访问。
- 块级作用域在如下情况被创建:
- 在一个函数内部
- 在一个代码块(由一对
{}
包裹)内部 - let 声明的语法与 var 的语法一致。
- 基本上可以用 let 来代替 var 进行变量声明,但会将变量的作用域限制在当前代码块中。
- 块级作用域有以下几个特点:
- 声明变量不会提升到代码块顶部
- let/const 声明并不会被提升到当前代码块的顶部,因此你需要手动将 let/const 声明放置到顶部,以便让变量在整个代码块内部可用。
递归函数
-
在编程世界里面,递归就是一个自己调用自己的手段
-
递归函数: 一个函数内部,调用了自己,循环往复
// 在函数内部调用了自己,函数一执行,就调用自己一次,在调用再执行,循环往复,没有止尽 function fn() { fn() } fn()
-
其实递归函数和循环很类似
-
需要有初始化,自增,执行代码,条件判断的,不然就是一个没有尽头的递归函数,我们叫做 死递归
简单实现一个递归
-
我们先在用递归函数简单实现一个效果
-
需求: 求 1 至 5 的和
- 先算 1 + 2 得 3
- 再算 3 + 3 得 6
- 再算 6 + 4 得 10
- 再算 10 + 5 得 15
- 结束
-
开始书写,写递归函数先要写结束条件(为了避免出现 “死递归”)
function add(n) { // 传递进来的是 1 // 当 n === 5 的时候要结束 if (n === 5) { return 5 } } add(1)
-
再写不满足条件的时候我们的递归处理
function add(n) { // 传递进来的是 1 // 当 n === 5 的时候要结束 if (n === 5) { return 5 } else { // 不满足条件的时候,就是当前数字 + 比自己大 1 的数字 return n + add(n + 1) } } add(1)