这是我在JS学习路上的大山,不断学习,不断更新中~

关于JS函数有9个十分重要但又不好理解的要素,随着不断学习,对他们认识也一直在改变,先从一个小白的视角来总结一下,对于其中难懂的要素会单独写博客来总结。

时间 修改内容
2020.11.23 第一版

9个要素

  • 调用时机 👉JS函数的执行时机
  • 作用域 👉(会单独写博客)
  • 闭包 👉(和作用域一起写博客)
  • 形式参数
  • 返回值
  • 调用栈
  • arguments(除了箭头函数)
  • this(除了箭头函数)👉(会单独写博客)

每一个函数都有这9个要素,但ES6新版语法的箭头函数没有arguments和this

定义函数

函数其实是处理数据的方法,JavaScript 把它当成一种数据类型,可以赋值给变量,这为编程带来了很大的灵活性,也为 JavaScript 的“函数式编程”奠定了基础。

函数也是对象,先从最简单的定义函数开始:

具名函数

function 函数名(形式参数1, 形式参数2){

 语句

 return 返回值

}

这种方式声明的函数,全局可调用,并且JS会进行函数提升,把它提升到代码的最前面。

匿名函数

let a = function(x, y){ return x+y }

上面的具名函数,去掉函数名就是匿名函数,上面例子中使用函数表达式将匿名函数赋值给变量,这里的赋值跟将对象赋值给变量一样,a中存储的就是函数对象的地址。之后就可以通过a()来调用函数。

💁🏼‍♀️插一嘴aa()

a是指函数本身 ,a()是指调用(执行)这个函数 ,a是一个变量保存了函数体的地址,如果一个变量存的是一个函数的地址,那通过变量来执行函数时就是对函数的引用。

立即执行函数

立即执行函数就是通过匿名函数来实现的

在ES5时代,为了得到局部变量,必须引入一个函数,在函数里去声明局部变量,但是这个函数如果有名字,就得不偿失,于是这个函数必须是匿名函数。

于是声明匿名函数,然后立即加个 () 执行它,并在匿名函数前面加个运算符来让其符合JS语法,!、~、()、+、-运算符都可,这种方法形成的函数就是立即执行函数。

!function(i){
   语句
}(i)

ES6新语法出现后,想要得到局部变量就十分简单了,使用 let/const的块作用域特性来声明局部变量

{
let i = 'moji';
}

箭头函数

箭头函数是函数声明的简写形式,在使用递归调用、构造函数、事件处理器时不建议使用箭头函数。

  • 无参数时使用空扩号即可
  • 函数体为单一表达式时不需要 return 返回处理,会自动返回表达式计算结果,可以省略花括号
  • 只有一个参数时可以省略括号
(参数1, 参数2, …, 参数N) => { 函数声明 }
(参数1, 参数2, …, 参数N) => 表达式(单一)
//相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }

// 当只有一个参数时,圆括号是可选的:
(单一参数) => {函数声明}
单一参数 => {函数声明}

// 没有参数的函数应该写成一对圆括号。
() => {函数声明}

构造函数

let f = new Function('x', 'y', 'return x+y')

所有函数都是 Function 构造出来的,包括 Object、Array、Function

调用时机

JS函数的执行时机和函数被调用的时机有关,函数被调用时才会被执行,调用时机不同,函数的执行结果也不同。

关于调用时机引发的问题 ,另一篇博客有详细探讨👉JS函数的执行时机

作用域

先记录一下对函数作用域最直白的理解,再读完《你不知道的JavaScript上卷》之后,再单独写一篇博客来总结。

只有一个全局作用域就是window,每个函数都会默认创建一个作用域,编译器运行时会将变量定义在所在作用域,作用域外就无法访问到变量。所以,变量可分为全局变量和局部变量,在顶级作用域(window)声明的变量是全局变量,其他都是局部变量。.如果在一个函数里面声明了let const声明了一个变量,就是局部变量,作用域在函数里面

作用域规则——「就近原则」

  • 如果多个作用域有同名变量 a,那么查找 a 的声明时,就向上取最近的作用域
  • 确定是哪个作用域的变量a的时候是不看函数执行的,只有确定变量a的值的时候才和函数的执行有关
  • 跟函数执行没有关系的作用域叫做静态(词法)作用域,反之,动态作用域

