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, ...
随机推荐
- 勤快的love枫[ZJOI2007]
题目描述 小绝恋love 枫是一个出纳,经常需要做一些统计报表的工作.今天是绝恋love 枫的生日,小绝恋love 枫希望可以帮爸爸分担一些工作,作为他的生日礼物之一.经过仔细观察,小绝恋love 枫 ...
- LoadRunner+Android模所器录制脚本
为了测试Android软件的服务端的功能,需要重现某些客户端操作,便于发现功能问题,性能问题.也方便客户端与本机服务端特别是服务端代码进行断点调试.这个时候需要对网络操作进行重现. loadRunne ...
- Weex的原生开发
weex概念与特性 最形象的理解就是类似react native. Weex几大特点: 1.帮助你构建原生应用 与 Web App.HTML5 App 或 hybrid App 不同,您可以使用 We ...
- hibernate中对象的3种状态----瞬时态、持久态、脱管态
Hibernate的对象有3种状态,分别为:瞬时态(Transient). 持久态(Persistent).脱管态(Detached).处于持久态的对象也称为PO(Persistence Object ...
- RMAN数据库恢复测试
RMAN恢复实践 RMAN> list backup; using target database control file instead of recovery catalog List ...
- layer弹出层详解
前言:学习layer弹出框,之前项目是用bootstrap模态框,后来改用layer弹出框,在文章的后面,我会分享项目的一些代码(我自己写的). layer至今仍作为layui的代表作,她的受众广泛并 ...
- 第一阶段项目(3body)
<div class="H1"> <div class="top-nav"> < ...
- fs模块(二)
1. renameSync 01. 重命名 02. 移动文件夹,相当于剪切作用 var fs = require('fs'); // 01 文件重命名 var renameFile = (oldFil ...
- CORS跨域资源共享你该知道的事儿
"唠嗑之前,一些客套话" CORS跨域资源共享,这个话题大家一定不陌生了,吃久了大转转公众号的深度技术好文,也该吃点儿小米粥溜溜胃里的缝儿了,今天咱们就再好好屡屡CORS跨域资源共 ...
- datagrid实现行的上移和下移(转)
<html> <head> <meta http-equiv="Content-Type" content="text/html; char ...