结论:函数是一个特殊对象
如何定义一个函数
具名函数
//写法1function 函数名(形参){语句return 返回值}//写法2,变量a存储fn()的返回值//这里要注意,在调用函数的时候,不能写成fn(1,2),会报错,因为fn这个函数的作用域只在等号右边let a = function fn(x,y){return x+y}
匿名函数
//因为是匿名函数,所以写成如下形式,右边叫函数表达式,需要用一个变量存储返回值,let a = function(x,y){return x+y}
箭头函数
let f1 = x => x*xlet f2 = (x,y) => x + ylet f3 = (x,y) => {return x+y}let f4 = (x,y) => ({name:x,age:y})//创建对象
用构造函数
let f = new Function('x','y','return x+y')基本上没人用,但是能让你知道函数是谁构造的所有函数都是Function构造出来的包括Object、Array、Function也是
函数自身(fn)&函数调用(fn())
let fn = () => console.log('hi')let fn2 = fnfn2()
上面的代码运行的结果:
- fn保存了匿名函数的地址
- 这个地址被赋值给了fn2
- fn2()调用了匿名函数
- fn和fn2都是匿名函数的饮引用而已
- 真正的函数既不是fn也不是fn2
函数的几大要素:
调用时机
例1
let a = 1function fn(){console.log(a)}上面的代码,最终的打印结果是?没有结果,因为没有调用代码
例2
let a = 1function fn(){console.log(a)}a = 2fn()//打印结果是:2
例3
let a = 1function fn(){console.log(a)}fn()a = 2//打印结果是:1
例4
let a = 1function fn(){setTimeout(()=>{console.log(a)},0)}fn()a = 2//打印的结果是:2//setTimeout的时间虽然是设置为0,但是它会在代码执行完以后再执行setTimeout,所以结果是2
例5
let i = 0for (i = 0;i<6;i++){setTimeout(()=>{console.log(a)},0)}fn()//打印的结果是:6个6//setTimeout但会在for循环代码执行完以后再执行setTimeout,所以结果是6个6
例6
for (let i = 0;i<6;i++){setTimeout(()=>{console.log(a)},0)}fn()//打印的结果是:0,1,2,3,4,5//js在for和let一起用的时候,每次循环都会多创建一个i,for内用let相当于在每个循环{}内重新定义并赋值i。
例7(来自网上查找的结果)
除了for let配合,还有下面的方法可以打印出0,1,2,3,4,5//方法1:创建一个立即执行函数(匿名函数),把i作为参数传给这个函数let ifor(i = 0; i<6; i++){!function(value) {setTimeout(()=>{console.log(value)},0)}(i)}//方法2:利用const关键字let ifor(i = 0; i<6; i++){const x = isetTimeout(()=>{console.log(x)})}//方法3:利用setTimeout的第三个参数,将i传进去当做value打印出来即可let ifor(i = 0; i<6; i++){setTimeout((value)=>{console.log(value)},0,i)}
作用域
例1
function fn(){let a = 1}console.log(a)//a不存在
例2
function fn(){let a = 1}fn()console.log(a)//a还是不存在,因为a的作用域只在{}中
例3
function f1(){let a = 1function f2(){let a = 2console.log(a)//此时的a在f2()的作用域,所以a的值是2}console.log(a)//此时的a是在f1()的作用域中,所以a的值是1a = 3f2()}f1()
作用域规则
如果多个作用域有同名变量a
- 那么查找a的声明时,就向上去最近的作用域——就近原则
- 查找a的过程与函数执行无关(静态作用域,也叫词法作用域),但a但值与函数执行有关(动态作用域)
例4
function f1(){let a = 1function f2(){let a = 2function f3(){console.log(a)//就近原则,这里但a在上层的let a = 2中}a = 22f3()//a的值是22}console.log(a)a = 100f2()}f1()
全局变量&局部变量
在顶级作用域声明的变量是全局变量,window的属性是全局变量,其他都是局部变量
```javascript let b = 1 function f2(){ window.c = 2 let b } f2() function f1(){ console.log(c) } f1() //在window上面声明变量c以后,这个变量就变成了全局变量,f2()的let b只作用在f2()里面
<a name="1odV6"></a>### 闭包<a name="JOuuR"></a>#### 如果一个函数用到了外部的变量,那么这个函数加这个变量,就叫做闭包,可以看例4,f2()的a和f3()组成了闭包<a name="6ZHWy"></a>### 形参<a name="OvDF7"></a>#### 形参的意思就是非实际参数,可以理解成变量声明```javascriptfunction f1(x,y){return x+y}//x,y就是形参f1(1,2)//调用函数时填入的参数就是实参//上面的代码近似等价于下面的代码function f1(){var x = arguments[0]var y = arguments[1]return x+y}
形参可多可少
function f1(x){return x + arguments[1]//形参有1个,当传进来的参数有多个时,可以用arguments[n]获取}f1(1,2)
返回值
每个函数都有返回值,函数执行完以后才会返回,只有函数才有返回值
function hi(){console.log('hi')}hi()//因为没有写return,所以返回值是undefinedfunction hello(){return console.log('hello')}hello()//返回值为console.log('hello')的值,即undefined
调用栈
什么是调用栈:
JS引擎在调用一个函数前,需要吧函数所在的环境push到一个数组里 这个数组叫做调用栈 等这个函数执行完了,就会把环境弹(pop)出来,然后return到之前的环境,继续执行后续代码
例:
- console.log(1)
- console.log(‘1+2的结果为’ + add(1,2))
- console.log(2)
函数提升
什么是函数提升
function fn(){}//不管把具名函数声明在哪里,它都会跑到第一行例如:add(1,2)function add(x,y){return x+y}//这样的顺序,函数还是会跑到调用的前面
什么不是函数提升
let fn = function(){}//这是赋值,右边的匿名函数声明不会提升
arguments(除了箭头函数)
arguments是包含了所有参数的伪数组
this(除了箭头函数)
如果不给任何条件,那么this默认指向window
如果传进去的this不是个对象,js会自动封装成对象
箭头函数里面的this就是外面的this,就算加call都没用
```javascript console.log(this)//window let fn = () => console.log(this) fn()//window
结论:
this是隐藏参数,arguments是普通参数
