五个小例子教你搞懂 JavaScript 作用域问题

原文:五个小例子教你搞懂 JavaScript 作用域问题

众所周知,JavaScript 的作用域和其他传统语言(类C)差别比较大,掌握并熟练运用JavaScript 的作用域知识,不仅有利于我们阅读理解别人的代码,也有助于我们编写自己的可靠代码。

下面笔者将使用五个小例子来给大家分析下 JavaScript 的作用域要注意的问题。

感谢 例子的来源 (这5个例子我做错了2个 [嘿嘿,尽情鄙视吧],笔者就是要 死磕自己,奉献大家!)

先给出五个例子:

每个例子旁边都会给出答案的链接,如果你全部都正确了,你可以忽略这篇短文,并深深的鄙视下笔者。

例一: 答案

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(1, 2, 3);

 例五:答案

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

写在答案前面的话:

页面中JavaScript代码在加载的时候,执行顺序是按照脚本标签<script>的顺序一致的,但如果设置该标签async或defer属性的话,则不能保证执行顺序(这点说起来惭愧,笔者没有认真测试过)。

JS代码在解释执行前,会对进行一次“预编译”:

在预编译的过程中,用var声明的变量被设置为活动对象(啥是活动对象?)的属性,默认值为“undefined”,

以function定义的函数也被添加为活动对象的属性,它们的值就是函数的定义,匿名函数将不被解析(这句话啥意思?)。

变量初始化过程即赋值过程发生在解释执行期,而不是"预编译期"。

例一答案:

有人大概会犯下面两种情况的错误:

情况一:if 分支里声明a变量(var a = 1;),在if 外访问不到变量a,所以对话框弹出 'undefined'。

这说明你对JavaScript 中没有块级作用域不太理解。请翻翻基础书籍。笔者也会在后续的博文中 深入浅出地介绍JavaScript 变量、作用域和内存问题。(到时候会给出链接的)

情况二:a 变量,不在window对象中,所以进入if 分支,声明 a 并赋值为 1,又由于JS没有块作用域,所以对话框弹出 1。

这说明你大概了解块作用域(可能只是知道,但并不知道原理)。这时候你可能需要了解下啥是作用域链,多问问为什么没有块作用域(后续博文会推出的,但笔者仍希望你通过读书的方式了解下)。

但是你还是对JS代码执行前的情况不太了解。

真正的情况是这样的:

JS在预编译的时候,var 声明的变量 被设置为活动对象(本例为 window )的属性,默认值是‘undefined’,

由于没有块作用域,所以if 块中的 变量声明被预编译了,因此 a 是window的属性 (a in window is true ) ,于是就能理解对话框弹出 'undefined'.

例二答案:

错误的情况我就不多做介绍了,无非是弹出函数b的定义,或者弹出1。

下面解释下本例的情况,本例的代码执行和下列代码执行是一样的:

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

第一行是一个变量的声明。

第二行是函数字面量(函数表达式,详细用法请参见:深入浅出 JavaScript 函数 v 0.5),只不过该表达式没有省略函数名(a),为什么不省略呢? 因为该函数要递归啊,不然咋递归?

但是残酷的是,函数名在函数外部是未定义的,所以对话框弹出的是 1 。

针对本例还有一种说法是 逗号操作符,不知道是顺序的还是倒序的,但是针对本例,顺序还是逆序,真没什么关系。

例三答案:

本例错误的大部分情况都是弹出'undefined'.

错误的原因就是不太了解JS的预编译过程。

本例中JS的预编译过程是这样的,首先声明变量 a (并未初始化哟),然后再初始化为function, 后面 var a ; 只是声明变量,但是并未给a 赋值,所以其值还是function。

拿下面一段代码做比较,可以印证上面的解释:

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

谁最后对同一个变量初始化(可以理解成赋值),最后变量就保留谁的值。

例四答案:

理解本例的关键在于对参数对象的理解,arguments 的详细介绍,在深入浅出 JavaScript 函数 v 0.5中有详细的介绍。

arguments 是一个特殊的对象,有数组的特性,但不是数组,arguments 对象不是只读的,arguments [2] = 10; 

这句话就把参数 a (其实可以理解成是函数的内部变量) 更改为10,所以弹出 10。

arguments [2] 和 a 指向的是同一个值。

例五答案:

a 作为一个函数,在JS中函数也是对象,对象当然有属性和方法了。

JavaScript 就为函数对象提供了两个间接调用函数的方法 call() 和apply(),这两个内容的详细解释在深入浅出 JavaScript 函数 v 0.5中有详细的介绍。

call () 方法的语法是这样的:

call([thisObj[,arg1[, arg2[, [,.argN]]]]])    // thisObj 是this要绑定的对象,后面是逗号分隔开的参数

第一个参数是函数要执行的作用域,本例中传入null ,就是说函数的执行没有作用域的跳转,还是在声明函数的作用域中执行。

函数声明的作用域对象为 window 对象,所以对话框弹出 [Object Window] 。

本例中涉及的 this 的用法,请参见深入浅出 JavaScript 函数 v 0.5 。

写在后面的话:

什么是活动对象?

当函数被调用,活动对象(activation object) 就被创建了。它包含普通参数(formal parameters) 与特殊参数(arguments)对象(具有索引属性的参数映射表)。

活动对象在函数上下文中作为变量对象使用。

预编译阶段,匿名函数将不会被解析。这句话的理解:

一句话,函数声明在"预编译阶段"被解析,函数字面量(函数表达式) 在执行阶段被解析。

例子: 

