js怎么js 调用闭包函数closure闭包里面的对象

聊一下JS中的作用域scope和闭包closure-jquery-爱编程
聊一下JS中的作用域scope和闭包closure
聊一下JS中的作用域scope和闭包closure
  scope和closure是javascript中两个非常关键的概念,前者JS用多了还比较好理解,closure就不一样了。我就被这个概念困扰了很久,无论看别人如何解释,就是不通。不过理越辩越明,代码写的多了,小程序测试的多了,再回过头看看别人写的帖子,也就渐渐明白了闭包的含义了。咱不是啥大牛,所以不搞的那么专业了,唯一的想法就是试图让你明白什么是作用域,什么是闭包。如果看了这个帖子你还不明白,那么多写个把月代码回过头再看,相信你一定会有收获;如果看这个帖子让你收获到了一些东西,告诉我,还是非常开森的。废话不多说,here we go!
  1、function
  在开始之前呢,先澄清一点(废话咋这么多捏),函数在JavaScript中是一等公民。什么,你听了很多遍了?!!!。那这里我需要你明白的是,函数在JavaScript中不仅可以调用来调用去,它本身也可以当做值传递来传递去的。
  2、scope及变量查询
  作用域,也就是我们常说的词法作用域,说简单点就是你的程序存放变量、变量值和函数的地方。
  块级作用域
  如果你接触过块级作用域,那么你应该非常熟悉块级作用域。简单说来就是,花括号{}括起来的代码共享一块作用域,里面的变量都对内或者内部级联的块级作用域可见。
  基于函数的作用域
  在JavaScript中,作用域是基于函数来界定的。也就是说属于一个函数内部的代码,函数内部以及内部嵌套的代码都可以访问函数的变量。如下:
  上面定义了一个函数foo,里面嵌套了函数bar。图中三个不同的颜色,对应三个不同的作用域。①对应着全局scope,这里只有foo②是foo界定的作用域,包含、b、bar③是bar界定的作用域,这里只有c这个变量。在查询变量并作操作的时候,变量是从当前向外查询的。就上图来说,就是③用到了a会依次查询③、②、①。由于在②里查到了a,因此不会继续查①了。
  这里顺便讲讲常见的两种error,ReferenceError和TypeError。如上图,如果在bar里使用了d,那么经过查询③、②、①都没查到,那么就会报一个ReferenceError;如果bar里使用了b,但是没有正确引用,如b.abc(),这会导致TypeError。
  严格的说,在JavaScript也存在块级作用域。如下面几种情况:
  ①with