例1:

function f1(){
  let a = 1
  function f2(){
    let a = 2
    function f3(){
      console.log(a)
    }
    a = 22
    f3() 
  }
  console.log(a) 
  a = 100
  f2()
}
f1()
// 1 
// 22

解析:函数 f3中console.log(a)的a可以通过就近原则确认是第四行声明的变量a,但是f3被调用前a被重新赋值为22,可以得到打印为22

闭包

JS的函数会就近的选择最近的变量来执行,如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包,闭包一般在子函数(嵌套函数)本身作用域以外执行。例1中f3用到了外部的变量a,a 和 f3 组成了闭包。

形式参数

形式参数的意思就是非实际参数,形式参数可以认定为变量声明,调用函数时形参都是对实参内存中stack里存储的值进行复制,普通类型直接复制值,复制类型(对象)复制是地址。

function add(x, y){
  return x+y
}
// 其中 x 和 y 就是形参,因为并不是实际的参数
add(1,2)
// 调用 add 时,1 和 2 是实际参数,会被赋值给 x y

形参可认为是变量声明

// 上面代码近似等价于下面代码
function add(){
  var x = arguments[0]
  var y = arguments[1]
  return x+y
}

返回值

每个函数都有返回值且只有执行完的函数才有返回值,没有写return的函数 ,返回值undefined

调用栈

调用栈就是执行一个函数前会在栈里记录(压入)当前所处的环境位置(执行完函数后要回到的环境位置),等函数执行完毕,再弹出刚才记录的位置信息,回到之前位置继续执行后续代码。

递归函数的调用栈:先递进后回归,递进的过程就是把环境位置信息一步步压栈的过程,f(4)就压入了4四次,回归就是把位置环境信息一步步弹栈,子函数得到结果后再一步步的回归到父环境得到最终结果(参考上图)。

arguments

arguments对象是所有(非箭头)函数中都可用的局部变量。arguments是函数所有参数组成的伪数组(没有数组的共有属性),可以使用arguments对象在函数中引用函数的参数。

function func1(a, b, c) {
  console.log(arguments[0]);
  // expected output: 1

  console.log(arguments[1]);
  // expected output: 2

  console.log(arguments[2]);
  // expected output: 3
}

this

🤟先来了解一下this的起源:

写函数时,在还不知道对象的名字(对象还没有声明)时拿到一个对象的属性,可以怎么做呢? 第一种:使用形式参数,调用函数的时候再传入 ,python使用了这个方法

let person = {
  name: 'moji',
  sayHi(p){
    console.log(`你好,我叫` + p.name)
  }
}
person.sayHi(person) 

​ 这显然和 JS 中对象调用函数的方式不一样,JS中是直接person.sayHi(),

第二种:JS解决方法是,在每个函数里加入了this,可以使用this来调用还不知道名字的对象的属性值,在被对象调用时,this里的值就是对象的地址,使用this来获取对未知对象的引用。

let person = {
  name: 'moji',
  sayHi(this(省略)){
    console.log(`你好,我叫` + this.name)
  }
}
person.sayHi()

person.sayHi()会隐式地把 person(person 是个地址) 作为 this 传给 sayHi,这样,每个函数都能用 this 获取一个未知对象的引用了。

🤏简单来说,this就是最终调用函数的对象

ps:函数里不给其他条件,this默认指向window,箭头函数自己没有this,函数外面的this就函数里调用的this,call也无法使用

call()和apply()

call 方法第一个参数也是作为函数上下文的对象,后面传入的是一个参数列表,而不是单个数组。

apply 方法传入两个参数:一个是作为函数上下文的对象,另外一个是作为函数参数所组成的数组。

作用:

  • 改变this的指向

  • 调用函数

    person.sayHi()=person.sayHi.call(person) ,不需要传this时,使用undefined来占位

bind()

它和 call 很相似,接受的参数有两部分,第一个参数是是作为函数上下文的对象,第二部分参数是个列表,可以接受多个参数。使用bind来绑定this,让this不再改变