今天记录一个js的经典面试题,该编程题涉及到了js的变量提高、执行环境、做用域链问题。前端
一、变量提高
js没有块级做用域,使用var声明的变量会自动添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境。若是初始化变量时没有使用var变量,该变量会自动被添加到全局环境。下面两幅图是等价的,结果都是控制台打印出1 2 3 4 5面试
二、 执行环境
每一个函数都有本身的执行环境。当执行流进入一个函数时(即调用该函数),函数的环境就会被推入一个环境栈中。而在函数执行以后,将其环境弹出栈,把控制权返回给以前的执行环境。全局执行环境是最外围的一个执行环境。全局执行环境被认为是window对象,全局执行环境直到应用程序退出--例如关闭网页或浏览器---时才会被销毁。编程
function a(){ //执行a功能代码 } a(); //函数a的环境被推入一个环境栈中。 function b(){ //执行b功能代码 var c=function(){ //执行c功能代码 function d(){ //执行d功能代码 } retrun d(); } return c(); } b(); //函数b、c、d依次被推入一个环境栈中,当调用b()函数时,其依次被弹出
其执行的具体流程以下图所示:浏览器
三、做用域链
当代码在一个环境中执行时,会建立变量对象的一个做用域链。做用域链的用途,是保证对执行环境有权访问的全部变量和函数的有序访问。做用域链的前端,始终都是当前执行的代码所在环境的变量对象。做用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一致延续到全局执行环境;全局执行环境的变量对象始终都是做用域链中的最后一个对象。函数
var a=1; function b(){ //执行b功能代码 var bVar=1; var c=function(){ //执行c功能代码 var cVar=2; function d(){ //执行d功能代码 var dVar=3; cVar=3; } retrun d(); } return c(); } b();
以上代码共涉及4个执行环境:全局环境,b()的局部环境、c()的局部环境、d()的局部环境。全局环境有一个变量a和一个函数b()。b()的局部环境中有一个变量bVar和一个函数c.....依次。位于最里边的函数能够访问外部环境的全部变量和函数,由于外部环境是它的父执行环境。总结:内部环境能够经过做用域链访问全部的外部环境,但外部环境没法访问到内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每一个环境均可以向上搜索做用域链,以查询变量和函数名(服从就近原则);但任何环境都不能经过向下搜索做用域而进入另外一个执行环境。spa
经过上面介绍执行环境与做用域的两幅图能够看出,浏览器在执行js时,首先会将window对象(全局执行环境)压入环境栈,每次执行一个函数时,被调用的函数(按照调用的前后顺序)依次压入环境栈中。而压入栈中的环境相似于容器,往栈底方向的容器包含了上面的容器。容器中存放的是本身的变量和函数以及上面的容器。咱们能够把容器的玻璃的材质想象为车窗户(能够从里边看到外面,可是没法从外面看到里边),当在某个环境(容器)中执行代码块时,就比如咱们站在当前容器里,此时咱们能够看到外部容器(父级环境)的变量和函数,但却看不到内部容器的任何东西,这就是做用域链。code
下面进入正题,说下我对该面试题的理解对象
1 var foo = {n:1}; 2 (function (foo) { 3 console.log(foo.n); 4 foo.n=3; 5 var foo = {n:2}; 6 console.log(foo.n); 7 })(foo); 8 console.log(foo.n);
上面的代码其实能够写成这样:blog
1 var foo = {n:1}; 2 (function (foo) { 3 var foo; 4 console.log(foo.n); 5 foo.n=3; 6 var foo = {n:2}; 7 console.log(foo.n); 8 })(foo); 9 console.log(foo.n);
一、声明一个变量,为引用类型
2和八、声明一个匿名函数,并当即执行,传递的参数是第1行中的foo。将一个对象类型赋值给一个新的变量,因为对象是引用类型,实质上是指将对象的地址赋值给该变量(也就是说这两个变量指向同一个地址空间),所以改变新的变量中的属性值或方法,对应的原来对象的值也会改变。
三、原题中的第5行,因为存在变量提高,所以会在函数开始就声明,此时为undefined;然而因为一个变量的声明优先级低于形参,因此这行没有任何效果
四、打印形参的foo.n,打印1
五、改变第1行变量foo的属性n的值为3;
六、从新声明并定义了一个变量,开辟了新的内存空间,n为2
七、因为js中的代码是自上而下执行,因此此时输出2
九、上面的函数调用结束后,局部变量被销毁,而以前的内存空间值已经变为3,因此输出3
因此最终的结果为:1 2 3图片