函数是对象

定义函数的4种方式

具名函数

  1. // 函数名为fn
  2. function fn (x, y) {
  3. return x + y
  4. }
  5. fn() // 调用函数

匿名函数

  1. // 将函数的地址复制给a, 右边的也叫函数表达式
  2. let a = function (x, y) {
  3. return x + y
  4. }
  5. a() // 调用函数

具名函数也可以赋值给变量,不过不能用函数名调用
image.png

箭头函数

  1. let f1 = () => console.log("hello") // 无参数
  2. f1()
  3. let f2 = x => x * x // 一个参数, 省略了return
  4. f2(10)
  5. let f3 = (x, y) => {return x + y} // 两个参数就要加(), 有return或2个以上语句就要加{}
  6. f3(2, 3)
  7. let f4 = x => ({name: x}) // {}优先解释为代码块,返回对象要加()

构造函数

  1. let fn = new Function('x', 'y', 'return x + y')
  2. // 所有函数都由Function构造出来

调用时机

  1. let a = 1
  2. function fn(){
  3. setTimeout(()=>{
  4. console.log(a)
  5. },0)
  6. }
  7. fn()
  8. a = 2
  9. // 打印出2
  1. let i = 0
  2. for(i = 0; i<6; i++){
  3. setTimeout(()=>{
  4. console.log(i)
  5. },0)
  6. }
  7. // 打印出6个6
  1. for(let i = 0; i<6; i++){
  2. setTimeout(()=>{
  3. console.log(i)
  4. },0)
  5. }
  6. // 打印出0,1,2,3,4,5
  7. // JS在for和let一起用时会加东西,每次循环都保留那个i,再新创建一个i

作用域

let 的作用域只在{}里

  1. function fn(){
  2. let a = 1
  3. }
  4. fn()
  5. console.log(a) // 访问不到a

在顶级作用域声明的变量为全局变量
window的属性为全局变量
其他为局部变量

给window加属性,可以在任何位置

  1. let b = 1
  2. function f2() {
  3. window.c = 2 // 给window添加属性
  4. }
  5. f2() // 必须执行,才能声明c
  6. function f1() {
  7. console.log(c)
  8. }
  9. f1()

函数嵌套

  1. function f1(){
  2. let a = 1
  3. function f2(){
  4. let a = 2
  5. console.log(a) // // step2: a = 2
  6. }
  7. console.log(a) // step1: a = 1
  8. a = 3
  9. f2()
  10. }
  11. f1()
  12. // 输出结果为 1, 2
  1. function f1(){
  2. let a = 1
  3. function f2(){
  4. let a = 2
  5. function f3(){
  6. console.log(a)
  7. }
  8. a = 22
  9. f3() // step2: a = 22
  10. }
  11. console.log(a) // step1: a = 1
  12. a = 100
  13. f2()
  14. }
  15. f1()
  16. // 输出结果为: 1, 22

嵌套的函数并不能从外部调用
image.png

作用域规则

  • 如果多个作用域有同名变量a, 查找a的声明时,向上取最近的作用域
  • 简称“就近原则”
  • 查找a的过程与函数执行无关
  • 但a的值与函数执行有关
  • 与函数执行无关的作用域叫静态作用域(词法作用域)

闭包 closure**

如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
image.png

形参和返回值

形参 parameter 实参 argument

形参只是声明

  1. function add(x) {
  2. return x + arguments[1]
  3. }
  4. add(1, 2)

返回值

  • 只有函数才有返回值
  • 且每个函数都有返回值,没写return则返回undefined
    1. function xxx() {
    2. return console.log("hi") // 返回值是undefined
    3. }

    调用栈

    JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫做调用栈。
    等函数执行完毕,就会把环境pop出来,回到之前的环境,继续执行后续代码

递归:先递进后回归 递进是压栈,回归是弹栈

image.png
栈的容量是有限的,压入过多,就会爆栈
image.png

函数提升

function fn(){}
不管把具名函数声明在哪里,都会跑到第一行

let fn = function(){}
用匿名函数赋值变量不会函数提升

let不允许重复声明
image.png

arguments 和 this

JS三座大山

  • 闭包
  • this
  • AJAX

翻过三座大山才算入门

arguments是包含所有参数的伪数组
image.png
调用fn就传入arguments

不给条件,this默认指向window
image.png

用call传入this,如果传入的不是对象,会被自动转为对象
image.png
‘use strict’ 阻止转换
image.png

调用对象里的函数,this默认传的是对象本身
可以使用call显示的传入this
image.png
call第一个参数是this, 之后的参数都是arguments

image.png

总结:

  1. 在 new fn() 调用中,fn 里的 this 指向新生成的对象,这是 new 决定的
  2. 在 fn() 调用中, this 默认指向 window,这是浏览器决定的
  3. 在 obj.fn() 调用中, this 默认指向 obj,这是 JS 的隐式传 this
  4. 在 fn.call(xxx) 调用中,this 就是 xxx,这是开发者通过 call 显式指定的 this
  5. 在 arrow() 调用中,arrow 里面的 this 就是 arrow 外面的 this,因为箭头函数里面没有自己的 this
  6. 在 arrow.call(xxx) 调用中,arrow 里面的 this 还是 arrow 外面的 this,因为箭头函数里面没有自己的 this

以后都使用call调用函数
fn.call(undefined, 1, 2, 3)
fn.apply(undefined, [1, 2, 3])

绑定this
使用.bind让this不被改变

  1. function f1(p1, p2){
  2. console.log(this, p1, p2)
  3. }
  4. let f2 = f1.bind({name:'xxx'})
  5. f2() // 等价于 f1.call({name:'xxx'})
  6. // 也可以绑定参数
  7. let f3 = f1.bind({name:'xxx'}, 1, 2}

箭头函数没有arguments和this
image.png

立即执行函数

立即执行函数用来造局部变量
image.png

用let可以轻松制造局部变量
image.png