作用域在JavaScript中是非常重要的概念,理解了它对更深入地理解闭包等概念都有很大的帮助,这篇文章就来谈谈我对作用域的理解。

一、全局作用域与局部作用域

      在JavaScript中没有块级作用域的概念,它的作用域都是以函数作为划分的。JavaScript的作用域分为全局作用域和局部作用域。能在代码中的任何地方访问到的变量具有全局作用域,只能在固定代码段,例如函数内部,访问到的变量具有局部作用域。

     全局作用域主要包括:1. 定义在最外层的变量和函数 2. 没经过声明,直接定义的变量,及不包含var关键字的变量。3. window对象的属性都具有全局作用局。例如:window.setTimeout, window.location

     局部作用域主要包括: 1. 函数内部通过var关键字声明的变量  2.函数传入的参数

var foo = 'global';  //全局变量
function(bar) {
var goo = 'inside'; //局部变量
env = 1; //全局变量
}

 

二、作用域链

    作用域链定义了函数执行时可访问到的变量的范围。一个函数的作用域链创建分为两个步骤:一是在函数定义时创建的[[scope]]属性中包含的对象,二是在函数执行时创建的活动对象。

1. [[scope]]属性

    函数的[[scope]]属性中包含的是函数在创建时所在的作用域中可访问的对象。只有JavaScript引擎可以访问到。例如:

var foo = 1;
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
add(foo, 1);

    在函数add定义的时候,创建了[[scope]]内部属性,因为add定义在全局作用域中,所以[[scope]]属性中包含了所有的全局变量。

2. 活动对象

    函数每次调用都会创建一个执行上下文,这个执行上下文会有自己的作用域链,也就是函数的作用域链。作用域链被创建时,会初始化为[[scope]]属性中包含的对象。接下来,将函数传入的参数arguments,以及函数中声明的局部变量集合起来,组成了活动对象。并把活动对象压入作用域链的最前端。在本例中add函数的活动对象主要包括传入的参数num1、num2以及局部变量sum。

     此后在进行变量名解析时,会从作用域链的顶端开始往下查找,找到则返回对应变量,找不到返回错误信息。

    当函数执行完毕,执行上下文会被销毁,活动对象也随之销毁。函数的每次调用都会创建新的执行上下文,并关联新的作用域链。

三、改变作用域

    要想人为地改变函数的作用域链有两种方法:with语句和try-catch中的catch语句。

1. width语句

    当运行到width语句时,会把width的对象的所有属性包裹成一个对象压入作用域链的最前端。

function create() {
width(document) {
var list = createElement('li');
list.onclick = function() {
//dosomething
}
};
}

 

    在这个例子中会在当前create函数的作用域链的最前端加入document包含的所有属性,例如例子中用到的createElement方法。这样写貌似代码更简洁了,可是却造成了性能问题。因为作用域链的最前端是with对象的所有属性方法,这样函数内部的局部变量都变成作用域链中的第二级了,要查找到局部变量需要先遍历第一级,使查找时间变长。所以一般来讲程序中尽量不要出现with语句。

2. catch 语句

    使用try-catch语句当遇到异常跳到catch语句中时,会把catch到的错误对象压入作用域链的最前端。

try{
dosomething
} catch(er) {
alert(er);
}

 

    如上例,当运行到catch语句时,会把er对象压入作用域链的最前端。

三、变量声明提升

    JavaScript会提升变量声明,被提升的声明包括var表达式和function函数声明,它们会被提升到当前作用域的最顶部。

bar();
var bar = function() {};
var someValue = 42; test();
function test(data) {
if(false) {
goo = 1;
} else {
var goo = 2;
}
for(var i=0; i<100; i++) {
var e = data[i];
}
}

 

如上例子,在变量提升后会变成:

var bar, someValue;  //var 表达式声明被提升,但是赋值语句没有跟着提升,现在它们的值是undefined

//函数声明被提升
function test(data) {
var goo, i, e; //局部变量的声明也会被提升,
if(false) {
goo = 1;
} else {
goo = 2;
}
for(i=0; i<100; i++) {
e = data[i];
}
} bar(); //出错,因为bar的值还是undefined
bar = function() {}; //赋值语句没有提升
someValue = 42;
test();

 

    变量声明提升有时会带来一些不容易发现的问题,例如在没有提升前函数foo的false语句看起来是要改变全局变量goo,但是提升后可以清晰地看到它其实改变的是局部变量goo。对于这些情况一定到多注意。我认为能减少这类错误的方法是在函数内部将要用到的局部变量都先声明在最前面,手动提升。而函数声明尽量使用function声明方式而不是赋值语句,这样无论调用语句在前面还是后面都不会出错。

