首页 > 作用域面试题

作用域面试题

function box(obj){         
        obj.name='Lee';
        var obj = new Object();    
        obj.name='kkk';
    }
    var obj = new Object();
    box(obj);
    alert(obj.name);

求解这里为什么打印是lee` 而不是kkk呢?


我觉得这就是sf和stackoverflow的一点区别,这么简单的问题反而这么多人关注回答,那么有意义的问题反而没人问津


这道题应该考察的是javascript函数调用的参数传递方式。看回答的人也有说到这一点的,但是说错了,这里给出正确的说法,以免其他人被带入歧途。
javascript其实和java一样,参数传递只有一种形式:值传递,如果传入参数是对象,参数实际上是该对象的引用的copy。知道这一点这道题就不会错了。
@tudewutong 能想到声明前置,我还真没想到这一点。从这道题可以看出来,再声明前置之后接着就把声明的变量的引用值赋予了函数参数的引用,不知道是不是这样,还让高人指点。


首先说明两点:

  1. 使用var声明的变量会存在声明提前

  2. 函数的参数会有声明变脸的效果

根据上述两点变量已经声明了变量obj,那个var obj = new Object()等价于obj = new Object();

function box(obj){         
    obj.name='Lee';//此处的obj为传入的值
    obj = new Object(); //此处将obj覆盖   
    obj.name='kkk';//这里的obj为后面新建的obj
}
box(obj);
console.log(obj.name);//弹出外面的obj的name

还有函数传值全部是按值传递,没有按引用传递


多谢指出
传送都是按值传送,只是对象,数组等传送的是其内容的地址

obj.name='Lee'

这时obj指向的是外面那个obj,对外面obj的name赋值

var obj=new Object();

此时obj指向的是一个新的对象,一个和外面obj不一样的对象

obj.name='kkk';

新对象的name赋值

so,外面的obj对象赋值后就没有改变过


这个的原因是js在传递对象(Object,Array,Date等)是是“引用传递”,但在传递基本数据类型(Number,String, Boolean等)时是“值传递”。
值传递,将数值进行复制后,作为参数。
比如:

function add(x) {x = x + 1; return x}; var a = 10; add(a); // 11
console.log(a); // 10

这个a的值不会变是“值传递”。参数x用a进行初始化,类似于x=a;但由于a是基本类型,所以执行后,x=10;a=10;但这两个“10”是内存里面不同的地方,对其中任何一个的修改都不会影响到另一个值。(也就是说,基本类型在赋值的时候会把这个值完全复制一遍,然后给x)。
引用传递,将参数的“指针”进行赋值。

var a = {data:1};
var b = a;
b.data = 10;
console.log(a.data); // 10
console.log(b.data); // 10

对象在赋值的时候,进行的是“引用传递”,并没有生成一个新的对象,而是将之前对象的“指针”,赋值给了b。(也就是说,执行var b = a;后,a,b指向的是同一块内存区域。所以对b的修改会影响a。

回到这题来,实际上就是(我将外面的obj改名为obj0了,便于区分):

function box(obj){         
        // 进入后,函数里面的obj和传入的参数obj0指向同一个对象
        obj.name='Lee'; // 修改那个对象,也就是obj0.name被改变了。
        var obj = new Object();    // 新建一个对象,这是obj指向那个新的对象了(此时obj0的修改已经完成)
        obj.name='kkk'; // 在刚才新建的对象上进行操作,和外部的obj0没有关系。
    }
var obj0 = new Object();
box(obj0);
alert(obj0.name);//所以结果为Lee

leftstick老师说的很对,我把他的意思解释一下。

function box(obj){         
        obj.name='Lee';//传入了对象一,将对象一的name赋值为lee
        var obj = new Object();  //这里又新建了一个对象,此时,作用域变为了新的对象二  
        obj.name='kkk'; //对象二的name被赋值为kkk
    }
    var obj = new Object();//这里新建了一个对象,即对象一,作用域为对象一
    box(obj);//如上面的函数所解释,运行一遍之后,对象一的name是lee
    alert(obj.name);//此时对象一的那么为lee

这个问题的关键在于,new方法执行了构造函数之后,作用域就切换到新构造的对象了,详见MDN.

至于变量提升,提升的只是声明,赋值并不会提升。
ps:DC大神认为new是javascript的糟粕,建议不要使用。


我这么说好不好,

我给了你一个苹果A,让你在上面刻个lee,你刻了;然后扔掉这个苹果,自己又拿了一个苹果B,在上面刻了kkk

现在问你,我之前给你的苹果A上,刻的什么?


给你写全了你就懂了

function box(obj){         
        obj.name='Lee';
        var obj = new Object();    
        window.obj.name='kkk'; //依然强行赋值给全局的obj
    }
    var obj = new Object();
    box(obj);
    alert(obj.name); //此时就是kkk了

上面说到变量提升,那么提升后应该是这样:

function box(obj) {
    var obj;
    obj.name = 'Lee';
    obj = new Object();
    obj.name = 'kkk';
}

var obj = new Object();
box(obj);
console.log(obj.name);

OK,然后让我用简单粗暴的办法,在 box() 里面插几条 console.log(obj),然后在全局的 obj 声明之后给 name 属性赋个值。就变成这样:

function box(obj) {
    var obj;
    console.log(obj);
    
    obj.name = 'Lee';
    console.log(obj);
    
    obj = new Object();
    console.log(obj);
    
    obj.name = 'kkk';
    console.log(obj);
}

var obj = new Object();
obj.name = 'test';
box(obj);
console.log(obj.name);

// 返回的结果
// { name: 'test' }
// { name: 'Lee' }
// {}
// { name: 'kkk' }
// Lee

可以看到,即使 box() 中声明的 obj 提升到函数顶部,后面 log 出来的依旧还是传入的 obj。提升后的变量声明由于已经存在了传入的 obj,所以实质上这句是没用的。

虽然函数的参数是按值传递的,但是对象的值其实是内存中的指针地址,所以函数中的 obj(未在函数中再次赋值之前)与全局作用域 obj 实际上是同一个。

参考:javascript传递参数如果是object的话,是按值传递还是按引用传递?

在函数中再次赋值之后,obj 变成了这个局部作用域中的变量(new 了一个新的 Object,并赋值给了 objobj 的值不再指向全局作用域下的 obj),所以后面再怎么修改都跟全局作用域下的 obj 没有关系了。

以上是查阅以及测试之后,思考得出。如果有地方不对,望高人指点。


函数里定义的var obj=new Object()局部变量在函数外是不能被引用的


box(obj);

这个obj可以看作obj1,他的父作用域是window;

function box(obj){         
    obj.name='Lee';
    //这里重新定义了obj父作用域为box
    var obj = new Object();    
    // 此时的obj.name是box函数的局部变量;
    obj.name='kkk';
}

所以打印是lee 而不是kkk


obj这个对象的名字和参数名字重复了,参数名重新换个名字就能区分开了,因为你始终都用的obj这个名字.
就像是夫妻俩已经有一个儿子了,叫张三,后来有怀了一个孩子,夫妇俩商量叫啥,俩人嫌麻烦就又给还怀着的孩子也起了个名字叫“张三”,你说这俩孩子是同一个人吗,还是别把俩孩子的名字起成一样的了吧,明白我说的意思了吗?


var obj = new Object();    
obj.name='kkk';

这个地方的obj可以看做遮盖了上面的obj对象, 于是下面的语句操作的是这个内部的局部变量.


外面的obj对象在执行引擎来看变量名是window.obj,box函数里的obj变量名是window.box.obj(js高级程序设计中说过,函数的本质是对象),box内的变量声明当然存在提升效应,而且提升在传人的window.obj之前


补充一下,关于声明提前的,提前的只是声明,不包括赋值。


这就是JavaScript高级程序设计(第三版)的一部分示例。

【热门文章】
【热门文章】