四种方式定义函数
具名函数
function 函数名(){
return xxx;
}
注意:let a = function fn(x,y){return x+y}这种情况函数的作用域仅在等号右边。
匿名函数
let a = function(x,y){return x+y}
箭头函数
用构造函数
let f = new Function('x','y','return x+y')
基本没人用,但是通过这个函数可以看出,所有函数都是由function构造出来的。
函数自身和函数调用
fn 和 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,fn和fn2都是匿名函数的引用而已,真正的函数既不是fn也不是fn2。
调用时机
let i
for(let i = 0 ; i<6 ; i++){
setTimeout(()=>{
console.log(i)
},0)
}
输出6个6,setTimeout会等 整个代码运行完了之后再运行,i的最终值为6,最后连续打印
6次i。
for(let i = 0 ; i<6 ; i++){
setTimeout(()=>{
console.log(i)
},0)
}
会输出0,1,2,3,4,5
因为JS在 for和let 一起使用的时候会加东西,每次循环内存中都会生成一个新的i。
除了let和for结合,还有什么办法打印出0,1,2,3,4,5吗
答案就是利用闭包
let i
for(let i = 0 ; i<6 ; i++){
!function(i){
setTimeout(()=>{
console.log(i)
},0)
}
}
解析:
- 这里利用立即执行函数在每一次的循环中都执行一次这个函数,而这个函数的参数为i,记录着每一次循环中i的值。
- 一个立即执行函数会生成一个块级作用域,这作用域里面有两个东西,一个是i的值,一个是匿名函数(箭头函数)。
- 由于我们循环了六次,也就是说生成了六个块级作用域,且这六个块级作用域中的i都是不同的(原因见1)。
作用域:就近原则&闭包
在顶级作用域声明的变量是全局变量
window的属性是全局变量
其他都是局部变量
如果一个函数用到了外部变量,那么这个函数加这个变量就叫做闭包。
let a = 2
function f3(){
console.log(a)
}
参数和返回值
function add(x,y){
return x+y
}
x 和 y是形式参数
add(1,2)中 1和2 是实际参数。
形参的本质就是变量声明。
返回值
function hi(){
console.log('hi')
}
没写return,所以返回值是undefined
函数执行完了后才会返回,只有函数有返回值,1+2的值为3不能叫返回值。
递归,调用栈与爆栈
调用栈类似于玩游戏存档
JS在调用一个函数前,需要把函数所在环境压(push)到一个数组里,这个数组叫调用栈,等函数执行完了再把函数弹(pop)出来,然后return到之前的环境继续执行代码。
this
每次调用函数时,都会对应产生一个 arguments
我们应该尽量不对 arguments 内的元素进行修改,修改 arguments 会让代码变得令人疑惑
arguments(伪数组,没有数组的共有属性push等) 和 this 每个函数都有,除了箭头函数。
用Array.from()把任何不是数组的变成数组。
如果不给任何条件,默认的this指向window(通常不用默认的this)。
如何传arguments
调用fn,fn(1,2,3)那么arguments就是[1,2,3]伪数组
如何传this
在 fn() 调用中, this 默认指向 window,这是浏览器决定的
fn.call(xxx,1,2,3)传this和arguments
而且xxx会被自动转化为对象。
两种调用
- 小白调用法
person.sayHi()
会自动把person传到函数里,作为this。 - 大师调用法(强推)
person.sayHi.call(person)
需要自己手动person传到函数里,作为this
call指定this
如果没有用到this
add.call(undefined,1,2)
为什么要多写个undefined
因为第一个参数要作为this,但是代码中没有用this,所以要用undefined占位,别的也可(比如null)
this的两种使用方法
隐式传递
fn(1,2) //等价于fn.call(undefined,1,2)
obj.child.fn(1) //等价于obj.child.fn.call(obj.child,1)
显式传递
fn.call(undefined,1,2)
fn.apply(undefined,[1,2])
箭头函数
没有arguments 和 this
新版js没有this,直接用箭头函数
- 在 arrow() 调用中,arrow 里面的 this 就是 arrow 外面的 this,因为箭头函数里面没有自己的 this
- 在 arrow.call(xxx) 调用中,arrow 里面的 this 还是 arrow 外面的 this,因为箭头函数里面没有自己的 this
立即执行函数
ES5时代,为了得到局部变量必须引入一个函数,这个函数必须为匿名函数,申明匿名函数然后加个()去执行它。
最终发现,只要在匿名函数前加个运算符(!、~、()、+、-)即可,推荐!。