专栏名称: 前端JavaScript
分享 | 学习 | 交流 | 原创 分享是学习的开始;学习不必要从头开始,是从现在开始;交流能沟通你我,提高你的学识;期待你的加入!!! web前端技术交流,JavaScript,HTML5,CSS3……
目录
相关文章推荐
51好读  ›  专栏  ›  前端JavaScript

[S3-E380]深入理解 JavaScript 中的作用域和上下文

前端JavaScript  · 公众号  · Javascript  · 2017-07-10 06:31

正文

本文:【第3章第380回】 更多文章点击 目录 查看


在本教程中,我们将深入学习 JavaScript 中作用域(Scope)的一切。 所以,来吧。



01 介绍


JavaScript中有一个被称为作用域(Scope)的特性。虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,我会尽我所能用最简单的方式来解释作用域。理解作用域将使你的代码脱颖而出,减少错误,并帮助您使用它强大的设计模式。


什么是作用域(Scope)?

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。


为什么说作用域是最小访问原则?

那么,为什么要限制变量的可见性呢,为什么你的变量不是在代码的任何地方都可用呢?一个优点是作用域为您的代码提供了一定程度的安全性。计算机安全的一个常见原则是用户应该一次只能访问他们需要的东西。


想象一下计算机管理员。由于他们对公司的系统有很多控制权限,因此向他们授予超级管理员权限就好了。他们都可以完全访问系统,一切工作顺利。但突然发生了一些坏事,你的系统感染了恶意病毒。现在你不知道谁犯的错误?你意识到应该授予普通用户权限,并且只在需要时授予超级访问权限。这将帮助您跟踪更改,并记录谁拥有什么帐户。这被称为最小访问原则。看起来很直观?这个原则也适用于编程语言设计,在大多数编程语言中被称为作用域,包括我们接下来要研究的 JavaScript 。


当你继续在你的编程旅程,您将意识到,您的代码的作用域有助于提高效率,帮助跟踪错误并修复它们。作用域还解决了命名问题,在不同作用域中变量名称可以相同。记住不要将作用域与上下文混淆。它们的特性不同。



02 JavaScript中的作用域


在JavaScript中有两种类型的作用域:

  • 全局作用域

  • 局部作用域(也叫本地作用域)


定义在函数内部的变量具有局部作用域,而定义在函数外部的变量具有全局范围内。每个函数在被调用时都会创建一个新的作用域。


全局作用域

当您开始在文档中编写JavaScript时,您已经在全局作用域中了。全局作用域贯穿整个javascript文档。如果变量在函数之外定义,则变量处于全局作用域内。

// 默认全局作用域var name = 'Hammad';

在全局作用域内的变量可以在任何其他作用域内访问和修改。

var name = 'Hammad';console.log(name); // logs 'Hammad'function logName() {    console.log(name); // 'name' 可以在这里和其他任何地方被访问}logName(); // logs 'Hammad'


局部作用域

函数内定义的变量在局部(本地)作用域中。而且个函数被调用时都具有不同的作用域。这意味着具有相同名称的变量可以在不同的函数中使用。这是因为这些变量被绑定到它们各自具有不同作用域的相应函数,并且在其他函数中不可访问。

// Global Scopefunction someFunction() {    // Local Scope #1    function someOtherFunction() {        // Local Scope #2    }}// Global Scopefunction anotherFunction() {    // Local Scope #3}// Global Scope


块语句

块语句,如 if 和 switch 条件语句或 for 和 while 循环语句,不像函数,它们不会创建一个新的作用域。在块语句中定义的变量将保留在它们已经存在的作用域中。

