- 作用链域
- 闭包
- 闭包的特性:
- 说说你对闭包的理解
- 使用闭包的注意点
- 总结
- 扩展 循环中使用闭包解决 var 定义函数的问题
- 解决办法有三种
作用链域
JavaScript 的作用域链(Scope Chain)是指在代码中访问变量时的查找路径。
当 JavaScript 引擎在执行代码时,会根据作用域链来确定变量的可访问范围。
作用域链由多个执行上下文(Execution Context)组成,每个执行上下文都有一个关联的变量对象(Variable Object),变量对象保存了当前执行环境中定义的所有变量和函数。
当需要访问一个变量时,引擎会从当前执行上下文的变量对象开始查找,如果找不到,则沿着作用域链继续向上查找,直到全局执行上下文的变量对象。
如果在作用域链的任何级别上找不到该变量,则会抛出 ReferenceError。
作用域链的形成是由变量的作用域规则决定的。
在 JavaScript 中,有三种类型的作用域:
- 全局作用域、
- 函数作用域
- 块级作用域。
每个作用域都会创建一个新的执行上下文,并将其添加到当前的作用域链中。
作用域链是 JavaScript 用来管理变量访问权限的一种机制,它决定了变量在代码中的可见性和可访问性。
通过理解作用域链,我们能更好地理解 JavaScript 中的作用域和变量的生命周期。
JavaScript 的作用域链是指在代码中访问变量时的查找路径。我将通过一个例子来说明作用域链的概念。
更多详细内容,请微信搜索“前端爱好者
“, 戳我 查看 。
var globalVariable = 'Global'; // 全局变量function outerFunction() {var outerVariable = 'Outer'; // 外部函数变量function innerFunction() {var innerVariable = 'Inner'; // 内部函数变量console.log(innerVariable); // 在内部函数中访问内部变量console.log(outerVariable); // 在内部函数中访问外部函数变量console.log(globalVariable); // 在内部函数中访问全局变量}innerFunction(); // 调用内部函数
}outerFunction(); // 调用外部函数
在上面的例子中,我们定义了三个不同级别的变量:
- 全局变量
globalVariable
、 - 外部函数变量
outerVariable
- 内部函数变量
innerVariable
。
当执行 outerFunction()
后,会创建一个包含 outerVariable
和 innerFunction
的函数执行上下文(即外部函数的执行上下文)。
然后,在 outerFunction()
中调用 innerFunction()
时,会再次创建一个包含 innerVariable
的函数执行上下文(即内部函数的执行上下文)。
当内部函数访问变量时,JavaScript 引擎会按照作用域链的顺序进行查找。
- 首先,它会在当前执行上下文的变量对象中查找变量,即在内部函数的执行上下文中查找
innerVariable
。- 如果找到了该变量,则直接使用。
- 如果没有找到,则会继续向上一级的执行上下文即外部函数的执行上下文中查找,即在外部函数的执行上下文中查找
outerVariable
。 - 如果还是找不到,则继续向上一级的执行上下文即全局执行上下文中查找,即在全局执行上下文中查找
globalVariable
。
在这个例子中,当 innerFunction()
执行时,会按照作用域链的顺序找到并访问到 innerVariable
、outerVariable
和 globalVariable
。
如果在作用域链的任何级别上找不到变量,就会抛出 ReferenceError
。
这就是作用域链的工作原理,它决定了变量在代码中的可见性和可访问性。
闭包
闭包就是 能够读取其他函数内部变量的函数
闭包是指 有权访问另⼀个函数作用域中变量的函数,创建闭包的最常⻅的方式就是: 在⼀个函数内创建另⼀个函数, 通过另⼀个函数访问这个函数的局部变量,利用闭包可以突破作用链域
闭包的特性:
- 函数内再嵌套函数
- 内部函数可以引用外层的参数和变量
- 参数和变量不会被垃圾回收机制回收
说说你对闭包的理解
- 使用闭包主要是为了设计私有的方法和变量 。
- 闭包的优点是可以避免全局变量的污染,
- 缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
在js中, 函数即闭包, 只有函数才会产生作用域的概念
-
闭包 的最大用处有两个,
- ⼀个是可以读取函数内部的变量,
- 另⼀个就是让这些变量始终保持在内存中
-
闭包的另⼀个用处, 是封装对象的私有属性和私有方法
- 好处:能够实现封装和缓存等;
- 坏处:就是消耗内存 、不正当使用会造成内存溢出的问题
使用闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中, 内存消耗很大,所以不能滥用闭包, 否则会造成网页的性能问题,在IE中可能导致内存泄露
- 解决方法是,在退出函数之前,将不使用的局部变量全部删除
总结
闭包的定义其实很简单: 函数A内部有⼀个函数B , 函数
就是闭包B 可以访问到函数 A 中的变量,那么函数B就是闭包
function A ( ) {let a = 1window .B = function ( ) {console . log( a)}
}
A ( )
B ( ) // 1
闭包存在的意义就是让我们可以间接访问函数内部的变量
扩展 循环中使用闭包解决 var 定义函数的问题
for (var i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i)}, i * 1000)
}
首先因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕, 这时候 i 就是 6 了,所以会输出⼀堆 6
解决办法有三种
第⼀种是使用闭包的方式
for (var i = 1; i <= 5; i++) {;(function(j) {setTimeout(function timer() {console.log(j)}, j * 1000)})(i)
}
在上述代码中, 我们首先使用了立即执行函数将 i 传入函数内部, 这个时候值就被固定在了参数 j 上面不会改变, 当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j ,从而达到目的
第⼆种就是使用 setTimeout 的第三个参数
这个参数会被当成 timer 函数的参数传入
for (var i = 1; i <= 5; i++) {setTimeout(function timer(j) {console.log(j)},i * 1000,i)
}
setTimeout 的第三个参数
在 JavaScript 中,setTimeout
函数是用于延迟执行一个函数或一段代码。它的语法如下:
setTimeout(callback, delay, arg1, arg2, ...);
其中:
callback
是要执行的函数或代码块。delay
是延迟的时间(以毫秒为单位)。arg1
,arg2
, … 是可选的参数,在延迟结束后会作为参数传递给回调函数。
现在来看第三个参数,它表示延迟结束后作为参数传递给回调函数的值。这个参数可以是任意类型的数据。
让我们通过一个例子来说明第三个参数的使用:
function greet(name) {console.log("Hello, " + name + "!");
}setTimeout(greet, 2000, "John");
在上面的例子中,setTimeout
函数将延迟 2000 毫秒后执行 greet
函数,并且将字符串 "John"
作为参数传递给 greet
函数。当延迟结束后,greet
函数会被调用,并输出 "Hello, John!"
。
这里的 "John"
就是 setTimeout
的第三个参数,它会在延迟结束后作为参数传递给回调函数 greet
。
需要注意的是,setTimeout
的第三个参数是可选的,如果不提供,则回调函数不会接收任何参数。如果提供了多个参数,它们会按照顺序传递给回调函数。
第三种就是使用 let 定义 i 了来解决问题了
这个也是最为推荐的方式
for (let i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i)}, i * 1000)
}