javascript中闭包与作用域的理解
很多js的框架与插件编写都用到了闭包,所以,阅读和掌握闭包很有必要。最近学习vue框架时,经常会猜想很多功能的native js实现,很多都应用到了闭包,闭包除了目前已知的一些特性,如:可以保持局部变量以减少对全局作用域的污染外,一些情况下必须用闭包才能实现。
先贴一个根据自己的理解绘制的闭包原理:
关于闭包的几个重要的知识点需要理解:
1. javascript中,每次运行函数,都会创建一个新的对象,用来保存函数的内部变量,称之为——局部变量保存对象
2. 每次运行外部函数,内部闭包函数都会被重新定义一遍
3. 函数运行时,局部变量保存对象会保存在函数定义时的作用域链中,当函数执行完毕后,没有其他引用指向该作用域链时,会释放上述对象;若有其他引用指向时(如函数运行结束后返回一个闭包的引用),该对象不会被释放。
一些遇到的,必须用闭包的情况:
一、for循环后的外部引用指向同一个作用域,和设计期待背道而驰
在模拟vue的数据监控逻辑时,刚开始写的代码如下:
function Observe (obj){ for( prop in obj ){ var oldval = obj[prop]; Object.defineProperty( obj,prop,{ get:function(){ return oldval; }, set:function(newval){ console.log( prop+'的数据改变了,老的值:'+oldval+"新的值:"+newval ); oldval = newval; } } ); } } var data = { name:'tester', age:16, class:'04071135' }; Observe( data );
在firefox控制台打印data.name,状况如下:
很明显,虽然这里为data的每个属性(name,age,class)定义了getter和setter存取器——即每个属性的getter/setter外部应用都指向了每次重新定义的闭包,但这些闭包共享了一个父函数作用域,在循环执行完毕后,该作用域内挂在的变量对象信息如下:
prop: 'class';
oldval:'04071135'
所以,当调用虽有的外部引用——属性的存取器getter和setter时,访问的是同一个变量对象,所以,执行结果都是一样的,再多打印一些信息看看:
经上述分析可知,因为这些外部引用都指向了同一个作用域里的变量对象才导致的bug,解决的办法就是,让每个外部引用都能保有自己独立的作用域,这就想到了闭包:
修改上述Observe函数为:
function Observe( obj ){ function defineprop( obj,prop ){ //在没有开始define之前获取属性值,这样就不会陷入死循环 var oldval = obj[prop]; Object.defineProperty(obj,prop,{ get:function(){ return oldval; }, set:function(newval){ console.log( prop+'的数据改变了,老的值:'+oldval+"新的值:"+newval ); oldval = newval; } }); } var keys = Object.keys( obj ); for( var i=0;i<keys.length;i++ ){ defineprop( obj,keys[i] ); } } Observe( data );
再在控制台打印测试,结果如下:
这里就是运用了闭包的特点,虽然内部定义了defineprop函数后就调用了,如果没有外部引用指向时,就会释放defineprop里的prop,oldval等,但因为data的属性存取器指向了defineprop内的闭包(get和set),所以,defineprop里的局部变量得得以保留。每个属性存取器都有了自己的作用域—持久化的变量保存对象,因此,访问起来就没什么问题了。
再看下面一个小例子,是上面的简化版;
var obj_arr = [{},{},{}]; for( var i=0;i<obj_arr.length;i++ ){ obj_arr[i]["getdata"] = function(){ return i; } }
此时在控制台访问一下对象的getdata看看:
这些引用均访问同一块公用的作用域;
根据上述的闭包思路重新改写代码:
var obj_arr = [{},{},{}]; function addData( val ){ obj_arr[val]["getdata"] = function(){ return val; } } for( var i=0;i<obj_arr.length;i++ ){ addData(i); }
打印结果如下,正常。
二、闭包在作用域绑定方面也很有用
将方法的执行绑定在对象上以后,用户不用再关注方法执行的作用域,直接调用即可,这种思路的实现可以参考:https://www.cnblogs.com/surfer/p/9625725.html
未完待续...