JavaScript经典面试题详解(上)

js面试题+解析(上)

以下面试题是我认为比较有代表性的几道题,每道题目都配有我自己的见解,可能部分题目讲解存在差错,欢迎大家多多交流指正

第一题 程序分析

var a;
console.log(a);

答:运行结果为打印undefined。
首先,以上代码完全运行的话需要引擎,编译器,作用域的配合操作,(引擎负责整个JavaScript程序的编译及执行过程,编译器负责词法分析及代码生成等,作用域负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。)首先遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续执行编译,否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为a。然后引擎会对console进行RHS查询(RHS可以看成是对某个变量的值进行查找,LHS查询则是试图找到变量的容器本身,从而可以对其赋值),检查得到的值中是否有一个叫做log的方法,找到log方法后,引擎对a进行RHS查询,作用域中存在之前声明的a,但是变量a中没有赋值,所以把它传递给log,会打印undefined。如果对a进行RHS查询时没有找到a,那么引擎就会抛出ReferrnceError异常,因为在任何相关的作用域中都无法找到它。接下来,如果RHS查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或着引用null或undefined类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫作TypeError。

第二题

            console.log(typeof [1, 2]);
            console.log(typeof 'leipeng');
            var i = true; 
            console.log(typeof i); 
            console.log(typeof 1); 
            var a; 
            console.log(typeof a);
            function a(){};
            console.log(typeof a);
            console.log(typeof 'true'); 

答:运行结果为依次打印object string boolean number function function string.
引擎会在解释JavaScript代码之前对齐进行编译。编译阶段的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。而声明会从它在代码中出现的位置被“移动”到各自作用域的顶端,这个过程叫做提升。相较之下,当引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非“严格模式”下。另外值得注意的是,每个作用域都会进行提升操作,而且函数声明会被提升,但是函数表达式却不会被提升。函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个“重复”声明的代码中)是函数会首先被提升,然后才是变量。
所以,function a(){}会被先提升到作用域顶端,当编译到 var i =true;时,JavaScript会将其看成两个声明:var i;和i=true;。第一个定义声明是在编译阶段进行的,并把声明提前到作用域(此程序中为全局作用域)的顶端第二个声明会被留在原地等待执行阶段。同理编译到var a时,把声明提升,但它是重复的声明,因此被忽略了。所以代码片段会被引擎理解为如下形式:

        function a(){};
        var i
1.      console.log(typeof [1, 2]);
2.      console.log(typeof 'leipeng');
            i = true; 
3.      console.log(typeof i); 
4.      console.log(typeof 1); 

5.      console.log(typeof a);

6.      console.log(typeof a);
7.      console.log(typeof 'true'); 

ECMAScript中有5种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和String。还有1种复杂数据类型——Object,Object本质上是由一组无序的名值对组成的。typeof是一种用来检测给定变量数据类型的操作符,对一个值使用typeof操作符可能返回下列某个字符串:

“undefined” 如果这个值未定义;
“boolean” 如果这个值是布尔值;
“string” 如果这个值是字符串;
“number” 如果这个值是数值;
“object” 如果这个值是对象或null或数组;
“function” 如果这个值是函数。

1. 在第一个打印中要判断的值为[1, 2],这是一个数组,按照上面的说法,它会返回object。如果要检测一个数组,可以使用以下几种方法:a.instanceof,对于一个网页或者一个全局作用域而言,使用instanceof操作符就能得到满意的结果。

            var arr1=[1,2];
            console.log(arr1 instanceof Array);//true

instanceof操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。
为了解决这个问题,ECMAScript 5新增了Array.isArray()方法。这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。这个方法的用法如下。

            var arr1=[1,2];
            console.log(Array.isArray(arr1));//true

2 . 第二个要判断的值为’leipeng’ ,字符串可以用双引号或者单引号表示,这是一个字符串,所 以打印string
3 . 第三个要判断的值为变量i,对i进行RHS查询,得到之前被赋的值true,所以等同于
console.log(typeof true);
Boolean类型是ECMAScript中使用得最多的一种类型,该类型只有两个字面值:true和false。这两个值与数字值不是一回事,因此true不一定等于1,而false也不一定等于0。可以对任何数据类型的值调用Boolean()函数,而且总会返回一个Boolean值。或者在判断语句的条件里填其他数据类型的值来转换为Bollean值,例如
for(1){
console.log(123)//123
}
至于返回的这个值是true还是false,取决于要转换值的数据类型及其实际值。下表给出了各种数据类型及其对应的转换规则。
数据类型 转换为true 转换为false的值
Boolean true False
String 任何非空字符串 “”(空字符串)
Number 任何非零数字值(包括无穷大) 0和NaN
Object 任何对象 null
Undefined undefined

4 . 第四个要判断的值为1,1即为Number类型,上面解答中也说到了这两个值与数字值不是一回事,因此true不一定等于1,而false也不一定等于0。所以打印Number
5.6. 第五个和第六个相同,引擎会在作用域顶端找到function a(){};所以判断的类型值为function。
7. 要判断的值为“true”,前面说到了字符串的表示方法,可以用双引号或者单引号表示,即“true”的类型为String

第三题

            for(i=0, j=0; i<4, j<6; i++, j++){
                k = i + j;
            }
            console.log(k);

答:结果为在控制台打印10。
在for循环语句中一般写法为:

        for(语句1;语句2;语句3){
            被执行的代码块
            }

