关于defer 的用法

  要是以前,我铁定整天到处找教程看,光说不练,现在觉悟了,看教程看得最多,不一定能看完,看完了不一定能比作者更明白,看明白了不一定能用得好。所以看教程其实好处不大,只能作为小小的参考。很多东西看别人的始终是没有用。只有将实验进行到底才是王道……

  这儿主要是代码分析。

  研究工具:Dreamweave cs3(装那个extJs 2.0插件老装不上)、Aptana(一个好处,好看代码,有括号匹配,json语法好是好,就是括号多了,搞清在哪儿结束)

  发现,extJs的代码最喜欢用json语法定义,类基本上都是用json语法定义的。而不是在外面一大路的xx.prototype.yyyy=function(){……}。不过这种语法蛮清晰。我喜欢。

  extJs时面只有一个类:Ext,它是一个静态类。提供了经常要用到的函数。

Ext.apply = function(o, c, defaults){
    if(defaults){
        // no "this" reference for friendly out of scope calls
        Ext.apply(o, defaults);
    }
    if(o && c && typeof c == 'object'){
        for(var p in c){
            o[p] = c[p];
        }
    }
    return o;
};

  这是apply函数,作用其实相当于克隆,它把对象c中的成员全部复制到o中去。如果有defaults,也把它的内容复制到o中。这儿其实揭示javascript的一种语法:

  javascript中的对象的成员有两种引用方法:

  一、o.propertyName

  二、o[propertyName]

  这段代码关键就在o[p]=c[p]。这个要理解。尽管如此,但是不能像下面一样做:

  var newelem=new Object();
  Ext.apply(newelem,Ext.getDom("a1"));
  Ext.getDom("form1").appendChild(newelem);

 

  下面一大段的代码,由于dw不好看代码,半天才晓得那儿是个(function(){……Ext.apply(Ext,{……}})(),这是 我把概述出来。这样写呢,实在有点叫人别扭,作者的意图是想把这相关的一段全部放到括号中,以免造成理解上的混乱。能理解。不过,这种写法不大招人喜欢。

 

        applyIf : function(o, c){
            if(o && c){
                for(var p in c){
                    if(typeof o[p] == "undefined"){ o[p] = c[p]; }
                }
            }
            return o;
        }

  这是applyIf的代码,事实上,在文档上面,它的描述有问题,应当是是当o,c两个对象都存在时,则把o中不存在,c中存在的属性复制到o 中,且属性名不变。而不是所谓“如果o不存在时就把属性复制到o中”,哪有这种说法的。另外,判断一个对象是不是存在,最严谨的还是用typeof的方 法。

 

addBehaviors : function(o){
            if(!Ext.isReady){
                Ext.onReady(function(){
                    Ext.addBehaviors(o);
                });
                return;
            }
            var cache = {}; 
            for(var b in o){
                var parts = b.split(
'@');
                if(parts[1]){ // for Object prototype breakers
                    var s = parts[0];
                    if(!cache[s]){
                        cache[s] = Ext.select(s);
                    }
                    cache[s].on(parts[1], o[b]);
                }
            }
            cache = null;
        }

  这个地方巧妙在于依赖于Ext.isReady。这个属性我估计应当是在onload第一行将它设成true的,它的作用就是用于标志当前是不 是已经文档模型加载完了。前面几行的意思:如果dom模型还没有加载完,没有准备好,就将这些事件注册代码交给onload去做。即 Ext.onReady。

  如果DOM模型已加载完,那么就马上注册事件,区别:前者是延迟注册、后者是马上注册。为什么要延迟,因为DOM都没有创建完,有些元素在DOM树中还不存在,当然就没法设置它了。其余的地方则不足道,后面的关键就是Ext.select了。

 

        id : function(el, prefix){
            prefix = prefix || "ext-gen";
            el = Ext.getDom(el);
            var id = prefix + (++idSeed);
            return el ? (el.id ? el.id : (el.id = id)) : id;
        }

  这儿有一个技巧:prefix = prefix || "ext-gen",这是最简捷的代码啊。本来要一个if语句的。

 

  extend、namespace两个函数硬是没有看懂,等水平高了再来研究。

 

  urlEncode的源代码原理简单,但是,要是我的话还是没法写得这么清楚,主要是情况比较多。这儿主要是学到了数组的push,原来以为 push只能传一个参数,没想到能一次传多个。发现,很多时候,在构造一个复杂的字符串时都是用到数组的。至于urlEncode的作用,就是把一个 JSON对象编码成一个查询字符串。

        each : function(array, fn, scope){
            if(!Ext.isArray(array)){
                array = [array];
            }
            for(var i = 0, len = array.length; i < len; i++){
                if(fn.call(scope || array[i], array[i], i, array) === false){ return i; };
            }
        }

  这个函数的功能并不是像它的名字一样简单啊,这儿又学到了:

  一、原来构造单元素数组可以直接这样写:a=[a]。

  二、scope在这儿是默认伪调用者,同时,还把当前数组元素值、序号、数组引用都传过去了。这个可能在fn中用得着。要注意。

  另外就是x===false这个语句要注意。要知道undefined==false。

        callback : function(cb, scope, args, delay){
            if(typeof cb == "function"){
                if(delay){
                    cb.defer(delay, scope, args || []);
                }else{
                    cb.apply(scope, args || []);
                }
            }
        }

  吃了一惊,Function什么时候有个成员叫defer了?后来才知,defer是extJs扩展出来的。delay是时延。老实说 scope这个东西不能言传只可意会,不看代码是不清楚的。事实上javascript中的确是存在defer属性的。用于修饰script元素的,确实 是用于延迟script里面内容的加载。详情见此处

        destroy : function(){
            for(var i = 0, a = arguments, len = a.length; i < len; i++) {
       var as = a[i];
      if(as){
        if(typeof as.destroy == 'function'){
          as.destroy();
      }
      else if(as.dom){
          as.removeAllListeners();
          as.remove();
      }
                }
            }
        }

  这个函数用来销毁对象,由代码可知一点,extJs鼓励大家在创建自己的类有必要的话就写destroy。如大量没用的dom元素。在这 里,destory相当于析构造函数一样。至于removeAllListenners,remove这两个函数,它们是Ext.Element类的成 员。

  removeNode : isIE ? function(){
            var d;
            return function(n){
                if(n && n.tagName != 'BODY'){
                    d = d || document.createElement('div');
                    d.appendChild(n);
                    d.innerHTML = '';
                }
            }
        }() : function(n){
            if(n && n.parentNode && n.tagName != 'BODY'){
                n.parentNode.removeChild(n);
            }
        }

  这个代码作用显然,就是删除一个结点。但是这个代码的写法实在有点让人难以接受啊。最郁闷是如果ie,那么,那个参数n是怎么传进去的呢,因为 外面罩住的那个函数没有参数,本来没有参数也好办,关键是外面的那个函数根本没有传参数给return里面的函数,这居然也能传进去,见识到了。

  经过一番实验与琢磨,发现,其实并不是外面的函数能传参给里面的那个函数,实在是因为那个()用得好,如有:

  var do1=function(){return function(n){}}();

  关键是要外面的函数{}之后要马上“自调用”一下,这样就会返回一个结果,这个结果是个函数表达式,它就能传参了。所以如果外面的函数没有()的话,那么实际调用将必须写成:do1()(3)的形式,连写两个括号。。这个问题我想了好久,终于想清楚了。

    createCallback : function(/*args...*/){
        // make args available, in function below
        var args = arguments;
        var method = this;
        return function() {
            return method.apply(window, args);
        };
    }

  顾名思意,回调。这个函数是对Function对象的原型扩展。所以,所有函数都有这个成员。例如:

  function A(){}

  B=A.createCallback();

  B();

  最后B()执行调用的是A。有人说,既然调用B就相当于调用A,还不如直接用

  function B(){A.apply(window,this.argments);}

  的确,这样确实可以达到差不多的目的,但是,写代码要注意封装。尽管这只有一行代码,但是,相对于客户程序员来说,createCallback比apply亲切多了,而且,它还节省了不少字符,这就节省带宽。

  什么时回调?让别人来调,那为什么不定义在那个调用者里面?因为,只有定义在别人的里面才可以获得别人的信息。

  当然,在这儿我还是学到了一点,以前没意识到,怎样把外层的this传给内层的function。只需method=this。

  有一些Ext下的函数并没有定义在ext.js中。如:Ext.onReady、Ext.reg、Ext.select、Ext.query。

<think>好的,我现在需要处理用户关于Go语言中defer关键字的使用方法的查询。用户可能已经看过一些资料,但想要更详细的教程。首先,我应该回顾defer的基本概念和常见用途,确保覆盖正确的语法和最佳实践。 根据引用资料,特别是引用[1]和[5],defer主要用于资源释放、异常处理和并发编程。我需要强调这些应用场景,并提供示例代码。同时,引用[2]提到不要在循环中使用defer,或者对命名返回值进行操作,这可能导致意外结果,这点必须重点指出。 接下来,用户可能需要知道defer的执行顺序,比如后进先出(LIFO),这点在引用[4]中有所涉及。另外,引用[3]提到defer的原理,虽然用户没有直接问原理,但简单提及可能有帮助。 需要注意的常见错误,如延迟调用含有闭包的函数时的变量捕获问题,以及性能方面的考虑。引用[1]提到性能敏感场景要谨慎使用,这可能涉及到资源释放的必要性与性能之间的权衡。 还要检查是否有其他注意事项,比如错误处理语句的位置,引用[2]建议将defer放在错误检查之后,避免panic。此外,命名返回值的处理,如引用[4]中的执行顺序影响,需要举例说明。 最后,用户可能需要相关的问题来进一步学习,比如defer的执行时机、与匿名函数结合使用的效果,以及如何避免资源泄漏。这些问题需要涵盖常见的疑惑点,帮助用户深入理解。</think>以下为Go语言中`defer`关键字的详细使用教程,结合实践场景和注意事项: --- ### 一、`defer`的基本作用 `defer`用于延迟函数或方法的执行,**在所属函数返回前按后进先出(LIFO)顺序执行**[^4]。典型应用场景包括: 1. 资源释放(文件关闭、锁释放) 2. 异常处理(`recover`与`panic`配合) 3. 日志记录和状态清理 示例: ```go func readFile(filename string) { file, _ := os.Open(filename) defer file.Close() // 确保文件在函数返回前关闭 // 文件操作... } ``` --- ### 二、核心使用规则 1. **执行顺序** 多个`defer`按声明倒序执行: ```go defer fmt.Print("A") // 第3执行 defer fmt.Print("B") // 第2执行 defer fmt.Print("C") // 第1执行 ``` 输出:`CBA` 2. **参数求值时机** `defer`的参数在声明时立即求值,而非执行时: ```go x := 1 defer fmt.Println(x) // 输出1(x的当前值) x = 2 ``` 3. **返回值陷阱** 当函数使用命名返回值时,`defer`可以修改返回值: ```go func demo() (result int) { defer func() { result++ }() // 最终返回1 return 0 } ``` --- ### 三、高级使用技巧 1. **错误恢复** 配合`recover`实现异常捕获: ```go func safeOperation() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered:", r) } }() // 可能触发panic的代码... } ``` 2. **耗时监控** 记录函数执行时间: ```go func trackTime() { start := time.Now() defer func() { fmt.Println("耗时:", time.Since(start)) }() // 业务逻辑... } ``` 3. **资源清理链** 多个资源的有序释放: ```go func processDB() { conn := connectDB() defer conn.Close() tx := conn.Begin() defer tx.Rollback() // 默认回滚,若成功再提交 // 事务操作... tx.Commit() } ``` --- ### 四、需要避免的陷阱 1. **循环中的`defer`** 错误用法: ```go for _, file := range files { f, _ := os.Open(file) defer f.Close() // 可能导致文件描述符泄漏 } ``` 正确做法:将循环体封装为函数 ```go for _, file := range files { func(f string) { file, _ := os.Open(f) defer file.Close() // 操作文件... }(file) } ``` 2. **闭包变量捕获** 延迟函数捕获的是变量的最终值: ```go for i := 0; i < 3; i++ { defer func() { fmt.Print(i) }() // 输出333 } ``` 解决方法:通过参数传递当前值 ```go for i := 0; i < 3; i++ { defer func(n int) { fmt.Print(n) }(i) // 输出210 } ``` 3. **性能敏感场景** `defer`会产生额外的函数调用开销,在关键路径代码中可考虑直接调用清理函数。 --- ### 五、最佳实践原则 1. 将`defer`紧跟在资源获取语句后,增强可读性 2. 避免在`defer`中进行复杂逻辑操作 3. 对需要错误处理的函数,先进行错误检查再使用`defer`[^2] 4. 优先用于必须执行的清理操作(如解锁、关闭连接) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值