• 从一道面试题分析javascript闭包


    据说是一不注意就会做错的五道javascript面试题之一,我们来看看这道题长什么样

    function Container( properties ) {  
        var objthis = this;  
        for ( var i in properties ) {  
            (function(){  
                    var t = properties[i];  
                    objthis[ "get" + i ] = function() {return t;};  
                    objthis[ "set" + i ] = function(val) {t = val;};  
            })();  
        }  
    }  
       
    var prop = {Name : "Jim", Age : 13};  
    var con = new Container(prop);  
    console.log(con.getName());  
       
    con.setName("Lucy");  
    console.log(con.getName());  
    console.log(prop.Name);  

    现在请写出每次console.log的输出结果:()

    A: Jim,Lucy,Lucy  B: Jim,Jim,Jim C : Lucy ,Lucy,Lucy D: Jim,Lucy,Jim

    如果你选了D,排除运气的成份,你可以不用往下看了,因为可以确定你是资深老手了。

    如果是选的别的选项,请继续看下面的分析。

    首先大概扫瞄一下整个Container函数:函数的作用应当是想要给传进来的对象的每一个属性都添加一个get和 set 的方法。

    下面开始从上至下的逐行分析:

    function Container( properties ) {  
        var objthis = this;  // 用objthis 保存对this的引用 
        for ( var i in properties ) {  //循环properties中的内容
            (function(){  
              //这里就是闭包所创建的空间
              // 闭包内声明一个局部变量保存存properties中的值
                    var t = properties[i];  
                    //在objthis对象上添加两个方法
                    objthis[ "get" + i ] = function() {return t;};  
                    objthis[ "set" + i ] = function(val) {t = val;};  
            })();  //创建一个立即执行函数,目的是形成一个闭包,因为我们知道,闭包的作用是即使外层函数调用后,由于闭包引用的关系,外层函数不会被立即回收,因此闭包函数还可以继续使用外层函数所声明的变量和方法。
        }  
    }  
    var prop = {Name : "Jim", Age : 13};  
    var con = new Container(prop);  
    console.log(con.getName());  // Jim
       
    con.setName("Lucy");  
    console.log(con.getName());  // Lucy
    

    初看起来,没有什么问题,那这个坑到底在哪里呢?

    如果我们继续打印 console.log(prop.Name); // Jim

    是不是感到很惊讶?为什么 con.getName() 得到Lucy,而prop.Name 得到Jim ?

    ===============================================================

    我们先从这个立即执行函数看起,如果省略它,那么t始终都是循环之后的值。这个立即执行函数就是为了解决这个问题的,所以这也没有什么问题。

    然后就是这个objthis,它是Container实例的一个内部指针,和 properties 没有什么关系,但是properties 和prop是有关系的。因为prop是对象

    是引用类型的,所以properties 可以看成是prop引用的一个副本(关于javascript的传参,后面再细说)。所以在Container内部,能过修改properties

    是可以改变prop本身的,当然也可以访问到prop的属性。

    我来看看 

    var con = new Container(prop); 这个con到底有什么内容

    1. getAgefunction () {return t;}
    2. setAgefunction (val) {t = val;}
    3. getNamefunction () {return t;}
    4. setNamefunction (val) {return t;}

     看来con是根据prop的属性名,生成了对应的set/get方法,但是方法体内找不到properties的痕迹。里边只有一个t,而

    t=properties[i], 这不就是prop.Name或prop.Age的值吗?我第一反应就是这个set方法有问题。t = val; 根据对象属性赋值的常识

    var t = prop.Name, t = 'xxx', prop.Name的值是肯定不会改变的。这改变的仅仅是t自己。

    con.setName("Lucy");  
    console.log(con.getName());  // Lucy
    但是为什么这里又更改生效了呢?

     我在这个地方郁闷了很久,我自己没有想清楚,最后还是请教了一位资深前辈才搞清楚。

    原因很简单,因为con上的set和get方法,访问的都是闭包内的t的值。与properties没有半毛钱关系了。

    con.setName("Lucy"); 相当于 t = "Lucy" ,而con.getName相当于 return t; 这是再普通不过的道理了。

    这样也就很好的解释了,为什么 con.getName() 的值是Lucy,而prop.Name还是jim.

    为什么我会产生那样的困惑呢?原因就在于没有搞清楚这个t,没有弄明白闭包的用法。那现在要怎么修改,才能实现Container最初的意图呢?

    既然明白了con.setName修改的是自身的t,con.getName访问的也是自身的t,那么只人把这个t换成prop.Name就好了。

    下面是我修改后的代码:

    function Container( properties ) {
        var objthis = this;
        for ( var i in properties ) {
            (function(){
                    var name = i;
                    objthis[ "get" + i ] = function() {return properties[name];};
                    objthis[ "set" + i ] = function(val) {properties[name] = val;};
            })();
        }
    }

    con.setName('frog')
    console.log(prop.Name,con.getName());

    // frog frog

    ======================================================================

    关于javascript中的参数的传递。

    根据我们c语言的知识,参数的传递有两种方式,按引用传递和按值传递。

    但是javascript中所以的参数都是按值传递的。即便参数是对像也是如此。

    function fun(a){

    }

    var o = {name:'frog'}

    fun(o);

    fun(o/*这里的o其实是复制了o对象的一个引用*/)

    这不是本篇的重点,只是提一下。不明白的私信我或查阅相关资料。

  • 相关阅读:
    C# XML 文档注释
    大数据知识学习
    现在的人,买个钢铁做的车,每天擦,每周打蜡。可对自已的身体最应该保养的“车”,却从不养护
    Asp.net项目因Session阻塞导致页面打开速度变慢
    AvoidRepeatSubmit通过Javascript避免客户端重复提交请求
    Linux下Attansic L2 网卡驱动安装
    如果知道dll文件是面向32位系统还是面向64位系统的?
    整理C# 二进制,十进制,十六进制 互转
    连接Oracle时出现“System.AccessViolationException: 尝试读取或写入受保护的内存。这通常指示其他内存已损坏。”错误的问题
    [转]删除hbase表region块脚本
  • 原文地址:https://www.cnblogs.com/afrog/p/4047699.html
Copyright © 2020-2023  润新知