函数

154 阅读7分钟

1 函数的返回值

函数体内可以使用retur关键字表示"函数的返回值"

image.png

函数的返回值

调用一个有返回值的函数,可以被当作一个普通值,从而可以出现在任何可以书写值的地方

image.png

image.png

遇见return即退出函数

调用函数时 一旦遇见return语句则会立即退出函数 将执行权交还给调用者

image.png

结合if语句的时候 往往不需要写else分支了

比如题目:请编写一个函数 判断一个数字是否是偶数

<script>
    //书写一个函数 函数的功能判断一个数字是否是偶数
    function checkEven (n) {
        if(n % 2 == 0) return true ; 
        return false
    }
    
    var result = checkEven(6)
    console.log(result)
</script>    

函数像一个"小工厂"

image.png

2 函数的参数与返回值

函数的参数

参数是函数内的一些待定值 在调用函数时 必须传入这些参数的具体值

函数的参数可多可少 函数可以没有参数 也可以有多少个参数 多个参数之间需要用逗号隔开

// 圆括号中定义"形式参数"
function add (a,b) {
    var sum = a + b ; 
    console.log("两个数字的和时" + sum )
}
// 调用函数时传入"实际参数"
add( 3 , 5 )

"形实结合"

image.png

形参和实参个数不同的情况

image.png

<script>
    function add (a,b){
        var sum = a + b ; 
        console.log("两个数字的和" + sum)
    }
    
    add(4,5)
    add(2,2)
    add(6,3)
</script>

分别计算1到10 5到12 14到35的整数和

<script>
    //定义一个函数 这个函数的功能就是计算ab之间所有的整数的和
    function calcSumFromAtoB (a,b) {
        for (var i = a , sum = 0 ; i <= b ; i++) {
            sum += i ; 
        }
        console.log("从" + a + "到" + b + "的所有整数和是" + sum)
    }
    
    calcSumFromAtoB(3,7)
</script>

arguments

函数内arguments表示它接收到的实参列表 它是一个类数组对象

类数组对象:所有属性均为从0开始的自然数序列 并且有length属性 和数组类似可以用方括号书写下标访问对象的某个属性值 但是不能调用数组的方法

<script>
    // 不管用户传入多少个实际参数 永远能够计算它们的和
    function fun () {
        // console.log(arguments)
        // []方括号内为下标
        // console.log(arguments[0])
        // console.log(arguments[1])
        var sum = 0 ; 
        for(var i = 0 ; i < arguments.length ; i++) {
            sum += arguments[i]
        }
        console.log("所有参数的和是" + sum)
    }
    
    fun(33,44,23,34)
</script>

3 函数的调用

函数的调用

执行函数体中的所有语句 就称为"调用函数"

调用函数非常简单 秩序在函数名字后书写圆括号对即可

fun() //调用函数

<script>
    function fun () {
        console.log("你好")
        console.log("今天天气真好")
    }
    //函数必须等到调用的时候才能被执行
    fun()
</script>

语句执行顺序

image.png

函数声明的提升

和变量声明类似 函数声明也可以被提升

image.png

函数表达式不能提升

如果函数是用函数表达式的写法定义的 则没有提升特性

image.png

函数优先提升

image.png

4 函数的定义与调用

函数的定义

和变量类似 函数必须先定义然后才能使用

使用function关键字定义函数 function是"功能"的意思

image.png

函数表达式

image.png

函数的调用

执行函数体中的所有语句 就称为"调用函数" 调用函数非常简单 只需在函数名字后书写圆括号对即可

fun() //调用函数

5 什么是递归

递归

函数的内部语句可以调用这个函数自身 从而发起对函数的一次迭代 在新的迭代中 又会执行调用函数自身的语句 从而又产生一次迭代 当函数执行到某一次时 不再进行新的迭代 函数被一层层返回 函数被递归

递归是一种较为高级的变成技巧 它把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解

求4的阶乘举例

image.png

递归的要素

边界条件 : 确定递归到何时终止 也成为递归出口

递归模式 : 大问题是如何分解为小问题的 也成为递归体

代码演示

