js--闭包与垃圾回收机制
前言
闭包和垃圾回收机制常常作为前端学习开发中的难点,也经常在面试中遇到这样的问题,本文记录一下在学习工作中关于这方面的笔记。
正文
1.闭包
闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。作为一个JavaScript开发者,理解闭包十分重要。
1.1闭包是什么?
闭包就是一个函数引用另一个函数的变量,内部函数被返回到外部并保存时产生,(内部函数的作用域链AO使用了外层函数的AO)
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。或者说闭包就是子函数可以使用父函数的局部变量,还有父函数的参数。
1.2闭包的特性
①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
③参数和变量不会被垃圾回收机制回收
1.3理解闭包
基于我们所熟悉的作用域链相关知识,我们来看下关于计数器的问题,如何实现一个函数,每次调用该函数时候计数器加一。
- var counter=0;
- function demo3(){
- console.log(counter+=1);
- }
- demo3();//1
- demo3();//2
- var counter=5;
- demo3(); //6
- function add() {
- var counter = 0;
- return function plus() {
- counter += 1;
- return counter
- }
- }
- var count=add()
- console.log(count())//1
- var counter=100
- console.log(count())//2
上面就是一个闭包使用的实例 ,函数add内部内嵌一个plus函数,count变量引用该返回的函数,每次外部函数add执行的时候都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址,把plus函数嵌套在add函数内部,这样就产生了counter这个局部变量,内次调用count函数,该局部变量值加一,从而实现了真正的计数器问题。
1.4闭包的主要实现形式
这里主要通过两种形式来学习闭包:
①函数作为返回值,也就是上面的例子中用到的。
- function showName(){
- var name="xiaoming"
- return function(){
- return name
- }
- }
- var name1=showName()
- console.log(name1())
闭包就是能够读取其他函数内部变量的函数。闭包就是将函数内部和函数外部连接起来的一座桥梁。
②闭包作为参数传递
上面这段代码中,函数foo作为参数传入到函数foo2中,在执行foo2的时候,25作为参数传入foo中,这时判断的x>num的num取值是创建函数的作用域中的num,即全局的num,而不是foo2内部的num,因此打印出了25。
1.5闭包的优缺点
优点:
①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③匿名自执行函数可以减少内存消耗
缺点:
①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。
1.6闭包的使用
- for (var i = 0; i < 5; i++) {
- setTimeout(function() {
- console.log( i);
- }, 1000);
- }
- console.log(i);
我们来看上面的问题,这是一道很常见的题,可这道题会输出什么,一般人都知道输出结果是 5,5,5,5,5,5,你仔细观察可能会发现这道题还有很多巧妙之处,这6个5的输出顺序具体是怎样的?5 -> 5,5,5,5,5 ,了解同步异步的人也不难理解这种情况,基于上面的问题,接下来思考如何实现5 -> 0,1,2,3,4这样的顺序输出呢?
- for (var i = 0; i < 5; i++) {
- (function(j) { // j = i
- setTimeout(function() {
- console.log( j);
- }, 1000);
- })(i);
- }
- console.log( i);
- //5 -> 0,1,2,3,4
这样在for循环种加入匿名函数,匿名函数入参是每次的i的值,在同步函数输出5的一秒之后,继续输出01234。
- for (var i = 0; i < 5; i++) {
- setTimeout(function(j) {
- console.log(j);
- }, 1000, i);
- }
- console.log( i);
- //5 -> 0,1,2,3,4
仔细查看setTimeout的api你会发现它还有第三个参数,这样就省去了通过匿名函数传入i的问题。
- var output = function (i) {
- setTimeout(function() {
- console.log(i);
- }, 1000);
- };
- for (var i = 0; i < 5; i++) {
- output(i); // 这里传过去的 i 值被复制了
- }
- console.log(i);
- //5 -> 0,1,2,3,4
这里就是利用闭包将函数表达式作为参数传递到for循环中,同样实现了上述效果。
知道let块级作用域的人会想到上面的方法。但是如果要实现0 -> 1 -> 2 -> 3 -> 4 -> 5这样的效果呢。
- for (var i = 0; i < 5; i++) {
- (function(j) {
- setTimeout(function() {
- console.log(new Date, j);
- }, 1000 * j); // 这里修改 0~4 的定时器时间
- })(i);
- }
- setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
- console.log(new Date, i);
- }, 1000 * i);
- //0 -> 1 -> 2 -> 3 -> 4 -> 5
还有下面的代码,通过promise来实现。
- const tasks = [];
- for (var i = 0; i < 5; i++) { // 这里 i 的声明不能改成 let,如果要改该怎么做?
- ((j) => {
- tasks.push(new Promise((resolve) => {
- setTimeout(() => {
- console.log(new Date, j);
- resolve(); // 这里一定要 resolve,否则代码不会按预期 work
- }, 1000 * j); // 定时器的超时时间逐步增加
- }));
- })(i);
- }
- Promise.all(tasks).then(() => {
- setTimeout(() => {
- console.log(new Date, i);
- }, 1000); // 注意这里只需要把超时设置为 1 秒
- });
- //0 -> 1 -> 2 -> 3 -> 4 -> 5
- const tasks = []; // 这里存放异步操作的 Promise
- const output = (i) => new Promise((resolve) => {
- setTimeout(() => {
- console.log(new Date, i);
- resolve();
- }, 1000 * i);
- });
- // 生成全部的异步操作
- for (var i = 0; i < 5; i++) {
- tasks.push(output(i));
- }
- // 异步操作完成之后,输出最后的 i
- Promise.all(tasks).then(() => {
- setTimeout(() => {
- console.log(new Date, i);
- }, 1000);
- });
- //0 -> 1 -> 2 -> 3 -> 4 -> 5
- // 模拟其他语言中的 sleep,实际上可以是任何异步操作
- const sleep = (timeountMS) => new Promise((resolve) => {
- setTimeout(resolve, timeountMS);
- });
- (async () => { // 声明即执行的 async 函数表达式
- for (var i = 0; i < 5; i++) {
- if (i > 0) {
- await sleep(1000);
- }
- console.log(new Date, i);
- }
- await sleep(1000);
- console.log(new Date, i);
- })();
- //0 -> 1 -> 2 -> 3 -> 4 -> 5
上面的代码中都用到了闭包,总之,闭包找到的是同一地址中父级函数中对应变量最终的值。
2.垃圾回收机制
JavaScript 中的内存管理是自动执行的,而且是不可见的。我们创建基本类型、对象、函数……所有这些都需要内存。
通常用采用的垃圾回收有两种方法:标记清除、引用计数。
1、标记清除
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。
而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间
2.引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。
相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,
则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,
它就会释放那些引用次数为0的值所占的内存。
总结
以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。
js--闭包与垃圾回收机制的更多相关文章
- 闭包拾遗 & 垃圾回收机制
闭包拾遗 之前写了篇<闭包初窥>,谈了一些我对闭包的浅显认识,在前文基础上,补充并且更新些对于闭包的认识. 还是之前的那个经典的例子,来补充些经典的解释. function outerFn ...
- js 闭包与垃圾回收-待删
关于闭包请看戳 串讲-解释篇:作用域,作用域链,执行环境,变量对象,活动对象,闭包,本篇写的不太好: 先摆定义: 函数对象,可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种 ...
- js中的垃圾回收机制
代码回收规则如下: 1.全局变量不会被回收. 2.局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁. 3.只要被另外一个作用域所引用就不会被回收 (闭包)
- js的垃圾回收机制
Js具有自动垃圾回收机制.垃圾收集器会按照固定的时间间隔周期性的执行. JS中最常见的垃圾回收方式是标记清除. 工作原理:是当变量进入环境时,将这个变量标记为“进入环境”.当变量离开环境时,则将其标记 ...
- python垃圾回收机制:引用计数 VS js垃圾回收机制:标记清除
js垃圾回收机制:标记清除 Js具有自动垃圾回收机制.垃圾收集器会按照固定的时间间隔周期性的执行. JS中最常见的垃圾回收方式是标记清除. 工作原理 当变量进入环境时,将这个变量标记为"进入 ...
- 关于JS垃圾回收机制
一.垃圾回收机制的必要性 由于字符串.对象和数组没有固定大小,所以当它们的大小已知时,才能对它们进行动态的存储分配.JavaScript程序每次创建字符串.数组或对象时,解释器都必须分配内存来存储那个 ...
- 你不知道的JavaScript--Item28 垃圾回收机制与内存管理
1.垃圾回收机制-GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性 ...
- (65)Wangdao.com第十天_JavaScript 垃圾回收机制 GC
垃圾积累过多,致使程序运行缓慢,什么是垃圾? 当堆中某个内容,再也没有指针指向它,我们将再也用不了它,此时就是一个垃圾. 出现这种情况是因为 obj = null; 此时,js 中的垃圾回收机制会自动 ...
- 闭包内的微观世界和js垃圾回收机制
一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话 ...
随机推荐
- docker(9)Dockerfile制作镜像
前言 如果我们已经安装了一个python3的环境,如果另一台机器也需要安装同样的环境又要敲一遍,很麻烦,这里可以配置Dockerfile文件,让其自动安装,类似shell脚本 Dockerfile编写 ...
- CF-1451 E Bitwise Queries 异或 交互题
E - Bitwise Queries 传送门 题意 有一组序列,长度为 \(n(4\le n \le 2^{16})\),且 \(n\) 为 2 的整数次幂,序列中数值范围为 [0,n-1], 每次 ...
- Codeforces Round #651 (Div. 2) D. Odd-Even Subsequence(二分)
题目链接:https://codeforces.com/contest/1370/problem/D 题意 给出一个含有 $n$ 个数的数组 $a$,从中选出 $k$ 个数组成子序列 $s$,使得 $ ...
- [CERC2014]Virus synthesis【回文自动机+DP】
[CERC2014]Virus synthesis 初始有一个空串,利用下面的操作构造给定串 SS . 1.串开头或末尾加一个字符 2.串开头或末尾加一个该串的逆串 求最小化操作数, \(|S| \l ...
- Educational DP Contest G - Longest Path (dp,拓扑排序)
题意:给你一张DAG,求图中的最长路径. 题解:用拓扑排序一个点一个点的拿掉,然后dp记录步数即可. 代码: int n,m; int a,b; vector<int> v[N]; int ...
- js--执行上下文和作用域相关问题
前言 如果你是或者你想成为一名合格的前端开发工作者,你必须知道JavaScript代码在执行过程,知道执行上下文.作用域.变量提升等相关概念,并且熟练应用到自己的代码中.本文参考了你不知道的JavaS ...
- ubuntu 16.04 i386 安装 ruby + bundler + rails ; 搭建简单的网站bitbar
参考 http://gorails.com/setup/ubuntu/16.04 概述 Project 2 主要探究对web的攻击,本次试验共有6个部分. Project 2中攻击的是一个提供电子货币 ...
- IFIX 5.9 历史数据 曲线 (非SQL模式)
装完 ifix 5.9 默认是没有Hist 开头的 历史数据源的,没存,至少我装的版本是这样. 那个Historian 也没有安装包,好像还要授权,自己研究不了. 1 先把数据存本地 在你的安装包里 ...
- Celery&Flower文档笔记
1.Celery # tasks.py from celery import Celery app = Celery('tasks', broker='redis://localhost:6379', ...
- Ubuntu下跑通py-faster-rcnn、详解demo运作流程
在不同的服务器不同的机器上做过很多次实验,分别遇到各种不一样的错误并且跑通Py-Faster-RCNN,因此,在这里做一个流程的汇总: 一.下载文件: 首先,文件的下载可以有两种途径: 1.需要在官网 ...