函数是一个特殊的对象
- 具名函数
- function 函数名(形式参数1,形式参数2){
语句
return 返回值
} - 匿名函数
- 上门的具名函数,去掉函数名就是匿名函数
- let a =function(x,y){return x+y}
- 也叫函数表达式
匿名函数
let a = function (x,y){
return x+y
}
函数fn
function fn(x,y){
return x+y
}
![2aa1f96bb707429ccc223fad23300139.png](https://i-blog.csdnimg.cn/blog_migrate/3f446c9b12b07cec9acd8f3020baacf9.png)
fn能作用域只能是等于号右边
- 箭头函数
- let f1 = x => x*x
- 输入参数 输出参数
![9a7377c1b8aac7636f5913ac137947a6.png](https://i-blog.csdnimg.cn/blog_migrate/cce87a4fd8d32e7be1e1fa91f2ba3229.png)
- let f2 = (x,y) => x+y // 圆括号 不能省
- let f3 = (x,y) => {return x+y} // 有两件事情 花括号不能省 一定要写 return
![d90c680cf5175ca9fa29bffd0f691aa6.png](https://i-blog.csdnimg.cn/blog_migrate/0b189313a9093232934c09b12808d17e.png)
- let f4 = (x,y) => ({name:x,age:y})
![f962f531eb70580e2f37cd60dd68fa5e.png](https://i-blog.csdnimg.cn/blog_migrate/17f32f6ac47b541add048a3a868129f4.png)
- 直接返回对象会出错,需要加个圆括号(头疼)
- 用构造函数
- let f =new Function('x','y','return x+y')
- 基本没人用,但是能让你知道函数是谁构造的
- 所有函数都是Function 构造出来的
- 包括 object Array Function 也是
- 函数自身 vs 函数调用
- fn v.s fn()
函数自身
- 代码
- let fn =() => console.log('hi')
- fn
- 结果
- 不会有任何结果
- 因为fn没有执行(调用)
函数调用
- 代码
- let fn = () => console.log ('hi')
- fn()
- 结果
- 打印出hi
- 有圆括号才是调用
在进一步
- 代码
- let fn =() => console.log('hi')
- let fn2 = fn
- fn2()
- 结果
- fn保存了匿名函数的地址
- 这个地址被复制给了fn2
- fn2()调用了匿名函数
- fn和fn2都是匿名函数的引用,真正的函数既不是fn也不是fn2
函数的要素
- 每个函数都有这个东西
- 调用时机
- 作用域
- 闭包
- 形式参数
- 返回值
- 调用栈
- 函数提升
- arguments(除了箭头函数)
- this (除了箭头函数)
调用时机---时机不同,结果不同--刻舟求剑
例子1
- 代码
- let a =1
- function fn(){
console.log(a)
} - 问答应出多少
- 不知,因为没有调用代码
例子2
- 代码
- let a =1
- function fn(){
console.log(a)
} - fn()
- 问打印出多少
- 答
- 1
例子3
- 代码
- let a = 1
- function fn(){
console.log(a)
} - a=2
- fn()
- 问打印出多少
- 答
- 2
例子4
- 代码
- let a =1
- function fn(){
console.log(a)
} - fu()
- a=2
- 问打印出多少
- 答1
例子5
- 代码
let a =1
function fn(){
setTimeout(()=>{
console,log (a)
},0)
}
fn()
a=2
请问打印出多少
答
2
例子6
let i = 0
for(i =0;i<6;i++){
setTimeout(()=>{
console.log(i)
},0)
}
问打印出多少
答
不是 0,2,3,4,5
而是6个6
例子7
for(let i = 0;i<6;i++){
setTimeout(()=>{
console.log(i)
},0)
}
问答应出多少
答
是0,1,2,3,4,5
因为js在for和let一起用的时候回加东西
每循环回多创建一个i
作用域 --每个函数都会默认创建一个作用域
例子
function fn(){
let a =1
}
console.log(a) // 不存在
问
是不是因为fn没执行导致
答
就算fn执行了,也访问不到作用域里面的a
例子2
function fn(){
let a =1
}
fn()
console.log(a) // a还是不存在
全局变量v,s 局部变量
在顶级作用域声名的变量是全局变量
window 的属性是全局变量
其他都是局部变量
![884b184decb916aee0797789b659acfd.png](https://i-blog.csdnimg.cn/blog_migrate/1aea57ef5e47aee0c9b1a753f866a72d.png)
函数可嵌套,作用域可以嵌套
![346a335127f1ed29fad67adfeb75b04f.png](https://i-blog.csdnimg.cn/blog_migrate/b97f34ce03fa5a293a4bf241f1b45a98.png)
作用域规则
- 如果多个作用域有同名变量a
- 那么久查找a的声名是,就向上去最近的作用域
- 简称 就近原则
- 查找a 的过程与函数的执行有关
例4
function f1(){
let a =1
function f2(){
let a = 2 如果一个函数用到外部的变量
function f3(){ 那么这个函数加这个变量 就叫做闭包
console.log(a) 左边的a和f3组成了闭包
}
a=22
f3()
}
console,log(a)
a = 100
f2()
}
f1()
形式参数
- 形式参数的意思就是非实际参数
- function add(x,y){
return x+y
} - //其中x和y就是形式参数,因为并不是实际的参数
- add(1,2)
- // 调用 add 时,1和2是实际参数,会被赋值给x y
- 形参可以认为是变量声明
- // 上门代码近似等价于下面的代码
- 上面代码近似等价于下面代码
- function add(){
var x = arguments[0]
var y =arguments[1]
return x+y
}
形参可多可少 ---形参只是给参数取名字
返回值
- 每个函数都有返回值
- function hi(){ console.log('hi')}
- hi ()
- 没写 return ,所以返回值是 undefined
- function hi(){return console .log )('hi')}
- hi()
- 返回值为 console.log('hi')的值 即undefinded
- 函数执行完后才会返回
- 只有函数有返回值
- 1+2 返回值为3 错误 口误
- 1+2 值为 3 正确
调用栈
- 什么是调用栈
- js引擎在调用一个函数前
- 需要把函数所在的环境 push 到一个数组里
- 这个数组叫调用栈
- 等函数执行完了,就会把环境弹出(pop)出来
- 然后return到之前的环境,继续执行后续代码
- 举例
- console.log(1)
- console.log('1+2的结果为'1+2的结果为'+add(1,2))
- console.log(2)
![bf3c00886d5e8dfe1ca9cf2bd35d9cb4.png](https://i-blog.csdnimg.cn/blog_migrate/d14bb7c3d1b88b0bb9c13c8afb569e74.jpeg)
递归函数
- 阶乘
- function f(n){
return n! == 1? n* f(n-1):1
} - 1=1
- 2=2*1=2
- 3=3*2=3*2*1=6
- 4=4*3=24
- 理解递归
- f(4)
- =4*f(3)
- =4*(3*f(2))
- =4*(3*(2*f(1)))
- =4*(3*(2*(1)))
- =4*(3*(2))
- =4*(6)
- 24
- 先递进,再回归
![5b576b6e43a721e7e45a0e605cfe9eff.png](https://i-blog.csdnimg.cn/blog_migrate/fae70f5b69f91c997201fcd31e63593a.png)
![499af6a2e9d288be3b3857371bb0b6bc.png](https://i-blog.csdnimg.cn/blog_migrate/92a7bc4da4d25b307fa76b7f6353f5ee.png)
爆栈---如果调用栈中压入的帧过多,程序就会崩溃
![0ca53c9d2f62940649ec343e105020f4.png](https://i-blog.csdnimg.cn/blog_migrate/466a1b2a273928df7fc628fc22391817.png)
函数提升
- 什么是函数提升
- function(){}
- 不管你把具名函数什么在哪里,他都会跑到第一行
![d28689ea9c932684a186008ea51f908d.png](https://i-blog.csdnimg.cn/blog_migrate/4317acd7f9f5a386e6d5add869ea32a6.png)
- 什么不是函数提升
- let fn =function(){}
- 这是赋值,右边的匿名函数声明不会提升
arguments 和 this ---,每个函数都有,除了箭头函数
- 代码
- function(){ console.log(arguments)console.log(this)}
- 如何传 arguments
- 调用 fn 即可传 arguments
- fn(1,2,3)那么 arguments 就是[1,2,3]伪数组
- 如何传this
- 目前可以用fn.call(xxx,1,2,3)传 this 和 arguments
![b35732e1c20b75342c2a21f9afb3ee90.png](https://i-blog.csdnimg.cn/blog_migrate/e2c174af73a11b9c9ef8147482e85785.png)
- 而且xxx会被自动转化成对象(js的糟粕)
箭头函数 ---没有 arguments 和this
- 里面的this 就是外面的this
- console.log(this)//window
- let fn =()=>console.log(this)
- fn()//window
![9b1bf1b1ad4b45b9ef77a8a44bdd4598.png](https://i-blog.csdnimg.cn/blog_migrate/abbc112ada55d4f239a09c26aedf11d5.png)
- 就算你加call都没有
- fn.call({name:'frank'})//window
立即执行函数
- 原理
- ES5时代,为了得到局部变量,必须引入一个函数
- 但是这个函数如果有名字,就得不偿失
- 于是这个函数必须是匿名函数
- 声名匿名函数,然后立即加个()执行它
- 但是js标准认为这种语法不合法
![b59b47c800f5150132935b06176c5662.png](https://i-blog.csdnimg.cn/blog_migrate/e4e9951f6a9d4a18a381e9938f12efb9.png)
- 所以js程序员寻求各种方法
- 最终发现,只要在匿名函数前加个运算符号即可
- !,~,(),+,-都可以
- 但是这里面有些运算符会往上走
- 所以推荐永远用!来解决