1 var obj = {a: 2, b: 2, c: 2};
2 with (obj) { //均作用于obj上
  let是ES6新增的定义变量的方法,其定义的变量仅存在于最近的{}之内。如下:
var foo = true;
if (foo) {
let bar = foo * 2;
bar = something( bar );
console.log( bar );
console.log( bar ); // ReferenceError
  ③const
  与let一样,唯一不同的是const定义的变量值不能修改。如下:
1 var foo = true;
2 if (foo) {
var a = 2;
const b = 3; //仅存在于if的{}内
b = 4; // 出错,值不能修改
8 console.log( a ); // 3
9 console.log( b ); // ReferenceError!
  3、scope的如何确定
  无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的。理解这一点非常重要。
  4、变量名提升
  这也是个非常重要的概念。理解这个概念前,需要了解的是,JS代码的执行过程分为编译过程和执行。举例如下:
1 var a = 2;
  以上代码其实会分为两个过程,一个是 一个是 a = 2; &其中是在编译过程中执行的,a =2是在执行过程中执行的。理解了这个,那么你就应该知道下面为何是这样的结果了:
1 console.log( a );//undefined
2 var a = 2;
  其执行效果如下:
2 console.log( a );//undefined
  我们看到,变量声明提前了,这就是为什么叫变量名提升了。所以在编译阶段,编译器会将函数里所有的声明都提前到函数体内的上部,而真正赋值的操作留在原来的位置上,这也就是上面的代码打出undefined的原因。需要注意的是,变量名提升是以函数为界的,嵌套函数内声明的变量不会提升到外部函数体的上部。希望你懂这个概念了,如果不懂,可以参考我之前写的《也谈谈规范JS代码的几个注意点》及评论回答部分。
  5、闭包
  了解这些了后,我们来聊聊闭包。什么叫闭包?简单的说就是一个函数内嵌套另一个函数,这就会形成一个闭包。这样说起来可能比较抽象,那么我们就举例说明。但是在距离之前,我们再复习下这句话,来,跟着大声读一遍,&无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的&。
1 function foo() {
var a = 2;
function bar() {
console.log( a ); // 2
  我们看到上面的函数foo里嵌套了bar,这样bar就形成了一个闭包。在bar内可以访问到任何属于foo的作用域内的变量。好,我们看下一个例子:
1 function foo() {
var a = 2;
function bar() {
console.log( a );
8 var baz = foo();
9 baz(); // 2
  在第8行,我们执行完foo()后按说垃圾回收器会释放foo词法作用域里的变量,然而没有,当我们运行baz()的时候依然访问到了foo中a的值。这是因为,虽然foo()执行完了,但是其返回了bar并赋给了baz,bar依然保持着对foo形成的作用域的引用。这就是为什么依然可以访问到foo中a的值的原因。再想想,我们那句话,&无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的&。
  来,下面我们看一个经典的闭包的例子:
1 for (var i=1; i&=9; i++) {
setTimeout( function timer(){
console.log( i );
  运行的结果是啥捏?你可能期待每隔一秒出来1、2、3...10。那么试一下,按F12,打开console,将代码粘贴,回车!咦???等一下,擦擦眼睛,怎么会运行了10次10捏?这是肿么回事呢?咋眼睛还不好使了呢?不要着急,等我给你忽悠!
  现在,再看看上面的代码,由于setTimeout是异步的,那么在真正的1000ms结束前,其实10次循环都已经结束了。我们可以将代码分成两部分分成两部分,一部分处理i++,另一部分处理setTimeout函数。那么上面的代码等同于下面的:
// 第一个部分
i++; // 总共做10次
// 第二个部分
setTimeout(function() {
console.log(i);
setTimeout(function() {
console.log(i);
}, 1000); // 总共做10次
  看到这里,相信你已经明白了为什么是上面的运行结果了吧。那么,我们来找找如何解决这个问题,让它运行如我们所料!
  因为setTimeout中的匿名function没有将 i 作为参数传入来固定这个变量的值, 让其保留下来, 而是直接引用了外部作用域中的 i, 因此 i 变化时, 也影响到了匿名function。其实要让它运行的跟我们料想的一样很简单,只需要将setTimeout函数定义在一个单独的作用域里并将i传进来即可。如下:
1 for (var i=1; i&=9; i++) {
(function(){
setTimeout( function timer(){
console.log( j );
}, 1000 );
  不要激动,勇敢的去试一下,结果肯定如你所料。那么再看一个实现方案:
1 for (var i=1; i&=9; i++) {
(function(j){
setTimeout( function timer(){
console.log( j );
}, 1000 );
  啊,居然这么简单啊,你肯定在这么想了!那么,看一个更优雅的实现方案:
1 for (let i=1; i&=9; i++) {
setTimeout( function timer(){
console.log( i );
}, 1000 );
  咦?!肿么回事呢?是不是出错了,不着急,我这里也出错了。这是因为let需要在strict mode中执行。具体如何使用strict mode模式,自行谷歌吧!
  6、运用
  撤了这么多,你肯定会说,这TM都是废话啊!囧,那么下面就给你讲一个用处的例子吧,也作为本文的结束,也作为一个思考题留给你,看看那里用到了闭包及好处。
1 function Person(name) {
function getName() {
console.log( name );
getName: getName
9 var littleMing = Person( "fool" );
10 littleMing.getName();
 哎,码了个把小时文字,也是挺累的啊!凑巧你看到这个文章了,又凑巧觉得有用,赞一个呗!(欢迎吐槽!)
版权所有 爱编程 (C) Copyright 2012. . All Rights Reserved.
闽ICP备号-3
微信扫一扫关注爱编程,每天为您推送一篇经典技术文章。在Javascript中闭包(Closure)_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
在Javascript中闭包(Closure)
上传于||暂无简介
阅读已结束,如果下载本文需要使用
想免费下载本文?
下载文档到电脑,查找使用更方便
还剩5页未读,继续阅读
你可能喜欢如何通俗易懂的解释javascript里面的‘闭包’?
按投票排序
我的年龄是秘密,你想知道。但是每次我都含糊其辞的对你说 undefined;为了防止我自己也忘记或搞错自己的年龄,我办了一张身份证,上面记录我的年龄信息,藏在我家里。你知道了这件事,为了得到我的年龄,决定对我投其所好,于是你送我一只逗比间谍猫。作为感谢我给了你一把我家的钥匙,方便你有空来看猫。这只猫实在太神奇了,每次都能找到我的身份证,并把信息传递给你。于是你每次想知道我的年龄的时候就来看猫,然后间谍猫每次都能把我的最新的年龄信息反馈给你。var day=0;
var timer=setInterval("dayChanges()",(24*60*60*1000)); //定时器,
function dayChanges(){
} //每过24小时 次函数运行一次,我的年龄又多了一天
------------ 下面的代码就是一个比较常见的闭包。
function isMyHome(){ //我家
var myAge=0; //我的身份证信息
if(day%365==0){ //我的年龄变化
function myHomeKey(){ // 我家钥匙
return myAge; //return 就是间谍猫
return myHomeKey; //给你我家的钥匙。
var key=isMyHome(); //你拿到我家钥匙
var you=key();
//得到年龄。
乱写一气,求大神拍砖 ;)
觉得理解了下面这句话,基本上就不用纠结闭包这个词了(好像是出自《JavaScript语言精粹》):JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。例如下面这个常见的例子:var foo = function(){
var name = "exe";
return function inner(){
console.log( name );
var bar = foo();//这里虽然得到的是函数inner的引用,而不是那一坨代码
bar();//这里开始执行inner函数,回头看看上面加粗的那句话
当然理解这句话的前提是要理解值传递和引用传递如果这个概念没有理解的话,可能对那句话还是很费解个人认为这应该是大多数人理解闭包的难点所在另外推荐一本:《你不知道的JavaScript》,里面详细的解释了为什么会出现闭包这种东东(词法作用域),及与之相对应的东东(动态作用域),我想 把两个东东对立起来就应该会好理解些吧。
个人觉得闭包要结合js的scope概念才能理解清楚,js的scope基于function,而js中函数是可以作为普通对象到处传递的,于是便会有一个function内部定义的函数放到其他任何地方使用的情况。而所谓的闭包就是当函数在其他地方使用的时候能保存下函数所需要的运行环境,也即是函数能保存下函数诞生时的环境。另外,如果用过chrome的develop工具之类单步过也会看到函数那儿有个closure,可以点开看看里面的内容,如果有写框架的经验你会对闭包的用途理解得更深刻。
你们做了这么多比喻,又是“年龄”,又是“箱子”的,但我怎么感觉越看越晕呀。至于什么闭包模拟私有,这些都是基于闭包可以得到的某种实现,并不能解释其本身的概念。只有大水,还有一个匿名用户写的比较言简意赅。闭包其实特别简单的,在JS里的话,用比较通俗的话描述的话:就是一种允许函数向关联的父级作用域寻址的访问特权。********************************************简单作答,完****************************************************总体上来说,它是一种闭合函数表达式(lambda)的机制。 从表现上来说,closure就是把函数以及其所依赖的所有外部自由变量保存在一起的结合体。(这段看不懂的话可以跳过,想深入一点了解的话,see this: )而在JS中的实现原理么,就是你们熟悉的scope chain了。我只能再用这段代码来举例了,这就是最简单的一个和闭包有关的函数表达式(用模拟私有这种案例来解释闭包都太复杂了..):var a=1
var b=function(){
return a+1
b() // =& 2, //这个案例中,变量a是一个依赖闭包机制所捕获的自由变量,也因此函数b可以被正常执行。
以上就是全部正文,完。就是这样~
目前以上答案似乎都只是再说闭包是个什么样子。补充一点吧。近似正确的短答案:闭包就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来了。短答案:JavaScript中的闭包,无非就是变量解析的过程。首先看一段话:每次定义一个函数,都会产生一个作用域链(scope chain)。当JavaScript寻找变量varible时(这个过程称为变量解析),总会优先在当前作用域链的第一个对象中查找属性varible ,如果找到,则直接使用这个属性;否则,继续查找下一个对象的是否存在这个属性;这个过程会持续直至找到这个属性或者最终未找到引发错误为止。看个简单版的例子:(function(){
var hello="hello,world";
function welcome(hi){
alert(hi);
//解析到作用域链的第一个对象的属性
alert(hello);
//解析到作用域链的第二个对象的属性
welcome("It's easy");
运行结果很简单,一个弹窗It's easy.一个弹窗hello,world。分析过程如下:对于函数welcome(),定义welcome的时候会产生一个作用域链对象,为了表示方便,记作scopechain。scopechain是个有顺序的集合对象。scopechain的第一个对象:为了方便表示记作sc1, sc1有若干属性,引用本函数的参数和局部变量,如sc1.hi ;scopechain的第二个对象:为了方便表示记作sc2,sc2有若干属性,引用外层函数的参数和局部变量,如sc2.hello;...scopechain的最后一个对象:为了方便表示记作scn,scn引用的全局的执行环境对象,也就是window对象!,如scn.eval();这里之所以可以弹出hello,world,原因就是变量解析时在welcome函数作用域链的第一个对象上找不到hello属性,然后就去第二个对象上找去了(结果还真找到了)。所以,JavaScript中的所谓的高大上的闭包其实很简单,根本上还是变量解析。而之所以可以实现,还是因为变量解析会在作用域链中依次寻找对应属性的导致的。
假设你在内裤里放了个摄像机
。。。然后外面的大屏幕上可以看直播。。。
对象:包含逻辑的数据闭包:包含数据的逻辑
转载,非原创。内容来自作者: 日期: 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。下面就是我的学习笔记,对于Javascript初学者应该是很有用的。一、变量的作用域要理解闭包,首先必须理解Javascript特殊的变量作用域。变量的作用域无非就是两种:全局变量和局部变量。Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。  var n=999;  function f1(){    alert(n);  }  f1(); // 999另一方面,在函数外部自然无法读取函数内的局部变量。  function f1(){    var n=999;  }  alert(n); // error这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!  function f1(){    n=999;  }  f1();  alert(n); // 999二、如何从外部读取局部变量?出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。  function f1(){    var n=999;    function f2(){      alert(n); // 999    }  }在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!  function f1(){    var n=999;    function f2(){      alert(n);     }    return f2;  }  var result=f1();  result(); // 999三、闭包的概念上一节代码中的f2函数,就是闭包。各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。四、闭包的用途闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。怎么来理解这句话呢?请看下面的代码。  function f1(){    var n=999;    nAdd=function(){n+=1}    function f2(){      alert(n);    }    return f2;  }  var result=f1();  result(); // 999  nAdd();  result(); // 1000在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。五、使用闭包的注意点1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。六、思考题如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。代码片段一。  var name = "The Window";  var object = {    name : "My Object",    getNameFunc : function(){      return function(){        return this.      };    }  };  alert(object.getNameFunc()());代码片段二。  var name = "The Window";  var object = {    name : "My Object",    getNameFunc : function(){      var that =      return function(){        return that.      };    }  };  alert(object.getNameFunc()());(完)版权声明:自由转载-非商用-非衍生-保持署名()
任务:猜猜猜。规则:猜是啥。(需求及接口定义:比如可以问箱子里是啥动物,箱子里动物是死还是活)一个不透明的密封的箱子。(外层入口函数)你放了个小动物进去。(入口函数参数以及内部保存变量)你放了个麻袋堵住了大箱子的入口。(返回函数)但是你的手可以通过深入麻袋摸到小动物。(返回函数的参数)麻袋厚薄程度不同,你感知的效果不同。(选择是否暴露内部函数,封装程度和数据转换)别人问你箱子里是什么。(函数返回值)箱子和麻袋组成的体验就是闭包。
你没法“通俗易懂”地理解一个你原本不理解的东西,只有你通过努力去学习、认识、实践,真的理解了,才会有“通俗易懂”的感觉,会觉得:原来不过如此,当初怎么没人跟我这么解释呢,我赶紧给别人“通俗易懂”地解释下,让人家少走些弯路....有句话大概是这么说的:不走“捷径”,才能少走弯路。
再怎么通俗易懂的描述也不如照着例子自己多试几次理解的快。
其实就是函数和相关的环境的集合,楼上各种比喻但是举的例子并不是非得要闭包假设你有如下代码(Python代码,但是并不影响)def f(x):
return x + y
print q # q = 3
好了,那么此时我们执行了p的赋值之后,我们知道p实际上是把x替换成1的g(y),q调用p(2)就相当于调用f(1)(2)=3但是要知道x是个栈上的参数,f函数执行完之后我们并不知道x的值,那么到后面执行q=p(2)的时候,我们怎么知道return x + y的x是多少呢?这时候就要依靠闭包,闭包是放在堆上的,会保存和函数相关的上下文信息,其中return x + y的x的值就保存在闭包里楼上说的undefined和猜猜猜只是闭包的应用,并不是闭包的定义
简单粗暴的理解,最终极的用途就是把函数当类用,说是闭包,其实也可以叫封装。
先占坑,一会儿来答
一句话解释:
闭包就是跨作用域访问变量!函数内访问全局变量大家都知道,那因为全局变量的作用域已经覆盖到函数:var age=18;
function a()
alert(age)
当全局变量访问函数内局部变量时,因为局部变量作用域只在函数内,所以无法访问。但通过闭包就可以让全局变量跨作用域访问局部变量:var a=function()
var age=18;
return function(){return age; }
var b=a()();
一句话:子函数能够访问父函数的局部变量,反之则不行。而那个子函数就是闭包!
怒答!被各种博客坑过无数次,告诉我的都是怎么创建一个闭包,而不是告诉你到底什么是闭包!闭包:在爷爷的环境中执行了爸爸,爸爸中返回了孙子,本来爸爸被执行完了,爸爸的环境应该被清除掉,但是孙子引用了爸爸的环境,导致爸爸释放不了。这一坨就是闭包。简单来讲,闭包就是一个引用了父环境的对象,并且从父环境中返回到更高层的环境中的一个对象。
我觉得可以这么比喻函数就像一个封闭的咨询公司调用这个函数就是给他们一些要求和资料(参数)公司根据社会上的公开信息(全局变量),和公司内部数据库(局部变量)进行处理后,返回给你一个结果可能是一份报告(一个变量),事情就算结束了比较复杂的话也可能是返回给你一个部门经理来和你协调之后的事情这个部门经理就是个闭包函数,可以访问公开信息(全局变量)和公司内部数据库(局部变量)你只有通过他去访问这公司的内部数据库嗯……写到这里感觉把整个程序比喻成一个公司算了,函数是各个部门
一个被父函数返回的内嵌函数,从外界来看就是一个闭包。2. 闭包保存了父函数的当前状态。
我理解的闭包就是,让别人来决定,这段我写的代码要什么时候执行。而别人也不知道我要执行什么。也就是说:闭包提供了在不干扰代码“正常”执行过程时的自定义特性的功能。然后还有一种功能(一般js才会说):就是书上说的,可以延长某些东西的生命周期(或者叫变量环境)。TJS2中对象的表示方法,其代表的运行时环境,与闭包的关系_小组_ThinkSAAS
TJS2中对象的表示方法,其代表的运行时环境,与闭包的关系
TJS2中对象的表示方法,其代表的运行时环境,与闭包的关系
对一个对象实例调用(string)转换时,可能会看到这样的结果: (object 0x01AAD840:0x01A9EEC4)
object到string的转换可以通过显式或隐式方式调用.这个转换在tjsVariant.cpp中实现.
kirikiri2srccoretjs2tjsVariant.cpp
void tTJSVariant::ToString()
switch(vt)
case tvtVoid:
String=NULL;
case tvtObject:
tTJSVariantString * string = TJSObjectToString(*(tTJSVariantClosure*)&Object);
ReleaseObject();
case tvtString:
case tvtInteger:
String=TJSIntegerToString(Integer);
case tvtReal:
String=TJSRealToString(Real);
case tvtOctet:
TJSThrowVariantConvertError(*this, tvtString);
tTJSVariantString * TJSObjectToString(const tTJSVariantClosure &dsp)
if(TJSObjectTypeInfoEnabled())
// retrieve object type information from debugging facility
tjs_char tmp[256];
TJS_sprintf(tmp, TJS_W("(object 0x%p"), dsp.Object);
ttstr ret =
ttstr type = TJSGetObjectTypeInfo(dsp.Object);
if(!type.IsEmpty()) ret += TJS_W("[") + type + TJS_W("]");
TJS_sprintf(tmp, TJS_W(":0x%p"), dsp.ObjThis);
type = TJSGetObjectTypeInfo(dsp.ObjThis);
if(!type.IsEmpty()) ret += TJS_W("[") + type + TJS_W("]");
ret += TJS_W(")");
tTJSVariantString * str = ret.AsVariantStringNoAddRef();
str-&AddRef();
tjs_char tmp[256];
TJS_sprintf(tmp, TJS_W("(object 0x%p:0x%p)"), dsp.Object, dsp.ObjThis);
return TJSAllocVariantString(tmp);
注意到,上面例子(object 0x01AAD840:0x01A9EEC4)中的两个数字,前者是dsp.Object,后者是dsp.ObjThis. tTJSVariantClosure的这两个成员是由tTJSVariantClosure_S继承而来.
tTJSVariantClosure_S的定义如下:
struct tTJSVariantClosure_S
iTJSDispatch2 *O
iTJSDispatch2 *ObjT
由此得知,两个数字皆是地址(指针自身的内容都是地址).
我现在还不是太确定Object成员是不是在编译到中间代码的时候就已经构造出了实例,但从多个同类型的对象实例共享一个Object成员的实例来看,应该不是到脚本执行的时候才实例化的.现在虽然找到了几个关键点(T_NEW, VM_NEW, CreateNew, TJS_GET_VM_REG等),但面向对象程序的代码就是很难通过静态分析完全确定动态行为,我仍然理不出运行时行为的头绪.糟糕的是我暂时没办法以debug模式把TJS2的解释器正确编译出来,要调试也很麻烦.
Anyway,还不确定的就先放一边,来看看我确定的部分.上面提到了,每个tTJSVariantClosure实例都会有Object和ObjThis指针为成员.并且,tTJSVariantClosure拥有由iTJSDispatch2接口类型所拥有的各方法.在调用这些方法时,需要经由tTJSVariantClosure来访问,以确保执行时的上下文的正确性.
简单点说,TJS2中每个对象实例都有"两个指针",一个是Object,指向类中的程序代码(如初始化用的代码和函数等),由同类型的所有类型所共享,无状态;另一个是ObjThis,也就是TJS2中以"this"访问可以得到的对象,保存着实例自身的上下文(context,也可以理解为环境environment),不与其它实例共享,用于保持状态信息.在TJS2中,将这个上下文称为闭包(closure).可以参考中"类(class)"一节.这里"闭包"可以理解为状态(主要是成员变量)与一个由"类"的语法作用域所形成的范围发生绑定的现象.这样,这个范围就对其中的成员变量形成了闭包.注意到函数也同样可以被赋值给变量,并且函数声名与将函数表达式赋值给一个变量近似等价,因此成员函数(作为变量)也是上下文的一部分.拥有一个专门指向上下文的指针,就意味着绑定可以在运行时改变.使用incontextof运算符就能做到"可调用对象"(Callable, 这里具体指类定义或函数)的上下文的显式指定.
下面的代码可以清晰的展示这一特性:
var value ="A";
function printField() {
rm(value);
function printFunc() {
function print2() {
rm("A.print2");
var value ="B";
function print2() {
rm("B.print2");
var a = new A();
var b = new B();
(a.printField incontextof b)(); // 使用incontextof运算符显式改变上下文
(a.printFunc incontextof b)(); // 这两行调用的上下文被替换为与b的相绑定
a.printField(); // 正常的方法调用
a.printFunc(); // 这两行调用的上下文都与a的绑定
// 运行结果: 依次显示B, B.print2, A, A.print2
要注意的是,TJS2里的"闭包"与一般支持嵌套定义函数的编程语言中所指的闭包并不相同;TJS2里"闭包"的用法相当独特,特别不应与JavaScript中所支持的闭包所混淆.
在一般的编程语言中,"闭包"的概念是如何会出现的呢? 下面简单解释一下.下面一段会混用"过程""函数""方法"等几个名词,请自行注意分辨区别.也可以查阅.该条目原本有些描述不太对(缺乏对命令式语言中闭包的考虑),现在已经订正了一些.
根据《编译原理与实践》一书,可以将基于栈(stack)的运行时环境分为三类:
- 没有局部过程的基于栈的环境
- 带有局部过程的基于栈的环境
- 带有过程参数的基于栈的环境
假如一种语言不允许嵌套声明过程/函数/方法,则所有的函数如果不是局部的就一定是全局的.因而很容易为变量分配空间——全局变量可以放在一个全局区域,而所有函数内的局部变量则直接分配在栈上(或寄存器上).函数总是能访问到它的作用域内的所有变量,外加全局变量.一个函数在被调用的时候,会在栈上压入它的活动记录,在函数结束时销毁;因而所有局部变量将随着函数的退出而被销毁.
int global = 1;
void foo() {
int local = 2;
// 此处global与local都能被访问到
// 此处就不再能访问到local了
假如一种语言允许嵌套声明过程/函数/方法,但不允许将过程/函数/方法当作参数来传递,前一种运行时环境就无效了.试想下面的伪代码(以C的语义来考虑):
int global = 1;
void foo() {
int local = 2;
void goo(int local) {
void hoo() {
// 此处能访问到global
// 但是local呢?
// 因为hoo()嵌套于foo()之中,我们希望hoo()也能访问到foo()的local
goo(local);
void main(void) {
这段代码片段中,对hoo()而言global依然是全局变量因而可以正常访问,但local是非局部非全局的变量,不能再按照前面的方式考虑.按照标准的静态作用域则无法在任何活动记录中找到local的信息;如果接受动态作用域,那么可以通过控制链向上找到在goo()中的local.即使goo()中没有local,还可以继续向上找到foo()的local.但这样每次能找到"local"的偏移量都会随着调用的不同而不同,而我们想要的很可能不是这样的.
要解决这问题,仍然可以实现静态作用域.可以用一个访问链(access link)去记录一个函数的包围(enclosing)函数的活动记录,使内部的函数能访问到外部函数的局部变量.在上面的代码里,也就是说hoo()可以访问到foo()的(而不是goo()的)local变量.
前面两种情况都能通过灵活使用栈而得到顺利解决,但下面的这种情况就很难单独依靠栈来维持运行时环境了.假如一个语言允许将过程/函数/方法当作参数来传递,则编译程序无法再像前一种情况生成代码计算调用点上的访问链.为解决非局部引用的问题,函数应包含一个访问指针对,其中一个是代码指针一个是访问链或环境指针(注意到这里跟TJS2的实现的微妙相似与相异).它们通称为闭包(closure),因为访问链"闭合"了由非局部引用引起的"洞"."闭包"的概念来自微积分中λ算子,这里就不深入了.通过闭包,内部函数可以捕捉到外部函数所能访问到的所有引用,包括全局引用和外部函数的局部引用,外加内部函数自身的局部引用;也就是说,内部函数的作用域"捕捉"到了外部的.然而这个内部函数一旦被作为参数传递(例如说被外部函数作为返回值返回),它所能访问到的作用域不能马上被销毁,而要继续存在到没有任何引用继续指向该内部函数为止.这就与前面使用栈的方式相异,很难只用栈来放置活动记录就维持运行时环境.更一般的做法是把闭包相关的活动记录在堆(heap)上分配,然后由垃圾收集器处理销毁的工作.
参考下面代码(ActionScript3):
function add( lhs : int ) : Function {
return function ( rhs : int ) : int {
return lhs +
var addFive = add( 5 );
var twelve = addFive( 7 ); // twelve == 12
总之,要特别引起注意的地方是这里引出"闭包"这改变的问题,来自以遵守静态作用域为前提,允许嵌套声明的函数对来自包围它的作用域的非局部非全局引用的访问.
在这个意义上,TJS2并没有实现一般的闭包.在TJS2中可以嵌套定义函数,但无论嵌套多少层,一个函数的上下文总是绑定于离它最近的"类"的作用域中;假如一个函数向嵌套的外层数去,数到头都没有类声明,则它的上下文绑定于全局对象(global).下面的代码将展示这点:
function foo() {
function goo() {
this.val ="a string";
rm(val); // 或者写为global.val都一样.
// 显示a string
幸好吉里吉里2的作者,W.Dee氏已经意识到了这点.在设计TJS2的接班者Risse时,能实现下面的代码:
function test()
var i = 0;
return function inc()
return ++i;
var inc_func = test();
Log.message(inc_func()); // =& 1
Log.message(inc_func()); // =& 2
TJS2不但没实现一般意义的闭包,同时也没有遵循一般意义的静态作用域.TJS2中incontextof运算符可以在静态作用域的基础上将一个可调用对象的上下文重新绑定到任意对象级别上.TJS2解释器会在编译的时候检查是否可执行代码是否有可能调用了非局部(但是是成员的;"全局"在TJS2中是一个特殊的内建对象,全局变量可以认为是global的成员)的引用.如果有的话,则会通过"this proxy"(对应于%-2寄存器)来寻找那些引用.这么做可以说很灵活,但也很容易写出让人难以理解的代码(因为函数与上下文的绑定可能经常变化).建议慎用.
个人倾向于将这种作用域的做法成为"半自动动态作用域".总之关键就是不要以纯静态作用域的假设去阅读TJS2代码,否则一定会吃到苦头...
===========================================
十分有趣的一点是,TJS2中的==与===运算符,分别是由tTJSVariant::NormalCompare与tTJSVariant::DiscernCompare实现的;其中,前者在比较者类型为tvtObject(object)时,比较的是左右操作数的Object.O后者在比较者类型为tvtObject(object)时,比较的是左右操作数的Object.Object与Object.ObjThis.这解释了下面代码行为的原因:
var someF // member field
function A() {} // ctor
function foo() {} // member method
var a = new A();
rm((string)A.foo + (string)a.foo); // 显示两个相同的Object地址
rm(A.foo == a.foo); // 1, 即equality相等
rm(A.foo === a.foo); // 0, 即identity不相等
rm(A.foo === (a.foo incontextof null)); // 1, 即identity也相等
PHP开发框架
服务器环境
ThinkSAAS商业授权:
ThinkSAAS为用户提供有偿个性定制开发服务
ThinkSAAS将为商业授权用户提供二次开发指导和技术支持
手机客户端
ThinkSAAS接收任何功能的Iphone(IOS)和Android手机的客户端定制开发服务
让ThinkSAAS更好,把建议拿来。

我要回帖

更多关于 调用闭包函数 的文章

 

随机推荐