if (true) {    // 'if' 条件语句块不会创建一个新的作用域    var name = 'Hammad'; // name 依然在全局作用域中}console.log(name); // logs 'Hammad'

ECMAScript 6 引入了 let 和 const 关键字。可以使用这些关键字来代替 var 关键字。

var name = 'Hammad';let likes = 'Coding';const skills = 'Javascript and PHP';

与 var 关键字相反,let 和 const 关键字支持在局部(本地)作用域的块语句中声明。

if (true) {    // 'if' 条件语句块不会创建一个新的作用域    // name 在全局作用域中,因为通过 'var' 关键字定义    var name = 'Hammad';    // likes 在局部(本地)作用域中,因为通过 'let' 关键字定义    let likes = 'Coding';    // skills 在局部(本地)作用域中,因为通过 'const' 关键字定义    const skills = 'JavaScript and PHP';}console.log(name); // logs 'Hammad'console.log(likes); // Uncaught ReferenceError: likes is not definedconsole.log(skills); // Uncaught ReferenceError: skills is not defined

只要您的应用程序生活,全球作用域就会生存。 只要您的函数被调用并执行,局部(本地)作用域就会存在。



03 上下文


许多开发人员经常混淆 作用域(scope) 和 上下文(context),很多误解为它们是相同的概念。但事实并非如此。作用域(scope)我们上面已经讨论过了,而上下文(context)是用来指定代码某些特定部分中  this 的值。作用域(scope) 是指变量的可访问性,上下文(context)是指 this 在同一作用域内的值。 我们也可以使用函数方法来改变上下文,将在稍后讨论。


在全局作用域(scope)中上下文中始终是Window对象。(愚人码头注:取决于JavaScript 的宿主换环境,在浏览器中在全局作用域(scope)中上下文中始终是Window对象。在Node.js中在全局作用域(scope)中上下文中始终是Global 对象)

// logs: Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage…}console.log(this);function logFunction() {    console.log(this);}// logs: Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage…}// 因为 logFunction() 不是一个对象的属性logFunction();

如果作用域在对象的方法中,则上下文将是该方法所属的对象。

class User {    logName() {        console.log(this);    }}(new User).logName(); // logs User {}

(new User).logName() 是一种将对象存储在变量中然后调用logName函数的简单方法。在这里,您不需要创建一个新的变量。


您会注意到,如果您使用 new 关键字调用函数,则上下文的值会有所不同。然后将上下文设置为被调用函数的实例。考虑上面的示例,通过 new关键字调用的函数。


function logFunction() {    console.log(this);}new logFunction(); // logs logFunction {}

当在严格模式(Strict Mode)中调用函数时,上下文将默认为 undefined。



04 执行期上下文(Execution Context)


这部分解释建议先查看这篇文章,更加通俗易懂,http://www.css88.com/archives/7262


上面我们了解了作用域和上下文,为了消除混乱,特别需要注意的是,执行期上下文中的上下文这个词语是指作用域而不是上下文。这是一个奇怪的命名约定,但由于JavaScipt规范,我们必须链接他们这间的联系。


JavaScript是一种单线程语言,因此它一次只能执行一个任务。其余的任务在执行期上下文中排队。正如我刚才所说,当 JavaScript 解释器开始执行代码时,上下文(作用域)默认设置为全局。这个全局上下文附加到执行期上下文中,实际上是启动执行期上下文的第一个上下文。


之后,每个函数调用(启用)将其上下文附加到执行期上下文中。当另一个函数在该函数或其他地方被调用时,会发生同样的事情。


每个函数都会创建自己的执行期上下文。


一旦浏览器完成了该上下文中的代码,那么该上下文将从执行期上下文中销毁,并且执行期上下文中的当前上下文的状态将被传送到父级上下文中。 浏览器总是执行堆栈顶部的执行期上下文(这实际上是代码中最深层次的作用域)。


无论有多少个函数上下文,但是全局上下文只有一个。


执行期上下文有创建和代码执行的两个阶段。


创建阶段

第一阶段是创建阶段,当一个函数被调用但是其代码还没有被执行的时。 在创建阶段主要做的三件事情是:

  • 创建变量(激活)对象

  • 创建作用域链

  • 设置上下文(context)的值( `this` )


变量对象

变量对象,也称为激活对象,包含在执行期上下文中定义的所有变量,函数和其他声明。当调用函数时,解析器扫描它所有的资源,包括函数参数,变量和其他声明。包装成一个单一的对象,即变量对象。

'variableObject': {    // 包含函数参数,内部变量和函数声明}

作用域链

在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量或其他任何资源为止。作用域链可以简单地定义为包含其自身执行上下文的变量对象的对象,以及其父级对象的所有其他执行期上下文,一个具有很多其他对象的对象。

'scopeChain': {    // 包含自己的变量对象和父级执行上下文的其他变量对象}

执行期上下文对象

执行期上下文可以表示为一个抽象对象,如下所示:

executionContextObject = {    'scopeChain': {}, // 包含自己的变量对象和父级执行上下文的其他变量对象    'variableObject': {}, // 包含函数参数,内部变量和函数声明    'this': valueOfThis}


代码执行阶段

在执行期上下文的第二阶段,即代码执行阶段,分配其他值并最终执行代码。



05 词法作用域


词法作用域意味着在一组嵌套的函数中,内部函数可以访问其父级作用域中的变量和其他资源。这意味着子函数在词法作用域上绑定到他们父级的执行期上下文。词法作用域有时也被称为静态作用域。

function grandfather() {    var name = 'Hammad'






请到「今天看啥」查看全文