前言
该文章记录一些函数基本知识,慢慢将会添加到一些函数高级知识,所有内容均从网上整理而来,加上自己得理解做一个整合,方便工作中使用。
1.创建函数
- 函数表达式创建函数(匿名函数)
let fn = function([形参1,形参2,...形参N]){ console.log("我是匿名函数")//代码语句 } fn()//执行函数 - 使用函数声明创建函数
function 函数名([形参1,形参2,...形参N]){ console.log("我是一个函数")//代码语句 } 函数名() //执行函数 - 箭头函数(属于匿名函数)
let fn3 = ([形参1,形参2,...形参N])=>{ //代码语句 } //箭头函数的执行语句只有一句时,可以省略{},省略return;只有一个形参省略小括号 let fn4 = () => console.log(123) let fn5 = name => name //相当于rerutn name - 立即执行函数(匿名函数,只会调用一次,很少使用)
//写法一 (function(){ let a =10 console.log(a) })() //写法二 (function(){ let a =10 console.log(a) }())
2.函数的参数
- 在函数创建时,可以设置形参,数量不受限制,形参之间逗号隔开(相当于在函数内部声明了对应的变量,且并未赋值)
function sum(a,b){ //a,b就是函数的形参,相当于在函数内声明变量a,b,且未赋值 } - 在调用函数时,可以传递实参
function fn(num1,num2){ return num1+num2 } // 实参将会赋值给函数中对应的形参,调用函数时解析器不会检查实参的类型,实参可以是任意数据类型 fn('abc',123) // 不会检查实参的数量,多余参数不会被赋值;实参数量少于形参,则没有对应实参的形参则是undefined fn(12) fn(123,345,567,892) - 定义形参时,可以为形参设置默认值
function fn( num1=10,num2=2 ){ return num1+num2 } fn() //不传递实参,函数的形参则使用默认值,函数返回值为12 fn(1) //函数形参num2未接收到实参,则使用默认值,函数返回值为3 - 函数每次调用 - 形参默认值会重新被赋值
// 函数每次调用,都会重新创建默认值,重新被赋值 function fn2(a={name:"李白"}){ console.log(a.name) a.name="杜甫" console.log(a.name) } fn2() // 第一次调用函数,输出:李白、杜甫 fn2() // 第二次调用函数,输出:李白、杜甫 // 解释:function fn2(a={name:"李白"}){} == function fn2(){let a={name:"李白"} } //如果默认值这个对象是在函数中直接生成的,那么函数执行完也会销毁这个对象,下次函数执行则会重新生成 let obj={name:'李白'} function fn3(a=obj){ console.log(a.name) a.name="杜甫" console.log(a.name) } fn2() // 第一次调用函数,输出:李白、杜甫 fn2() // 第二次调用函数,输出:杜甫、杜甫 //obj对象是在函数外声明定义的,第一次函数执行obj对象的name属性值已经被改变, //第二次函数再执行是,形参a被赋值时,obj中的name已经被改为"杜甫" - arguments参数(不建议使用)
- arguments是函数中一个隐藏参数,
箭头函数没有 - arguments是伪数组,不可以使用数组的方法,可以for循环遍历
- arguments用来存储函数的实参,无论函数是否定义形参,实参都会存储在其中
//不建议使用arguments function fn() { console.log(arguments) } fn(1, 2, 3, 4, 5) //输出Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ] - arguments是函数中一个隐藏参数,
- 可变参数
- 可变参数可以接收任意数量的实参,并将它们统一存储在一个数组中返回
- 可变参数的作用和arguments基本一致,但有不同点:
- 可变参数的名字可以自定义
- 可变参数就是一个数组
- 可变参数可以配合其他参数使用
//可变参数 function fn(...nums){ console.log(nums) } fn(1,2,3) //输出 [1,2,3] //和其他参数 function fn(num1, num2, ...nums){ console.log(nums) } fn(1,2,3,4,5) //输出[3,4,5]
3.函数的返回值
- 函数通过return返回值,可以声明一个变量接收函数返回值
function fn(){ returun 123 } let result = fn() - 函数return后的语句都不会再执行,函数可以return返回任意类型的值 - 函数若不写return,则返回undefined;或者return后没有值,默认返回undefined- 箭头函数简写返回对象
let fn = ()=>{name:"胡歌"} //错误 let fn = ()=>({name:"胡歌"}) //正确.需要小括号将对象的花括号包裹
4.函数的作用域
- 全局作用域
- 全局作用域在网页运行时创建,在网页关闭时销毁
- 全局作用域中的变量是全局变量,可以被任何作用域中使用
- 局部作用域
- 块作用域( 花括号包裹{代码块} ) --使用得少
- 块作用域是一种局部作用域,在代码块执行时创建,执行完毕就销毁
- 块作用域中的变量只能在块的内部使用,块的外部无法使用
- 函数作用域
- 函数作用域在函数调用时产生,调用结束后销毁,所以每次调用函数都会产生不同作业域
- 在函数中定义的变量,只能在函数中调用,不能在函数外调用
- 块作用域( 花括号包裹{代码块} ) --使用得少
- 作用域链
- 当要调用一个变量时,JS解析器优先在当前作用域寻找变量,从内到外,直到全局作用域中未找到,则报错
5.函数中this指向
-
当以函数的形式调用时,this指向window
function fn(){ console.log(this) } fn() //this指向的是window,fn() === window.fn(),实际就是fn就是window这个对象的方法 -
以对象的方法的形式调用时,this指向调用这个方法的对象
let obj={ name:"胡歌", say(){ console.log(this) return this } } obj.say() //this指向的是obj这个对象,因为是obj调用的say方法 obj.say() === obj //true 全等 -
箭头函数中的this(箭头函数没有自己的this,它的this由外层作用域决定,和调用方式无关)
//声明函数 const fn1 = function(){console.log(this)} //箭头函数 const fn2 = ()=> console.log(this) fn1() //函数式调用---输出window fn2() //函数式调用---输出window //赋值给对象,作为对象方法 let student={ name:"胡歌", fn1, fn2 } student.fn1() //方法式调用---输出student这个对象 student.fn2() //箭头函数---方法式调用---输出window,箭头函数和调用无关let person = { name:'胡歌', say(){ function fn1(){ console.log(this) } fn1() //函数式调用---输出window --- let fn2 = ()=>console.log(this) fn2() //箭头函数---函数式调用---输出person这个对象,因为此箭头函数外层作用域中的this指向person对象 } }注意:可以使用 call()、apply()、bind() 改变this指向;放在补充部分
6. 高阶函数
- 函数的参数或者返回值是函数,则该函数被称为高级函数
- 函数作为参数的作用(回调函数的作用):可以动态的传递代码,不改变原函数添加功能
function sum(num1, num2) { return num1 + num2 } console.log(sum(1, 2)) //输出3 //新增提示功能 num1 + num2 = xxx function tips(callback) { return (num1, num2) => { let res = callback(num1, num2) return `${num1}+${num2} = ${res}` } } let res = tips(sum) console.log(res(3, 4)) //输出3+4 = 7 //解析一下 // tips() --> (num1, num2)=>{ xxxx } 其实返回的是一个函数 // 所以res = tips(sum) 等同于 //res=(num1, num2) => { // let res = sum(num1, num2) // return `${num1}+${num2} = ${res}` // } //这样既用上了原函数,又不改变原函数,又新增了功能
7. 闭包
-
为什么要使用闭包?
- 有些变量不想在全局作用域被访问调用,就可以用闭包
-
闭包构成条件
- 函数嵌套
- 内部函数引用外部函数中声明的变量
- 内部函数要作为外部函数的返回值
let star = '胡歌' function say() { console.log(star) } say() //输出:胡歌 function sayHi() { let star = '李白' return () => { console.log(star) } } let result = sayHi() console.log(result) //输出的是sayHi函数返回的一个函数 result() //输出:李白 总结: 1.定义在全局的star如果在某处被重新赋值,则say函数打印的就改变了 2.定义在函数中的star变量则不会被修改 -
函数的作用域
- 函数的作用域在函数创建时,(词法作用域)就已经确定了,与调用函数时的位置无关
- 闭包就是利用词法作用域
let star = '胡歌' function say() { console.log(star) } say() //输出:胡歌 function sayHi() { let star = '李白' say() } sayHi() //输出:胡歌总结:为什么sahHi里say函数输出的不是'李白'?
1.根据作用域链,say函数中的star先在函数自身寻找,没有找到就会根据作用域链往外查找 2.在sayHi函数中,say函数外面一层作用域不是sayHi函数的作用域,而是全局,因为say在全局创建的,和调用位置无关
-
闭包的生命周期
- 闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新的闭包
- 在内部函数丢失时销毁
function saveNum() { let num = 0 return () => { console.log(++num) } } let result = saveNum() result() //输出1 result() //输出2 result() //输出3 //销毁内部函数 result = null //重新生成内部函数,值又重新计算 result = saveNum() result() //输出1 result() //输出2
8. 递归
-
递归函数
- 调用自身的函数成为递归函数
- 递归的作用和循环基本一致;循环执行性能好,递归思路清晰,多用循环
-
递归函数条件
- 基线条件 -- 递归终止条件
- 递归条件 -- 对问题进行拆分
-
案例
//阶乘问题 //1!= 1 //2!= 1! * 2 //3!= 2! * 3 //4!= 3! * 4 //··· //1.循环解决阶乘问题 function jieCheng(num) { let result = 1 for (let i = 2; i <= num; i++) { result *= i } return result } let result = jieCheng(5) console.log(result) // 输出120 //2.用递归解决阶乘问题 function jieCheng(num) { // 基线条件--跳出递归 if (num === 1) return 1 // 递归条件 return jieCheng(num - 1) * num } let result = jieCheng(5) console.log(result) //输出120 //解析: //jieCheng(5)开始执行 // - 不满足基线条件,执行return jieCheng(4) * 5,执行一个新函数jieCheng(4) // - 不满足基线条件,执行return jieCheng(3) * 4,执行一个新函数jieCheng(3) // - 不满足基线条件,执行return jieCheng(2) * 3,执行一个新函数jieCheng(2) // - 不满足基线条件,执行return jieCheng(1) * 2,执行一个新函数jieCheng(1) // - 满足基线条件 返回1,然后依次返回上面函数的执行结果 // 递归就是当不满足基线条件时,一直调用自身,不断执行新函数,先执行的函数没结果就等着,直到满足基线条件 //然后逐渐返回每个函数的结果
9. 补充关于函数this指向
- 根据函数调用方式不同,this的指向也不同
- 以函数形式调用,this是window
- 以方法形式调用,this是调用方法的对象
- 构造函数例外:this指向新生成的实例对象
- 箭头函数例外:没有this,只由创建时外层作用域决定,和调用方式无关
- 通过call和apply调用的函数,它们的第一个参数就是函数的this
- 通过bind返回的函数,this也是由bind方法第一个参数确定,且this不可被再次修改
- call和apply
- 函数调用方法除了" 函数名( ) "这种形式外,还可以使用"函数名.call( )"、"函数名.apply( )"
function fn() { console.log('函数调用了', this) } fn() //输出window fn.call() //输出window fn.apply() //输出window - 使用 call( thisArg , arg1, arg2, ··· ) 方法
- 第一个参数thisArg是函数的this的值,后面接着写函数的实参
function fn(num) { console.log(num, this) } let obj = { name: '胡歌' } fn(18) //输出: 18 Window fn.call(obj, 18) //输出:18 {name: '胡歌'}
- 第一个参数thisArg是函数的this的值,后面接着写函数的实参
- 使用 apply( thisArg, argsArray ) 方法
- 第一个参数thisArg是函数的this的值,所有实参必须写在一个数组中
function fn(num) { console.log(num, this) } let obj = { name: '胡歌' } fn(18) //输出: 18 Window fn.apply(obj, [18]) //输出:18 {name: '胡歌'} //apply方法传参需要用数组包裹所有实参,函数根据数组顺序对应分配给形参
- 第一个参数thisArg是函数的this的值,所有实参必须写在一个数组中
- 函数调用方法除了" 函数名( ) "这种形式外,还可以使用"函数名.call( )"、"函数名.apply( )"
- bind方法
- bind( )是函数的方法,返回一个新的函数
- bind的第一个参数,为新函数绑定this,锁死this
function fn(num) { console.log(num, this) } let obj = { name: '胡歌' } fn(18) //输出: 18 Window let newfn = fn.bind(obj) //bind返回一个新函数 newfn(20) //输出: 20 {name: '胡歌'} //注意:如果fn.bind(obj,10) 如果传了实参,则返回的新函数中形参num的值固定永远为10 let newfn2 = fn.bind(obj,10) //bind返回一个新函数 newfn2(20) //输出: 10 {name: '胡歌'} ,即使在传递新实参,也不能改变