JS核心原理理解闭包

Dax 等级 738 0 0

前置概念 在正式看闭包之前,我们先来学习一下前置知识,那就是JS中的作用域,我们知道,在ES5之中,作用域分为两种:全局作用域和函数作用域,随着ES6的到来,新增了块级作用域,想更好的理解闭包,那么搞清楚作用域是首要条件

全局作用域

我们知道,对于变量而言,我们一般会分成两类:全局变量和局部变量,一般定义在最外围环境的为全局变量,定义在函数当中的为局部变量,在web浏览器中,全局变量一般挂载在window对象上,所以全局变量在任何地方都可以进行访问,但是局部变量便只能在所在作用域内才可以被访问

我们结合一个例子来简单的理解一下: var globalVar = '全局变量' function func() { console.log(globalVar) // 全局变量 var localVar = '局部变量' console.log(localVar) // 局部变量 } func() console.log(globalVar) // 全局变量 console.log(localVar) // 报错:localVar is not defined

========分割线================ function func() { globalVar = '全局变量' } console.log(globalVar) // 全局变量 console.log(window.globalVar) // 全局变量 从这段代码我们可以发现,globalVar作为全局变量和localVar作为局部变量的特点:

全局变量拥有全局作用域,无论在哪都可以访问,在web浏览器端是挂载在window对象上面的 局部变量是被定义在特有的局部作用域内,并且只能在所在的作用域内被访问 JS中没有经过定义直接被赋值的变量默认为全局变量(不考虑严格模式下) 函数作用域

函数作用域,顾名思义,那就是函数内部的作用域,在函数内部定义的变量称之为函数变量,那么此时函数内部定义的变量便只能在该函数中被调用

一样看一个简单的例子: function fun() { var funVar = '函数内变量' console.log(funcVar) // 函数内变量 } fun() console.log(funcVar) // funcVar is not defined 如果只是根据前面的作用域的内容来看,这个程序是无法正常运行的,会报错,说变量a没有定义,但是我们结合了闭包的定义之后我们发现它是可以正常打印的,也就是说在func2里面访问到了func1中的变量,结合这些现在你是否理解了红宝书中对闭包的定义了呢?

为什么会产生闭包

了解了闭包的定义和基本概念,那接下来我们再来具体分析一下为什么会产生闭包呢?我们先来了解一个概念:作用域链,其实比较好理解,比如我们在访问一个变量的时候,会首先在所在作用域内查找,如果没有找到就会往上找到上层作用域内,一层层向上直到找到或者到达顶层作用域window(web浏览器端)为止,这整个形成的一个链条状的就是作用域链

我们也来看一个简单的例子: var b = '全局作用域变量' function func1() { var b = 'func1作用域变量' function func2() { var b = 'func2作用域变量' console.log(b) // func2作用域变量 } return func2 } func1()() 我们来看这个🌰,此时func1的作用域就指向全局作用域和自己本身作用域,而func2就从下往上依次链接自己本身->func1作用域->全局作用域

所以还记得MDN中那句话吗:“一个函数和对其周围状态的引用捆绑在一起”,也就是说产生闭包的原因就是需要当前函数内保持对上层作用域的引用

闭包的具体应用

前面两部分分析了一下闭包的主要内容,开篇我们就说起闭包在我们日常开发其实是非常常见的,只是可能我们平时并没有太过注意,那接下来我们就来盘点一下闭包的一些具体场景

1、我们平时肯定都有用过定时器、事件监听以及Ajax请求等这类使用回调函数的,基本都利用到了闭包,使用定时器的例子,如防抖/节流: // 防抖 const debounce = (fn,delayTime) => { let timerId, result return function(...args) { timerId && clearTimeout(timerId) timerId = setTimeout(()=>result=fn.apply(this,args),delayTime) return result } } // 节流 const throttle = (fn, delayTime) => { let timerId return function(...args) { if(!timerId) { timerId = setTimeout(()=>{ timerId = null return result = fn.apply(this,args) },delayTime) } } } 2、IIFE(立即执行函数),这种函数比较特别,它拥有独立的作用域,不会污染全局环境,但是同时又可以防止外界访问内部的变量,所以很多时候会用来做模块化或者模拟私有方法

举个例子: var global = '全局变量' let Anonymous = (function() { var local = '内部变量' console.log(global) // 全局变量 })() console.log(Anonymous.local) // local is not defined

=======分割线==============

var global = '全局变量' let Anonymous = (function() { var local = '内部变量' console.log(global) // 全局变量 return { afterLocal: local } })() console.log(Anonymous.afterLocal) // 内部变量 3、函数作为参数传递的形式 var a = '全局变量' function func1() { var a = 'func1内部变量' function func2() { console.log(a) } func3(func2) // func1内部变量 } function func3(fn) { // 闭包产生 fn() }

func1() 经典面试题 我们先来看具体代码: for(var i = 1; i < 6; i++){ setTimeout(function() { console.log(i) }, 0) } 这道题相信我们很多人曾经都遇到过,我们打印出来结果发现是打印的5个6,那为什么是这个结果呢?那如果我想改造之后让他打印12345该怎么做呢?

首先我们来回答为什么会是这个结果,以前我们主要是是站在eventLoop的角度来说的,现在我们可以从两部分来说:

因为setTimeout是宏任务,但是JS是单线程,由于eventLoop机制,需要先执行主线程同步代码之后才会执行宏任务,所以会打印全是6 因为setTimeout是一种闭包,它引用上层作用域中的全局变量i,而此时i已经是6了,所以就全部打印的都是6 那我们怎么改造让他按照顺序打印结果呢,这里提供几种常见的方法:

1、ES6的let:这是改造成本最小的一种方法,因为let创造了块级作用域,代码的执行以块为单位来进行,便可以达到我们的要求

for(let i = 1; i < 6; i++){ setTimeout(function() { console.log(i) }, 0) } 2、IIFE(立即执行函数):利用这种方法每次循环的时候,都将此时的变量i传入到setTimeout当中

for(let i = 1; i < 6; i++){ (function(j) { setTimeout(function() { console.log(j) }, 0) })(i) } 3、利用setTimeout的第三个参数:我们一般就只用前两个参数,第三个参数其实就是可以进行传参数给函数

for(let i = 1; i < 6; i++){ setTimeout(function(j) { console.log(j) }, 0, i) } 等等。。。。。。。。。。。。。。

收藏
评论区

相关推荐

44道JS难题,还不来考考?
国外某网站给出了44道JS难题,试着做了下,只做对了17道。这些题涉及面非常广,涵盖JS原型、函数细节、强制转换、闭包等知识,而且都是非常细节的东西,透过这些小细节可以折
彻底理解js闭包
在文章开头,我先放出MDN给出的定义: 闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。 现在不需要看懂它,我会在第一个例子中解释清楚它的意思。让我们开始吧! 2018.3.20更新:现在MDN上的定义已经改为:"A closure is the comb
JS核心原理理解闭包
前置概念在正式看闭包之前,我们先来学习一下前置知识,那就是JS中的作用域,我们知道,在ES5之中,作用域分为两种:全局作用域和函数作用域,随着ES6的到来,新增了块级作用域,想更好的理解闭包,那么搞清楚作用域是首要条件全局作用域我们知道,对于变量而言,我们一般会分成两类:全局变量和局部变量,一般定义在最外围环境的为全局变量,定义在函数当中的为局部变量,在we
js去除字符串
js去除字符串js<DOCTYPE html<html<head <title</title</head<body</body<script type"text/javascript" function delHtmlTag(str){   return str.replace(/<^/g,""); } var s
你不可不知的JS面试题(第三期)
1、什么是闭包?如图所示,闭包就是一个定义在函数内部的函数,其作用是将函数内部和函数外部连接起来。大家知道,作用域的问题,就是在函数内部定义的变量称为局部变量,外部取不到值。下面我们通过代码来更加详细地看一下: function A()        let x  1;        return function B()            c
Cocos Creator3.x中使用AES加密解密
Cocos Creator升级3x版本之后就不再支持js了,直接装包cryptojs会报错,require 函数在ts里面 根本就不能识别,但是我们项目中需要用到js的包来实现AES加密解密,尝试了多种方法终于修成正果 使用方法import CryptoJS from "cryptojs.min.js";const aseKey "12345678"
ES6的语法
一,定义变量let(类似var) ================ 在js一直有一个bug是var: 1、var 声明的变量会有变量提升 console.log(name); //jhon var name = 'jhon'; 2、var 没有块级作用域 var name2 = 'jjjon'; { var name2 = 'tom';
JSON 与 JS 对象的关系
很多人搞不清楚 JSON 和 Js 对象的关系,甚至连谁是谁都不清楚。简单来说: JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。 如 var obj = {a: 'ni', b: 'hao'}; //这是一个对象,注意键名也是可以使用引号包裹的
JS中正则表达式
### 正则表达式的定义 js中的正则表达式使用RegExp对象表示,两种创建正则表达式对象的方法 #### 直接量定义 将表达式包含在斜杠之间 var pattern = /js/; #### 构造函数定义: 使用RegExp()构造函数定义 var pattern = new RegExp('js');
JS判断是否为移动版浏览器
使用javascript(JS)判断浏览器是否为移动版浏览器。 浏览器信息获取 ------- 判断浏览器访问终端。 //判断访问终端 var browser = { versions: function () { var u = navigator.userAgent, app = naviga
D3.js selectAll()函数与enter()函数
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>D3 Test</title> <script type="text/javascript" src="d3.v3.js"></script> </head> <body>
JavaScript 两个叹号含义
先起个例子吧~  这个用的是谋智的js引擎monkey spider  darion.yaphet@localhost.localdomain:/home/darion.yaphet> js               js> var i; js> print(i) undefined js>  js> va
JavaScript 作用域
在学习js的过程对闭包什么的,理解不好,偶然搜到这篇文章。豁然开朗,随翻译。 Javacript 中有一系列作用域的概念。对于新的JS的开发人员无法理解这些概念,甚至一些经验丰富的开发者也未必能。这篇文章主要目的帮助理解JavaScript中的一些概念如:scope,closure, this, namespace, function scope,
JavaScript函数——闭包
### 闭包 #### 概念 只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁 **例子** function outer(){ var localVal = 30; return localVal; }
JavaScript易错知识点整理
前言 本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES6的知识点。 JavaScript知识点 ------------- ### 1.变量作用域 `var a = 1; functio