美文网首页
解两道关于JS“引用类型”和“变量提升”的面试题

解两道关于JS“引用类型”和“变量提升”的面试题

作者: 喜剧之王爱创作 | 来源:发表于2020-11-16 15:53 被阅读0次

    在前端面试中,少不了关于JS引用类型和变量提升的题目,今天就分享两道面试题并附上详解过程。供读者学习巩固基础知识。

    关于引用类型的题目

    var obj = { a: 1 }
    
    function test (obj) {
        obj.a = 2
        obj = { a: 3 }
        obj.b = 3
    }
    test(obj)
    console.log(obj) // 求输出结果
    

    笔者分析:

    从我以往的经验来看,凡是这种一开始定义一个引用类型的变量,然后再在代码里各种修改属性,最后求输出结果的,那九成是考查"JS引用类型"的。下面我们简单说一下这个引用类型。

    笔者聊基础:

    关于引用类型,用我个人的白话概括就是,“引用类型的相互赋值只是单纯的把值复制了过去,而两个变量还是共享同一块内存区域,所以修改其中一个,另外一个也会跟着变化”。当然,这只是我个人的白话解释,关于更详细的定义个更官方的解释,大家自行网上学习,这里不做赘述,用一个很好的例子来说明上面的白话就是

    var a = { x: 10, y: 20 }
    var b = a
    b.x = 100
    b.y = 200
    
    console.log(a) // { x: 100, y: 200 }
    console.log(b) // { x: 100, y: 200 }
    

    上面是关于引用类型最直观的解释。其实这里面也是有一些坑的

    笔者聊坑

    var a = { x: 10, y: 20 }
    var b = a
    b.x = 100
    b.y = 200
    
    b = { x: 1, y: 2}  // 注意这里!! 
    
    console.log(a) // { x: 100, y: 200 }
    console.log(b) // { x: 1, y: 2}
    

    我们虽然知道了,引用类型之间共享内存区域的,但是一旦其中一个变量重新赋值,那么引用关系将不再存在,将中断关联!!!
    也就是上面代码中展示的样子。

    我们先不着急解上面的面试题,先来看这样一个例子。

    var a = 1
    function test (param) {
        param = 2
    }
    test(a)
    console.log(a) // 1
    
    var a = { a: 1 }
    function test (param) {
        param.a = 2
    }
    test(a)
    console.log(a) // { a: 2 }
    

    上面的例子中,我们给函数中分别传入了基本类型和引用类型的参数,输出显示,当传入的参数是基本类型时,那么函数内部对参数的修改是不会影响原数据的,而传入的是引用类型时,函数内部对参数的操作是会影响到原来数据的,所以总结下面两点

    • 函数的参数如果是简单类型,会将一个值类型的数值副本传到函数内部,函数内部不影响函数外部传递的参数变量
    • 如果是一个参数是引用类型,会将引用类型的地址值复制给传入函数的参数,函数内部修改会影响传递参数的引用对象。

    之所以,造成上面的问题,是因为方法的参数是一个局部变量,也就是,传参的过程中,相当于一个赋值的过程。对于引用类型,它们引用同一个地址,于是就造成了,“内部修改,外部也修改”的现象,而基本类型不会存在这种情况。

    笔者解答

    上面讲了这么多知识点,我想这个题的答案已经很明确了,不过还是要一步一步解答一下

    var obj = { a: 1 }
    function test (obj) {
    // obj为一个局部变量,和外部的obj共享一个地址
        obj.a = 2 // 内部obj的修改将导致外部修改为{ a: 2 }
        obj = { a: 3 } // 局部变量的赋值操作,导致关联中断,指向不再和外部obj共享,故,对外部不再有影响
        obj.b = 3 // 内部和外部的obj已经关联中断,此时内部obj为{ a: 3, b: 3}, 外部为 { a: 2 }
    }
    test(obj)
    console.log(obj)
    

    通过上面的注释分析,输出为{ a:2 },很明确。

    避免混淆

    和上面的题相似的还有一个变形,可能会容易和上面的题混淆,这里抛出来。

    var obj = { a: 1 }
    obj.a = 2
     obj = { a: 3 }
    obj.b = 3
    console.log(obj) // { a: 3, b: 3 }
    

    我们去掉了函数传参的形式,这里就只是对一个变量的操作了,没有所谓的“引用关系”后,也就结果比较明了了。

    小结

    上面,我们通过一个“引用类型”的面试真题,对引用类型的应用做了分析和讲解,希望对大家有所帮助,如果你对引用类型的基本知识还不够了解,那么就要加强补基础喽!

    关于变量提升的题目

    console.log(fn1, fn2, fn3 )
    var fn1 = function () {
        console.log('i am func1')
    }
    function fn2 () {
        console.log('i am func2')
    }
    var fn3 = 'i am string'
    console.log(fn1, fn2, fn3 )
    

    笔者分析

    根据我个人的经验,凡是在代码第一行就打出访问变量的代码的题目,考察的点八成是在"JS变量提升"。下面咱俩简单聊一下JS的变量提升。

    笔者聊基础

    其实这都是比较基础的知识了,官方的定义和社区的完善解释会更多,这里我们不纠结定义,想具体搞一下的,可以自己学习。这里我用自己的理解和白话来解释。想要知道什么是变量提升,我们先来看看,不是变量提升的情况。


    我使用了let关键字声明变量,熟悉ES6的读者应该都知道let没有变量提升,我们在一个变量还没有声明的时候,就去访问它,如果该变量还没有定义,那么就会报错,这是不具备变量提升的情况,反之,变量提升就是我们可以先访问后声明一个变量
    在声明变量的关键字中只有var具有变量提升,let不具备。也就是上面的错误代码,我们换成var声明就会正确

    我们需要注意的是,上面的代码不再报错了,并且其打印的结果为undefined
    这里之所以强调其打印结果为undefined。是因为字符串的变量提升和函数的提升的效果是不同的!这里有一个小坑

    笔者聊坑

    说到这里,我们不得不插一句JS的“词法分析”了,这都是JS中最基础的知识,由于开发中很少注意这些细节,也被大多数开发者忽略,同事因为使用变量提升的代码,会让代码的可读性变得更差,所以开发中一般很少使用变量提升,再加上现在是ES6的时代,本身就不再有这个概念,但,作为JS使用者的前端开发人员,这是你不得不知道的知识点!
    同样,我还是用自己的白话和理解来写,想看更官方更底层的解释,自行学习。词法分析就是,在JS运行的过程中,分为“编译阶段”和“执行阶段”。也就是说,我们通过var a = 1声明的变量a,实际上是经过了两个过程

    1. 在内存中开辟一个区域出来,起名为a。(这一步叫变量声明)这时候他没有值,所以a = undefined
    2. 执行a = 1,将值赋给上面声明的内存区域

    变量的声明会提升到其作用域的顶端去执行!也就是说

    console.log(a)
    var a = 1
    

    这段代码实际的执行过程是

    var a
    console.log(a)
    a = 1
    

    注意: 只是声明被提升了,赋值不会!赋值就是普通的执行代码,顺序执行
    这里之所以说有个坑,是因为函数的声明提升和其他的变量提升是不一样的,他有一个专门的名字叫“函数提升”,其实都是属于变量提升,这里函数提升有一个特点就是函数提升比变量提升优先级高!那么他是如何体现的呢?
    说到函数提升,你得先搞明白,函数声明,在JS中,具名函数的声明方式有两种

    1. 函数声明式
    function test () {
    /*这里是内容*/
    }
    
    1. 函数字面量式
    var test = function () {
    /*这里是内容*/
    }
    

    无论你是哪一种函数的声明方式,都具有变量提升!!不同的是,函数声明式会存在“函数提升”,而函数字面量式的声明和上面的变量提升结果是一样的。两种不同的函数声明方式,具有不同的提升优先级。其中,函数提升比变量提升优先级高。看代码:

    看出区别来了吗?通过函数声明式声明的函数,连初始化的东西也打印了出来,也就是函数提升;而函数字面量式声明的函数也就是通过普通的变量提升,只是打印声明的结果,而初始化的过程不会被打印。这样就导致,访问同一个变量,一个在后面声明为函数,一个声明为变量,声明为函数的因为有代码块,就会优先执行,这就是其优先级更高的原因,也就是下面这个代码为什么输出1的原因

    foo(); // 1
    var foo;
    function foo () {
        console.log(1);
    }
    foo = function () {
        console.log(2);
    }
    

    讲到这里,我想大家对于面试题的结果已经很明了了。结果不重要,享受分析的过程才重要,所以,请容我简单做个小总结。

    关于变量提升的总结

    • JavaScript 中,函数及变量的声明都将被提升到作用域的最顶部。其中函数会把整个代码块提升,而变量(这里叫法区别一下函数,不要纠结)只会提升其声明的过程,不会提升其初始化的过程,也就是赋值的过程。
    • 函数声明比变量声明的优先级要高!
    • 在开发中尽量少的使用变量提升,那将使你的代码可读性变差
    • 对于大多数程序员来说并不知道 JavaScript 变量提升。
    • 如果程序员不能很好的理解变量提升,他们写的程序就容易出现一些问题。
    • 为了避免这些问题,通常我们在每个作用域开始前声明这些变量,这也是正常的 JavaScript 解析步骤,易于我们理解。

    好了,说这么多,解题吧

    笔者解答

    console.log(fn1, fn2, fn3 )
    // fn1 变量提升,打印undefined, fn2 函数提升,打印整个函数体f(), fn3 变量提升,同fn1
    var fn1 = function () {
        console.log('i am func1')
    }
    function fn2 () {
        console.log('i am func2')
    }
    var fn3 = 'i am string'
    console.log(fn1, fn2, fn3 )
    // fn1 被初始化,打印函数体f(), fn2已经声明,打印函数体f(), fn3被初始化,打印字符串‘i am string’
    

    结果就是这么的枯燥无味,是吧?hiahiahia~~~

    避免混淆

    本节,我们主要讲变量提升,不过也说一点容易混淆的事吧。这里是用来提醒新手的

    var fn1 = function () {
        console.log('i am func1')
    }
    console.log(fn1)
    

    有时候会产生误解,“把一个函数赋给一个变量,那么这个变量不是等于这个函数的返回值吗?”哦哦哦,这里赶紧解释一下,不是的,把一个函数的执行结果给一个变量才是等于其return的值的,给函数体的话,那就是一个函数体,这里需要新手注意一下。

    写在最后

    本文通过两道面试题,引出了“引用类型”和“变量提升”两个JS中的基础知识点,也是在一些面试中,经常踩到的点,大家对于本文,不要把目光放在这面试题的结果上,枯燥的结果不如分析过程来的让人兴奋和收获,希望能够帮助到更多的人~

    相关文章

      网友评论

          本文标题:解两道关于JS“引用类型”和“变量提升”的面试题

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