作用域、闭包等概念的理解
2015-08-11 17:02
120 查看
总结一下我对JS中这些基本却略纠结的概念的理解。
作用域
我们知道,JS不支持块级作用域,只支持函数作用域。函数体内,既不是局部变量,也不是参数的变量称为自由变量。如果没搞清楚函数的作用域,有时某些自由变量的值会与你所想的很不一样。举个简单例子
先说说执行上下文,也就是每一步的执行环境。执行上下文里有变量对象,作用域链和this。
变量对象存储着定义在上下文中的函数声明和变量,相当于保管数据。
作用域链指定了各级作用域的优先顺序,比如在当前上下文中的变量对象没有找到,就会去父上下文中去找,顺着作用域链一直向上找。
this是执行上下文的一个属性,很多地方说this是函数或构造函数的属性的说法是错误的。在进入上下文的时候确定了this的值,指向调用该函数的对象,this的值不会为null,所以如果没有明确调用该函数的对象那么this指向window,在整个上下文持续期间this的值不变。this不是变量,所以给this赋值是无效的。
函数的作用域scope=AO+[[scope]]
当函数被激活调用时,函数上下文中的变量对象称为活动对象AO,它除了存储定义在函数里的变量和函数之外,还包括传递给函数的参数。活动对象在函数被调用时创建。[[scope]]是函数的一个内部属性,它是所有父级变量对象的层级链,注意是所有父级,包括父的父级。函数被创建时就获得了[[scope]]属性,[[scope]]是静态的,也就是说在定义函数时就有它了,而且保持不变,不管函数有没有被调用,直到函数被销毁为止,[[scope]]属性都保持不变。注意,活动对象是和执行上下文相关的,而[[scope]]是函数的一个内部属性,它们结合在一起构成了函数的作用域。
闭包
官方定义看起来比较高大上,闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。换句话说,闭包是代码块和创建该代码块的上下文中数据的结合(详见汤姆大叔深入理解javascript系列)。如果你对上面作用域理解了,那么你可能会问,那JS中的所有函数岂不都是闭包吗。是的,理论上说JS中的所有函数都是闭包,因为函数创建时就把父上下文的数据保存在[[scope]]里了,即使是在window下创建的函数,按照定义它也是闭包。有点像在玩文字游戏,主要是为了加深理解。
从实践角度,我们通常所说的闭包,我的理解是函数保留了对父上下文中变量的引用,简单说就是引用了自由变量,那么即使被引用变量所在的上下文销毁了,这个变量没有被GC回收,仍存在于函数的作用域链中;或者函数由父上下文返回被外部的变量引用了,即使创建函数的上下文销毁了,函数仍然存在。这样的函数,叫做闭包。换言之,其作用在于使变量和函数不会正常地被GC回收而可以通过闭包的作用域访问到。
需要注意的是,同一个父上下文中创建的闭包共用一个[[scope]]属性,这是理所当然的,也就是说某一个闭包对它的[[scope]]里的变量进行了修改,那么其它闭包的[[scope]]里相应的变量也会变化,道理大家都懂,但初学时在实际应用中还是容易犯错误。
原型链
原型的知识网上书上太多了,这里说一下prototype和__proto__的区别。
prototype是函数的一个属性,JS中每个函数都有这个属性,它指向一个对象,就是我们常说的原型。
__proto__是对象的一个内部属性,所谓内部就是本意是不会被外部看到的,是JS内部用来查找原型链的,只不过某些浏览器(chrome等)将其暴露出来了。
作用域
我们知道,JS不支持块级作用域,只支持函数作用域。函数体内,既不是局部变量,也不是参数的变量称为自由变量。如果没搞清楚函数的作用域,有时某些自由变量的值会与你所想的很不一样。举个简单例子
var a = 10; function getA() { alert(a); } (function() { var a = 20; getA(); //10 })(); (function(fn) { var a = 20; fn(); //10,作为参数传入也是一样 })(getA);
先说说执行上下文,也就是每一步的执行环境。执行上下文里有变量对象,作用域链和this。
变量对象存储着定义在上下文中的函数声明和变量,相当于保管数据。
作用域链指定了各级作用域的优先顺序,比如在当前上下文中的变量对象没有找到,就会去父上下文中去找,顺着作用域链一直向上找。
this是执行上下文的一个属性,很多地方说this是函数或构造函数的属性的说法是错误的。在进入上下文的时候确定了this的值,指向调用该函数的对象,this的值不会为null,所以如果没有明确调用该函数的对象那么this指向window,在整个上下文持续期间this的值不变。this不是变量,所以给this赋值是无效的。
函数的作用域scope=AO+[[scope]]
当函数被激活调用时,函数上下文中的变量对象称为活动对象AO,它除了存储定义在函数里的变量和函数之外,还包括传递给函数的参数。活动对象在函数被调用时创建。[[scope]]是函数的一个内部属性,它是所有父级变量对象的层级链,注意是所有父级,包括父的父级。函数被创建时就获得了[[scope]]属性,[[scope]]是静态的,也就是说在定义函数时就有它了,而且保持不变,不管函数有没有被调用,直到函数被销毁为止,[[scope]]属性都保持不变。注意,活动对象是和执行上下文相关的,而[[scope]]是函数的一个内部属性,它们结合在一起构成了函数的作用域。
闭包
官方定义看起来比较高大上,闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。换句话说,闭包是代码块和创建该代码块的上下文中数据的结合(详见汤姆大叔深入理解javascript系列)。如果你对上面作用域理解了,那么你可能会问,那JS中的所有函数岂不都是闭包吗。是的,理论上说JS中的所有函数都是闭包,因为函数创建时就把父上下文的数据保存在[[scope]]里了,即使是在window下创建的函数,按照定义它也是闭包。有点像在玩文字游戏,主要是为了加深理解。
从实践角度,我们通常所说的闭包,我的理解是函数保留了对父上下文中变量的引用,简单说就是引用了自由变量,那么即使被引用变量所在的上下文销毁了,这个变量没有被GC回收,仍存在于函数的作用域链中;或者函数由父上下文返回被外部的变量引用了,即使创建函数的上下文销毁了,函数仍然存在。这样的函数,叫做闭包。换言之,其作用在于使变量和函数不会正常地被GC回收而可以通过闭包的作用域访问到。
需要注意的是,同一个父上下文中创建的闭包共用一个[[scope]]属性,这是理所当然的,也就是说某一个闭包对它的[[scope]]里的变量进行了修改,那么其它闭包的[[scope]]里相应的变量也会变化,道理大家都懂,但初学时在实际应用中还是容易犯错误。
var data = []; for (var i = 0; i < 3; i++) { data[i] = function() { alert(i); }; } data[0](); //3,而不是0 data[1](); //3,而不是1 data[2](); //3,而不是2
原型链
原型的知识网上书上太多了,这里说一下prototype和__proto__的区别。
prototype是函数的一个属性,JS中每个函数都有这个属性,它指向一个对象,就是我们常说的原型。
__proto__是对象的一个内部属性,所谓内部就是本意是不会被外部看到的,是JS内部用来查找原型链的,只不过某些浏览器(chrome等)将其暴露出来了。
function Person() {} var p = new Person(); alert(p.__proto__ === Person.prototype); //true
相关文章推荐
- CSS单位和值
- 怎么用cmd 运行python 快捷键(WIN+R)在“运行”中输入“cmd ”然后在命令提示符中输入set PATH=%PATH%;D:\乱七八糟的软件\Python-3.4.3,接下来,再在当前的 cmd下输入python,即可运行。———没嘛用 直接文件夹shift右键
- 推荐一款仿iPhone桌面的代码. ___王朋.
- HDU 2242 双连通分量 考研路茫茫——空调教室
- 实现模糊查询
- Win10系统怎么设置默认WPS打开方式?
- HDU 5294 Tricks Device(spfa+最大流-Dinic)
- 《机器学习实战》(三)决策树(decision trees)
- SQLite数据存储
- Android-加速传感器或者OrientationEventListener做横竖屏切换
- android--访问网络权限
- POCO C++库学习和分析——任务
- 技术文档
- js apply/call/caller/callee/bind使用方法与区别分析
- CC2541的几种工作状态
- python爬虫 scrapy框架 知乎zhihu 模拟登陆
- 【leetcode】Recover Binary Search Tree
- python logging
- HDU 5374 模拟俄罗斯方块
- 通过SQLServer的数据库邮件来发送邮件