ES6之前只有全局作用域与函数作用域两种,ES6出现之后,新增了块级作用域。
在JavaScript中,全局变量是挂载在window对象下的变量,所以在网页中的任何位置你都可以使用并且访问到这个全局变量。
当我们定义很多全局变量的时候,会容易引起变量命名的冲突,所以在定义变量的时候应该注意作用域的问题:
var globalName = 'global'
function getName() {
console.log(globalName) // global
var name = 'inner'
console.log(name) // inner
}
getName()
console.log(name) // 报错
console.log(globalName) // global
function setName() {
vName = 'setName'
}
setName()
console.log(vName) // setName
console.log(windwo.vName) // setName
在JavaScript中,函数定义的变量叫作函数变量,这个时候只能在函数内部才能访问到它,所以它的作用域也就是函数的内存,称为函数作用域。
当这个函数被执行完之后,这个局部变量也相应会被销毁。所以你会看到在getName函数外面的name是访问不到的:
function getName() {
var name = 'inner'
console.log(name) // inner
}
getName()
console.log(name) // 报错
ES6新增了块级作用域,最直接的表现就是新增的let关键词,使用let关键词定义的变量只能在块级作用域中被访问,有"暂时性死区"的特定,也就是说这个变量在定义之前是不能被使用的。
if语句及for语句后面的{...}这里面所包括的,就是块级作用域:
console.log(a) // a is not defined
if (true) {
let a = '123'
console.log(a) // 123
}
console.log(a) // a is not defined
红宝书:闭包是指有权访问另外一个函数作用域中的变量的函数 MDN:一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
闭包其实就是一个可以访问其他函数内部变量的函数。即一个定义在函数内部的函数,或者直接说闭包是个内嵌函数也可以。
因为通常情况下,函数内部变量是无法在外部访问的(即全局变量和局部变量的区别),因此使用闭包的作用,就具备实现了能在外部访问某个函数内部变量的功能,让这些内部变量的值始终可以保存在内存中。
function fun1() {
var a = 1
return function () {
console.log(a)
}
}
fun1()
var result = fun1()
result() // 1
当访问一个变量时,代码解释器会首先在当前的作用域查找,如果没找到,就去父级作用域去查找,直到找到该变量或者不存在父级作用域中,这样的链路就是作用域链。
var a = 1
function fun1() {
var a = 2
function fun2() {
var a = 3
console.log(a) // 3
}
}
// fun1 函数的作用域指向全局作用域(window)和它自己本身;fun2 函数的作用域指向全局作用域(window)、fun1 和它本身;而作用域是从最底层向上找,直到找到全局作用域 window 为止,如果全局还没有的话就会报错
function fun1() {
var a = 2
function fun2() {
console.log(a) // 2
}
return fun2
}
var result = fun1()
result()
// 那是不是只有返回函数才算是产生了闭包呢?其实也不是,回到闭包的本质,**我们只需要让父级作用域的引用存在即可**
var fun3
function fun1() {
var a = 2
fun3 = function () {
console.log(a)
}
}
fun1()
fun3()
闭包产生的本质:当前环境中存在指向父级作用域的引用。
返回一个函数,上面将原因的时候已经说过,这里就不在赘述了。
在定时器、事件监听、Ajax请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。
// 2.1定时器
setTimeout(function handler() {
console.log('1')
}, 1000)
// 2.2事件监听
$('app').click(function () {
console.log('Event Listener')
})
作为函数参数传递的形式,比如下面的例子:
// 3.作为函数参数传递的形式
var a = 1
function foo() {
var a = 2
function baz() {
console.log(a)
}
bar(baz)
}
function bar(fn) {
// 这就是闭包
fn()
}
foo() // 输出2,而不是1
IIFE(立即执行函数),创建了闭包,保存了全局作用域(window)和当前函数的作用域,因此可以输出全局的变量,如下所示:
// 4.IIFE(立即执行函数)
var a = 2
(function IIFE() {
console.log(a) // 输出2
})()
IIFE 这个函数会稍微有些特殊,算是一种自执行匿名函数,这个匿名函数拥有独立的作用域。这不仅可以避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域,我们经常能在高级的 JavaScript 编程中看见此类函数。
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i)
}, 0)
}
// 依次输出 5个6
setTimeout 为宏任务,由于 JS 中单线程 eventLoop 机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后 setTimeout 中的回调才依次执行。
因为 setTimeout 函数也是一种闭包,往上找它的父级作用域就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 已经是 6 了,因此最后输出的连续都是 6。
利用 IIFE,当每次 for 循环时,把此时的变量 i 传递到定时器中,然后执行:
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j)
}, 0)
})(i)
}
let 让 JS 有了块级作用域,代码的作用域以块级为单位进行执行:
for(let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log()
},0)
}
setTimeout 作为经常使用的定时器,它是存在第三个参数的,日常工作中我们经常使用的一般是前两个,一个是回调函数,另外一个是时间,而第三个参数用得比较少:
for(var i=1;i<=5;i++) {
setTimeout(function(j) {
console.log(j)
},0,i)
}
第三个参数的传递,改变了 setTimeout 的执行逻辑,从而实现我们想要的结果,这也是一种解决循环输出问题的途径。
闭包(closure)是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠...
使用闭包的方式实现一个累加函数 addNum,参数为 number 类型,每次返回的结果 = 上一次计算的值 + 传入的值
JavaScript 变量可以是局部变量或全局变量。私有变量可以用到闭包。闭包就是将函数内部和函数外部连接起来的一座桥梁。函数的闭包使用场景:比如我们想要一个函数来执行计数功能。
for循环中let 和var的区别,setTimeout(func,time)函数运行机制,一个需求,一个数组array[1,2,3,4,5],循环打印,间隔1秒
这篇文章主要介绍了JavaScript的闭包机制,针对内嵌函数的变量访问等问题分析了JS的闭包,写的十分的全面细致,具有一定的参考价值,JavaScript 变量可以是局部变量或全局变量。 私有变量可以用到闭包。
变量作用域:一个变量的作用域是程序源代码中定义这个变量的区域,在函数内声明的变量是局部变量,它只在该函数及其嵌套作用域里可见(js 函数可嵌套定义);
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另一个函数。来看下面的示例:
使用闭包能够让局部变量模拟全局变量一样,但是它只能被特定函数使用。我们都知道:1.全局变量可能会造成命名冲突,使用闭包不用担心这个问题,因为它是私有化,加强了封装性,这样保护变量的安全
在学习闭包之前,你必须清楚知道JS中变量的作用域。JS中变量的作用域无非就全局变量和局部变量,两者之间的关系是函数内部可以直接访问全局变量,但是函数外部是无法读取函数内部的局部变量的
JavaScript的运行机制:(1)所有同步任务都在主线程上执行,形成一个执行栈。(2)主线程之外,还有一个“任务队列”,只要异步任务有了运行结果,就在“任务队列”之中放置一个事件。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!