Javascript学习日志(三):闭包
说实话,前面一节的原型和原型链在当初学的时候并没有很头疼,对着高级编程第三版撸了几遍就理解透了,闭包这一节真的挺头疼的,很惭愧,看了差不多十来遍吧,还翻看了网上的其他博客和解释文档,五花八门的表达方式,虽然核心思想都一致,但是实在是不能做到自己的理解,后来结合函数作用域链,好不容易有点开窍,趁着热乎劲儿,赶紧写下来,感兴趣的可以参考一下。
闭包:
高级编程上面的解释是指有权访问另一个函数作用域中的变量的函数,(是一个函数);
创建闭包的常见方式,就是在一个函数内部创建另一个函数。
在理解闭包之前,先要清楚一个函数在创建到调用再到结束后的一系列过程:
当一个函数被调用的时候,会先创建一个执行环境以及相应的作用域链,然后用arguments对象和其他命名参数的值来初始化函数的活动对象,但在作用域链中,外部函数的活动对象始终是排在第二,外部函数的外部函数的活动对象排在第三。。。。。。最后一个是全局执行环境下的活动对象。(一圈一圈往外,就像射箭的靶子,中间的红心就是当前的执行环境下的活动对象)
以一个函数为例:
function compare(value1, value2){
return ……
}
var result = compare(5, 10);
在创建函数compare()的时候,会先创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]](就是作用域)属性中,当调用compare()函数的时候,会为函数创建一个执行环境,然后通过复制[[Scope]]中的对象来构建起执行环境的作用域链,此后会将活动对象推到执行环境作用域链的前端。此时的compare()的作用域链上有两个变量对象,第一个是本地变量对象:arguments,value1,value2;第二个则是全局变量对象:result,compare。当函数执行完毕后,局部活动对象就会被销毁,只留下全局执行环境下的变量对象。
但是闭包的情况又有所不同:
如果在函数内部再定义一个函数,则里面的函数会将外部函数的活动对象添加到它的作用域链中,但是它的作用域链的前端还是它自己的活动对象,后面依次是外部函数的活动对象,外部函数的外部函数的活动对象。。。。。直到全局执行环境下的变量对象。
function compare(value1, value2){
return function(name){
…….
}
}
var compareA = compare(1,2);
var result = compareA(“aaaa”);
当匿名函数在compare()中被返回之后,它的作用域链会被初始化,包含compare()的活动对象以及全局变量对象。当compare()执行完毕后,它的活动对象不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。也就是说,compare()函数返回后,它的执行环境的作用域链会被销毁,但是它的活动对象仍然保留在内存中,直到匿名函数被销毁后,它的活动对象才会销毁。拿上面的例子来说明,当执行compare(1,2)的时候,如果没有闭包,则执行完后compare的执行环境就会被销毁,活动对象也不会保留在内存中,内存中只剩下全局变量对象,但是如果函数内部有闭包,此时里面的内部函数返回了,这时候初始化内部函数的作用域链,会保留内部函数外的活动对象(也就是compare的活动对象),还有全局变量对象在内存中,所以这时候,compare的活动对象还是可以访问的,没有被销毁,虽然此时的compare的作用域链已经销毁了。
=====
做一个总结:
当一个函数被创建的时候,同时会产生一个作用域链,这个作用域链是全新的,上面没有什么当前活动对象,只有全局对象,这条作用域链是核心链子,保存在内部”作用域“属性中,一直都在的,当函数被调用的时候,会先创造一个执行环境(关于执行环境,活动对象,变量对象这些名词请参考第一篇随笔),然后复制刚刚内部的“作用域”中的对象来构建一条执行环境的作用域链,这条跟创建函数时所保存的作用域链不是同一条,这条是专门给执行环境准备的,所以当前变量对象,也就是活动对象会被顶到这条作用域链的最前端,此时这条链子上有两个变量对象,最前面的是当前活动对象,后面的是全局环境下定义的变量对象,如果没有闭包,当函数执行完毕之后,当前执行环境就会销毁,同时当前活动对象也会销毁,作用域链上面只剩下全局环境下的变量对象。在第一篇中我们说到如何访问一个变量,标识符解析的时候就是沿着作用域链逐层向上找。所以这时候我们要访问当前变量对象就没办法了。
此时闭包的作用就有了。
在函数内部返回一个新函数,此时新函数被调用,那么此时又会给新函数创建一个新的执行环境,同样当前执行环境下的变量对象(也是活动对象)就会被顶到作用域链的最前端,而刚刚那个外层的函数的执行环境下的变量对象就被挤到了第二个,由于外层的函数已经执行完了,这时候它对应的执行环境已经销毁,但是它的执行环境下的变量对象还在这条作用域链上,在第二个,所以我们依然可以通过这条作用域链来访问到刚刚已经执行过的函数的执行环境下所定义的变量对象,虽然它已经执行过并且被销毁了。
我再做进一步总结:
闭包的作用:能够访问到内部函数的变量对象,
实现方法:在函数内部再返回一个新函数
实现原理:在作用域链上,在要访问的变量对象前面再塞一个新的变量对象,就能保证要访问的变量对象不会销毁并且可以访问到了。(就像串糖葫芦一样,你担心最上面那颗可能会掉,那就在那颗上面再串一颗,就能保证第二颗不掉了)
=====
闭包与变量:
闭包只能取得包含函数(外部函数)中的任何变量的最后一个值。闭包保存的是整个变量对象,而不是某个特殊的变量。
下面是经典案例:
function createFunctions(){
var result = new Array();
for(var i = 0; i<10; i++){
result[i] = function(){
return i
}
}
return result
}
每个函数都会返回10,因为每个函数中的作用域链中都保存着createFunctions()的活动对象,而它的活动对象是i,当createFunctions()执行完毕后,i的值为10,所以每个函数的作用域中的内部i都是10。这是按引用传递,如果想要达到我们期待的结果,就要按值传递。
function createFunctions(){
var result = new Array();
for(var i = 0; i<10; i++){
result[i] = (function(num){
return function(){
return num
}
})(i)
}
return result
}
关于this对象:
匿名函数的执行环境具有全局性,因此其this对象通常指向window。所以闭包中的this通常是全局变量对象下的。
Javascript学习日志(三):闭包的更多相关文章
- JavaScript学习总结(三)——闭包、IIFE、原型、函数与对象
一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...
- javascript学习日志:前言
javascript学习日志系列的所有博客,主要理论依据是<javascript权威指南>(犀牛书第6版)以及<javascript高级程序设计第三版>(红色书),目前js行业 ...
- JavaScript学习记录三
title: JavaScript学习记录三 toc: true date: 2018-09-14 23:51:22 --<JavaScript高级程序设计(第2版)>学习笔记 要多查阅M ...
- JavaScript学习第三天
今天学习第三天. 凡事都是需要坚持的,坚持下去. 学习内容: 1.document.getElementById(""),document.getElementByTagName( ...
- Javascript学习笔记三——操作DOM(二)
Javascript学习笔记 在我的上一个博客讲了对于DOM的基本操作内容,这篇继续巩固一下对于DOM的更新,插入和删除的操作. 对于HTML解析的DOM树来说,我们肯定会时不时对其进行一些更改,在原 ...
- JavaScript学习笔记(三)——this、原型、javascript面向对象
一.this 在JavaScript中this表示:谁调用它,this就是谁. JavaScript是由对象组成的,一切皆为对象,万物皆为对象.this是一个动态的对象,根据调用的对象不同而发生变化, ...
- JavaScript学习笔记(二)——闭包、IIFE、apply、函数与对象
一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...
- JavaScript学习总结(三)——this、原型、javascript面向对象
一.this 在JavaScript中this表示:谁调用它,this就是谁. JavaScript是由对象组成的,一切皆为对象,万物皆为对象.this是一个动态的对象,根据调用的对象不同而发生变化, ...
- JavaScript学习总结(二)——闭包、IIFE、apply、函数与对象
一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...
随机推荐
- [补档][COGS 426]血帆海盗
[COGS 426]血帆海盗 题目 传送门:http://cogs.pro/cogs/problem/problem.php?pid=426 随着资本的扩大,藏宝海湾贸易亲王在卡利姆多和东部王国大陆各 ...
- 八数码问题+路径寻找问题+bfs(隐式图的判重操作)
Δ路径寻找问题可以归结为隐式图的遍历,它的任务是找到一条凑够初始状态到终止问题的最优路径, 而不是像回溯法那样找到一个符合某些要求的解. 八数码问题就是路径查找问题背景下的经典训练题目. 程序框架 p ...
- K个最近的点
前段时间在网上看到一些准备找工作的人会在LintCode上刷题,然后我今天上去看了一下,也打算开始做题,然后把每天做的题目和以后的优化记录下来. 2017年8月6日 21:17:27 第一题: 描述: ...
- ABP+AdminLTE+Bootstrap Table权限管理系统第九节--AdminLTE模板页搭建
AdminLTE 官网地址:https://adminlte.io/themes/AdminLTE/index2.html 首先去官网下载包下来,然后引入项目. 然后我们在web层添加区域Admin以 ...
- vc类型转换函数大全
windows c++中存在各种类型,在实际应用过程中也需要将类型互相转换,故整理了常用类型之间的转换并将之封装成函数,仅供参考,有什么不对的地方,还请指正! ****************** ...
- NYOJ--128--前缀式计算(表达式求值)
前缀式计算 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 先说明一下什么是中缀式: 如2+(3+4)*5这种我们最常见的式子就是中缀式. 而把中缀式按运算顺序加上括 ...
- NYOJ 128 前缀表达式的计算
前缀式计算 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 先说明一下什么是中缀式: 如2+(3+4)*5这种我们最常见的式子就是中缀式. 而把中缀式按运算顺序加上括 ...
- 从Javascript单线程谈Event Loop
假如面试回答js的运行机制时,你可能说出这么一段话:"Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕之后 ...
- Entity Framework Core 2.0 新特性
本文翻译来自:https://docs.microsoft.com/en-us/ef/core/what-is-new/index 一.模型级查询过滤器(Model-level query filte ...
- 【Spring】的【bean】管理(XML配置文件)
Bean实例化的三种方式 说明:通过配置文件创建对象就称为Bean实例化. 第一种:使用类的无参构造创建(重点) 实体类 package com.tyzr.ioc; public class User ...