WuShaolin

悟已往之不谏,知来者之可追!

0%

初探JS的函数

什么是函数

函数是对象的一种,也是一段可以重复使用的代码块,开发人员为了完成某项功能,把相关代码块放到一起。

函数内部可以传参,也可以被当做参数传递

目前定义函数有五种方法

具名函数来定义

function f(x, y){
  return x + y
}
f.name //'f'

匿名函数来定义

var f
f = function(x, y){
  return x + y
}
f.name //'f'

具名函数定义了又赋值给了变量

var f1
f1 = function f(a, b){
  return a + b
}
f1.name //'f'

要注意:虽然f1.name='f',但是f只在函数内部可用,实际上函数的名字还是f1

window.Function来构造

var f2 = new Function('x', 'y', 'return x + y')
f2.name //'anonymous'

箭头函数

var f3 = (x, y) => {return x - y}
var sum = (x, y) => x + y //函数体内只有一行代码,可以省略大括号和return
var n2 = n => n*n //只有一个参数,可以省略小括号

常用的定义方法是1、2、5这三种方法。

函数的一些必备知识

函数的name属性

由上面的五种定义方法,我们可以知道函数具有name属性,而且不同的定义方法,name属性也很奇葩。

函数如何调用

为了理解后面的this,推荐使用call()方法,而不是使用常见的f()

以第一种定义方法为例

f.call(undefined, 1, 3)
4

call()方法的第一个参数就是this,后面的参数才是函数的执行参数。

下面用代码检验一下

function f1(m, n){
    console.log(this)
    console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window,} //不是应该打印undefined,为啥是window呢?
4 //这才是函数的执行内容

执行f1.call(undefined, 1, 3)后,this不是应该打印出undefined吗,为啥打印了Window呢(注意实际上是个小写的window,不是浏览器打印的大写的Window),可以用代码验证打印的就是小写的window

function f1(m, n){
    console.log(this === window)
    console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
true //说明是小写的window
4
function f1(m, n){
    console.log(this === Window)
    console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
false //并不是大写的Window
4

我真是服啦,那window和Window有啥区别呢。真是蛋疼啊,竟然考虑这个问题……

答案就是 var object = new Object,那var window = new Window。而且Window毫无探讨的意义,倒是这个window是个全局属性,多少有点用。


有时候自己真是有点钻牛角尖,钻进去后,还不会举一反三。如果立刻想到obj的例子就不用浪费时间了。


这就是藏着的this

这是因为浏览器捣的鬼,他把undefined变成了window。接下来使用严格模式,让undefined现身

function f1(m, n){
    'use strict'
    console.log(this)
    console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
undefined //这个undefined就是call()方法的第一个参数undefined
4
  • 而且call()的第一个参数是啥,this就是啥
function f1(m, n){
    'use strict'
    console.log(this)
    console.log(m + n)
}
undefine
f1.call('我是啥this就是啥', 1, 3)
我是啥this就是啥 //打印的依然是call()的第一个参数
4

arguments

前面分析了call()的第一个参数,那后俩参数是啥呢。

对,你没猜错,那就是arguments。

当你写call(undefined, 1, 3)的时候。undefined可以被认为是this[1, 3]就是arguments

函数的call stack

上面我们接触了call()方法,现在我们学习一下当有多个函数调用的时候,JavaScript解析器是如何调用栈的。

MDN的解释如下

调用栈是解析器(如浏览器中的的javascript解析器)的一种机制,可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回控制的点。(如什么函数正在执行,什么函数被这个函数调用,下一个调用的函数是谁)

  • 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
  • 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
  • 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
  • 如果栈占用的空间比分配给它的空间还大,那么则会导致“堆栈溢出”错误。

以下是通过三个方面去理解call stack这个概念的。

普通调用

代码如下,直观的动图可以看上述的链接

function a(){
    console.log('a')
  return 'a'  
}

function b(){
    console.log('b')
    return 'b'
}

function c(){
    console.log('c')
    return 'c'
}

a.call()
b.call()
c.call()

如上的代码,先有三个函数声明,然后是三个调用。浏览器先执行a.call(),然后执行b.call(),c.call(),下面结合图具体详细分析。

普通调用

  • 第一步:浏览器入口是a.call(),a函数入栈,执行a函数内部代码
  • 第二步:console.log(‘a’)执行完毕,就出栈,接着a函数结束,出栈死亡
  • 第三步:b.call()入栈,执行b函数内部代码
  • 第四步: console.log(‘b’)执行完毕就出栈,接着b函数结束,出栈死亡
  • 第五步:c.call()入栈,执行c函数内部代码
  • 第六步:console.log(‘c’)执行完毕就出栈,接着c函数结束,出栈死亡。
  • 整个代码结束,浏览器恢复平静。

嵌套调用

function a(){
    console.log('a1')
    b.call()
    console.log('a2')
  return 'a'  
}
function b(){
    console.log('b1')
    c.call()
    console.log('b2')
    return 'b'
}
function c(){
    console.log('c')
    return 'c'
}
a.call()
console.log('end')

嵌套调用

  • 第一步:浏览器的入口还是a.call(),a.call()入栈,执行a函数内部的代码
  • 第二步: a函数的第一行语句console.log(‘a1’),入栈,打印出a1,这句话就出栈死亡。此时a函数继续执行下面的代码。
  • 第三步: a函数的第二行语句b.call()入栈。执行b函数内部的代码。
    • 第四步:进入b函数内部,b函数的第一行语句console.log(‘b1’)入栈,打印出b1,就出栈死亡。
    • 第五步:b函数的第二行c.call()入栈,又进入c函数内部
      • 第六步:进入c函数的内部,第一行语句console.log(‘c’)入栈,打印出c,就出栈死亡。
      • 第七步:c函数执行完毕,出栈死亡。
    • 第八步:回到b函数内部,执行第三行代码console.log(‘b2’)入栈,打印出b2,出栈死亡。
    • 第九步: b函数执行完毕,出栈死亡。
  • 第十步: 回到a函数内部,执行第三行代码console.log(‘a2’),入栈,打印出a2,就出栈死亡。
  • 第十一步:a函数执行完毕,出栈死亡。
  • 第十二步:console.log(‘end’)入栈,打印出end,出栈死亡。
  • 整个代码运行完,浏览器归于平静。

递归调用

递归调用就是上面的嵌套调用的复杂变化,细心点,分析就能明白具体的代码顺序。

函数作用域

除了全局变量,其他变量只能在自己的函数内部被访问到,其他区域无法访问。通过几个面试题来学习一下。

  • 第一道面试题
var a = 1
function f1(){
    alert(a) // 是多少
    var a = 2
}
f1.call()

问:alert出什么东西?

这种题切忌上去就做,容易打错成了 a是2 一定要先把变量提升。变成如下这样的

var a = 1
function f1(){
     var a 
    alert(a) 
    a = 2
}
f1.call()

这样一提升就知道啦,答案:a是undefined

  • 第二道面试题
var a = 1
function f1(){
    var a = 2
    f2.call()
}
function f2(){
    console.log(a) // 是多少
}
f1.call()

问:a是多少

这个题用就近原则好做。

树形结构

用树形结构来分析,当上面的代码被浏览器渲染之后

  • 全局变量里面有:var a = 1,f1、f2函数
  • f1函数作用域里面又重新声明了一个var a = 2
  • f2函数作用域里面是console.log(a)

所以打印的那个a就是全局的a,答案是a=1