<script>
    // 书写一个函数 这个函数内部自己会调节自己 从而形成递归
    function factorial (n) {
        //  !为阶乘
        // 这个函数的功能是计算n的阶乘 n!不就是 n * (n - 1)!么?
        // 这个就是递归的出口 如果计算1的阶乘 可以不用递归了 直接告诉你答案就是1
        if (n == 1 ) return 1
        // 如果询问的不是1的阶乘 就返回 n * (n - 1)
        return n * factorial(n - 1)
    }
    
    var result = factorial(4)
    
    alter(result)
</script>

6 实现深克隆

复习引用类型值的相关知识

JavaScript中的数据类型有两类 : 基本类型和引用类型值

image.png

复习浅克隆

使用 var arr2 = arr1 这样的语句不能实现数组的克隆

浅克隆 : 准备一个空的结果数组 然后使用佛如循环遍历原数组 将遍历大的项都推入结果数组

浅克隆值克隆数组的一层 图个数组的多维数组 则克隆的项会"藕断丝连"

<script>
    // 准备原数组
    var arr1 = [33,44,11,22]
    // 测试一些这样的语句能否克隆数组
    var arr2 = arr1
    
    arr1.push(66)
    console.log(arr2)
    console.log(arr1 == arr2)
</script>
<script>
    // 准备原数组
    var arr1 = [33,44,11,22,[77,88]]
    // 准备一个结果数组
    var result = []
    
    // 遍历原数组 将遍历到的项都推入到结果数组中
    for (var i = 0 ; i < arr1.length ; i++) {
        result.push(arr[i])
    }
    
    // 
    arr1[4].push(arr1[i])
    
    // 输出原数组
    console.log(result)
    
    // 测试是否实现了克隆 就是说本质上是内存中的不同数组了
    console.log(arr1 == result)
    
    arr1[4].push(99)
    
    arr1.push(666)
    
    console.log(result)
</script>

实现深克隆

使用递归思想 整体思路和浅克隆类似 但稍微进行一些改动 如果遍历到项是基本类型值 则直接推入结果数组 如果遍历到的项是又是数组 则重复执行浅克隆的操作

<script>
    // 原数组
    var arr1 = [33,44,11,22,[77,88,[33,44]]]
    
    // 函数 这个函数会被递归
    function deepClone(arr) {
        // 结果数组
        var result = []
        // 遍历数组的每一项
        for (var i = 0 ; i < arr.length ; i++){
            // 类型判断 如果遍历到的项是数组
            if(Array.isArray(arr[i])){
                // 递归
                deepClone(arr[i])
            }else{
                // 如果遍历到的项不是数组 是基本类型值 就直接推入到结果数组中
                // 相当于是递归的出口
                result.push(arr[i])
            }
        }
        // 返回结果数组
        return result ; 
    }
    
    // 测试一下
    var arr = deepClone(arr1)
    console.log(arr2)
    
    // 是否藕断丝连
    console.log(arr1[4] == arr2[4][2])
    arr[4].push(99)
    console.log(arr1)
    console.log(arr2)
</script>

7 全局变量和局部变量

遍历作用域

JavaScripts 函数级作用域编程语言: 变量只在其定义时所在的function内部有意义

image.png

全局变量

如果不将变量定义在任何函数内部 此时这个变量就是全局变量 它在任何函数内都可以被访问和更改

image.png

<script>
    var a ;
    function fun () {
        a = 5
        a++
    }
    function()
    console.log(a)
</script>

遮蔽效应

如果函数中也丁轶可和全局同名的变量 则函数内的变量会将全局的变量"遮蔽"

image.png

<script>
    // 全局变量
    var a = 10
    
    function fun {
        // 局部变量 会把全局的变量a遮蔽
        var a = 5
        a++
        console.log(a)
    }
    fun()
    console.log(a)
</script>

注意考虑变量声明提升的情况

这个程序的运行结果是什么呢?

image.png

<script>
    var m = 
    
    function fun () {
        m++
        var m = 4 
        consolelog(m)
    }
    fun()
    consolelog(m)
    
</script>

8 形参也是局部变量

作用域链

先来认识函数的嵌套:一个函数内部也可以定义一个函数 和局部变量类似 定义在一个函数内部的函数是局部函数

image.png

在函数嵌套中 变量会从内到外逐层寻找它的定义

image.png

<script>
    var a = 1
    var b = 2
    function fun () {
        c = 3
        c++
        var b = 4
        b++
        console.log(b)  // 5
    }

    fun()
    console.log(b) // 2
    // 在函数外部 的可以访问变量C的
    console.log(c)
</script>

