• 解析js中作用域、闭包——从一道经典的面试题开始


    如何理解js中的作用域,闭包,私有变量,this对象概念呢?

    就从一道经典的面试题开始吧!

    题目:创建10个<a>标签,点击时候弹出相应的序号

    先思考一下,再打开看看
         //先思考一下你会怎么写,是不是这样?        可是结果呢,弹出来的都是10,为啥?
         
         var i,a
         for(i=0;i<10;i++){
              a=document.createElement('a')
              a.innerHTML=i+'<br>'
              a.addEventListener('click',function(event){
                   var event=event||window.event
                   event.preventDefault()
                   alert(i)                  
              })
              document.body.appendChild(a)
         }
    View Code

    这个题目答案最后再说,当然,你就可以直接翻到最后了解,不过我不建议这样!!


    涉及到今天的变量、作用域、闭包等等,在变量、作用域知识中已经说了一些,再重新简单的说一下。

    执行环境:也称之为执行上下文,两个,一个是全局环境(window),一个是函数环境(每个函数都是一个函数环境)

    作用域链:当代码在某一个环境中执行,会创建变量对象的一个作用域链,以保证对执行环境有权访问的所有变量和函数的有序访问。外部环境访问不到内部环境;内部环境通过作用域链可以访问外部环境。

    块级作用域:js没有块级作用域,类似循环语句、条件判断语句内作用域都是全局的;只有两个执行环境

    变量:var操作符判断是局部变量还是全局变量。

    浏览器解析:浏览器解析时,先将所有的变量提升,变量都存为undefined,当执行到哪一行才给予赋值;函数声明一开始就是完整的,而函数表达式跟解析变量是一样的

         console.log(a)         //undefined
         var a=100
         console.log(a)         //100
         
         person("double")       //double,20
         function person(name){ //声明函数
              age=20
              console.log(name,age)
              var age
         }

    this:引用执行环境的对象;只有在执行时才能确认,定义时无法确认

            //普通的构造函数;this就是构造函数的实例
            function Foo(name){
                //var this
                this.name=name
                //return this
            }
            var f=new Foo("double")
    
            //普通对象;this就是这个对象
            var obj={
                name:"double",
                printName:function(){
                    console.log(this.name)
                }
            }
            obj.printName()
    
            //普通函数 this就是window
            function fn(){
                console.log(this)
            }
            fn()
    
            //call apply bind来设置this的值
            function fn1(name,age){
                alert(name)
                console.log(this)
            }
            fn1.call({x:100},'double',20)      //this 就是{x:100}这个对象
            fn1.apply({x:100},["double",20])   //this 就是{x:100}这个对象
    
            var fn2=function (name,age){
                alert(name)
                console.log(this)
            }.bind('double',20)                //this 就是double了     
            fn2()

    闭包

    这个概念有些抽象,这样理解:有权访问另一个函数作用域中的变量的函数;也就是说一个函数可以访问另一个函数作用域里的变量

    一般两个地方,一个作为返回值,一个作为传递的参数

    //闭包
    
    //函数作为返回值
    function F1(){
        var  a=100;
    
        //返回一个函数(函数作为返回值)
        return function(){
            console.log(a)
        }
    }
    
    //f1 得到一个函数
    var f1=F1()
    var a=200
    f1()          //a=100     执行返回函数,函数在定义函数的作用域查找变量(而非执行函数的作用域),没有才会往父级作用域查找
    
    
    //函数作为参数传递
    
    function F1(){
        var a=100
        return function(){
            console.log(a)
        }
    }
    
    var f1=F1()   //这是个函数
    
    function F2(fn){
        var a=200
        fn()
    }
    F2(f1)        //a=100       给函数传参给F2

     以闭包为返回值为例,详细说一下

         //这是一个闭包以返回值的例子
    function create(){ var a=100 return function(){ console.log(a) } } var a=200 var result=create() //得到返回函数 result() //执行返回函数 a=100 函数会在定义函数的作用域中查找,然后再在父级中查找 当某个函数被调用的时候,会创建一个执行环境及相应的作用域链,使用arguments和其他参数 来初始化函数的活动对象,但在作用域链中,外部函数的活动对象始终处于第二位, 对于create函数内部的匿名函数,其作用域链被初始化包含三个的活动对象,一个自身的,一个为create函数的,一个是全局环境的 一般来说,函数执行完毕,其活动对象就已经被销毁; 但是对于闭包来说,当create函数执行完毕,函数的活动对象依然不会销毁,因为内部的匿名函数一直引用其中的活动对象

    再来看一下这个

         //普通对象 的 一个方法
         var person={
             name:"double",
             getName:function(){
                  console.log(this.name)
             }
         }
         person.getName()     //double
         
         //普通对象方法返回的是匿名函数
         var person={
               name:"double",
               getName:function(){
                      return function(){
                             console.log(this.name)
                      }
               }
         }
         var name="single"
         person.getName()()     //single
       
         //普通对象方法返回的是匿名函数
         var person={
             name:"double",
             getName:function(){
                  var that=this           //在定义匿名函数前将this对象赋予给that的变量  this和arguments差不多,也要将其赋值给某个变量
                  return function(){
                         console.log(that.name)
                  }
             }
         }
         var name="single"
         person.getName()()    //double

     ok,了解闭包、作用域链的知识后,回到那道面试题,应该知道其中的原因

        正确的写法
    var i for(i=0;i<10;i++){ (function(i){ var a=document.createElement('a') a.innerHTML=i+'<br>' a.addEventListener('click',function(event){ var event=event||window.event event.preventDefault() alert(i) //去父作用域查找 }) document.body.appendChild(a) })(i) } //js没有块级作用域,所以alert(i)的i是在全局作用域下的,返回的当然是10, //如何让i是每个弹出来的呢? //我们知道环境分函数环境和全局环境,只需要将每个i都有自己的执行环境就可以了 //怎么弄? //直接加个自执行函数,让每个i都有自己的函数环境,也就是构建出私有作用域

    内存泄露 

    由于闭包的存在,外部函数的变量始终被引用着,闭包不结束,则引用始终存在,内存也就不会被回收

           //内存泄露
           function person(){
                  var element=document.getElementById("someElement")
                  element.onclick=function(){
                           alert(element.id)
                  }
           }
           
           //解决内存泄露
           function person(){
                  var element=document.getElementById("someElement")
                  var id=element.id    //将所需内容赋值给一个变量
                  element.onclick=function(){
                           alert(id)
                  }
               element=null         //闭包会引用包含函数的整个活动对象,其中包含着element
           }

    模仿块级作用域

    都是知道的,js没有块级作用域,所以要模仿,怎么模仿呢?

    上面的面试题已经告诉了:通过匿名函数来构造函数环境,形成私有作用域

    下面解释有关涉及到匿名函数,函数声明,函数表达式的东西,说明一下块级作用域

         //变量名只是个值的另一种形式
         var count=10
         alert(count)  
         alert(10)
          
         //既然变量可以,那么函数呢 
         var person=function(){
               alert("hello")
         }
         person()
    
         var person=function(){
              alert("hello")
         }()
    
         
         function(){
              alert("hello")
         }()    //报错
         //为啥?因为js将function关键字作为一个函数声明的开始,而函数声明是不能加圆括号
         
         (function(){
             alert("hello")
         })()
         //将函数声明转为函数表达式即可,外部加个圆括号即可,也就是形成自己的私有作用域

    看了汤姆大叔的博客,关于这个,他是这么说的

         //对于函数声明和函数表达式的转换
         //js中括号()是不包含语句的,和js中的&&,||,逗号等操作符是在函数表达式和函数声明上消除歧义的
         //所以
         var i=function(){console.log("hello")}()
         true &&function(){console.log("hello")}()
         0,function(){console.log("hello")}()
         !function(){console.log("hello")}()
         ~function(){console.log("hello")}()
         -function(){console.log("hello")}()
         +function(){console.log("hello")}()
         new function(){console.log("hello")}
         var value=10
         new function(){console.log(value)}(value)   //括号传递参数

    关于闭包的知识,就说这么多

  • 相关阅读:
    看所访问网站的IP
    FLEX是什么及与FLASH的关系的介绍
    [设计模式]Head First Design Patterns[转]
    ASP.NET 2.0移动开发入门之使用模拟器
    在一篇文章的基础上总结了一下(接口,组合,继承,组件)
    抽象类与接口的区别 Java描述(转)
    C#:代表(delegate)和事件(event) (转)
    C#分页控件(自己做的)
    一个站点控制另一个站点的文件(添加,删除)用Webservices
    .net remoting程序开发入门篇
  • 原文地址:https://www.cnblogs.com/iDouble/p/8413214.html
Copyright © 2020-2023  润新知