• 03-JavaScript-函数


    函数就是将特定功能的代码封装起来,当需要实现特定功能时,直接调用函数实现即可。

    一、函数定义和使用

    1.1 定义函数
    • 语句式命名函数
    function sum(a,b) {
    	return a + b;
    }
    
    • 1
    • 2
    • 3
    • 变量函数表达式
      是一个匿名函数,它没有函数名
    let sum = function(a,b) {
    	return a + b;
    };
    
    • 1
    • 2
    • 3
    1.2 调用函数

    按顺序传入参数

    console.log(sum(1,3))
    function sum(a,b) {
    	return a + b;
    }
    
    let sum2 = function(a,b) {
    	return a + b;
    };
    console.log(sum2(1,3))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    函数声明提升:
    语句式命名函数存在函数声明提升,函数表达式声明函数函数时,不可以把函数调用写在函数声明前面,因为变量赋值不存在提升。

    1.3 arguments
    • 在函数内部起作用,指向当前函数的调用者传入的所有参数,它是一个类似数组的对象
    let sum2 = function(a,b) {
        console.log(arguments)  //[1, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
        console.log(arguments[2]) //4
    
    	return a + b;
    };
    sum2(1,3,4,5)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • arguments…length 获取调用函数实参个数
    • callee 属性,指向拥有这个arguments对象的函数
    1.4 rest参数

    剩余参数,遵循传入顺序后的,所有参数。rest参数只能写在最后,前面用…标识

    let sum = function(a,b,...rest) {
        console.log(rest) //[4, 5]
    
    	return a + b;
    };
    sum2(1,3,4,5)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、变量作用域与解构赋值

    2.1 变量提升

    JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部

    2.2 全局作用域

    函数之外声明的变量,会成为全局变量,就具有全局作用域

    2.3 局部作用域

    函数中声明的变量,会成为函数的局部变量,在函数外部不能访问

    2.4 作用域链

    当前作用域没有定义的变量,会顺着作用域向父作用域找,如果父级也没有找到,再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。

    2.5 名字空间

    用于解决相同的全局变量、相同名字的函数,造成命名冲突。

    var NAME_SPACE = {}
    NAME_SPACE.name = ''
    NAME_SPACE.sum = function() {}
    
    • 1
    • 2
    • 3
    2.6 常量

    let、var声明的是变量,要声明一个常量,使用ES6的const声明,通常用全部大写的变量来表示“这是一个常量,不要修改它的值”,谷歌浏览器下,赋值会报错。

    const PI = 3.14
    PI = 3 // index.html:53 Uncaught TypeError: Assignment to constant variable.
    console.log(PI)
    
    • 1
    • 2
    • 3
    2.7 解构赋值

    可以同时对一组变量进行赋值

    • 数组解构(注意嵌套层次和位置要保持一致)
    let arr = ['诸葛亮', ['刘备', '关羽'], '张飞'];
    let [o, [p,q], r] = arr;
    console.log(o,p,q,r) //诸葛亮 刘备 关羽 张飞
    
    • 1
    • 2
    • 3
    • 对象进行解构赋值
      正常解构
    let obj = {
        name: '张飞',
        age: 37,
        country: '蜀国'
    }
    let {name, age, country} = obj
    console.log(name,age,country) //张飞 37 蜀国
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    嵌套的对象属性进行赋值:
    address不是变量,而是为了让country、province、city获得嵌套的address对象的属性

    let obj = {
        name: '张飞',
        age: 37,
        address: {
            country: '蜀国',
            province: '四川',
            city: '成都'
        }
    }
    let {name, age, address:{country,province,city}} = obj
    console.log(name,country,province,city) //张飞 蜀国 四川 成都   
    console.log(address) //Uncaught ReferenceError: address is not defined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当解构赋值,对应属性不存在时,会赋值undefined

    let obj = {
        key: '004',
        name: '张飞',
        age: 37,
        address: {
            country: '蜀国',
            province: '四川',
            city: '成都'
        }
    }
    // key不是变量,而是为了让变量code获得key属性
    let {name,id,key:code} = obj
    console.log(name) //张飞
    console.log(id) // undefined
    console.log(code) // 004
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    解构赋值还可以使用默认值

    let obj = {
        key: '004',
        name: '张飞',
        age: 37,
        address: {
            country: '蜀国',
            province: '四川',
            city: '成都'
        }
    }
    let {name,id='001'} = obj
    console.log(name) //张飞
    console.log(id) // 001
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 使用场景
      1、直接获取某对象里面的属性值,不需要通过.来获取和连续声明变量
    let {x,y,z} = {}
    
    • 1

    2、函数接收一个对象作为参数,可以直接用解构把对象属性赋值给变量

    sum({x,y,z=0}){
    	return x+y+z
    }
    
    • 1
    • 2
    • 3

    3、变量值替换,减少中间变量

    let x=1, y=2;
    [x,y] = [y,x]
    console.log(x,y) //2 1
    
    • 1
    • 2
    • 3

    三、方法

    一个对象中绑定函数,这个函数称为这个对象的方法。

    let obj = {
        name: '张飞',
        birth: 1993,
        age: function() {
            let y = new Date().getFullYear()
            return y - this.birth
        }
    }
    console.log(obj.age()) //30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    3.1 this指向

    this是执行环境上下文对象 ,它指向谁取决于它什么时候被调用、被谁调用。

    • 在方法中,this表示该方法所属的对象
      也就是说,谁执行这个方法,this就指向谁
    let person = {
        name: '张飞',
        birth: 1993,
        say: function() {
            console.log(this)
            console.log(this.name)
        }
    }
    // person对象调用了方法,this指向person
    person.say()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    let person = {
        name: '张飞',
        birth: 1993,
        say: function() {
            console.log(this)
            console.log(this.name)
        }
    }
    // 全局对象调用say方法,因此this指向window
    let say = person.say
    say()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 单独使用,this 表示全局对象,浏览器中指向向window
    • 函数中使用this,表示全局对象,浏览器中指向向window
    • 事件中的this,指向了接收事件的 HTML 元素
    • 显示函数绑定
      js中函数也是对象,对象中则有方法,apply 和 call 就是函数对象的方法,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。
    let personBase = {
        name: '张飞',
        birth: 1993,
        say: function() {
            console.log(this)
            console.log(this.name)
        }
    }
    let person = {
        name: '刘备',
    }
    let say = personBase.say
    say.call(person) //指向person
    say.apply(personBase) //personBase
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.2 调用函数扩展

    • 函数名(实参列表):say(a,b)
    • 函数名.call(执行环境对象,实参列表):say.call(person,a,b,c…)
      call 方法用来改变 this 的指向,第一个参数表示 this 的指向,后面的参数表示传递的实参,可以零个、一个或多个
    • 函数名.apply(执行环境对象,实参列表数组):say.apply(person,[a,b,c])
      apply 方法和 call 方法基本相同,也是用来改变 this 的指向,不同点在于,apply 传递实参时,采用数组的形式。
    • 函数名.bind(执行环境对象)(实参列表):say.bind(person).(a,b,c)
      bind 也是用来改变 this 的指向的,但是 bind(执行环境对象) 返回的是函数,还没有被执行,需要再使用()执行函数。
    • 回调函数
      主函数的事先做完,回头再调用传进来的那个函数

    四、立即执行函数

    声明函数的同时立即调用这个函数,立即执行函数是一个匿名函数

    4.1 作用

    1、页面加载完成后只执行一次的设置函数。
    2、将设置函数中的变量包裹在局部作用域中,不会泄露成全局变量

    4.2 书写方式
    // a b 形参
    (function(a,b){
    	//函数体内容
        console.log(a,b)
    }(1,2));//形参  立即执行函数结束要加分号
    
    (function(a,b){
        console.log(a,b)
    })(3,4)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    4.3 用例

    由于log调用前,循环执行结束,所以输出5

    for(let i=0; i<6; i++) {
        function log() {
            console.log(i)
        }
    }
    log() //5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用立即执行函数

    for(let i=0; i<6; i++) {
        (function(j) {
            console.log(i)  //0,1,2,3,4,5
        })(i)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    五、闭包

    闭包是指有权访问另一个函数中的变量函数。(定义一个在函数内部的函数,内部函数可以访问外包函数的局部变量)

    5.1 闭包形成条件
    • 函数嵌套
    • 内部函数使用到了外部函数内容(变量、函数等)
    • 参数和变量不会被回收
    function fun () {
        var x = 0, y = 1;
        function sum() {
            console.log(x + y)
            return x + y
        }
        return sum
    }
    let f = fun()
    f()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    5.2 闭包的作用
    • 可以读取函数内部的变量
    • 让这些变量的值始终保持在内存中
    function fun () {
        var n = 0;
        function sum() {
            n += 1
            console.log(n)
        }
        return sum
    }
    var f = fun()
    f() //1
    f() //2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    fun执行两次, n分别输出1 和 2,说明fun中的局部变量n一直存在内存中,没有在fun调用后,自动清除。
    未清除的原因:sum函数是fun的子函数,fun执行后,把sum函数赋值给了全局变量f,全局变量f一直存在,所以sum函数一直存在,而sum又依赖于fun函数,所以fun一直存在内存中,未在调用结束后被垃圾回收机制回收。

    5.3 闭包的注意事项
    • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题
    • 闭包会在父函数外部改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。多个子函数都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响

    六、箭头函数

    箭头函数相当于匿名函数,简化了函数定义

    6.1 定义方式
    // 省略简写方式
    let fun1 = (x,y)=> x+y;
    
    // 多条语句
    let fun2 = (x,y)=> {
        console.log(x)
        return x+y
    }
    fun1(1,2)
    fun2(2,3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    6.2 this问题

    箭头函数内部的this是词法作用域,由上下文确定。
    箭头函数的外层如果有普通函数,那么箭头函数的this就是这个外层的普通函数的this,箭头函数的外层如果没有普通函数,那么箭头函数的this就是全局变量。

    • 箭头函数是匿名函数,不能作为构造函数,不可以使用new命令,否则后抛出错误
      箭头函数没有原型对象prototype这个属性
    • 箭头函数不绑定arguments,取而代之用rest参数解决,同时没有super和new.target
    • 箭头函数可以通过拓展运算符获取传入的参数。
    let fun2 = (...args)=> {
        console.log(args)
        return args[0]+args[1]
    }
    fun2(2,3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 使用call,apply,bind并不会改变箭头函数中的this指向
      当对箭头函数使用call或apply方法时,只会传入参数并调用函数,并不会改变箭头函数中this的指向。
      当对箭头函数使用bind方法时,只会返回一个预设参数的新函数,并不会改变这个新函数的this指向。
  • 相关阅读:
    【已实现】服务器jar包启动脚本、shell脚本
    JVS多账号统一登录方式介绍(包括低代码与原生应用)
    音视频(1) - FFmpeg4.3.4编译
    【Python】PySpark 数据计算 ④ ( RDD#filter 方法 - 过滤 RDD 中的元素 | RDD#distinct 方法 - 对 RDD 中的元素去重 )
    Postgresql中触发器的使用
    Mac终端常用命令
    Redis未授权访问漏洞复现
    堆叠式自动编码器(SAE)--学习笔记
    java计算机毕业设计家庭财务管理系统设计与实现MyBatis+系统+LW文档+源码+调试部署
    030:vue中使用md5进行数据加密示例
  • 原文地址:https://blog.csdn.net/qq_42563079/article/details/132981221