语句1: 1.初始化变量; 2.是可选的,也可以不填;3.可以写任意多个,与语句3中变量名对应
语句2: 1.执行条件 2. 是可选的(若不填,循环中必须要有break,不然死循环)3. 如果出现多个一逗号为间隔的判断依据,则以分号前的最后一项为准。
语句3: 1. 改变初始变量的值 2.是可选的
所以这个for循环的真正的执行条件是j<6 ,每执行一次循环i和j就加一,共执行6次循环,即最后i=j=5;k=10,打印10。

第四题

            var name = 'laruence';     
            function echo()
            {         
                console.log(name);   
            }      
            function env()
            {
                var name = 'eve';         
                echo();   
            }      
            env();

答:打印 laruebnce

作用域负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。作用域共有两种主要的工作模型。第一种是最为普遍的,被大多数编程语言所采用的词法作用域。另外一种叫作动态作用域,仍有一些编程语言在使用(比如Bash脚本、Perl中的一些模式等)
需要说明的是JavaScript中的作用域是词法作用域。当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。
词法作用域是一套关于引擎如何寻找变量以及会在何处找到变量的规则。词法作用域最重要的特征是它的定义过程发生在代码的书写阶段(假设没有使用eval()或with)。而动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。根据下面代码可以看出区别:

        function fun() {
        console.log( a ); 
        }
        function bar() {
        var a = 0;
        fun();
        }
        var a = 1;
        bar();

以上代码会打印1;因为词法作用域让fun()中的a通过RHS引用到了全局作用域中的a,因此会输出1;
但如果JavaScript具有动态作用域,理论上,上面的代码最终会打印0;因为当fun()无法找到a的变量引用时,会顺着调用栈在调用fun()的地方查找a,而不是在嵌套的词法作用域链中向上查找。由于fun()是在bar()中调用的,引擎会检查bar()的作用域,并在其中找到值为0的变量a。
综上所述,词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。在本题中首先对代码进行编译,题中代码片段会被引擎理解为以下形式;

            function echo()
            {         
                console.log(name);   
            }      
            function env()
            {
                var name = 'eve';         
                echo();   
            }      
            var name

            name = 'laruence';
            env();      

首先引擎运行到env()时,在全局作用域中进行RHS查询,找到函数env并执行,然后在env函数作用域中对echo进行RHS查询,但并没有查询到该函数,所以根据作用域嵌套原理在该作用域的外层即全局作用域中进行查找,找到函数echo并执行该函数,然后对console进行RHS查询找到log方法,然后对name进行RHS查询,在自己作用域内没有找到然后到全局作用域中找到name,然后对name进行LHS查询,同理在全局作用域中找到name=“lanuence”,然后打印laruence。

第五题

            var a = '' + 3;  
            var b = 4;
            console.log(typeof a);
            console.log(a+b);
            console.log(a-b);

            var foo = "11"+2+"1"; 
            console.log(foo);
            console.log(typeof foo);

答:依次打印string 34 -1 1121 string
一元加操作符以一个加号(+)表示,放在数值前面,对数值不会产生任何影响,例如:
var num = 25;
num = +num; // 仍然是25
不过,在对非数值应用一元加操作符时,该操作符会像Number()转型函数一样对这个值执行转换。换句话说,布尔值false和true将被转换为0和1,字符串值会被按照一组特殊的规则进行解析,而对象是先调用它们的valueOf()和(或)toString()方法,再转换得到的值。例如:

        var o = { valueOf: function() { return -1; } }; 
        console.log(+'01')//1
        console.log(typeof (+'01'))//number
        console.log(+'z')//NaN
        console.log(+false)//0
        console.log(+o)//-1

一元减操作符主要用于表示负数,例如将1转换成1。下面的例子演示了这个简单的转换过程:

        var num = 25; 
        num = -num; // 变成了-25

在将一元减操作符应用于数值时,该值会变成负数(如上面的例子所示)。而当应用于非数值时,一元减操作符遵循与一元加操作符相同的规则,最后再将得到的数值转换为负数,如下面的例子所示:

            var o = { valueOf: function() { return -1; } }; 
            console.log(-'01')//-1
            console.log(typeof (-'01'))//number
            console.log(-'z')//NaN
            console.log(-false)//0
            console.log(-o)//1

以上用法只是把加减符号用于单个值上,当使用加减符号对多个值进行组合使用时,情况会发生变化,当两个或多个数值进行加减操作时,其运行结果和数学运算相同(除去各边浮点数及无穷),但当运算值存在非数值时,加减两个运算符存在差异。
字符串之间使用加号表示把两边的内容进行拼接,当一个数值与字符串相加时,会把数值转换为字符串然后进行拼接,当数值与其他类型值相加时会遵循与一元加操作符相同的规则。而多个值之间使用减号不会存在拼接,而是和单个数值使用减号的规则一致。例如:

            console.log('1'+2);//12
            console.log(true+1)//2
            console.log('1'-2);//-1
            console.log(true-1)//0

综上所述,本题中a被一个字符串+数值给赋值,所以a=“3”,类型为String
同理a+b是一个字符串和数值相加,先把数值转换为字符串,然后进行拼接即打印34;
但a-b会先把a,b转换为数值然后b取负数再相加,即3+(-4)=-1,所以打印-1
同理 “11”+2+“1”会先把2转换为“2”,然后进行拼接即foo=“1121”,类型为字符串。

欢迎阅读下部分JavaScript经典面试题详解(下)

发布了3 篇原创文章 · 获赞 1 · 访问量 2398
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览