聊一聊JavaScript作用域和作用域链


Posted in Javascript onMay 03, 2016

每种编程语言,其变量都有一定的有效范围,超过这个范围之后,变量就失效了,这就是变量的作用域。从数学的角度来看,就是自变量的域。

作用域是变量的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在 JavaScript 中, 对象和函数同样也是变量,变量在声明他们的函数体以及这个函数体嵌套的任意函数体内部都是有定义的。

一、静态作用域和动态作用域

静态作用域

是指声明的作用域是根据程序正文在编译时就确定的,也称为词法作用域。大多数现代程序设计语言都是采用静态作用域规则,JavaScript就是采用的这种作用域。
采用静态作用域的语言中,基本都是最内嵌套作用域规则:由一个声明引进的标识符在这个声明所在的作用域里可见,而且在其内部嵌套的每个作用域里也可见,除非它被嵌套于内部的对同名标识符的另一个声明所掩盖。
为了找到某个给定的标识符所引用的对象,应该在当前最内层作用域里查找。如果找到了一个声明,也就可以找到该标识符所引用的对象。否则我们就到直接的外层作用域里去查找,并继续向外顺序地检查外层作用域,直到到达程序的最外嵌套层次,也就是全局对象声明所在的作用域。如果在所有层次上都没有找到有关声明,那么这个程序就有错误。如下:

function cha(){
 var name = "xiao;"
 function chb() {
 function chc() {
 console.log(name);
 }
 } 
}

首先函数从chb()搜索有没有name的定义,然后继续一层一层的向上搜索,最后在cha()中搜到了name的定义,如果没有搜到,则会报错。

2、动态作用域

动态作用域的语言中,程序中某个变量所引用的对象是在程序运行时刻根据程序的控制流信息来确定的。

二、JavaScript的作用域

JavaScript中有两种作用域,分别为全局作用域和局部作用域。

1、全局作用域(Global Scope)

在代码中任何位置都是有定义的。即使在html 页面中嵌套的一段js代码中定义了一个全局变量,在引用的js文件中仍能访问到该变量。这就很有可能会造成全局变量的污染。

以下三种情况的变量都会被视为全局变量
(1)最外层的函数和最外层的变量拥有全局作用域
(2)未经定义而直接赋值的变量自动被声明为拥有全局作用域
(3)所有window对象的属性拥有全局作用域

2、局部作用域(Local Scope)

局部作用域一般只能在固定的代码片段中才能访问,如函数内部的变量(函数作用域)

var name = "xuxiaoping";
function echoName() {
 var firstname = "xu";//局部作用域
 secondname = "xiao";//全局作用域
 function echoFirstName() {
 console.log(first name);//xu
 }
 console.log(secondname);
 return echoFirstName;
}
console.log(name);//全局作用域

var f = echoName();
f();
console.log(firstname);
console.log(secondname);

结果为:
xuxiaoping
xiao
xu//内层函数可以访问外层函数的变量
undenfined  //在函数外部无法访问函数的内部变量
xiao

JavaScript将全局变量附加到了window对象上,成为了window对象的属性。

3、函数作用域

块级作用域:任何一对花括号中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的。大多数类C语言都是有块级作用域的。
然而JavaScript的有个重要的特点就是没有块级作用域。

function echoi() {
 for(var i = 0;i<10;i++){
 ;//console.log(i);
 }
 if(true){
 var str = "hello";
 }
 console.log(i);
 console.log(str);
}
echoi();

输出结果为:

10
 hello

可见,在for语句外(也可以是if,while),块中定义的变量i仍然是可以访问的。也就是说,JavaScript并不支持块级作用域,它只支持函数作用域,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都是可见的。作为一个一开始编程就学习学习C和java的人来说,这个有点难以适应。据我测试PHP也是这样的。

当然可以利用JavaScript的闭包的特性,模拟个块级作用域

function echoi() {
 (function() {
 for(var i = 0;i<10;i++){
 ;//console.log(i);
 }
 })();
 if(true){
 var str = "hello";
 }
 console.log(i);
 console.log(str);
}
echoi();

结果为:i undefined

这样就隔离了变量的定义。在js中,为了防止命名冲突,应该尽量避免使用全局变量和全局函数,因此这种闭包的用的特别的多。

4、JavaScript 变量生命周期

JavaScript 变量生命周期在它声明时初始化。
局部变量在函数执行完毕后销毁。
全局变量在页面关闭后销毁。

三、JavaScript的作用域链

一看是链,大概就可以跟数据结构中的链表相结合起来

在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。

当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。例如定义下面这样一个函数:

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

在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只例举了全部变量中的一部分):

聊一聊JavaScript作用域和作用域链

函数add的作用域将会在执行时用到。例如执行如下代码:

var total = add(5,10);

执行此函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。

这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:

聊一聊JavaScript作用域和作用域链

在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。

四、作用域链和代码优化

从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。如上图所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。例如下面的代码:

function changeColor(){
document.getElementById("btnChange").onclick=function()
{ 
document.getElementById("targetCanvas").style.backgroundColor="red";
 };
}

这个函数引用了两次全局变量document,查找该变量必须遍历整个作用域链,直到最后在全局对象中才能找到。这段代码可以重写如下:

function changeColor(){
 var doc=document;
 doc.getElementById("btnChange").onclick=function(){
 doc.getElementById("targetCanvas").style.backgroundColor="red";
 };
}

