![fbfc391cb958312ab0cd577305bed837.png](https://i-blog.csdnimg.cn/blog_migrate/fb1f79cc2ef15adfd017b88048671971.jpeg)
四种定义函数
- 匿名函数
- 具名函数
- 赋值函数
- 箭头函数
匿名函数
function (){
console.log('hi')
}
具名函数
function sayHi(){
console.log('hi')
}
赋值函数
let sayHi = function (){
console.log('hi')
}
箭头函数
箭头函数表达式的语法比函数表达式更简洁。更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
let sayHi = ()=> console.log('hi')
变量
全局变量
- 顶级作用域里声明的变量
- http://window.xxx
let a = 1
window.a = 1
局部变量
- 用
{ }
包括起来的代码为局部变量,除了使用window.a = 1
声明变量,也不能使用var
声明(历史遗留问题) - 作用域可以嵌套
{ let b = 1 }
b
// Uncaught ReferenceError: b is not defined
作用域嵌套规则:默认就近原则。查找声明时,向上取最近的作用域,查找声明过程与函数执行无关,声明值才是与函数执行相关。
静态作用域也叫「词法作用域」(编译原理的知识)
闭包:一个函数用到外部变量,那么这一组组合就形成闭包
函数提升
具名函数function fn()
不管在何处声明,最终JS引擎解析的时候都会提升到前面。
add(3,5);
function add(x,y){return x+y};
// 8
匿名函数let fn = funticon(){}
fn只是赋值,并不是函数名称,实际上该function
并没有名称,这种==匿名函数的声明并不进行函数提升。==
add(3,5);
let add = function(x,y){return x+y};
// Uncaught ReferenceError: add is not defined
形式参数
形式参数的意思就是非实际参数,在函数内部相当于==变量声明==
function add(x,y){ return x + y}
// 以上函数与 ↓ 函数近乎等价
function add(){
var x = arguments[0]
var y = arguments[1]
return x + y
}
疑问:形参与对象、值有函数执行后有不同的结果
let a = 1 // 数据存储区域不同,“1”放在“stni "区
let b = {value:1}
形参只是给参数起名字,形参可多可少
function add(x){
return x + arguments[1]
}
add(1,2);
//3
函数返回值
没return
返回值为undefined
console.log('hi');
hi // 打印值
undefined // 返回值
⚠️注意:只有函数才有返回值
JS的调用时机
JS定义一个函数后并不会自动的执行它。函数仅仅是赋予函数以名称并明确函数被调用时该做些什么。
调用函数才会以给定的参数真正执行这些动作。例如:定义了函数add
,并在之后调用函数
function add(x,y){ // 定义一个函数为”add“
return x + y
}
add(1,2) // 传入参数并执行函数
定义与执行不一定同时进行,其中的过程就存在着==调用时机==,不同时间调用函数会有不同的效果
function f() {console.log(a) }
let a = 0;
f() // 0
a = 1;
function f() {console.log(a) }
let a = 0;
a = 1;
f() // 1
循环结构
其中for...let
是最简洁的循环结构
for(i = 0; i<6; i++){
console.log(i)
}
除了for...let
的循环体,还有function
可以运用
function f(n,b){
if(b === undefined ){b=n,n=0}
if(n <= b){
console.log(n)
f(n+1,b)
}
}
// 以下两种函数调用
f(5)
f(0,5)
setTImeout
setTimeout()
是设定一个指定等候时间 (单位是千分之一秒, millisecond),时间到浏览器就会执行一个指定的代码。
setTimeout("alert('对不起, 要你久候')", 3000 )
⚠️注意:变态的情况是即便将setTimeout
等待时间设置为0
,JS也会在所有代码都执行完再马上执行setTimeout(console.log('hi'),0)
可以理解为JS将所有代码顺序执行,一旦设置setTimeout
后,该代码的顺序会延后执行。
for(var i = 0; i<6; i++){ // 使用var,后续会解析
setTimeout(()=>{
console.log(i)
},0)
}
// 6 6 6 6 6 6
当i<6; i++
条件执行完i = 6
跳出循环体,然后setTimeout
才会一条条的执行,所以最终打印出6个6。
针对这种情况,新版ES6才会独立为for...let
的循环体进行优化。
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
// 这样才会打印出 0 1 2 3 4 5
证明setTimeout的变态行为,先声明变量i
一旦不使用for...let
循环结构,就会出现6个6的问题
let i
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
调用栈
JS引擎在调用一个函数前,需要将函数所在的环境push
到一个数组里,等函数执行完,把环境pop
出来,然后return
到调用前的环境里,执行后续的代码。
递归
也就是JS执行函数时会进入另外一个空间执行函数,这个空间成为调用栈。若一个函数里面嵌套自己,最终会形成多个调用栈,一层层递进运算再回退运算称为递归运算
function f(n){
return n != 1 ? n*f(n-1) : 1
}
f(4) // 24
函数内部的运算过程
f(4)
= 4 * f(4-1)
= 4 * (3 * f(3-1))
= 4 * (3 * (2 * f(2-1)))
= 4 * (3 * (2 *( 1 * f(1)))) // f(1),n = 1 不满足条件,停止调用
递进完成后,进行回归运算
f(4)
= 4 * f( 4-1 )
= 4 * ( 3 * f( 3-1 ))
= 4 * ( 3 * ( 2 * f( 2-1 )))
= 4 * ( 3 * ( 2 *( 1 * f( 1 )))) // f(1),n = 1 不满足条件,停止调用
-----------
= 4 * ( 3 * (2 *( 1 )))
= 4 * ( 3 * ( 2 ))
= 4 * ( 6 )
= 24
---
上面每一个=
都代表将函数push
到调用栈里面。---
每一个=
都代表将函数pop
到调用栈里,直到最后一个值运算完成,才将结果return
到函数调用前的环境里。
如果函数需要过多的调用栈,超出浏览器JS引擎的范围就会==爆栈==。
function f(n){
return n != 1 ? n+f(n-1) : 1 // 相乘的值过大,运行相加来验证
}
![6cf1d30f50220799b38d066420d94ed8.png](https://i-blog.csdnimg.cn/blog_migrate/0b9f1acadf8b839e1651726a1b205c50.jpeg)
end.
语雀原文