JavaScript作用域详解的更多相关文章

  1. javascript 作用域详解

    作用域理解:定义的变量.函数生效的范围.javascript 有全局作用域和函数作用域两种.注:es6实现let 块级作用域不是js原生的,底层同样是通过var实现的.如果想了解具体细节,请访问bab ...

  2. Javascript作用域详解。

    javascript的作用域 是按照   函数来划分的. 网址:http://www.cnblogs.com/rubylouvre/archive/2009/08/21/1551270.html

  3. javascript中的this作用域详解

    javascript中的this作用域详解 Javascript中this的指向一直是困扰我很久的问题,在使用中出错的机率也非常大.在面向对象语言中,它代表了当前对象的一个引用,而在js中却经常让我觉 ...

  4. JavaScript事件详解-jQuery的事件实现(三)

    正文 本文所涉及到的jQuery版本是3.1.1,可以在压缩包中找到event模块.该篇算是阅读笔记,jQuery代码太长.... Dean Edward的addEvent.js 相对于zepto的e ...

  5. JavaScript事件详解-Zepto的事件实现(二)【新增fastclick阅读笔记】

    正文 作者打字速度实在不咋地,源码部分就用图片代替了,都是截图,本文讲解的Zepto版本是1.2.0,在该版本中的event模块与1.1.6基本一致.此文的fastclick理解上在看过博客园各个大神 ...

  6. JavaScript正则表达式详解(一)正则表达式入门

    JavaScript正则表达式是很多JavaScript开发人员比较头疼的事情,也很多人不愿意学习,只是必要的时候上网查一下就可以啦~本文中详细的把JavaScript正则表达式的用法进行了列表,希望 ...

  7. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  8. PHP常量、变量作用域详解(一)

    PHP 中的每个变量都有一个针对它的作用域,它是指可以在其中访问变量(从而访问它的值)的一个领域.对于初学者来说,变量的作用域是它们所驻留的页面.因此, 如果你定义了 $var,页面余下部分就可以访问 ...

  9. PHP变量作用域详解(二)

    学过C的人用PHP的时候一般会相当顺手,而且感到PHP太方便太轻松.但在变量作用域这方面却与C有不同的地方,搞不好会相当郁闷,就找不到错误所在.昨晚就与到这么一个问题,是全局变量在函数中的问题.今天搜 ...

随机推荐

  1. python:随机数 random

    #随机数 import random print(random.randint(10,12))#生成10-12之间的整数 print(random.uniform(10,12))#生成10-12之间的 ...

  2. 【307】◀▶ Python 相关功能实现

    目录: 1. Python 实现下载文件 2. 删除文件名中的点 “.” 3. 让 Python 脚本暂停执行的方法 4. 添 1. Python 实现下载文件 使用 urllib 模块提供的 url ...

  3. 从一个子视图或者一个View中刷新其他UITableView

    被问到了一个问题:如何从一个子视图或者一个View中刷新其他UITableView,常规的写法可能是这样的 TestTVC*testTVC =[[TestTVC alloc] init];[testT ...

  4. ubuntu10.10手工安装jdk1.6

    声明:以下操作是在root用户下操作. 一.下载JDK首先,在Oracle的官网上下载JDK.http://www.oracle.com/technetwork/java/javase/downloa ...

  5. sax解析xml文件,封装到对象中

    创建User.java类 public class User { private String id; private String name; private String age; private ...

  6. Jenkins一天中构建多次

    Build after other projects are built:在其他项目触发的时候触发,里面有分为三种情况,也就是其他项目构建成功.失败.或者不稳定的时候触发项目: Poll SCM:定时 ...

  7. linux 下删除乱码文件-乾颐堂

    在linux下删除文件,遇到特殊字符是一件非常头疼的事情. 1. 如果文件名带 ‘-’ 或者‘--’这样的字符 删除办法为:rm -- 文件名 如文件名为:-pythontab.tgz 如果用普通方法 ...

  8. spring boot☞Swagger2文档构建及单元测试

    首先,回顾并详细说明一下在快速入门中使用的@Controller.@RestController.@RequestMapping注解.如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建 ...

  9. funk_SVD 个人理解

    目标函数: $ J = \frac{1}{2} \left\| R - PQ \right\|^{2} + \lambda \left( \left\|P \right\|^{2} +\left\| ...

  10. CMDB和运维自动化

    IT运维,指的是对已经搭建好的网络,软件,硬件进行维护.运维领域也是有细分的,有硬件运维和软件运维 硬件运维主要包括对基础设施的运维,比如机房的设备,主机的硬盘,内存这些物理设备的维护 软件运维主要包 ...