函数的要素
每个函数都有这些东西
- 调用时机
- 作用域
- 闭包
- 形式参数
- 返回值
- 调用栈
- 函数提升
- arguments(除了箭头函数)
- this(除了箭头函数)
一、调用时机
看到函数先直接跳过,一定要看到调用语句才会执行函数。
例1
- 以下代码打印出什么?
- 答:2,先
a=2,再调用fn
例2
- 以下代码打印出什么?
- 答案:2
setTimeout是等一会执行- 比喻:一个人在打游戏,妈妈喊他吃饭,他说等一下去吃饭,意思是吃饭之前要把游戏打完。
- 因此:这里
setTimeout中的0表示执行完了马上做等一会做的事情
例3
- 以下代码打印出什么?
- 答案:6个6
- 等一会执行,则是要
for循环结束后马上执行,一共执行了6次,当for循环结束后i=6,因为只有一个i,所以是6个6
例4
- 以下代码打印出什么?
- 答: 0,1,2,3,4,5
- 因为JS在
for和let一起用的时候会加东西 - 每次循环就会复制一个
i,i=0、i=1....,6次循环一共生成除了6个新的i setTimeout会打印出当时的值
作用域
每个函数都会默认创建一个作用域
作用域的重点就是:就近原则
例5
- a不存在
fn执行了,也访问不到作用域里面的a,因为a的作用域就在{}里let的作用域:往前找到一个{,往后找到一个},出了{}就不存在
1.全局变量 V.S. 局部变量
- 在顶级作用域里声明的变量是全局变量
window的属性是全局变量 (如果是在window上面,则不需要写在顶级作用域,可以写在任何地方)- 其他都是局部变量
2.函数可嵌套
作用域也可嵌套,局部作用于套局部作用域\
2.1作用域规则
如果多个作用域有同名变量a
- 那么查找
a声明时,就向上取最近的作用域 - 简称[就近原则]
- 查找
a的过程与函数执行无关
和函数执行无关的作用域叫做静态作用域,也叫做词法作用域(了解即可) - 但a的值和函数执行有关
在确定a的值时,才需要注意执行函数和a的先后顺序
闭包
定义:如果一个作用域用到了他外面的变量,就叫闭包
形式参数
1.形式参数的意思就是非实际参数
-
其中
x和y就是形式参数(xy并不代表任何实际的值,只代表参数的顺序和位置),因为并不是实际的参数 -
调用
add时,1和2是实际参数,会被赋值给x y -
赋值:
1.传统说法 JS传参分为 值传递 和 地址传递
2.新的理解:不需要区分值和地址,所有都是根据内存图全部复制,值的复制和地址的复制
3.也就是:根据内存图,Stack记的什么,就是什么 -
形参可以认为是变量声明
形参可多可少
形参只是给参数取名字
没必要把形参全部声明完
返回值
1.每个函数都有返回值
返回值是执行之后才使用的
- 没写
return,返回值是undefined
console.log('hi')的返回值是undefined
注意:只是打印了一个hi,返回值还是undefined(区分打印值和返回值)
因此下面hi()的值是undefined- 函数执行完了才会返回
- 只有函数有返回值
调用栈
定义:
- 在调用一个函数的时候,需要进入另外一个环境(函数)去执行它,执行完了再返回来
调用栈实际就是记录进入一个函数后,回去应该回到什么地方
就类似于打游戏存档读档 - JS引擎在调用一个函数前
- 需要把函数所在的环境推
push到一个数组里 - 这个数组叫做调用栈
- 等函数执行完了,就会把环境弹(pop)出来
- 然后return到之前的环境,继续执行后续代码
- 由于每次进入一个函数都要记下来等会回到哪里
- 所以把回到的地址写到栈里面
- 如果进入一个函数还要进入一个函数,又把地址放到栈里面
- 函数执行完了就弹栈,把地址弹出去,让函数执行结果根据地址回到相应位置
爆栈
如果调用栈压入的帧过多,程序就会崩溃
1.递归函数
1.1如果使用递归函数,那么很有可能把栈压满
理解递归:先层层递进计算,得到值后一层一层回归
函数提升
1.什么是函数提升
- 如果函数声明是:
function fn(){} - 不管你把具名函数声明在哪里,它都会跑到第一行
一般是先声明在使用,但函数的声明会默认到第一行,因此可以先写调用后面跟声明
let不允许在已经有一个函数的情况下再声明add(let的特性是如果变量已经存在则不允许再声明)