js闭包、作用域

以下均出自《javaScript忍者秘籍》

理解闭包:

闭包允许函数访问并操作函数外部的变量。只要变量或函数存在于声明函数时的作用域内,闭包即可使函数能够访问这些变量或函数。


分析一个例子:

是什么魔法使得在内部函数的作用域消失之后再执行内部函数时,其内部变量仍然存在呢?
当在外部函数中声明内部函数时,不仅定义了函数的声明,而且还创建了一个闭包。该闭包不仅包含了函数的声明,还包含了在函数声明时该作用域中的所有变量。当最终执行内部函数时,尽管声明时的作用域已经消失了,但是通过闭包,仍然能够访问到原始作用域。

也就是说,只要内部函数一直存在,内部函数的闭包就一直保存着该函数的作用域中的变量。

var outerValue = 'samurai'

var later
function outerFunction() {
    var innerValue = "ninja"
    function innerFunction() {
        console.log(outerValue)
        console.log(innerValue)
    }
    later = innerFunction
}
outerFunction()
later()

这就是闭包:

闭包创建了被定义时的作用域内的变量和函数的安全气泡,因此函数获得了执行时所需的内容。该气泡与函数本身一起包含了函数和变量。
虽然这些结构不容易看见(没有包含这么多信息的闭包对象可以进行观察),存储和引用这些信息会直接影响性能。谨记每一个通过闭包访问变量的函数都具有一个作用域链,作用域链包含闭包的全部信息,这一点非常重要。因此,虽然闭包是非常有用的,但不能过度使用。使用闭包时,所有的信息都会存储在内存中,直到JavaScript引擎确保这些信息不再使用(可以安全地进行垃圾回收)或页面卸载时,才会清理这些信息。
 

封装私有变量:

许多编程语言使用私有变量,这些私有变量是对外部隐藏的对象属性。这是非常有用的一种特性,因为当通过其他代码访问这些变量时,我们不希望对象的实现细节对用户造成过度负荷。遗憾的是,原生JavaScript不支持私有变量。但是,通过使用闭包,我们可以实现很接近的、可接受的私有变量,

下边案例:

我们可通过闭包内部方法获取私有变量的值feints ,但是不能直接访问私有变量feints 。这有效地阻止了读私有变量不可控的修改,这与真实的面向对象语言中的私有变量一样。

function Ninja() {
    var feints = 0;
    this.getFeints = function () {
        return feints;
    }
    this.feint = function () {
        feints ++
    }
}
var ninja1 = new Ninja()

通过执行上下文来跟踪代码


JavaScript代码有两种类型:

(1)一种是全局代码,在所有函数外部定义;

(2)一种是函数代码,位于函数内部。JavaScript引擎在执行代码时,函数里的每一条语句都处于特定的执行上下文中。
既然具有两种类型的代码,那么就有两种执行上下文:全局执行上下文和函数执行上下文。

二者最重要的差别是:

全局执行上下文只有一个,当JavaScript程序开始执行时就已经创建了全局上下文;而函数执行上下文是在每次调用函数时,就会创建一个新的。


创建上下文

我们时刻使用函数,使用函数进行计算,使用函数更新UI,使用函数达到复用代码的目的,使用函数让我们的代码更易于理解。为了达到这个目标,第一个函数可以调用第二个函数,第二个函数可以调用第三个函数,以此类推。当完成函数调用时,程序会回到函数调用的位置。

JavaScript引擎是如何跟踪函数的执行并回到函数的位置的呢?

JavaScript代码有两种类型:

(1) 一种是全局代码,在所有函数外部定义;

(2) 一种是函数代码,位于函数内部。JavaScript引擎执行代码时,每一条语句都处于特定的执行上下文中。

既然具有两种类型的代码,那么就有两种执行上下文:全局执行上下文和函数执行上下文。二者最重要的差别是:全局执行上下文只有一个,当JavaScript程序开始执行时就已经创建了全局上下文;而函数执行上下文是在每次调用函数时,就会创建一个新的。

当调用函数时我们可通过关键字访问函数上下文。函数执行上下文,虽然也称为上下文,但完全是不一样的概念。执行上下文是内部的JavaScript概念,JavaScript引擎使用执行上下文来跟踪函数的执行。


JavaScript基于单线程的执行模型:在某个特定的时刻只能执行特定的代码。一旦发生函数调用,当前的执行上下文必须停止执行,并创建新的函数执行上下文来执行函数。当函数执行完成后,将函数执行上下文销毁,并重新回到发生调用时的执行上下文中。所以需要跟踪执行上下文——正在执行的上下文以及正在等待的上下文。最简单的跟踪方法是使用执行上下文栈(或称为调用栈)。
栈是一种基本的数据结构,只能在栈的顶端对数据项进行插入和读取。这种特性可类比于自助餐厅里的一叠托盘,你只能从托盘堆顶端拿到一个托盘,服务员也只能将新的托盘放在这叠托盘的顶端。

下边这段代码,首先定义了skulk函数,skulk函数调用report函数。然后在全局中调用 skulk 函数两次:skulk(" kuma ") 和 skulk(" yoshi ") 。通过这段基础代码,我们可以探索执行上下文是如何创建的,如图。

function skulk(ninja) {
    report(ninja + 'skulking')
}
function report(message) {
    console.log(message)
}
skulk("kuma")
skulk("yoshi")

猜你喜欢

转载自blog.csdn.net/qq_42778001/article/details/106936214