结论:函数是一个特殊对象
如何定义一个函数
具名函数
//写法1
function 函数名(形参){
语句
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*x
let f2 = (x,y) => x + y
let 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 = fn
fn2()
上面的代码运行的结果:
- fn保存了匿名函数的地址
- 这个地址被赋值给了fn2
- fn2()调用了匿名函数
- fn和fn2都是匿名函数的饮引用而已
- 真正的函数既不是fn也不是fn2
函数的几大要素:
调用时机
例1
let a = 1
function fn(){
console.log(a)
}
上面的代码,最终的打印结果是?
没有结果,因为没有调用代码
例2
let a = 1
function fn(){
console.log(a)
}
a = 2
fn()
//打印结果是:2
例3
let a = 1
function fn(){
console.log(a)
}
fn()
a = 2
//打印结果是:1
例4
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
fn()
a = 2
//打印的结果是:2
//setTimeout的时间虽然是设置为0,但是它会在代码执行完以后再执行setTimeout,所以结果是2
例5
let i = 0
for (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 i
for(i = 0; i<6; i++){
!function(value) {
setTimeout(()=>{
console.log(value)
},0)
}(i)
}
//方法2:
利用const关键字
let i
for(i = 0; i<6; i++){
const x = i
setTimeout(()=>{
console.log(x)
})
}
//方法3:
利用setTimeout的第三个参数,将i传进去当做value打印出来即可
let i
for(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 = 1
function f2(){
let a = 2
console.log(a)//此时的a在f2()的作用域,所以a的值是2
}
console.log(a)//此时的a是在f1()的作用域中,所以a的值是1
a = 3
f2()
}
f1()
作用域规则
如果多个作用域有同名变量a
- 那么查找a的声明时,就向上去最近的作用域——就近原则
- 查找a的过程与函数执行无关(静态作用域,也叫词法作用域),但a但值与函数执行有关(动态作用域)
例4
function f1(){
let a = 1
function f2(){
let a = 2
function f3(){
console.log(a)//就近原则,这里但a在上层的let a = 2中
}
a = 22
f3()//a的值是22
}
console.log(a)
a = 100
f2()
}
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()组成了闭包
![image.png](https://cdn.nlark.com/yuque/0/2020/png/1444038/1591587565442-b5e21431-2dc9-4a79-9cf5-44075e3420d5.png#align=left&display=inline&height=334&margin=%5Bobject%20Object%5D&name=image.png&originHeight=334&originWidth=668&size=27458&status=done&style=none&width=668)
<a name="6ZHWy"></a>
### 形参
<a name="OvDF7"></a>
#### 形参的意思就是非实际参数,可以理解成变量声明
```javascript
function 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,所以返回值是undefined
function 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是普通参数