alert (add (2,3));    //5
function add(a,b) {
    return a+b;
}    // 函数声明提升
//=====为了方便,笔者写在了一起,在测试的时候,可不要在一个作用域中执行哟===============
alert (add (2,3));    //error
var add = function (a,b) {
    return a+b;
}; //函数字面量,注意结尾的分号哟(细节很重要)。

广了个告::(祝大家劳动节快乐,为我们这些劳动者鼓掌)

更过关于函数的内容,尽在 深入浅出 JavaScript 函数 v 0.5

 

时间: 2024-10-03 10:14:08

五个小例子教你搞懂 JavaScript 作用域问题的相关文章

几个小例子教你如何实现正则表达式highlight高亮_正则表达式

程序员在编写代码的时候少不了和字符串以及"查询"打交道,两者的交集中有一个叫做正则表达式的的东西,这家伙用好了可以提高编程效率,用不好的话...你可以先去好好学一学. 关于正则的使用,举个简单的例子: 复制代码 代码如下: var m = location.href.match(/(\w+:)\/{0,3}([^\/]+)(?:(\/[^\?#]*))?(?:(\?[^#]+|.+))?(?:(#.*))?/);var res = {    protocol: m[1],    hos

几个小例子教你如何实现正则表达式highlight高亮

程序员在编写代码的时候少不了和字符串以及"查询"打交道,两者的交集中有一个叫做正则表达式的的东西,这家伙用好了可以提高编程效率,用不好的话...你可以先去好好学一学. 关于正则的使用,举个简单的例子: 复制代码 代码如下:var m = location.href.match(/(\w+:)\/{0,3}([^\/]+)(?:(\/[^\?#]*))?(?:(\?[^#]+|.+))?(?:(#.*))?/);var res = {    protocol: m[1],    host

五句话搞定JavaScript作用域

全局作用域 -> Func函数作用域 -> inner函数作用域 当执行[ret();]时,由于其代指的是inner函数,此函数的作用域链在执行之前已经被定义为:全局作用域 -> Func函数作用域 -> inner函数作用域,所以,在执行[ret();]时,会根据已经存在的作用域链去寻找变量. JavaScript的作用域一直以来是前端开发中比较难以理解的知识点,对于JavaScript的作用域主要记住几句话,走遍天下都不怕... 一.JavaScript中无块级作用域 在Jav

如何在两小时内搞懂PHOTOSHOP基础工具?

  初学者面对Photoshop这个庞然大物,第一直观感受是不知道从何下手.那么多工具,学完这个忘那个,有木有什么教程可以一次性学完呢?今天这篇好文,只要童鞋们打起精神来,两个小时就可以搞懂基础的工具使用!绝对是新手速成Photoshop的一个好机会!赶紧来学习! 教程很有意思,从A--Z 依次讲解每个工具的使用,精辟彻底,不拖泥带水呦. 3D面板:作为Adobe Photoshop Extended的一部分,你可以在Adobe Photoshop CC中找到它.3D面板中展示了所有3D文件中允

8个问题带你搞懂《小门神》背后的技术

本文讲的是8个问题带你搞懂<小门神>背后的技术[IT168 资讯]被誉为"最美国产动画片"的<小门神>未映先热,而这又是如何炼成的?追光动画和阿里云的专家用8个热门问答带你理解动画片背后的渲染技术. 1.三维动画电影为什么会有渲染? 追光动画渲染TD程顺:三维动画电影的渲染就是把一个2D的图像变成一个3D的图像的过程,相对于以往传统类似于二维的动画电影或者日本的动漫,它们是一帧一帧的图画,每一帧都用手画出来,或者基于平面的软件制作出来,也是人工制作.但三维动画电

每个Java初学者都应该搞懂的六个问题

初学|问题 对于这个系列里的问题,每个学Java的人都应该搞懂.当然,如果只是学Java玩玩就无所谓了.如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列.内容均来自于CSDN的经典老贴. 问题一:我声明了什么! String s = "Hello world!"; 许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是"Hello world!".这样模糊的回答通常是概念不清的根源.如果要准确的回答,一半的人

每个初学者都应该搞懂的问题!

初学|问题 对于这个系列里的问题,每个学Java的人都应该搞懂.当然,如果只是学Java玩玩就无所谓了.如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列.内容均来自于CSDN的经典老贴.问题一:我声明了什么!String s = "Hello world!";许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是"Hello world!".这样模糊的回答通常是概念不清的根源.如果要准确的回答,一半的人大概会

彻底搞懂Oracle字符集

以下是对Oracle中的字符集进行了详细的分析介绍,需要的朋友可以参考下   基本概念字符集(Character set):是一个系统支持的所有抽象字符的集合.字符是各种文字和符号的总称,包括各国家文字.标点符号.图形符号.数字等.常见的字符集有ASCII,ZHS16GB231280,ZHS16GBK等. 字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其它的一个集合(如电脑编码)进行配对.即在符号集合与数字系统之间建

Windows密码忘了咋办 小编教你轻松破解

很多人出于保护隐私的目的,都习惯为自己的电脑设置开机密码,这原本是很好的使用习惯. 但是,现代社会作为一个被各种密码包围的社会,各种账户密码极易混淆,经常造成电脑开机密码记混,从而使得电脑无法正常开机,带来一系列麻烦. Windows密码忘了咋办小编教你轻松破解 正所谓未雨绸缪,今天笔者就从电脑开机密码的角度出发,教大家如何轻松破解Windows系统的开机密码,进入系统.(PS:仅限个人使用,勿做其他用途) 相信装过系统的朋友,都用过PE系统吧. 没错,最简单的Windows开机密码破解方法便是