初探JavaScript的函数

作者: 吴少在coding | 来源:发表于2018-01-05 16:37 被阅读62次

    什么是函数

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

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

    目前定义函数有五种方法

    具名函数来定义

    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(),下面结合图具体详细分析。

    [图片上传失败...(image-57cf0d-1515141332134)]

    • 第一步:浏览器入口是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

    相关文章

      网友评论

        本文标题:初探JavaScript的函数

        本文链接:https://www.haomeiwen.com/subject/rghsnxtx.html