JavaScript经典面试题详解

以下是我遇到的一些经典的JS面试题,结合我自己的理解写的详解,主要参考高程一书,欢迎大家批评指正

1.

 

           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。

 

           2.

           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

 

 

           3.

           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。

 

            4.

           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。

 

 

           5.

           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”,类型为字符串。

 

 

 

           6.

           var x=8;
            var objA = {
                x:'good',
                y:32
            }
            function add(x,y){
                console.log(x.y+y);
            }
            function fn(x,y){
                x.y=5;
                y(x,3);
            }
            fn(objA,add);
            console.log(objA);

 

 

          

    答:结果是依次打印8 {x:“good”,y:5};

   首先编译器对代码进行编译,先提升两个函数表达式,然后提升声明x,objA,所以代码片段会被引擎理解为如下形式:

           function add(x,y){
                console.log(x.y+y);
            }
           function fn(x,y){
                x.y=5;
                y(x,3);
            }
           var x;
           var objA;
           x=8;
           objA={
      x:‘good’,
      y:32
      }
           fn(objA,add);
            console.log(objA);

 

 

    然后引擎运行代码fn(objA,add),objA和add是实参被传入函数fn中,所以相当于运行函数fn(objA,add)。在函数内部出现x.y=5。首先对象取属性操作的优先级最高,其次访问对象属性有两种方法,一种是本题中使用的点表示法,这也是很多面向对象语言中通用的语法。不过,在JavaScript也可以使用方括号表示法来访问对象的属性。在使用方括号语法时,应该将要访问的属性以字符串的形式放在方括号中,从功能上看,这两种访问对象属性的方法没有任何区别。但方括号语法的主要优点是可以通过变量来访问属性。而点表示法 不能使用变量来访问属性,所以本题中的x.y=5等同于objA.y=5,即在全局作用域中找到objA并把其y属性改变为5。而y(x,3)就等同于add(objA,3),即运行函数add,并为形参x,y传入实参objA和3,而且同上面所讲对象取属性优先级更高,所以函数add内部可以看为console.log(objA.y+3),对objA进行RHS查询,并得到它的y属性值为5,所以打印值为5。

    若本题中改为

           var x=8;
            var objA = {
                x:'good',
                y:32
            }
            function add(x,y){
                console.log(x.y+y);
            }
            function fn(x,y){
                x[y]=5;//改变去属性表示方法
               y(x,3);
            }
            fn(objA,add);
            console.log(objA);

 

 

    则会第一个打印是38,因为x[y]=5等同于objA[add]=5,即给objA添加里一个add属性,而objA[y]还是等于35,所以console.log(x.y+y)等同于console.log(objA.y+3)即35+3等于38,所以会打印38.

 

           7.

           function changeObjectProperty (o) {
                o.siteUrl = "http://www.csser.com/";
                o = new Object();   
                o.siteUrl = "http://www.popcg.com/";
            }
            var CSSer = new Object(); 
            changeObjectProperty(CSSer);
            console.log(CSSer.siteUrl);

 

 

          

   答:打印http://www.csser.com/

    首先说明一点ECMAScript中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。访问变量有按值和按引用两种方式,而参数只能按值传递。

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ECMAScript的概念来说,就是arguments对象中的一个元素)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。请看下面这个例子:

 function addTen(num) {
num += 10;
return num;
}
           var count = 20;
var result = addTen(count);
alert(count); //20,没有变化
alert(result); //30

 

 

    这里的函数addTen()有一个参数num,而参数实际上是函数的局部变量。在调用这个函数时,变量count作为参数被传递给函数,这个变量的值是20。于是,数值20被复制给参数num以便在addTen()中使用。在函数内部,参数num的值被加上了10,但这一变化不会影响函数外部的count变量。参数num与变量count互不相识,它们仅仅是具有相同的值。假如num是按引用传递的话,那么变量count的值也将变成30,从而反映函数内部的修改。但如果使用对象,那么情况会有一点复杂。再举一个例子:

