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

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

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

先给出五个例子:

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

例一: 答案

  1. if (!("a" in window)) {
  2. var a = 1;
  3. }
  4. alert (a);

例二:答案

  1. var a = 1,
  2. b = function a (x) {
  3. x && a (--x);
  4. };
  5. alert (a);

 例三:答案

  1. function a (x) {
  2. return x * 2;
  3. }
  4. var a;
  5. alert (a);

例四:答案

  1. function b (x, y, a) {
  2. arguments[2] = 10;
  3. alert (a);
  4. }
  5. b(1, 2, 3);

例五:答案

  1. function a () {
  2. alert (this);
  3. }
  4. 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。

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

  1. var a = 1;
  2. var b = function a (x) {
  3. x && a (--x);
  4. };
  5. alert (a);

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

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

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

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

例三答案:

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

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

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

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

  1. function a (x) {
  2. return x * 2;
  3. }
  4. var a = 10;
  5. 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 () 方法的语法是这样的:

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

第一个参数是函数要执行的作用域(也就是说第一个参数都会变为this值),哪怕传入的参数是原始值或者null或undefined,在非严格模式下,传入的null和undefined都会被替换成全局对象,其他的原始值则会被响应的包装对象所替代。本例中传入null ,所以会替换成全局对象(window对象),因此对话框弹出 [Object Window] 。

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

写在后面的话:

什么是活动对象?

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

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

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

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

例子:

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

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

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

更多内容尽在这里:相关博客一览表

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

  1. 一张图彻底搞懂JavaScript的==运算

    一张图彻底搞懂JavaScript的==运算 来源 https://zhuanlan.zhihu.com/p/21650547 PS:最后,把图改了一下,仅供娱乐 : ) 大家知道,==是JavaSc ...

  2. 来一轮带注释的demo,彻底搞懂javascript中的replace函数

    javascript这门语言一直就像一位带着面纱的美女,总是看不清,摸不透,一直专注服务器端,也从来没有特别重视过,直到最近几年,javascript越来越重要,越来越通用.最近和前端走的比较近,借此 ...

  3. 彻底搞懂Javascript的“==”

    本文转载自:@manxisuo的<通过一张简单的图,让你彻底地.永久地搞懂JS的==运算>. 大家知道,==是JavaScript中比较复杂的一个运算符.它的运算规则奇怪,容让人犯错,从而 ...

  4. 彻底搞懂JavaScript中的继承

    你应该知道,JavaScript是一门基于原型链的语言,而我们今天的主题 -- "继承"就和"原型链"这一概念息息相关.甚至可以说,所谓的"原型链&q ...

  5. 一张图带你搞懂Javascript原型链关系

    在某天,我听了一个老师的公开课,一张图搞懂了原型链. 老师花两天时间理解.整理的,他讲了两个小时我们当时就听懂了. 今天我把他整理出来,分享给大家.也让我自己巩固加深一下. 就是这张图: 为了更好的图 ...

  6. 一篇文章教你搞懂日志采集利器 Filebeat

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 本文使用的Filebeat是7.7.0的版本,文章将从如下几个方面说明: Filebeat是什 ...

  7. Python小世界:彻底搞懂Python一切皆对象!!!

    前言 犹记得当初学习Python的时候,对于Python一切皆对象很是懵逼,因为Python是面向对象的动态型语言,而在函数及高阶函数的应用中,如若对于一切皆对象不是有很透彻的了解,基础不是那么牢固的 ...

  8. Webpack实战(八):教你搞懂webpack如果实现代码分片(code splitting)

    2020年春节已过,本来打算回郑州,却因为新型冠状病毒感染肺炎的疫情公司推迟了上班的时间,我也推迟了去郑州的时间,在家多陪娃几天.以前都是在书房学习写博客,今天比较特殊,抱着电脑,在楼顶晒着太阳,陪着 ...

  9. 微信小程序开发之搞懂flex布局2——flex container

    容器的概念,是用来包含其它容器(container)和项目(item). flex container——flex容器 A flexbox layout is defined using the fl ...

随机推荐

  1. java对象数组

    问题描述:     java 对象数组的使用 问题解决: 数组元素可以是任何类型(只要所有元素具有相同的类型) 数组元素可以是基本数据类型 数组元素也可以是类对象,称这样的数组为对象数组.在这种情况下 ...

  2. [转载]HTML5 Audio/Video 标签,属性,方法,事件汇总

    <audio> 标签属性: src:音乐的URL preload:预加载 autoplay:自动播放 loop:循环播放 controls:浏览器自带的控制条 <audio id=& ...

  3. select框的text与value值的获取(实用版)

    function def(){    var key = document.getElementById ('selectarea'); //select list var value = docum ...

  4. 如何定位Release 版本中程序崩溃的位置 ---利用map文件 拦截windows崩溃函数

    1       案例描述 作为Windows程序员,平时最担心见到的事情可能就是程序发生了崩溃(异常),这时Windows会提示该程序执行了非法操作,即将关闭.请与您的供应商联系.呵呵,这句微软的“名 ...

  5. CKEditor在线编辑器增加一个自定义插件

    CKEditor是一个非常优秀的在线编辑器,它的前身就是FCKEditor,CKEditor据官方说是重写了内核的,但功能和性能比FCKEditor更为强大和优越.记得07年的时候第一次接触FCKEd ...

  6. Tomcat 部署Undeployment Failure

    Tomcat 部署Undeployment Failure - yongjava的日志 - 网易博客 http://blog.163.com/qiangyongbin2000@126/blog/sta ...

  7. Tableau

    http://tableau.analyticservice.net/desktop.html

  8. Permutation Test 置换检验(转)

    Permutation Test 置换检验 显著性检验通常可以告诉我们一个观测值是否是有效的,例如检测两组样本均值差异的假设检验可以告诉我们这两组样本的均值是否相等(或者那个均值更大).我们在实验中经 ...

  9. caffe简易上手指南(三)—— 使用模型进行fine tune

    之前的教程我们说了如何使用caffe训练自己的模型,下面我们来说一下如何fine tune. 所谓fine tune就是用别人训练好的模型,加上我们自己的数据,来训练新的模型.fine tune相当于 ...

  10. JavaScript DOM高级程序设计 3.-DOM2和HTML2--我要坚持到底!

    由一个HTML进行说明,我就不敲了,直接copy <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" " ...