不加var将定义全局变量

在初次给变量赋值时 如果没有加var 则将定义全局变量

image.png

9 闭包

什么是闭包

JavaScript 中函数会产生闭包(closure) 闭包是函数本身和该函数声明时所处的环境状态的组合

image.png

什么是闭包

函数能够"记忆住"其定义时所处的环境 即使函数不在其定义的环境中被调用 也能访问定义时所处环境的变量

image.png

观察闭包现象

在JavaScript中 每次创建函数时都会创建闭包

但是 闭包特性往往需要将函数"换一个地方"执行 才能被观察出来

闭包非常实用

闭包很有用 因为它允许我们将数据与操作该数据的函数关联起来 这与"面向对象编程"有少许相似之处

闭包的功能:记忆性 模拟私有变量

闭包的用途1 记忆性

当闭包产生时 函数所处环境的状态会始终保持在内存中 不会在外层函数调用后被清除 这就是闭包的记忆性

10 闭包的记忆

闭包的记忆性举例

闭包的记忆性举例(n) 可以检查体温n是否正常 函数会返回布尔值

但是不同的小区有不同的体温监测标准 比如A小区体温合格线是37.1° 而B校区体温合格线是37.3° 应该怎么编程呢?

 <script>
    function createCheckTemp(standardTemp) {
        function checkTemp(n) {
            if (n <= standardTemp) {
                alert("你的体温正常")
            } else {
                alert("你的体温偏高")
            }
        }
        return checkTemp
    }
    // 创建一个check Temp函数 它以37.1°为标准线
    var checkTemp_A = createCheckTemp(37.1)
    // 再创建一个check Temp函数 它以37.3°为标准线
    var checkTemp_B = createCheckTemp(37.3)
    checkTemp_A(37.0)
    checkTemp_B(37.4)
</script>

11 闭包的应用

闭包用途2 模拟私有变量

题目:请定义一个变量a 要求是只能保证这个a只能被进行指定操作(如加1 乘2) 而不能进行其他操作 应该怎么编程呢?

在Java C++等语言中 有私有属性的概念 但是JavaScript中只能用闭包来模拟

<script>
    // 请定义一个变量a 要求是能保证这个a只能被进行指定操作(如+1  *2) 而不能进行其他操作 应该怎么编程呢?
    // 封装一个函数 责怪函数的功能就是私有化变量
    function fun () {
        // 定义一个局部变量a
        var a = 0

        return {
            getA: function() {
                return
            },
            add: function() {
                a++
            },
            pow: function() {
                a *= 2
            }
        }
    }
    var obj = fun()
    // 如果想在fun函数外面使用变量a 唯一的方法就是调用getA()方法
    console.log(obj.getA())
    // 想让变量a进行加一操作
    obj.add()
    obj.add()
    obj.add()
    console.log(obj.get())
    obj.pow()
</script>

使用闭包的注意点

不能滥用闭包 否则会造成网页的性能问题 严重时可能导致内存泄漏 所谓内存泄漏是指程序中已动态分配的内存由于某种原因未释放或无法释放

闭包的一道面试题

image.png

立即执行函数IIFE

什么是IIFE

IIFE(immediately Invoked Function Expression 立即调用函数表达式)是一种特殊的JavaScript函数写法 一旦被定义 就立即调用

image.png

形成IIFE的方法

函数不能直接加圆括号被调用

image.png

函数必须转为"函数表达式"才能被调用

image.png

IIFE的作用1 为变量赋值

为变量赋值:当给变量赋值需要一些较为复杂的计算时(如if语句) 使用IIFE显得语法更紧凑

image.png

<script>
    var age = 12
    var sex = "男"
    var title = (function () {
        if(age < 18) {
            return "小朋友"
        } else {
            if (sex == "男") {
                return "先生"
            } else {
                return "女士"
            }
        }
    })()
        
    alert(title)
</script>

IIFE的作用

先来看一个题目

image.png

<script>
    var arr = []
    for (var i = 0 ; i < 5 ; i++) {
        (function(i) {
            arr.push(function() {
                alert(i)
            })
        })(i)
    }
    arr[0]()
    arr[1]()
    arr[2]()
    arr[3]()
    arr[4]()
</script>

IIFE的作用 将全局变量变为局部变量

IIFE可以在一些场合(如for循环中) 将全局变量变为局部变量 语法显得紧凑

image.png