【翻译】Zakas解答Baranovskiy的JavaScript测验题
原文:http://www.nczonline.net/blog/2010/01/26/answering-baranovskiys-javascript-quiz/
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
上周我在Dmitry Baranovskiy的一篇博客里看到一些关于JavaScript的测验题,博客标题叫"你觉得你真的懂JavaScript吗"。像这种类型的题目,你只要回答一个问题:结果是什么?这些例子用来测试JavaScript引擎一些诡异的特性行为。我以前见过这种类型的测验题,有些人会拿来当做面试题,我认为这不仅不尊重面试者,而且毫无用处。你不会每天都遇到这种类型的怪异问题,所以用这样的问题来考察面试者,当做是最低的指标,就跟面试空姐时叫人家解释飞机飞行原理一样没用。
不过我仍然喜欢这些例子,它们可以很好的用来解释JavaScript语言的有趣现象,下面我会深入分析每个测验题是怎么执行的。
测验1
if(!('a' in window)){
var a=1;
}
alert(a);
这段看起来很怪的代码好像是说:"如果window对象没有属性'a',那么就定义一个给它,并把它的值设置为1"。你可能会以为弹出的结果是1,事实上结果是'undefined',要了解原因的话,你需要知道关于JavaScript的三件事。
首先,所有全局变量都是window对象的一个属性,你写'var a=1;'相当于写'window.a=1;',你可以用下面的方法来检测一个全局变量是否声明了:
'variable-name' in window
其二,所有变量声明都会提前到当前作用域的顶端,请看下面的代码:
alert('a' in window);
var a;
上面的结果是'true',虽然变量声明语句在检测语句之后,但是因为JavaScript引擎首先会扫描变量声明,然后把它们移动当前作用域的顶端,所以最终解析出来的代码如下:
var a;
alert('a' in window);
看这段代码,更容易理解为什么结果是'true'。
第三,你需要知道,虽然变量声明提前了,但是赋值操作却没有。下面的语句是变量声明和赋值在一起的:
var a=1;
你可以把它分为声明和赋值两部分:
var a;//声明
a=1; //赋值
当JavaScript引擎发现声明和赋值是一起时,就会自动把它们分开,然后把声明提前。为什么赋值不提前呢?因为在代码执行的时候这会影响变量的值并且产生不可预期的结果。
现在了解了JavaScript的这些特性,重新看下案例1的代码,实际上被解析成了如下代码:
var a;
if(!('a' in window)){
a=1;
}
alert(a);
看这段代码后,答案就显而易见了。首先声明变量'a',接着是if语句,判断说:"如果'a'没有声明,那么就初始化为1"。当然了,条件判断不可能为'true',所以变量的值还是默认值'undefined'。
测验2
var a=1,
b=function a(x){
x&&a(--x);
};
alert(a);
这段代码比表面上看起来更复杂,以上结果为'1',就是初始化的值。但是为什么会这样?这仍然需要了解JavaScript的三个要点。
第一点就是声明提前,在案例1已经讲过了。
第二点,函数声明提前。所有的函数声明跟变量声明一样,都会被提前至当前作用域的顶端,但是要明白函数声明是这样子的:
function functionName(arg1,arg2){
//function body
}
以下不是函数声明,而是函数表达式,相当于变量赋值:
var functionName=function(arg1,arg2){
//function body
}
要清楚,函数表达式是没有被提前的。这一点现在要明白,因为变量的初始化和赋值位置的改变,都会显著的影响程序的执行。
第三点,函数声明的优先级高于变量声明,但是不会覆盖变量的赋值。为了理解这句话的意思,请看下面的代码:
function value(){
return 1;
}
var value;
alert(typeof value); //'function'
即使变量声明在函数声明之后,但变量'value'的值依然是一个'function',在这种情况下,函数声明的优先级更高。但是,如果变量初始化了,那么结果又会不同。
function value(){
return 1;
}
var value=1;
alert(typeof value); //number
现在变量的值被设置为1,变量的初始化会覆盖函数声明。
回到测验2的代码来,那个函数是一个有名字的函数表达式。带有名字的函数表达式不是函数声明,所以不会覆盖变量声明。然而,你要注意变量b的值是函数表达式,而函数表达式的函数名是a,不同的浏览器处理方法不同。IE浏览器会把它当做是函数声明,所以会被变量的初始化覆盖,那么调用a(--x)就会造成错误。至于其它浏览器,虽然a在函数外还是一个数字,但在函数内还是可以调用a(--x)。基本上,在IE中调用b(2)会抛出JavaScript异常,但在其它浏览器会返回'undefined'。
理解上述内容之后,把题目换成更准确和更容易理解的版本:
var a=1,
b=function(x){
x&&b(--x);
};
alert(a);
这样的话,就很清楚了,变量a的值总是1。
测验3
function a(x){
return x*2;
}
var a;
alert(a);
如果你理解了上面那个例子,那么这道题就很简单了。有一点要记住的就是函数声明总是优先于变量声明,也就是说变量声明语句被忽略了,除非遇到变量初始化。这里没有初始化,所以弹出结果是函数a的源代码。
同名的变量不会重复声明。
测验4
function b(x,y,a){
arguments[2]=10;
alert(a);
}
b(1,2,3);
这段代码更好理解,只要判断结果是'3'或者'10'就可以了,所有的浏览器结果都是10。只要知道一个概念你就知道为什么结果是这样了,就是ECMA-262第三版10.1.8章节关于arguments对象的描述:
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.
太TM难理解了:对于每个非负整数,传进来的参数个数比'length'属性值还小,那么就会使用ToString(arg)创建一个属性和属性变量'{DontEnum}'。这个属性的初始值为调用者传进来的参数个数。第一个实参对应arg=0,第二个对应arg=1,依次类推。当实参的个数小于形参时,这个属性就会和激活的对象共享值。这意味着,改变属性的值,相应的激活对象的属性也会改变,反过来也一样。
简而言之,arguments对象中的每个实体都是形参的一个拷贝,虽然值是共享了,但是内存空间没有共享。两个内存空间由JavaScript引擎来保存同步,也就是说arguments[2]和a总是拥有相同的值,所以最终a的值是10。
测验5
function a(){
alert(this);
}
a.call(null);
我认为这是5道题中最简单的了,只需要理解两个概念。
首先,你要知道this的值是怎么定义的,当一个方法被对象调用时,this就指向该对象了,例如:
var object={}
method:function (){
alert(this==object) //true
}
;
object.method();
在这段代码里,当object.method()被调用的时候,this就指向了object。在全局作用域,this相当于window(也就是全局对象),所以如果一个function的定义不是属于一个对象属性的时候(也就是单独定义的函数),函数内部的this等于window,例如:
function method(){
alert(this===window); //true
}
method();
在这里,this指向的是全局对象window。
了解了上述概念之后,我们再来了解一下call()方法是干什么的。call方法可以让一个方法当做是其它对象的方法被调用,第一个参数是调用者,其它参数是被调用的方法的实参,例如:
function method(){
alert(this===window);
}
method(); //true
method.call(document); //false
这里因为method()方法被document调用,所以结果是'false'。
一个有趣的问题是当传入call方法的一个参数是null会发生什么,以下是ECMA-262第三版对这个问题的描述:
如果传入的第一个参数是'null'或'undefined',那么call方法会把全局对象(window)作为this的值,否则this的值就等于ToObject(thisArg)。
所以无论什么时候call()和apply()方法第一个参数传入null,其this的值都是window。根据以上知识,将案例5代码重新改为如下更容易理解:
function a(){
alert(this);
}
a.call(window);
很明显结果就是[object Window]。
总结
Dmitry把这些有趣的测验题放一起,你可以从中学习JavaScript的一些怪异特性,我希望这篇文章可以帮助每个人理解这些例子运行时所必须知道的细节,更重要的是,理解为什么会这样子。重申一下,我反对使用这类测验题当做面试题来考验面试者的能力,我认为这在实际中是没有多大用处的(如果你想知道我是如何面试前端工程师的,请看我前一篇文章)。
【翻译】Zakas解答Baranovskiy的JavaScript测验题的更多相关文章
- 六道JavaScript测验题
1.找出数字数组中最大的元素(使用Match.max函数) var a=[123,23432,345,3,34]; console.log(Math.max.apply(null,a)); 2.转化一 ...
- 小试牛刀3之JavaScript基础题
JavaScript基础题 1.让用户输入两个数字,然后输出相加的结果. *prompt() 方法用于显示可提示用户进行输入的对话框. 语法: prompt(text,defaultText) 说明: ...
- 小试牛刀2:JavaScript基础题
JavaScript基础题 1.网页中有个字符串“我有一个梦想”,使用JavaScript获取该字符串的长度,同时输出字符串最后两个字. 答案: <!DOCTYPE html PUBLIC &q ...
- JavaScript算法题之–随机数的生成
JavaScript算法题之–随机数的生成 需求描述:从一组有序的数据中生成一组随机并且不重复的数,类似于简单的抽奖程序的实现. 先来生成一个有序的数组: 1 var arr = [], 2 ...
- 44个 Javascript 变态题解析 (下)
承接上篇 44个 Javascript 变态题解析 (上) 第23题 [1 < 2 < 3, 3 < 2 < 1] 这个题也还可以. 这个题会让人误以为是 2 > 1 & ...
- FCC上的javascript算法题之中级篇
FCC中的javascript中级算法题解答 中级算法的题目中用到了很多js的知识点,比如迭代,闭包,以及对json数据的使用等等,现在将自己中级算法的解答思路整理出来供大家参考讨论.欢迎大家提出新的 ...
- USACO翻译:USACO 2014 MARCH Silver三题
USACO 2014 MARCH 一.题目概览 中文题目名称 农田灌溉 懒牛 牛叫 英文题目名称 irrigation lazy mooomoo 可执行文件名 irrigation lazy mooo ...
- USACO翻译:USACO 2014 US Open 三题
USACO 2014 US Open 一.题目概览 中文题目名称 牧场装饰 里程表 牛像展览 英文题目名称 decorate odometer fairphoto 可执行文件名 decorate od ...
- 汤姆大叔的6道javascript编程题题解
看汤姆大叔的博文,其中有篇(猛戳这里)的最后有6道编程题,于是我也试试,大家都可以先试试. 1.找出数字数组中最大的元素(使用Math.max函数) var a = [1, 2, 3, 6, 5, 4 ...
随机推荐
- Ztack学习笔记(2)-系统初始化分析
main函数先执行初始化工作,包括硬件.网络层.任务等的初始化. 一 系统初始化 系统初始化函数主要完成内存分配.消息队列头.定时器.电源管理.任务系统及内存栈等的初始化,具体如下代码所示: //os ...
- Windows Server 2008 R2 64bit兼容Chrome浏览器
近日更换系统Windows Server 2008 R2 64bit系统,发现谷歌浏览器插件无法正常运行,终于找到如下解决方案: 打开桌面谷歌浏览器属性,将target目标 C:\Users\Admi ...
- 解决VS2012新建MVC3等项目时,收到加载程序集“NuGet.VisualStudio.Interop…”的错误
vs2012来做一个mvc3的项目,哪知在创建ado数据模型时跳出这么一个东东 错 误: 此模板尝试加载组件程序集 “NuGet.VisualStudio.Interop, Version=1.0.0 ...
- UIViewController没有随着设备一起旋转的原因
对于iPhone app,UIViewController类提供了基本的视图管理模式.当设备改变方向的时候view controller的视图会自动随之旋转的.如果视图和子视图的autoresizin ...
- Django 学习笔记之五 Django中数据库中ManyToManyField及ForeignKey
1.model里面的代码: from __future__ import unicode_literalsimport django.utils.timezone as timezonefrom dj ...
- ios 框架学习笔记
ios主要的系统层次: 一.Cocoa Touch 层:创建应用程序主要使用的框架. 1.关键技术: AirDrop:实现应用间通信. Text Kit:处理文本和排版. UIKit Dynamics ...
- EXT4.2--Ext Designer 使用
前言: “画EXT”是一个美好的想法,如果有一款可视化工具能够只需进行拖拽而设计EXT,生成代码--那真是一件美丽的事.然而现实是,即使是为Eclipse装上EXT插件,用上idea,手写代码的提示也 ...
- MemSQL Start[c]UP 2.0 - Round 1
A. Eevee http://codeforces.com/contest/452/problem/A 字符串水题 #include<cstdio> #include<cstrin ...
- Node.js 随记
http://nodejs.org/ 下载并安装 node.js 最新版本 运行cmd,输入npm install xxxxxx 回车,安装扩展模块,如:express,socket.io等 运行c ...
- Window 2008 R2 + IIS7.5 + VS2013 错误代码 0x80070002
HTTP 错误 404.0 - Not Found 您要找的资源已被删除.已更名或暂时不可用.详细错误信息模块 IIS Web Core通知 MapRequest Handler处理程序 Static ...