代码改变世界

javascript谜题

2010-01-27 22:20  BlueDream  阅读(1348)  评论(13编辑  收藏  举报

昨天在NCZOnline博客谈到了几道谜题.觉得挺有意思.记录一下.大家无聊的时候可以测试玩玩.

这几道谜题.最起码可以从中学到一些东西.不算是一些技巧的卖弄. 下面一一分析,共有5道 大家可以试着一道道做 然后看下结果.

测试一

if(!("a" in window)) {
    
var a = 1;
}
alert(a);

测试二

var a = 1,
    b 
= function a(x) {
        x 
&& a(--x);
    };
alert(a);

测试三

function a(x) {
    
return x * 2;
}
var a;
alert(a);

测试四

function b(x, y, a) {
    arguments[
2= 10;
    alert(a);
}
b(
123);

测试五

function a() {
    alert(
this);
}
a.call(
null);

 

 

 

 

 

----------------------------------------------------------------- 答案分水岭 ------------------------------------------------------------------

 

 

 

 

答案:

1. undefined
21
3function a(x) { return x * 2; }
410
5. window

 

讲解:

第一题

if (!("a" in window)) {
    
var a = 1;
}
alert(a);
这段代码用语言描述就是
"如果window没有属性a,那么定义a等于1". 也许有人就会认为结果是1了.但测试
才会发现弹出的是undefined.这是问什么呢
?
这个问题考察以下两点:
1) 所有的全局变量都是window的属性. var a = 1实际上相当于window.a = 1. 所以你可以使用
"variable-name" in window来检测一个全局变量是否被定义.

2) 所有被var的变量声明 都要被提到作用域的顶部.考虑一个简单的例子:
alert(
"a" in window);
var a;
这个结果会弹出true;尽管变量定义在后面. 这是因为javascript引擎首先扫描定义的变量并把他的作用域提到顶部. 所以上面的代码近似于这样:
var a;
alert(
"a" in window);
读这段代码,你就会更清楚为什么弹出的是true.

3) 你需要知道咱们测试例子 当变量作用域定义并提前了.但却并没有初始化.下面这句是定义变量并初始化
var a = 1;
你可以分开看定义变量和初始化:
var a; //定义
= 1; 初始化
但JS引擎解析一个合并的var a 
= 1;是自动将其分离的.所以可以将定义被提前.但为什么值不提前呢? 因为值会影响到后面的值而导致不可预知的结果出现.

所以.知道了上面这三点.重新思考下刚才的例子.其实就可以分解为
var a;
if (!("a" in window)) {
    a 
= 1;
}
alert(a);
这样就可以看出if检测的时候a已经被定义了 默认为undefined所以分支不会走 最后弹出了
'undefined'

第二题

var a = 1,
    b 
= function a(x) {
        x 
&& a(--x);
    };
alert(a);

这个例子看上去要比实际复杂.这个结果显示1. 主要是看哪个a最后被实例化了.这个问题也需要搞清3个知识点.

1) 就是定义的变量作用域被提前.这个在实例1已经讲解了.

2) 就是所有的function定义的作用域也随着var变量定义一起提前.但必须搞清楚的是.一个函数定义是这样子的:
function functionName(arg1, arg2){
    
//function body
}
这个要区别于函数表达式.函数表达式是赋予一个变量,是这样的:
var functionName = function(arg1, arg2){
    
//function body
};
要搞清楚的是.函数表达式不会作用域提前.这样应该意识到.如果将value初始化值,从代码的一段移到另一段会产生影响.

3) 就是要必须理解记住函数声明重写的是变量声明.但不是变量初始化.理解这句话考虑下面的例子
function value(){
    
return 1;
}
var value;
alert(
typeof value);    //"function"
尽快变量声明在函数声明的后面.但函数声明会吧优先权让给变量声明.但是经过变量初始化你将会得到不同的结果
function value(){
    
return 1;
}
var value = 1;
alert(
typeof value);    //"number"
因为value = 1变量赋值的时候又覆盖了 函数声明 所以变成了1

回归到咱们的测试题 其实可以简化成下面这样:
var a = 1,
    b 
= function(x) {
        x 
&& b(--x);
    };
alert(a);
显而易见 就是弹出1

第三题

function a(x) {
    
return x * 2;
}
var a;
alert(a);
如果理解了前面的两个实例那么这个就很容易理解了.
var a;作用域被提前 然后函数a将其覆盖. 由于没有a变量赋值, 所以最后弹出了整个函数的source

第四题

function b(x, y, a) {
    arguments[
2= 10;
    alert(a);
}
b(
123);
这个例子在所有浏览器里都显示10.
在执行一个函数前,它会把它自身(callee),它的运行环境(callee.caller),传入参数的个数,构建成一个argument对象.
放上ECMA对arguments的说明.
ECMA
-262, 3rd Edition, section 10.1.8 says about an arguments object:

For each non
-negative integer, arg, less than the value of the length property, a property is created with name ToString(arg) and property attributes { DontEnum }. The initial value of this property is the value of the corresponding actual parameter supplied by the caller. The first actual parameter value corresponds to arg = 0, the second to arg = 1, and so on. In the case when arg is less than the number of formal parameters for the Function object, this property shares its value with the corresponding property of the activation object. This means that changing this property changes the corresponding property of the activation object and vice versa.

尤其最后这一句
This means that changing 
this property changes the corresponding property of the activation object and vice versa.
当arguments的属性值改变那么活动对象的对应属性也跟着变.反之亦然.
简而言之就是arguments的属性和相对性对象的属性 是值共享的 两者总是保持着相同的值.

所以由于arguments[
2= 10了 所以对应的活动对象属性也为10.

第五题

function a() {
    alert(
this);
}
a.call(
null);
这个其实理解起来没那么难.我们需要了解两个知识点.
1) 应该理解this的值是怎么确定的.当一个对象的方法被执行.this就指向该方法的拥有者这个对象.如
var object = {
    method: 
function() {
        alert(
this === object);    //true
    }
}
object.method(); 
在上面的代码里.当object.method()被执行时.this最终指向object.在全局环境里this指向window(当然如果非浏览器对象就指向默认的宿主).所以当一个函数不属于一个对象的属性是 那么this就等于window
function method() {
    alert(
this === window);    //true
}
method(); 
上面的this就指向了全局的对象 window.

2) 有了(1)的知识做基础.我们再需要的就是知道第二个重要的点.call做了什么.call()方法执行函数.比如另一个对象的方法. 这个方法的this将被call的第一个函数代替,然后其余的参数就按顺序传入到那个方法中.
function method() {
    alert(
this === window);
}
method();    
//true
method.call(document);   //false
此时call是method内部的this指向了document所以返回false.
**An interesting part of ECMA-262, 3rd edition describes what should happen when null is passed in as the first argument to call():

If thisArg is 
null or undefined, the called function is passed the global object as the this value. Otherwise, the called function is passed ToObject(thisArg) as the this value.
所以call(或者apply)如果传入null.将默认为全局对象.即浏览器的window或者其它的宿主环境.所以例子可以改写为:
function a() {
    alert(
this);
}
a.call(window);
这样结果就会显示window.
希望上面的5个例子能够让大家学到一些东西.