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, ...
随机推荐
- 2.跑nodejs文件
1.安装nodejs的环境,这里不介绍了. app.js 'use strict'; //process.argv 接受变量 var command = process.argv[2]; var a ...
- ue4 C++ 导入图片
void SDrawHouseTypeTools::OnButtonClickLoadImage() { // 当前如果是在硬装模块,则可进行导入户型操作 FHardEdM ...
- Unicode、UTF-8 和 ISO8859-1到底有什么区别
说明:本文转载于新浪博客,旨在方便知识总结.原文地址:http://blog.sina.com.cn/s/blog_673c81990100t1lc.html 本文主要包括以下几个方面:编码基本知识, ...
- [JLOI2013]删除物品 树状数组
当时考试时间剩下太短了然后就挂掉了..其实是个简单的数据结构. 话说一看最小还以为是动规呢.. 将两堆头对头排.比如样例就是 541|273 因为是必须有优先级次序,依次拿的话,看优先级大小相邻的两个 ...
- 使用DbFunctions来解决EF按照日期分组数据
如下一张表 要进行MyDate的date部分进行分组,我们会发现如下写法会报异常 那么如何才能使linq正确转化为sql语句呢,这就要使用到了DbFunctions这个工具类 转到定义可以看到此类在e ...
- <Mastering KVM Virtualization>:第三章 搭建独立的KVM虚拟化
在第二章,你了解了KVM的内部结构:在本章中,您将了解如何将Linux服务器设置为虚拟化主机.我们正在讨论将KVM用于虚拟化并将libvirt作为虚拟化管理引擎. KVM开启了虚拟化并利用你的服务器或 ...
- 【算法与数据结构】Java实现字符串的全排列及组合
注:本文记录了代码编写及调试过程,想直接浏览正确答案的请移步文章结尾. 一.字符串的全排列问题 1. 下面是最初的代码(答案有错误-重复输出) import java.util.Scanner; pu ...
- 模拟exit()退出命令实现
1.当输入exit命令是退出程序,如果输入其他的就打印====> 方法一while True: username=input("请输入你的用户名:>>>") ...
- Window检测网络连接情况
Window检测网络连接情况 #include "Wininet.h" #pragma comment(lib,"Wininet.lib") DWORD fla ...
- 截取TableView生成图片
先看一下实例效果: 如果所示,这是一个在APP中截图,并调起微信客户端,发送给好友的例子,图片就是一个tableView的截图. 先实现一个小例子,如果tableVIew里面的内容,没有超过当前屏幕显 ...