深入理解Javascript中的作用域链和闭包(3)

而早期IE浏览器( IE9之前 ) js 对象和 DOM 对象使用不同的垃圾收集方法, DOM对象使用计数垃圾回收机制, 只要匿名函数( 比如说onclick事件 )存在, DOM对象的引用便至少为1,因此它所占用的内存就永远不会被销毁.

有趣的是,不同的IE版本将导致不同的现象:

如果是IE 6, 内存泄漏,直到关闭IE进程为止;

如果是IE 7,内存泄漏, 直到离开当前页面为止;

如果是IE 8, GC回收器回收他们的内存,无论当前是不是compatibility模式.

总结一下, 闭包的优点: 共享函数作用域, 便于开放一些接口或变量供外部使用;

注意事项: 由于闭包可能会使得函数中变量被长期保存在内存中, 从而大量消耗内存, 影响页面性能, 因此不能滥用, 并且在IE浏览中可能导致内存泄露. 解决方法是,在退出函数之前,将不使用的局部变量全部删除.

for循环问题分析

我们再来看看开篇的for循环问题, 增加匿名函数后, for循环内部的变量便处于匿名函数的局部作用域下, 此时访问 length 属性, 或者访问 i 属性, 都只需要在匿名函数作用域内查找即可, 因此查询效率大大提升(测试数据发现提升有两百多倍).

使用匿名函数后, 不止是作用域查询更快, 作用域内的变量还与外部隔离, 避免了像 i , length 这样的变量对后续代码产生影响. 可谓一举两得.

踩个作用域的坑

下面我们来踩一个作用域经典的坑.

var div = document.getElementsByTagName("div"); for(var i=0,len=div.length;i<len;i++){ div[i].onclick = function(){ console.log(i); } }

上述代码的本意是每次点击div, 打印div的索引, 实际上打印的却是 len 的值. 我们来分析下原因.

点击div时, 将会执行 console.log(i) 语句, 显然 i 变量不在 click 事件的局部作用域内, 浏览器将沿着 scope chain 寻找 i 变量, 在 index1 的地方, 即 for循环开始的地方, 此处定义了一个 i 变量, 又 js 没有块作用域, 故 i 变量并不会在 for循环块执行完成后被销毁,又 i的最后一次自加使得 i = len, 于是浏览器在scope chain index=1索引的地方停下来了, 返回了i的值, 即len的值.

为了解决这个问题, 我们将根据症结, 对症下药, 从作用域入手, 改变click事件的局部作用域, 如下:

var div = document.getElementsByTagName("div"); for(var i=0,len=div.length;i<len;i++){ (function(n){ div[n].onclick = function(){ console.log(n); } })(i); }

由于 click 事件被闭包包裹, 并且闭包自执行, 因此闭包内 n 变量的值每次都不一样, 点击div时, 浏览器将沿着 scope chain 寻找 n 变量, 最终会找到闭包内的 n 变量, 并且打印出div 的索引.

this作用域

前面我们学习了作用域链, 闭包等基础知识, 下面我们来聊聊神秘莫测的this作用域.

熟悉OOP的开发人员都知道, this是对象实例的引用, 始终指向对象实例. 然而 js 的世界里, this随着它的执行环境改变而改变, 并且它总是指向它所在方法的对象. 如下,

function f(){ alert(this); } var o = {}; o.func = f; f();//[object Window] o.func();//[object Object] console.log(f===window.f);//true

当f单独执行时, 其内部this指向window对象, 但是当f成为o对象的属性func时, this指向的是o对象, 又f === window.f, 故它们实际上指向的都是this所在方法的对象.

下面我们来应用下

Array.prototype.slice.call([1,2,3],1);//[2,3],正确用法 Array.prototype.slice([1,2,3],1);//[], 错误用法,此时slice内部this仍然指向Array.prototype var slice = Array.prototype.slice; slice([1,2,3],1);//Uncaught TypeError: Array.prototype.slice called on null or undefined //此时slice内部this指向的是window对象,离开了原来的Array.prototype对象作用域,故报错~~

总结下, this的使用只需要注意一点:

this 总是指向它所在方法的对象.

with语句

聊到作用域链就不得不说with语句了, with语句可以用来临时改变作用域, 将语句中的对象添加到作用域的顶部.

语法: with (expression){statement}

例如:

var k = {name:"daicy"}; with(k){ console.log(name);//daicy } console.log(name);//undefined

with 语句用于对象 k, 作用域第一层为 k 对象内部作用域, 故能直接打印出 name 的值, 在with之外的语句不受此影响.
再看一个栗子:

var l = [1,2,3]; with(l) { console.log(map(function(i){ return i*i; }));//[1,4,9] }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wypddg.html