新手入门带你学习JavaScript引擎运行原理(2)

在前面的示例中,所有内容都是全局作用域的,这意味着我们可以从代码中的任何位置访问它。 现在,介绍下私有作用域以及如何定义作用域。

函数/词法作用域

考虑如下代码:

function a() { var myOtherVar = 'inside A' b() } function b() { var myVar = 'inside B' console.log('myOtherVar:', myOtherVar) function c() { console.log('myVar:', myVar) } c() } var myOtherVar = 'global otherVar' var myVar = 'global myVar' a()

需要注意以下几点:

全局作用域和函数内部都声明了变量

函数c现在在函数b中声明

打印结果如下:

myOtherVar: "global otherVar" myVar: "inside B"

执行步骤:

全局创建和声明 - 创建内存中的所有函数和变量以及全局对象和 this

执行 - 它逐行读取代码,给变量赋值,并执行函数a

函数a创建一个新的上下文并被放入堆栈,在上下文中创建变量myOtherVar,然后调用函数b

函数b 也会创建一个新的上下文,同样也被放入堆栈中

函数b的上下文中创建了 myVar 变量,并声明函数c

上面提到每个新上下文会创建的外部引用,外部引用取决于函数在代码中声明的位置。

函数b试图打印myOtherVar,但这个变量并不存在于函数b中,函数b 就会使用它的外部引用上作用域链向上找。由于函数b是全局声明的,而不是在函数a内部声明的,所以它使用全局变量myOtherVar。

函数c执行步骤一样。由于函数c本身没有变量myVar,所以它它通过作用域链向上找,也就是函数b,因为myVar是函数b内部声明过。

下面是执行示意图:

新手入门带你学习JavaScript引擎运行原理

请记住,外部引用是单向的,它不是双向关系。例如,函数b不能直接跳到函数c的上下文中并从那里获取变量。

最好将它看作一个只能在一个方向上运行的链(范围链)。

a -> global

c -> b -> global

在上面的图中,你可能注意到,函数是创建新作用域的一种方式。(除了全局作用域)然而,还有另一种方法可以创建新的作用域,就是块作用域。

块作用域

下面代码中,我们有两个变量和两个循环,在循环重新声明相同的变量,会打印什么(反正我是做错了)?

function loopScope () { var i = 50 var j = 99 for (var i = 0; i < 10; i++) {} console.log('i =', i) for (let j = 0; j < 10; j++) {} console.log('j =', j) } loopScope()

打印结果:

i = 10 j = 99

第一个循环覆盖了var i,对于不知情的开发人员来说,这可能会导致bug。

第二个循环,每次迭代创建了自己作用域和变量。 这是因为它使用let关键字,它与var相同,只是let有自己的块作用域。 另一个关键字是const,它与let相同,但const常量且无法更改(指内存地址)。

块作用域由大括号 {} 创建的作用域

再看一个例子:

function blockScope () { let a = 5 { const blockedVar = 'blocked' var b = 11 a = 9000 } console.log('a =', a) console.log('b =', b) console.log('blockedVar =', blockedVar) } blockScope()

打印结果:

a = 9000 b = 11 ReferenceError: blockedVar is not defined

a是块作用域,但它在函数中,而不是嵌套的,本例中使用var是一样的。

对于块作用域的变量,它的行为类似于函数,注意var b可以在外部访问,但是const blockedVar不能。

在块内部,从作用域链向上找到 a 并将let a更改为9000。

使用块作用域可以使代码更清晰,更安全,应该尽可能地使用它。

事件循环(Event Loop)

接下来看看事件循环。 这是回调,事件和浏览器API工作的地方

新手入门带你学习JavaScript引擎运行原理

我们没有过多讨论的事情是堆,也叫全局内存。它是变量存储的地方。由于了解JS引擎是如何实现其数据存储的实际用途并不多,所以我们不在这里讨论它。

来个异步代码:

function logMessage2 () { console.log('Message 2') } console.log('Message 1') setTimeout(logMessage2, 1000) console.log('Message 3')

上述代码主要是将一些 message 打印到控制台。 利用setTimeout函数来延迟一条消息。 我们知道js是同步,来看看输出结果

Message 1 Message 3 Message 2

打印 Message 1

调用 setTimeout

打印 Message 3

打印 Message 2

它记录消息3

稍后,它会记录消息2

setTimeout是一个 API,和大多数浏览器 API一样,当它被调用时,它会向浏览器发送一些数据和回调。我们这边是延迟一秒打印 Message 2。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/56a11a9d8eb55ba8d0afa55fac5e9db5.html