function setName(obj) {
obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"

 

 

    以上代码中创建一个对象,并将其保存在了变量person中。然后,这个变量被传递到setName()函数中之后就被复制给了obj。在这个函数内部,obj和person引用的是同一个对象。换句话说,即使这个变量是按值传递的,obj也会按引用来访问同一个对象。于是,当在函数内部为obj添加name属性后,函数外部的person也将有所反映;因为person指向的对象在堆内存中只有一个,而且是全局对象。有很多人错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,可以再看一看下面这个经过修改的例子:

          

function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"

 

    这个例子与前一个例子的唯一区别,就是在setName()函数中添加了两行代码:一行代码为obj重新定义了一个对象,另一行代码为该对象定义了一个带有不同值的name属性。在把person传递给setName()后,其name属性被设置为"Nicholas"。然后,又将一个新对象赋给变量obj,同时将其name属性设置为"Greg"。如果person是按引用传递的,那么person就会自动被修改为指向其name属性值为"Greg"的新对象。但是,当接下来再访问person.name时,显示的值仍然是"Nicholas"。这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

    当实参是数组时,情况也有一些特殊,例如:

           
var a=[1,2,3];
function foo(a){
    a.push(4); //调用引用类型方法,改变了形参a,也改变了全局变量a
    console.log(a); // [1,2,3,4] 此时的a是形参变量的值
    a=[5,6,7];      // 形参重新赋值不会改变全局变量a
    console.log(a); // [5,6,7] 形参变量a
};
foo(a);
console.log(a); // [1,2,3,4]
    对照下面代码:
              var a=[1,2,3];
function foo(a){
    a=[5,6,7]; // 形参a被重新赋值,不会改变全局a
    a.push(4); // 此时只改变了形参a,不会改变全局a
    console.log(a); // [5,6,7,4]
};
foo(a);
console.log(a); // [1,2,3]

 

    综上所述,在从一个变量向另一个变量复制基本类型值和引用类型值时,存在不同。如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,调用属性或方法改变其中一个变量,就会影响另一个变量,如下面的例子所示:对引用数据类型,会发现在函数里,形参被赋值或重新声明之前,对形参调用引用数据类型的属性(或方法)时,不仅会改变形参,还会改变全局变量。

    所以对于本题中代码片段,实参CSSer是一个在对象,在函数changeObjectProperty中把CSSer值复制给o,然后对形参调用siterUrl属性,不仅改变了形参o,也改变了CSSer,所以此时CSSer包含一个siteUrl属性,并且属性值为http://www.csser.com/,然后又将一个新对象赋给变量o,此时这个变量引用的就是一个局部对象了,而这个局部对象会在函数执行完毕后立即被销毁。所以在o.siteUrl = "http://www.popcg.com/";这一个操作里,只会改变o的属性,而不会改变外部CSSer的属性。所以最后打印结果为http://www.csser.com/

 

 

           8.

           var num=5;
            function func1(){
                 var num=3;
                 var age =4;
                 function func2(){
                     console.log(num);
                     var num ='ivan';
                     function func3(){
                       age =6; 
                     }
                     func3();
                     console.log(num);
                     console.log(age);
                }
                func2();
            }
            func1();

 

 

          

    答:结果为依次打印 undefined  ivan 6

根据本套面试题第一题及第二题中所写的声明提升等知识可以得首先引擎在解释JacaScript代码之前首先对其编译,编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。引擎会在全局作用域顶端声明函数func1,然后声明全局变量num。在函数func1的作用域顶端先声明函数func2。在函数func2内部作用域的顶端声明函数func3,然后声明变量num。引擎所理解的代码格式如下:

   

           function func1(){
              function func2(){
                  function func3(){
                     age =6; 
                  }
                  var num;
                  console.log(num);//undefined
                  num ='ivan';
                  func3();
                  console.log(num);//ivan
                  console.log(age);//6
              }
              var num;
              var age;
              num =3;
              age =4;
              func2();
           }
           var num;
           num=5;
           func1();

 

 

    然后引擎开始从上到下执行代码,首先执行num=5,对num进行LHS查询,在全局作用域中找到变量num并将值赋给它。

然后运行函数func1,依次对变量num,age进行LHS查询,查询规则为从里到外,即从自己的作用域依次查询嵌套它的外部作用域。所以这两个变量直接在自己作用域内找到已经被声明的变量空间,然后把值3 ,4依次赋值给它们。

然后运行函数func2,首先运行console.log(num);对num进行RHS查询,在func2作用域中找到被声明的变量num,但是该变量并未赋值,所以打印结果为undefined。然后运行num=’ivan’;对num进行LHS查询,在func2作用域中找到num并赋值为ivan。

然后运行函数func3,运行age=6;对age进行LHS查询,在func3作用域内没有找到该变量,然后到包裹该作用域的func2作用域中查找该变量,找到该变量,并对其重新赋值为6。

函数func3内部代码运行完后,再接着运行func2内部代码console.log(num),对num进行RHS引用,在其所在作用域内找到被赋值为ivan的变量num,然后把得到的值传给console.log(),即打印出ivan。

接着执行代码console.log(age);和上步同理,对age进行RHS查询,在其所在作用域内没有找到变量age,然后向上级作用域接着查找,找到已被重新赋值为6的变量age,并把值传递给console.log(),所以打印结果为6。

   

           9.

           var  fn1 = 'ivan';
            var  name ='good';
            var fn1 = function(y){
                y();
            }
            function fn1(x){       
                x(name); 
            }
            function fn2(x){
                console.log(x);
                console.log(name);
                var name = 'hello';
                console.log(name);
            }
            fn1(fn2);

 

          

    :结果为依次打印 undefined undefined hello

    根据本套面试题第一题及第二题中所写的声明提升等知识可以得首先引擎在解释JacaScript代码之前首先对其编译,编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。所以引擎会在全局作用域顶端先声明函数fn1,然后声明函数fn2,在fn2内部作用域的顶端声明name。在声明fn2下面声明变量fn1,因为与函数fn1声明重复,所以忽略该声明,然后声明变量name,然后声明fn1,与之前声明重复被忽略。最后结果如下:

           function fn1(x){       
              x(name); 
           }
           function fn2(x){
              var name;
              console.log(x);//undefined
              console.log(name);//undefined
              name = 'hello';
              console.log(name);//hello
           }
           // var  fn1;  声明被忽略
           var  name;
           // var fn1;  声明被忽略
           fn1 = 'ivan';
           name ='good';
           fn1 = function(y){
              y();
           }
           fn1(fn2);

 

 

          

    然后引擎开始执行代码,首先对fn1进行LHS查询,在全局作用域中找到该变量,然后对其重新赋值为ivan,(需要说明的是在JavaScript中可以通过改变变量的值来改变变量的属性)。

    然后对name进行LHS查询,在全局作用域内找到该变量,并赋值为good。

    然后对fn1进行LHS查询,在全局作用域内找到该变量,并把function(y){y();}赋值给它,并且该变了fn1的类型。

    然后运行函数fn1,其中y为形参,fn2为实参,对y(隐式的)进行LHS查询,把fn2赋给y。

然后运行函数y(),即运行函数fn2(),fn2中存在形参x,首先对x(隐式的)进行LHS查询,但并未查询到所对应的实参,所以x为空。然后运行代码console.log(x);即打印undefined。

然后运行console.log(name),对name进行LHS查询,在其所在作用域中找到没有赋值的变量name,所以打印undefined。

然后运行name=“hello”,对nameRHS查询并赋值。

然后再运行console.log(name),对name进行LHS查询,在其所在作用域中找到被赋值为hello的变量name,所以打印hello。

           10.

           var buttons = [{name:'b1'},{name:'b2'},{name:'b3'}];
            function bind(){
                for (var i = 0; i < buttons.length; i++) {
                   buttons[i].onclick = function() {
                       console.log(i);
                   }
                }
            };
            bind();
            buttons[0].onclick();//3
            buttons[1].onclick();//3
            buttons[2].onclick();//3

 

 

          

    :运行结果为依次打印3 3 3

    上面的代码在循环里包含着一个闭包,闭包可以简单理解为:当函数可以记住并访问所的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。在for循环里面的匿名函数执行 console.log(i)语句的时候,由于匿名函数里面没有i这个变量,所以这个i他要从父级函数中寻找i,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,

但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i,当找到这个i的时候,是for循环完毕的i,也就是3,所以这个bind()得到的是一三个相同的函数:

function(){console. log(3)}

    所以当运行buttons[0].onclick();和其他两个程序时时都会打印3。

    如果要想实现理想中的打印0 1 2的效果,需要更多的闭包作用域,特别是在循环的过程中每个迭代都需要一个闭包作用域。而函数自调用会通过声明立即执行一个函数来创建作用域。

例如:

               var buttons = [{name:'b1'},{name:'b2'},{name:'b3'}];
               function bind(){
                  for (var i = 0; i < buttons.length; i++) {
                      buttons[i].onclick = (function (i){
                         console.log(i)
                      }(i));
                  }
               };
                bind();
               buttons[0].onclick;//0
               buttons[1].onclick;//1
               buttons[2].onclick;//2

 

 

          

           11.

           function fun(n,o) {
                console.log(o)
                return {
                    fun:function(m){
                        return fun(m,n);
                    }
                };
            }
            var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
            var b = fun(0).fun(1).fun(2).fun(3);
            var c = fun(0).fun(1);  c.fun(2);  c.fun(3);

 

 

          

    : 结果为依次打印undefined 0 0 0 undefined 0 1 2 undefined 0 1 1

    var a =fun1(0);o没有被赋值打印undefined,a等于返回的一个对象

{fun:function(m){oturn fun1(m,0)};

                 

    a.fun(1);fun1(1,0)打印0,返回一个对象

{fun:function(m){oturn fun1(m,1)}}

    同理,a.fun(2);fun1(2,0)打印0

    同理,a.fun(3);fun1(3.0)打印0

 

    var b=fun1(0).fun(1).fun(2).fun(3);o没有被赋值打印undefined,b等于返回的一个对象

{fun:function(m){oturn fun1(m,0)};

    .fun(1)=function(1){oturn fun1(1,0)}  打印0

    返回一个对象 {fun:function(m) {oturn fun1(m,1)}}

    .fun(2)=function(2) {oturn fun1(2,1)} 打印1

    返回一个对象 {fun:function(m) {oturn fun1(m,2)}}

    .fun(3)=function(3) {oturn fun1(3,2)} 打印2

                 

    var c=fun1(0).fun(1);

    o没有被赋值打印undefined,c等于返回的一个对象{fun:function(m){oturn fun1(m,0)};

    .fun(1)=function(1){oturn fun1(1,0)}  打印0

    返回一个对象{fun:function(m){oturn fun1(m,1)};也就是等于c

    c.fun(2);

    c={fun:function(m){oturn fun1(m,1)};

    所以fun(2)=fun1(2,1),打印1               

    c.fun(3);

    c={fun:function(m){oturn fun1(m,1)};

    所以fun(3)=fun1(3,1),打印1 

 

 

            12.

            var name    = 'lili';
            var obj     = {
                name: 'liming',
                prop: {
                    name: 'ivan',
                    getname: function() {
                      return this.name;
                    }
                }
            };
            console.log(obj.prop.getname());//ivan
            var test = obj.prop.getname; 
            console.log(test()); //lili

 

           

    答:结果为依次打印ivan    lili

    在从一个变量向另一个变量复制基本类型值和引用类型值时,存在不同。如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,调用属性或方法改变其中一个变量,就会影响另一个变量。

    在本题中,在全局作用域中声明了一个变量test,并把obj.prop.getname;赋值给它,那么test便指向函数function() {return this.name; }。

    当引擎运行到console.log(obj.prop.getname());时对obj.prop.getname进行LHS查询,得到函数function() {return this.name; },然后运行函数,

    首先说明this并不是指向函数自身或是函数的词法作用域,this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。简单来说,this是指调用包含this最近的函数的对象。

    而要找到this代表什么,首先要找到调用位置,调用位置就是函数在代码中被调用的位置(而不是声明的位置)。首先最是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。调用位置就在当前正在执行的函数的前一个调用中。在代码

console.log(obj.prop.getname());

中,this所在函数是被prop调用的,即调用位置是obj的属性prop,所以this.name可以看成obj.prop.name,即ivan。

    声明在全局作用域中的变量(var test = obj.prop.getname;  )就是全局对象的一个同名属性。它们本质上就是同一个东西,并不是通过复制得到的,就像一个硬币的两面一样。接下来我们可以看到当调用test()时,this.name被解析成了全局变量name。因为在本题中,函数调用时应用了this的默认绑定,因此this指向全局对象。换句话说就是test的调用位置在全局作用域,所以this.name就在全局作用域中匹配,得到name=lili。

   

          

 

posted @ 2018-08-22 22:19  电里  阅读(1688)  评论(0编辑  收藏  举报