这段代码比较简单,重写后不会显示出巨大的性能提升,但是如果程序中有大量的全局变量被从反复访问,那么重写后的代码性能会有显著改善。

五、with改变作用域链

数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。

with语句是对象的快捷应用方式,用来避免书写重复代码。例如:

function initUI(){
 with(document){
 var bd=body,
 links=getElementsByTagName("a"),
 i=0,
 len=links.length;
 while(i < len){
 update(links[i++]);
 }
 getElementById("btnInit").onclick=function(){
 doSomething();
 };
 }
}

这里使用width语句来避免多次书写document,看上去更高效,实际上产生了性能问题。

当代码运行到with语句时,运行期上下文的作用域链临时被改变了。一个新的可变对象被创建,它包含了参数指定的对象的所有属性。这个对象将被推入作用域链的头部,这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问代价更高了。如下图所示:

聊一聊JavaScript作用域和作用域链

因此在程序中应避免使用with语句,在这个例子中,只要简单的把document存储在一个局部变量中就可以提升性能。

总结

1、变量的作用域就是变量在哪些范围有效。
2、变量的作用域链就是被创建的作用域中对象的集合。

以上就是本文的全部内容,希望对大家学习javascript程序设计有所帮助。

Javascript 相关文章推荐
鼠标图片振动代码
Jul 06 Javascript
jquery实现动态改变div宽度和高度
May 08 Javascript
js实现延时加载Flash的方法
Nov 26 Javascript
Node.js的Koa框架上手及MySQL操作指南
Jun 13 Javascript
Bootstrap登陆注册页面开发教程
Jul 12 Javascript
Vue.JS入门教程之自定义指令
Dec 08 Javascript
AngularJS实现给动态生成的元素绑定事件的方法
Dec 14 Javascript
Spring Boot+AngularJS+BootStrap实现进度条示例代码
Mar 02 Javascript
在vue组件中使用axios的方法
Mar 16 Javascript
微信小程序列表时间戳转换实现过程解析
Oct 12 Javascript
Js生成随机数/随机字符串的方法小结【5种方法】
May 27 Javascript
原生JavaScript实现刮刮乐
Sep 29 Javascript
小白谈谈对JS原型链的理解
May 03 #Javascript
基于Bootstrap使用jQuery实现输入框组input-group的添加与删除
May 03 #Javascript
JQuery的Pager分页器实现代码
May 03 #Javascript
个人网站留言页面(前端jQuery编写、后台php读写MySQL)
May 03 #Javascript
JQuery核心函数是什么及使用方法介绍
May 03 #Javascript
jquery对象访问是什么及使用方法介绍
May 03 #Javascript
Bootstrap与KnockoutJs相结合实现分页效果实例详解
May 03 #Javascript
You might like
php使用Session和文件统计在线人数
2015/07/04 PHP
PHP中的浅复制与深复制的实例详解
2017/10/26 PHP
一个用js实现控制台控件的代码
2007/09/04 Javascript
DOMAssitant最新版 DOMAssistant 2.5发布
2007/12/25 Javascript
多次注册事件会导致一个事件被触发多次的解决方法
2013/08/12 Javascript
js模拟hashtable的简单实例
2014/03/06 Javascript
javascript中with()方法的语法格式及使用
2014/08/04 Javascript
JavaScript设计模式之适配器模式介绍
2014/12/28 Javascript
JavaScript检测浏览器cookie是否已经启动的方法
2015/02/27 Javascript
JavaScript实现字符串与日期的互相转换及日期的格式化
2016/03/07 Javascript
jQuery四种选择器使用及示例
2016/06/05 Javascript
jQuery实现删除li节点的方法
2016/12/06 Javascript
浅析jsopn跨域请求原理及cors(跨域资源共享)的完美解决方法
2017/02/06 Javascript
jQuery实现获取h1-h6标题元素值的方法
2017/03/06 Javascript
ajax+node+request爬取网络图片的实例(宅男福利)
2017/08/28 Javascript
vue环境搭建简单教程
2017/11/07 Javascript
基于Vue中点击组件外关闭组件的实现方法
2018/03/06 Javascript
如何编写一个d.ts文件的步骤详解
2018/04/13 Javascript
在vue中给列表中的奇数行添加class的实现方法
2018/09/05 Javascript
用原生 JS 实现 innerHTML 功能实例详解
2019/04/03 Javascript
详解微信小程序获取当前时间及日期的方法
2019/04/28 Javascript
vue点击标签切换选中及互相排斥操作
2020/07/17 Javascript
jQuery列表动态增加和删除的实现方法
2020/11/05 jQuery
Python3中多线程编程的队列运作示例
2015/04/16 Python
python实现类之间的方法互相调用
2018/04/29 Python
Python实现随机创建电话号码的方法示例
2018/12/07 Python
Python机器学习算法库scikit-learn学习之决策树实现方法详解
2019/07/04 Python
提升Python效率之使用循环机制代替递归函数
2019/07/23 Python
Python实现性能自动化测试竟然如此简单
2019/07/30 Python
django使用F方法更新一个对象多个对象字段的实现
2020/03/28 Python
Python reques接口测试框架实现代码
2020/07/28 Python
matplotlib阶梯图的实现(step())
2021/03/02 Python
Clarks英国官方网站:全球领军鞋履品牌
2016/11/26 全球购物
四风问题查摆材料
2014/08/25 职场文书
2016年七夕爱情寄语
2015/12/04 职场文书
小学生一年级(书信作文)
2019/08/13 职场文书