javascript的关键所在---作用域链
javascript的关键所在---作用域链
javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于javascript作用域的相关知识。
很多人使用javascript时候会把{}作为作用域的边界,所以我们可以看看下面的代码:
![](https://common.cnblogs.com/images/copycode.gif)
function ftn01(){
var i = 1;
if (i == 1){
var a = "ok";
}
console.log("a = " + a);// a = ok
{var b = "bok";}
console.log("b = " + b);// b = bok
}
ftn01();
![](https://common.cnblogs.com/images/copycode.gif)
我们发现变量a和b都能被打印出来,这就说明if下的{}和单独的{}并不能保护变量a和b的作用域范围,我们将上面的代码修改下:
![](https://common.cnblogs.com/images/copycode.gif)
function ftn01(){
var i = 1;
if (i == 1){
var a = "ok";
}
console.log("a = " + a);// a = ok
{var b = "bok";}
console.log("b = " + b);// b = bok
}
console.log(a);
ftn01();
![](https://common.cnblogs.com/images/copycode.gif)
在firebug里会报出如下的错误:
可见函数的{}是可以保护变量的作用域的。
其实在javascript语言里只有函数是可以提供作用域,换句话说javascript里有且只有函数作用域,没有其他的作用域。因此要理解作用域必须从函数讲起,javascript里的函数同时也是一个对象,函数同时也是对象这句话让很多初学者误解,javascript这个特性和其他很多语言不太一样,要解释这个问题就必须从javascript创建函数的机制,javascript语言里有一个对象叫做Function,所有的函数都是该对象的实例,下面我们看看javascript里创建函数的三种方式:
![](https://common.cnblogs.com/images/copycode.gif)
var add01 = function add(a,b){
return a + b;
}
var add02 = function(a,b){
return a+ b;
}
var add03 = new Function("a","b","return a + b");
console.log(add01(1,2));// 3
console.log(add02(1,2));// 3
console.log(add03(1,2));// 3
console.log(typeof add01);//function
console.log(typeof add02);//function
console.log(typeof add03);//function
![](https://common.cnblogs.com/images/copycode.gif)
第一种方式叫做命名函数方式,第二种叫做匿名函数方式,第三种是直接使用Function对象来定义函数,三种方式是等价的,并且都可以用一个变量保存该函数。其实前两种创建函数的方式是第三种的变种,由此可以看到所有函数都是Function对象的实例。
函数的作用域功能是由函数内置的一个属性scope体现的,scope属性是一个类数组的集合,这个类数组的集合叫做函数的作用域链,作用域是函数的一个属性,作用域链其实就是该属性的数据类型。当我们创建一个函数时候,就是上面定义函数add01时候,函数的scope属性就会被同步创建,并且作用域链也会被构建,上面的例子里创建的函数都是属于全局的,因此作用域链只有一个元素即该类数组的length是1,类数组所包含的元素也是一个键值对类型,该元素包含所有全局变量例如:window,document,add01等等。
上面的例子里add01是函数的标识符,当add01(1,2)加上了小括号的时候就是执行该函数了,执行一个函数时候,函数会创建一个运行期上下文,英文全称是:execution context,运行期上下文是函数的一个内部对象和作用域一样也是不能被外部访问的,每个运行期的函数都会有一个自己独有的运行上下文,当函数执行完毕后,该函数的运行上下文也会被销毁。当函数执行的时候,首先会初始化函数自带的scope属性,然后将函数自带的scope里的作用域链复制到运行期上下文里,运行期上下文里也包含一个作用域的属性,该属性也是用一个作用域链的类数组表示,复制出来的函数的作用域链会放到运行期上下文的作用域链里,这一步做好后,运行期上下文会初始化函数内部的局部变量和命名参数例如add01函数里的a,b,把这些变量存储到一个活动对象的变量里,初始化完成后这个活动对象也会加入到运行期上下文的作用域链里,这个活动对象会放到运行期上下文作用域链的最前端。其实在函数执行完毕销毁的对象就是这个活动对象,活动对象被销毁了对应的运行期上下文也就被销毁,但是原来存储在函数里的作用域还是保留的。
作用域链的作用是对变量标识符进行解析,标识符就是变量的名字,例如add01、add02这些就是标识符,标识符的解析就是获取数据的位置或是如何存储数据。当函数执行时候,遇到每一个变量就会搜索运行期上下文的作用域链,这个过程都是从作用域链的头开始查找,也就是我上面说的从活动对象开始找起,如果找到与之对应的标识符,那么搜索过程便会停止,如果没有找到那么接着就会搜索作用域链的下一个对象,直到找到为止。不管是函数的作用域链还是运行期上下文的作用域链,链条的最后一个都是全局对象,其实全局对象是一个特殊的对象,如果我还是套用前面函数作用域的方式去理解全局对象,把一个网页当做一个最大的函数,这个函数对象就是window,网页的打开时这个window函数生命周期的开始,网页的关闭就是window函数的销毁,与window函数对应的还有一个全局的运行期上下文,那么用上面的理论理解全局变量应该就会简单多了。全局变量可以当做所有函数作用域的父作用域,当标识符解析到了全局变量时候,前面一定经过了n多个的作用域链中元素的遍历,因此到了遍历全局变量其实程序执行的效率是很低了,所以所有写高性能javascript代码的建议里都是要尽量减少全局变量的使用,如果非要使用全局变量也要在函数作用域内用一个局部变量替代全局变量,这是高性能javascript代码的一个基本要求,不过时下最新版的浏览器几乎都对这个特性进行了优化,访问全局变量不再像以前那么消耗性能了,不过ie的老版本的市场还是很大,而老版本的ie对全局访变量的访问那就是代码效率的毒瘤了。
下面我要讲讲闭包和作用域的关系,有很多人把闭包等价于作用域或者是作用域链,这个有一定的道理,但是如果真的以为闭包就是等价于的这些的话,这个等价于就是错误的,闭包在javascript语言里是一个特殊的函数,闭包产生于函数执行的时候,也就是函数的名字加上了小括号使用的时候,这个时候就会创建闭包或者叫做定义闭包,这个过程和函数创建作用域的过程一样,而闭包的作用域链就是函数的运行期上下文,当执行闭包或者说使用闭包的时候,那么就会构建出闭包的运行期上下文,这个时候闭包也会构建一个活动对象,这个活动对象被置于它的作用域链的最前端,同时闭包还包含执行函数的活动对象,但这个活动变量是在闭包活动变量的后面,这样就会导致函数销毁时候内存无法及时回收,造成大量的内存资源的占用,因此使用闭包是个十分消耗计算资源的应用,前面我讲到要尽量把全局变量用局部变量替换,其实碰到跨作用域的变量引用,都要将其用局部变量替代,这样代码的效率和安全性会更好。
还有很多童鞋认为this指针和作用域链没什么联系,这种理解是不正确的,其实this指针就是指向作用域链的上一级对象,但这种理解常常会误导某些人对this的理解,因为很多人在函数内部调用函数时候,发现被调用的函数this指针指向了全局变量,我们在实际开发里使用this指针最好是换个角度,this指针是调用某个函数上一级对象,例如obj.ftn(),那么ftn函数内部的this指针就是指向的obj,如果有个函数直接是ftn(),前面没有点,那么ftn函数里的内部指针就是window对象,this指针都是指向点号前面的对象,如果没有点号就是window对象,通过点号理解this指针比较方便,也不容易出错,但是通过作用域理解this为什么会有偏差了,原因就是全局作用域在作祟,在javascript里不管哪里直接调用函数,前面没有点的时候this都是指向全局的,我们不应该看这个方法是放到哪个函数里执行,其实this和作用域关系是紧密的,大家千万别怀疑这点。此外在作用域链的数组型的数据结构里,数组的每一个元素都含有一个this指针,this都是作为一个键值对预先定义好的,它的取值不由作用域链的创建所决定。因此有人认为this指针的使用其实也是跨域访问的观点也是不对,this指针的使用不存在跨域,它的效率也是非常高的。
理解了上面这个问题,那么我们对函数的apply和call方法深入理解就比较容易了,这两个方法的本质作用就是在特定的作用域里调用函数,这个解释比较抽象,这样我们先看下面的例子:
![](https://common.cnblogs.com/images/copycode.gif)
function test(){
console.log(this);
}
test.call(window);// window
var obj = {};
test.call(obj);// object
function ftn01(){
test.call(this);
}
ftn01();// window
![](https://common.cnblogs.com/images/copycode.gif)
这两个方法的第一个参数就是改变当前活动对象里this指针指向哪个对象,第一个参数就是函数this指针指向的对象,其本质也是改变作用域的一种方式,改变作用域的方法可以扩展方法的使用范围,因此调用对象和方法解耦,这样可以精简代码,提高代码的复用程度。
尽量少使用全局变量,多用局部变量是写高效javascript程序的一个基本要求,大家不要怀疑它会过时,不管浏览器如何演进,这点规则都是一条黄金规则。记住最优秀的javascript代码里全局变量最好只有一个。
最后我还要插一句,做过javascript都知道javascript代码压缩是一个提升网站效率的重要手段,我们使用雅虎或者谷歌的压缩代码的工具时候,会发现很多变量都会被A,B这样简单的代码所替换,这个替换规则都是对局部变量进行的,因此多使用局部变量会javascript压缩效果更好。
javascript的关键所在---作用域链的更多相关文章
- javascript笔记:javascript的关键所在---作用域链
javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于 ...
- javascript闭包和作用域链
最近在学习前端知识,看到javascript闭包这里总是云里雾里.于是翻阅了好多资料记录下来本人对闭包的理解. 首先,什么是闭包?看了各位大牛的定义和描述各式各样,我个人认为最容易一种说法: 外部函数 ...
- Javascript——闭包、作用域链
1.闭包:是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式:在一个函数内部创建另一个函数. function f(name){ return function(object){ var ...
- [译]JavaScript:函数的作用域链
原文:http://blogs.msdn.com/b/jscript/archive/2007/07/26/scope-chain-of-jscript-functions.aspx 在JavaScr ...
- JavaScript中的作用域链原理
执行环境 作用域链的形成与执行环境(Execution Environment)相关,在JavaScript当中,产生执行环境有如下3中情形: 1 进入全局环境 2 调用eval函数 3 调用func ...
- 认识javascript范围和作用域链
范围 作用域就是变量和函数的可訪问范围.控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域. 全局和局部作用域以下用一张图来解释: 单纯的JavaScri ...
- javascript 关于 this 作用域链
使用 function f() {} 或者 var f = function() {} 来定义的函数,this 是指向 全局对象 var a = { b: 1, c: funct ...
- javascript深入浅出图解作用域链和闭包
一.概要 对于闭包的定义(红宝书P178):闭包就是指有权访问另外一个函数的作用域中的变量的函数. 关键点: 1.闭包是一个函数 2.能够访问另外一个函数作用域中的变量 文章首发地址于sau交流学习社 ...
- JavaScript深入之作用域链
前言 在 <javascript深入之执行上下文栈> 中讲到,当javascript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution ...
随机推荐
- AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务-(个人拙笔)
AspNet.WebAPI.OData.ODataPQ 这是针对 Asp.net WebAPI OData 协议下,查询分页.或者是说 本人在使用Asp.Net webAPI 做服务接口时写的一个分页 ...
- ReactJS.NET 开发
初探ReactJS.NET 开发 ReactJS通常也被称为"React",是一个刚刚在这场游戏中登场的新手.它由Facebook创建,并在2013年首次发布.Facebook ...
- ZOJ3640之简单慨率DP
Help Me Escape Time Limit: 2 Seconds Memory Limit: 32768 KB Background If thou doest well, ...
- 项目管理实践 -- 健身小管家(Fitness housekeeper)的管理
最近在网上看到一篇文章<王石:我每天都强迫自己做的一件事>,[http://blog.sina.com.cn/s/blog_4dfc1c330102v0d0.html] 原始链接不详. ...
- 最流行的Node.js应用开发框架简介
最流行的Node.js应用开发框架简介 快速开发而又容易扩展,高性能且鲁棒性强.Node.js的出现让所有网络应用开发者的这些梦想成为现实.但是,有如其他新的开发语言技术一样,从头开始使用Node.j ...
- Object-C中Category类体验
Object-C中Category类体验 Object-C开发的时候有的时候会用到Category类,类似于Java和C#中扩展类,就是如果你觉得如果你觉得常用的方法在String中没有,可以根据业务 ...
- 从.net复制源代码中国农历阵列,必要做日历
从.net复制源代码中国农历阵列,必要做日历 const { 闰月的月份.春节的阳历日期(农历正月初一).农历的每一个月天数 } c_arrLunarInfo: array [1900 .. 2100 ...
- 【剑指offer】二叉搜索树转双向链表
转载请注明出处:http://blog.csdn.net/ns_code/article/details/26623795 题目描写叙述: 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表. ...
- C语言生成2000w行数据
最近一直抽空学习shell,脚本语言看多了多多少少有些蛋疼不适,所以捡起以前遇到的一个C语言的问题看看. 原先应该是在C++吧关注的一个帖子,楼主为了测试数据库性能需要如下形式的数据要求: 字符串长度 ...
- 【c#操作office】--OleDbDataAdapter 与OleDbDataReader方式读取excel,并转换为datatable
OleDbDataAdapter方式: /// <summary> /// 读取excel的表格放到DataTable中 ---OleDbDataAdapter /// </summ ...