• JavaScript -- 06.函数知识汇总


    函数

    1 函数介绍

    函数(Function)

    • 函数也是一个对象
    • 它具有其他对象所有的功能
    • 函数中可以存储代码,且可以在需要时调用这些代码

    语法:

    function 函数名(){
    	语句...
    }
    
    • 1
    • 2
    • 3

    调用函数:

    • 调用函数就是执行函数中存储的代码
    • 语法:函数对象()

    使用 typeof 检查函数对象时会返回 function

    image-20221201140842209

    2 函数的创建方式

    三种方式

    • 函数声明
    • 函数表达式
    • 箭头函数

    2.1 函数声明

    function 函数名(){
        语句...
    }
    
    • 1
    • 2
    • 3

    示例:

    function fn(){
        console.log("函数声明所定义的函数~")
    }
    
    • 1
    • 2
    • 3

    2.2 函数表达式

    const 变量 = function(){
        语句...
    }
    
    • 1
    • 2
    • 3

    示例:

    const fn2 = function(){
        console.log("函数表达式")
    }
    
    • 1
    • 2
    • 3

    2.3

    () => {
        语句...
    }
    
    • 1
    • 2
    • 3

    示例:

    const fn3 = () => {
        console.log("箭头函数")
    }
    
    const fn4 = () => console.log("箭头函数")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3 参数

    • 形式参数

      • 在定义函数时,可以在函数中指定数量不等的形式参数(形参

      • 在函数中定义形参,就相当于在函数内部声明了对应的变量但是没有赋值

    • 实际参数

      • 在调用函数时,可以在函数的()传递数量不等的实参

      • 实参会赋值给其对应的形参

      • 参数:

        • 如果实参和形参数量相同,则对应的实参赋值给对应的形参

        • 如果实参多于形参,则多余的实参不会使用

        • 如果形参多于实参,则多余的形参为undefined

      • 参数的类型

        • JS中不会检查参数的类型,可以传递任何类型的值作为参数
    // 计算两数之和
    function sum(a, b){
        console.log(a + b)
    }
    
    • 1
    • 2
    • 3
    • 4

    3.1 函数声明

    function 函数名([参数]){
        语句...
    }
    
    • 1
    • 2
    • 3

    3.2.函数表达式

    const 变量 = function([参数]){
    	语句...
    }
    
    • 1
    • 2
    • 3

    3.3 箭头函数

    ([参数]) => {
    	语句...
    }
    
    • 1
    • 2
    • 3

    例子:

    const fn = (a, b) => {
        console.log("a =", a);
        console.log("b =", b);
    }
    
    fn(123, 456)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    箭头函数只有一个参数的时候可以省略()

    const fn2 = a => {
        console.log("a =", a);
    }
    
    • 1
    • 2
    • 3

    3.4 默认参数

    定义参数的时候,可以指定默认值,默认值会在没有对应实参时候生效

    const fn3 = (a=10, b=20, c=30) => {
        console.log("a =", a);
        console.log("b =", b);
        console.log("c =", c);
    }
    
    fn3(1, 2) // a = 1, b = 2, c = 30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.5 使用对象作为参数

    对象可以作为参数传递,传递的是变量中存储的值,也就是对象的内存地址,所以形参和实参指向的就是同一个对象

    • 当修改形参指向的时候,只会影响当前变量(形参指向改变)
    • 当修改对象中的参数的时候,如果有其他变量指向该对象则所有指向该对象的变量都会受到影响

    这个必须得区分清楚

    function fn(a){
        console.log("a =", a)
        // a = {} // 修改变量时,只会影响当前的变量
        a.name = "猪八戒" // 修改对象时,如果有其他变量指向该对象则所有指向该对象的变量都会受到影响
        console.log(a)
    }
    
    let obj = {name:"孙悟空"}
    fn(obj)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果形参的默认值是一个对象的话,那么在函数每次调用的时候,都会重新创建新对象

    // 函数每次调用,都会重新创建默认值
    function fn2(a = {name:"沙和尚"}){
        console.log("a =", a)
        a.name = "唐僧"
        console.log("a =", a)
    }
    
    fn2() // 沙和尚 唐僧
    fn2() // 沙和尚 唐僧
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    但是如果是下面这个样子的话,每次指向的都是obj2,不会重新创建对象

    let obj2 = {name:"沙和尚"}
    
    // 函数每次调用,都会重新创建默认值
    function fn2(a = obj2){
        console.log("a =", a)
        a.name = "唐僧"
        console.log("a =", a)
    }
    
    fn2() // 沙和尚 唐僧
    fn2() // 唐僧 唐僧
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.6 函数作为参数

    在JS中,函数也是一个对象(一等函数),别的对象能做的事情函数也可以

    所以别的对象可以作为参数传递到函数中,函数也可以作为参数传递到函数中

    function fn(a) {
        console.log("a =", a)
        a()
    }
    
    function fn2() {
        console.log("fn2被调用了")
    }
    
    fn(fn2) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20221201143035741

    有了这种功能之后,我们就可以动态的执行代码

    fn(() => console.log("我是箭头函数"))
    
    function exec(desc, fn, arg1, arg2) {
        console.log(desc + " = " + fn(arg1, arg2))
    }
    
    function add(arg1, arg2) {
        return arg1 + arg2
    }
    
    function sub(arg1, arg2) {
        return arg1 - arg2
    }
    
    exec("3 + 5", add, 3, 5)
    exec("5 - 3", sub, 5, 3)
    exec("5 * 3", (arg1, arg2) => {return arg1 * arg2}, 5, 3) // 使用匿名函数作为参数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20221201143416306

    4 函数的返回值

    在函数中,可以通过return关键字来指定函数的返回值,返回值就是函数的执行结果,函数调用完毕返回值便会作为结果返回

    任何值都可以作为返回值使用(包括对象和函数之类),如果return后不跟任何值,则相当于返回undefined,如果不写return,那么函数的返回值依然是undefined

    function sum(a, b) {
        // console.log(a + b)
        // 计算完成后,将计算的结果返回而不是直接打印
        return a + b
    }
    
    let result = sum(2,3) // 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    return一执行函数立即结束

    function sum(a, b) {
        return a + b
        console.log(a + b) // 不会执行这条语句
    }
    
    • 1
    • 2
    • 3
    • 4

    箭头函数的返回值可以直接写在箭头后,如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来

    const sum = (a, b) => {
        return a + b
    }
    
    const sum1 = (a, b) => a + b
    const fn = () => ({name: "张三"})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5 作用域

    • 作用域指的是一个变量的可见区域
    • 作用域有两种:
      • 全局作用域
        • 全局作用域在网页运行时创建,在网页关闭时销毁
        • 所有直接编写到script标签中的代码都位于全局作用域中
        • 全局作用域中的变量是全局变量,可以在任意位置访问
      • 局部作用域
        • 块作用域
          • 块作用域是一种局部作用域
          • 块作用域在代码块执行时创建,代码块执行完毕它就销毁
          • 在块作用域中声明的变量是局部变量,只能在块内部访问,外部无法访问

    5.1 函数作用域

    • 函数作用域也是一种局部作用域
    • 函数作用域在函数调用时产生,调用结束后销毁
    • 函数每次调用都会产生一个全新的函数作用域
    • 在函数中定义的变量是局部变量,只能在函数内部访问,外部无法访问
    function fn(){
        let a = "fn中的变量a"
        console.log(a)
    }
    
    fn()
    console.log(a) // undefined 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.2 作用域链

    当我们使用一个变量时,JS解释器会优先在当前作用域中寻找变量,就近原则

    • 如果找到了则直接使用
    • 如果没找到,则去上一层作用域中寻找,找到了则使用
    • 如果没找到,则继续去上一层寻找,以此类推
    • 如果一直到全局作用域都没找到,则报错 xxx is not defined

    参照下面的例子理解

    报错的原因看下一节提升相关概念

    let a = 10
    
    {
        console.log(a) // 报错,Cannot access 'a' before initialization
        let a = "第一代码块中的a"
        console.log(a) // "第一代码块中的a"
        {
            console.log(a) // 报错,Cannot access 'a' before initialization
            let a = "第二代码块中的a"
            console.log(a) // "第二代码块中的a"
        }
        console.log(a) // "第一代码块中的a"
    }
    console.log(a) // 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    let b = 33
    
    function fn() {
        console.log(b) // 报错,Cannot access 'b' before initialization
        let b = 44
        console.log(b) // 44
        function f1() {
            console.log(b) // 报错,Cannot access 'b' before initialization
            let b = 55
            console.log(b) // 55
        }
        f1()
    }
    
    fn()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.3 练习

    练习1

    var a = 1
    
    function fn() {
        a = 2
        console.log(a) // 2
    }
    
    fn()
    console.log(a) // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    练习2

    var a = 1
    
    function fn() {
        console.log(a) //undefined
        var a = 2
        console.log(a) // 2
    }
    
    fn()
    console.log(a) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    练习3

    var a = 1
    
    function fn(a) {
        console.log(a) //undefined
        a = 2 	// 修改的是形参
        console.log(a) // 2
    }
    
    fn()
    console.log(a) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    练习4

    var a = 1
    
    function fn(a) {
        console.log(a) //10
        a = 2
        console.log(a) // 2
    }
    
    fn(10)
    console.log(a) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    练习5

    var a = 1
    
    function fn(a) {
        console.log(a) //1
        a = 2
        console.log(a) // 2
    }
    
    fn(a)
    console.log(a) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    练习6

    console.log(a)  // a指向的是第五行的函数,var只是声明,并不赋值,如果声明过不会重复赋值
    
    var a = 1
    console.log(a) // 1
    function a() {
        alert(2)
    }
    console.log(a) // 1
    var a = 3
    console.log(a) // 3
    var a = function () {
        alert(4)
    }
    console.log(a) // 打印11行函数
    var a	// 已经声明过了,根本不执行
    console.log(a) // 打印11行函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    6 window对象

    • 在浏览器中,浏览器为我们提供了一个window对象,可以直接访问
    • window对象代表的是浏览器窗口,通过该对象可以对浏览器窗口进行各种操作,除此之外window对象还负责存储JS中的内置对象和浏览器的宿主对象
    • window对象的属性可以通过window.对象名访问,也可以直接通过对象名访问
    • 函数就可以认为是window对象的方法
    • 向window对象中添加的属性会自动成为全局变量
    window.a = 50
    console.log(window.a) // 50
    console.log(a) // 50
    
    
    • 1
    • 2
    • 3
    • 4

    var:用来声明变量,作用和let相同,但是var不具有块作用域

    • 在全局中使用var声明的变量,都会作为window对象的属性保存

      • var b = 20 等价于 window.b = 20
    • 使用function声明的函数,都会作为window的方法保存

      function fn(){
          alert('我是fn')
      } 
      fn() // 等价于window.fn()
      
      • 1
      • 2
      • 3
      • 4
    • 使用let声明的变量不会存储在window对象中,而存在一个秘密的小地方(无法访问)

    • var虽然没有块作用域,但有函数作用域

      function fn2(){
          var d = 10 // var虽然没有块作用域,但有函数作用域
          m = 10 // 在局部作用域中,如果没有使用var或let声明变量,则变量会自动成为window对象的属性 也就是全局变量
      }
      
      fn2()
      
      console.log(d) // d is not defined 
      console.log(m) // 10
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    7 提升

    7.1 变量的提升

    使用var声明的变量,会在所有代码执行前被声明(而不是赋值),所以就可以在变量声明前就访问变量

    let声明的变量实际也会提升,但是在赋值之前解释器禁止对该变量的访问

    console.log(a) // 打印undefined
    var a = 10 
    
    console.log(b) // 报错 b is not defined
    b = 10
    
    console.log(c) // Cannot access 'c' before initialization
    let c = 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    7.2 函数的提升

    使用函数声明创建的函数,会在其他代码执行前被创建,所以我们可以在函数声明前调用函数

    只有以var, let或者function开头的变量才会提升

    fn() // 可以正常调用执行
    fn2() // 报错fn2 is not a function
    
    function fn(){
        alert("我是fn函数~")
    }
    var fn2 = function(){
        alert("我是fn函数~")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7.3 为什么要有提升

    是为了解决性能问题,要根据变量和函数的数量决定开多大的内存空间

    8 debug

    两种方式

    https://www.bilibili.com/video/BV1mG411h7aD?p=67

    1. 直接在代码中写debugger,在浏览器中打开控制台刷新页面后会停到这个位置

      debugger // 在代码中打了一个断点
      
      console.log(a) // 2
      var a = 1
      console.log(a) // 1
      
      • 1
      • 2
      • 3
      • 4
      • 5
    2. 直接打开浏览器,F12 -> 源代码 -> 指定位置点击行号 -> 刷新

      image-20221201185036935

    9 立即执行函数

    在开发中应该尽量减少直接在全局作用域中编写代码

    所以我们的代码要尽量编写的局部作用域

    如果使用let声明的变量,可以使用{}来创建块作用域,这样也不会相互干扰

    {
        let a = 10
    }
    {
        let a = 20
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    而如果现在函数里面,哪怕是使用var声明的变量也不会相互干扰,但是使用函数的时候需要再单独调用一下

    function fn(){
        var a = 10
        }
    
    fn()
    
    function fn2(){
        var a = 20
        }
    
    fn2()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们希望可以创建一个只执行一次的匿名函数

    立即执行函数(IIFE)

    • 立即是一个匿名的函数,并它只会调用一次
    • 可以利用IIFE来创建一个一次性的函数作用域,避免变量冲突的问题
    • 外面包裹的括号不能少,是用来防止这个函数被提升;另外,最后的分号也不能省略,不然js会解析成(xxx)(xxx)
    (function(){
        let a = 10
        console.log(111)
    }());
    
    (function(){
        let a = 20
        console.log(222)
    }());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    10 函数中的this

    10.1 普通函数的this

    • 函数在执行时,JS解析器每次都会传递进一个隐含的参数,这个参数就叫做 this
    • this会指向一个对象
      • this所指向的对象会根据函数调用方式的不同而不同
        • 1.以函数形式调用时,this指向的是window

        • 2.以方法的形式调用时,this指向的是调用方法的对象

        • 但实际上都是指向调用的对象,以函数方式调用的时候默认调用的对象是window

    • 通过this可以在方法中引用调用方法的对象
    function fn() {
        // console.log(this === window)
        console.log("fn打印", this)
    }
    fn()	// 就相当于 window.fn()
    
    const obj = { name: "孙悟空" }
    obj.test = fn
    obj.test()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    案例:为两个对象添加一个方法,可以打印自己的名字

    方法一:

    const obj3 = {
        name: "沙和尚",
        sayHello: function () {
            console.log(this.name)
        },
    }
    const obj4 = { 
        name: "唐僧",
        sayHello: function(){
            console.log(this.name)
        }
    }
    
    // 为两个对象添加一个方法,可以打印自己的名字
    obj3.sayHello()
    obj4.sayHello()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    方法二:

    const sayHello = function(){
        console.log(this.name)
    }
    
    const obj3 = {
        name: "沙和尚",
        sayHello: sayHello
    }
    const obj4 = { 
        name: "唐僧",
        sayHello: sayHello
    }
    
    // 为两个对象添加一个方法,可以打印自己的名字
    obj3.sayHello()
    obj4.sayHello()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    10.2 箭头函数的this

    箭头函数:([参数]) => 返回值

    例子:

    • 无参箭头函数:() => 返回值

    • 一个参数的:a => 返回值

    • 多个参数的:(a, b) => 返回值

    • 只有一个语句的函数:() => 返回值

    • 只返回一个对象的函数:() => ({...})

    • 有多行语句的函数:

      () => {
      	....    
      	return 返回值
      }
      
      • 1
      • 2
      • 3
      • 4

    箭头函数没有自己的this,它的this由外层作用域决定,箭头函数的this和它的调用方式无关,外面的this是谁,它就是,如果在最外层,那么它的this就是window

    function fn() {
        console.log("fn -->", this)
    }
    
    const fn2 = () => {
        console.log("fn2 -->", this) // 总是window
    }
    
    fn() // window
    fn2() // window
    
    const obj = {
        name:"孙悟空",
        fn, // fn:fn的简写
        fn2,
        sayHello(){
            console.log(this.name)
    
            function t(){
                console.log("t -->", this)
            }
            t() // window
    
            const t2 = () => {
                console.log("t2 -->", this)
            }
            t2() // obj
        }
    }
    
    obj.fn() // obj
    obj.fn2() // window
    
    obj.sayHello()// 
    
    • 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
    • 31
    • 32
    • 33
    • 34

    11 严格模式

    JS运行代码的模式有两种:

    • 正常模式

      • 默认情况下代码都运行在正常模式中,
        • 在正常模式,语法检查并不严格,它的原则是:能不报错的地方尽量不报错
      • 这种处理方式导致代码的运行性能较差
    • 严格模式

      • 在严格模式下,语法检查变得严格

        • 禁止一些语法
        • 更容易报错
        • 提升了性能
      • 使用方式

        "use strict" // 全局的严格模式
        let a = 10
        
        function fn(){
            "use strict" // 函数的严格的模式
        }
        
        // 如果在严格模式下定义如:
        a = 10 // 则会报错a is not defined
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
    • 在开发中,应该尽量使用严格模式,这样可以将一些隐藏的问题消灭在萌芽阶段,同时也能提升代码的运行性能

  • 相关阅读:
    python中的super是什么?
    GMSM,CSM总结
    【配置】如何在打包Spring Boot项目时按需使用日常、测试、预发、正式环境的配置文件
    《WEB前端框架开发技术》HTML5响应式旅游景区网站——榆林子州HTML+CSS+JavaScript
    计算机毕业设计node.js+vue+Web的校园疫情大数据平台
    MFC Windows 程序设计[260]之多种控件展示(附源码)
    杰理之CMD_SET_BT_ADDR【篇】
    蓝桥等考Python组别八级005
    大模型日报 2024-07-12
    分析日志的一般套路
  • 原文地址:https://blog.csdn.net/qq_46311811/article/details/128137396