js this 作用域沿作用域链寻找吗

 下载
 收藏
该文档贡献者很忙,什么也没留下。
 下载此文档
正在努力加载中...
原型、作用域、闭包的完整解释
下载积分:800
内容提示:原型、作用域、闭包的完整解释,作用域,jsp四个作用域,js作用域链,js this 作用域,js变量作用域,javascript 作用域,jsp四大作用域,session作用域,cookie作用域
文档格式:PDF|
浏览次数:1|
上传日期: 20:44:43|
文档星级:
该用户还上传了这些文档
下载文档:原型、作用域、闭包的完整解释.PDF
官方公共微信58cv网址导航js作用域链和函数表达式的深入讨论
[问题点数:40分]
js作用域链和函数表达式的深入讨论
[问题点数:40分]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关帖子推荐:
2013年12月 Web 开发大版内专家分月排行榜第三
2013年12月 Web 开发大版内专家分月排行榜第三
2013年12月 Web 开发大版内专家分月排行榜第三
2013年12月 Web 开发大版内专家分月排行榜第三
2013年12月 Web 开发大版内专家分月排行榜第三
2013年12月 Web 开发大版内专家分月排行榜第三
2013年12月 Web 开发大版内专家分月排行榜第三
2013年12月 Web 开发大版内专家分月排行榜第三
2013年12月 Web 开发大版内专家分月排行榜第三
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。1002人阅读
本文转载自:
据说,Javascript是基于词法作用域的解析性语言。而闭包就是在这个基础上的一个神奇的现象
一、闭包知识小觑。
词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。 with和eval除外,所以只能说JS的作用域机制非常接近词法作用域(Lexical scope)。
我,作为一个俗人通俗点的理解就是,程序并不需要执行我就可以看出他的作用域链,也就是我能看到他作为函数定义的嵌套层数。
1.我的局部变量不是我的:
1 function a(i) {
alert(arguments[0]);//arguments[0]应该就是形参 i
alert(arguments[0]);
疑问:上面的代码又会输出什么呢?
答案:在FireBug中的运行结果是第二个10,10,2,2,下面简单说一下具体执行过程
9 a 函数有一个形参i,调用 a 函数时传入实参 10,形参 i=10
10 第一个 alert 把形参 i 的值 10 输出
11 第二个 alert 把 arguments[0] 输出,应该也是 i
12 接着定义个局部变量 i 并赋值为2,这时候局部变量 i=2
13 第三个 alert 就把局部变量 i 的值 2 输出
14 第四个alert再次把 arguments[0] 输出
其实还有很多例子,这里举出一个比较有说服力的,就是局部变量与形参是共享作用域的!这说明,在预解析过程中,函数a内部生成了一个属性i,此时i的值为undefined。当执行的时候,先把传过来的形参付给i。然后后面的,你们懂的。。。。
2.告诉你们一个秘密,我以前不是静态变量,现在是了。
function a() {
var temp=1;
function(){
alert(temp);
var use=a();
我们可以看到,temp不是静态变量,但是每调用一次temp就自增1。
为什么会这样呢?理论上一个函数在运行结束后所创建的变量空间以及函数空间都应该被销毁。
但是,我把函数返回给全局作用链,所以此时返回的function里面的变量以及上层的作用域链都被保持了,他的现场(执行环境)亦称执行作用链被保持了。函数在被多次调用的时候,它的作用域链都是不同的。
但是用一次var use=a();,你就创建多一个赋值操作var uer=a(),用两次就创建两个,内存泄露就是这样来的。。。。
二、深入解析闭包与作用域链
我们可以分成两个过程来分析js作用域链的存在,产生,以及消亡。
在上面这句话。为什么存在会比产生先呢?这不是语误,只是站的角度不同。
JS是解析型语言,于是我们可以分两部分,预解析过程,以及解析执行!
1.预解析过程
上面说得词法作用域,其包含的意义就是作用域的确定不是在运行后才知道,而是运行前就知道了。这就是上面所说的预解析过程。确定其函数,变量,活动环境的作用域。
事实上,函数的词法作用域和作用域链是不同的东西,词法作用域是抽象概念,作用域链是实例化的调用对象链。函数在被定义的时候它的词法作用域就已经确定了,但它仍然是抽象的概念,没有也不能被实例化。
所以我们现在讨论的是词法作用域。
var i=1,k;
//全局环境的变量
function a()//全局环境的函数
var x=2;//a下的变量
function f(){//a下面的函数f
function f2(){//a下面的函数f2
var x=1;//f2下面的变量x
对这一小段的函数进行解析
var SyntaxTree ={
// 全局对象在语法分析树中的表示
variables:{
i:{ value:1},
k:{ value:undefined},
functions:{
variables:{
{f:tihis.f;
f2:this.f2;
scope:this.window
variables:{ }
Function{}
scope:this.a
variables:{ x:undefined}
Function{}
scope:this.a
从上面的解析式大家是不是觉得和我们所学的类非常相似,在预编译阶段,js会把代码生成一个语法树,将定义的变量,函数。作为一种类的形式定义起来,我们可以看到,只要在函数体里定义过得变量,都会被记录到该级的函数中。函数可以也可以说一个未实例化的类。
但是相比起类,他多了一样东西,就是作用域。这就是词法作用域,建立了这个词法作用域之后。我们被嵌套的函数就可以调用外层的函数或者变量,当然,他是优先选择自己内部的变量以及函数的。
把变量,函数格式化成伪类的形式,定义词法作用域的范围,这就是预解析阶段做的啊!
2.解析运行
我们可以发现,function a()/作为一个类除了变量和方法。还多了一些东西,就是最下方的活动执行代码段f2();。当全局环境执行a()时候惊醒对f()进行实例化的时候,代码段f2();也就被执行了。
JS代码是一段段执行的。也就是以函数内部的活动代码为单位,一段段活动代码执行。实例化就是调用对象(Call Object)的过程,上面我们一直说这个分析很像类结构。这次就真的是把这个像类的词法分析出来的结果进行伪类的实例化了。实例化的同时,这个调用对象的一个属性被初始化成一个名叫 arguments 的属性,它引用了这个函数的 Arguments 对象,Arguments 对象是函数的实际参数。也就是说,局部变量以及形参都是对这个Arguments 的引用。
var ActiveObject ={
variables:{
i:{ value:1},
k:{ value:undefined}
functions:{
variables:{
x:{value:2}
functions:{
f:SyntaxTree.f
f2:SyntaxTree.f2
parameters:{
arguments:[this.value.x]
variables:{
functions:{
parameters:{
arguments:[]
variables:{
x:{value:1}
functions:{
parameters:{
arguments:[this.variables.x]
这时候,我们就衍生出来我们最关心的问题。那活动时候的作用域该算那个? 我们说得作用域链是什么?
Javascript 里的函数作用域是在函数被定义的时候就确定了,所以它是静态的作用域,词法作用域又称为静态作用域。所以说,即使是运行的时候,函数的作用域,还是词法分析时候的作用域。
我们继续沿用上面的例子写出他的执行环境
2 * 执行环境:函数执行时创建的执行环境
4 var ExecutionContext ={
type:"global",
name:"global",
body:ActiveObject.window
type:"function",
body:ActiveObject.a,
scopeChain:this.window.body
type:"function",
body:ActiveObject.f,
scopeChain:this.a.body
type:"function",
name:"f2",
body:ActiveObject.f2,
scopeChain:this.a.body
上面每一个方法的执行环境都存储了相应方法的类型(function)、方法名称(funcName)、活动对象(ActiveObject)、作用域链(scopeChain)等信息,其关键点如下:
body属性,直接指向当前方法的活动对象scopeChain属性,作用域链,它是一个链表结构,根据语法分析树中当前方法对应的scope属性,它指向scope对应的方法的活动对象(ActivceObject)。
变量查找就是跟着这条链条查找的。在当前函数体里找不到的变量就会沿着作用链一直向上找。直到找到相应的变量,否则为undefined。当一个函数被调用时,在这个函数里,它能访问到它的这个内部状态,也就可以访问整个作用域链上的所有变量,当然也就包括了外部变量。
3.函数内部的全局变量
如果函数的内部定义了一个变量没有var的声明定符号,同时不能在它上面的作用域链找到对应的函数变量,说明他定义的是一个全局变量,但是这个不是在预解析阶段能够识别出来的,只能在执行的过程之中定义。如下面的例子
example()
//定义了一个局部变量y
alert (x); //抛出一个错误。因为x的值不在函数里,也不在作用域链中
x=10;//定义了一个全局变量x;
4从闭包看作用链。
&官方&的解释是:闭包是一个拥有许多 变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
我们从程序运行的角度上看,程序首先实例化a(),产生a的调用对象,然后a中又实例化f2(),产生f2对象。对象把值2付给x,此时作用域链为a-&f2,然后f2调用f方法,f方法的作用域链是a-&f.执行完f之后,f就被释放了。再从中断回去把f2也执行完之后,f2也被释放了。一切都好像很自然。
但是问题来了。如果f返回一个属性或者方法,被a引用或者,被全局空间引用,那么由于f里面的属性或则变量没有被释放。于是作用域链,活动对象,执行环境都会被保存起来。
闭包的一个简单的说法是,当嵌套函数在被嵌套函数之外调用的时候,就形成了闭包。 下面是一个比较官方的解释:所谓&闭包&,就是在构造函数体内定义 另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层外层函数体中的临时变量。这使得只要目标 对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目 标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。即使再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新 的值,和上次那次调用的是各自独立的。
不被释放,这样的好处有四个。
1、保护函数内的变量安全。 通过保护变量的安全实现JS私有属性和 私有方法(不能被外部访问)
2、在内存中维持一个变量。
3、闭包就是将函数内部和函数外部连接起来的一座桥梁。 让外部环境有接口访问内部变量
4、闭包函数可以访问所保持的作用域链上的外部环境。
注意!有些地方不是你不得不用闭包解决的。如下。
function test()
for (var i = 0; i & 5; i++)
setTimeout(function(){alert(i)},100);
上述程序的结果是什么呢,是如期的0.1.2.3.4么?不是的,他全部都是5,为什么呢,因为function(){alert(i)}形成了一个闭包,每次循环形成一个闭包。但是悲剧的是,里面没有i这个变量,所以他所调用的是window下的全局变量i,于是每次取值取得其实是window下的全局变量i。
function test()
for (var i = 0; i & 5; i++)
//如果没有这个闭包,不能正确得到0,1,2,3,4的结果
//因为setTimeout是在循环结束后才被&异步&调用的
(function(j){
setTimeout(function(){alert(j)}, 100);
这上面包括两个执行环境function(j)的和function(){alert(j)}两个在一个链上,所以形参j就因为闭包保持了,下次调用的时候就可以取到0.1.2.3.4,但这是一个解决办法,不是推荐的解决办法。有效使用闭包是好事,但是一旦闭包存在多的时候表示占用用户内存多,适度,适合地使用闭包让你的程序更优雅,更富有逻辑。
上面是我们常见产生闭包的方法。一不小心,我们就会产生错误的答案,当有循环或者嵌套的时候要检查是不是有不恰当的闭包的出现。
但是也有不好的地方,我们经常会无意识地使用了闭包,使得不必要的内存泄露。
5.Internet Explorer 的内存泄漏问题
Internet Explorer Web 浏览器(在 IE 4 到 IE 6 中核实)的垃圾收集系统中存在一个问题,即如果 ECMAScript 和某些宿主对象构成了 "循环引用",那么这些对象将不会被当作垃圾收集。此时所谓的宿主对象指的是任何 DOM 节点(包括 document 对象及其后代元素)和 ActiveX 对象。如果在一个循环引用中包含了一或多个这样的对象,那么这些对象直到浏览器关闭都不会被释放,而它们所占用的内存同样在浏览器关闭之前都不会交回系统重用。
当两个或多个对象以首尾相连的方式相互引用时,就构成了循环引用。比如对象 1 的一个属性引用了对象 2 ,对象 2 的一个属性引用了对象 3,而对象 3 的一个属性又引用了对象 1。对于纯粹的 ECMAScript 对象而言,只要没有其他对象引用对象 1、2、3,也就是说它们只是相互之间的引用,那么仍然会被垃圾收集系统识别并处理。但是,在 Internet Explorer 中,如果循环引用中的任何对象是 DOM 节点或者 ActiveX 对象,垃圾收集系统则不会发现它们之间的循环关系与系统中的其他对象是隔离的并释放它们。最终它们将被保留在内存中,直到浏览器关闭。
三、闭包的其他应用。
1.那些,我们编程中不经意间使用的闭包造成内存的泄漏
使用内部函数最常见的一种情况就是将其作为 DOM 元素的事件处理器。例如,下面的代码用于向一个链接元素添加 onclick 事件处理器:
var quantaty = 5;
function addGlobalQueryOnClick(linkRef){
if(linkRef){
linkRef.onclick = function(){
this.href += ('?quantaty='+escape(quantaty));
无论什么时候调用 addGlobalQueryOnClick 函数,都会创建一个新的内部函数(通过赋值构成了闭包)。从效率的角度上看,如果只是调用一两次 addGlobalQueryOnClick 函数并没有什么大的妨碍,但如果频繁使用该函数,就会导致创建许多截然不同的函数对象(每对内部函数表达式求一次值,就会产生一个新的函数对象)。
上面例子中的代码没有关注内部函数在创建它的函数外部可以访问(或者说构成了闭包)这一事实。实际上,同样的效果可以通过另一种方式来完成。即单独地定义一个用于事件处理器的函数,然后将该函数的引用指定给元素的事件处理属性。这样,只需创建一个函数对象,而所有使用相同事件处理器的元素都可以共享对这个函数的引用:
var quantaty = 5;
function addGlobalQueryOnClick(linkRef){
if(linkRef){
linkRef.onclick = forAddQueryOnC
function forAddQueryOnClick(){
this.href += ('?quantaty='+escape(quantaty));
在上面例子的第一个版本中,内部函数并没有作为闭包发挥应有的作用。在那种情况下,反而是不使用闭包更有效率,因为不用重复创建许多本质上相同的函数对象。
2.如何避免闭包。。。
上述会输出啥?对!就是1
这就是闭包,他无论return的函数赋到哪,他所使用的环境都是function a里面的参数和函数环境。
上面的又输出啥?其实答案是10!
为什么这样子呢,请关注下一个专题,原型链及其作用域。
最后,推荐大家去看看这几篇文章
/web/a/9.shtml
/jazzka702/archive//1636235.html&
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:310682次
积分:4584
积分:4584
排名:第2231名
原创:132篇
转载:39篇
评论:69条
(1)(2)(2)(1)(1)(2)(2)(8)(12)(1)(3)(11)(16)(19)(18)(9)(10)(3)(22)(28)}

我要回帖

更多关于 js this 作用域 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信