Js作用链、作用域

更新日期: 2019-07-18阅读: 2.2k标签: 作用域

作用域链

函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。

上面是一段很官方的话,毕竟是官方写的。当然官方说的很对,但有点难理解,下面我就依据上述的内容做补充。

简单的说作用域链就是是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

再简单点说就是内部上下文所有变量对象的列表。啥意思???看下面的实例一:

// 实例一
function foo() {
  var a = 1;
  function bar() {
    var b = 2;
    console.log(b);
  }
  bar();
}
foo();

上面的实例一,全局代码,foo函数、bar函数的执行上下文先后创建,每一个执行上下文都包含变量对象、this以及作用域链。其中bar函数的执行上下文:

barEC = {
  VO: {xxx}, // 变量对象
  this: xxx,
  scopeChain: [barContext.VO, fooContext.VO, globalContext.VO] // 作用域链
}

函数的作用链通常维护在该函数的执行上下文中scopeChain属性中,可以直接用一个数组来表示作用域链,数组的第一项为作用域链最前端,最前端是该函数的变量对象,数组的最后一项为作用域链最末端,最末端为全局变量对象。

实例一中一共会常见三个执行上下文,bar函数的执行上下文位于执行上下栈的最顶端,所以其执行上下文的作用域链包括当前执行上下文的变量对象以及其商城环境的一系列的变量对象VO(foo)和VO(global),所以bar函数的执行上下文的作用域链中有三个变量对象。

作用域链是一个链表,是一个线性表,也就是说当前作用域和上层作用域并不是包含关系,是一个有方向的链式关系,并且是单向的,最前端是起点,最末端是终点。所以我们可以沿着这个单向的链表查询变量对象中的标识符,这样也就可以访问到上一层作用域的变量。同时也保证了当前执行环境对符合访问权限的变量和函数的有序访问。接下来讲讲[[scope]]属性。


[[Scope]]

函数中有一个内部属性[[scope]],当函数创建的时候,就会保存所有的父变量对象到其中,也就可以理解[[scope]]就是所有父辈变量对象的层级链,但是[[scope]]并不表示完整的作用域链。看下面实例二:

// 实例二
function foo() {
  var a = 1;
  function bar() {
    var b = 2;
    console.log(b);
  }
  bar();
}
foo();

函数创建时,foo函数和bar函数的[[scope]]:

foo.[[scope]] = [
  globalContext.VO
]
bar.[[scope]] = [
  fooContext.VO, globalContext.VO
]

从上面的实例二看到,各自函数的[[scope]]只包含了各自所有父辈变量对象,没有把自己的变量对象存入,此时自己的变量对象还没创建,所以[[scope]]并不表示完整的作用域链。

当函数被激活时,进入函数执行上下文,也就创建了变量对象和作用域链,并会将自己的变量对象添加到作用域链的最前端。

barContext = {
  VO: {xxx}, // 变量对象
  this: xxx,
  scopeChain:  [barContext.VO].concat(bar.[[scope]])
}
// [barContext.VO].concat(bar.[[scope]]) => [barContext.VO, fooContext.VO, globalContext.VO]

上面就是作用域链创建的整个过程,回过头在看看刚开始的那个官方对作用域链的定义应该就很好理解了。


作用域

作用域,收集并维护一张所有被声明的标识符(变量)的列表,并对当前执行中的代码如何访问这些变量强制实施了一组严格规则。简单的说就是通过标识符名称查询变量的一组规则,明确定义了如何在某些位置存储变量,以及如何在稍后找到这些变量。

作用域决定了代码区块中的变量和其他资源的可见性。作用域可以理解为是一个独立的地盘,不会让变量外泄、暴露出去。也就是说作用域最大的作用就是隔离变量,不同作用域下同名变量不会有冲突。


词法作用域

在编程语言中,作用域分为两种:一种是词法作用域,另一种是动态作用域。JavaScript中作用域是词法作用域,词法作用域也叫做静态作用域,也就是在词法分析的阶段就被定义了,简单的说就是在代码书写的时候就被定义了。而动态作用域是值在代码被执行的时候才决定。看下面的实例三:

// 实例三
var a = 1;
function foo() {
  console.log(a); // 结果是啥??? => 1
}
function bar() {
  var a = 2;
  foo();
}
bar();

JavaScript采用的是静态作用域,输出的1。执行foo函数,会先从foo函数内部查找是否有局部变量a,如果有,则当前局部变量a的值;如果没有,就根据书写的位置,查找上层的代码,也就是到了全局作用域,也就是a等于1,所以会输出1。

如果JavaScript采用的是动态作用域,,输出的会2。执行foo函数,依然会从foo函数内部查找是否有局部变量a,如果没有,就从调用函数的作用域,也就是bar函数内部查找a变量,所以会输出2.

JavaScript采用的是静态作用域,输出的1。


全局作用域

在所有函数声明或者大括号之外定义的变量,都在全局作用域里。

// 默认全局作用域
var str = 'Hello world';

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

var str = 'Hello world';
function foo() {
  console.log(str); // Hello world 'str'可以在foo函数内访问
  str = 'Hello javascript';
  console.log(str); // Hello javascript 'str'可以在foo函数内访问和修改
}
console.log(str); // Hello world
foo();
console.log(str); // Hello javascript

所有未定义直接赋值的变量自动声明为拥有全局作用域。

function foo() {
  str = 'Hello world';
  var name = 'Hello javaScript';
}
foo();
console.log(str); // Hello world
console.log(name); // 'ReferenceError: name is not defined'

尽量可以在全局作用域定义变量,但是不推荐这样做,因为可能会引起命名冲突,两个或者多个变量使用相同的变量名。

如果定义变量时使用了const或者let,那么在命名冲突时,会报错,使用const或者let不允许变量重复声明。

let str = 'Hello world';
let str = 'Hello javaScript'; // 'Error, thing has already been declared'

如果定义变量使用的时var,是允许重复声明的,第二次定义会覆盖第一次定义。这样会让代码的调试变得很难,是不可取的。

var str = 'Hello world';
var str = 'Hello javaScript';
console.log(str); // Hello javascript

所以,尽量不要使用全局变量,使用局部变量。


局部作用域

局部作用域是相对全局作用域而言。在代码某一个具体范围内使用使用的变量都可以在局部作用域内定义。

JavaScript中有两种局部作用域:函数作用域和块级作用域。

函数作用域

函数作用域是指,属于这个函数的全部变量都可以在整个函数的范围内使用以及复用。在函数之外,无法访问到。

function foo() {
  var str = 'Hello world';
  console.log(str); // Hello world
}
foo();
console.log(str); // 'ReferenceError: str is not defined'

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

function foo() {
  var str = 'Hello world';
  function bar() {
    var str = 'Hello javaScript';
    console.log(str); // Hello javaScript
  }
  bar();
  console.log(str); // Hello world
}
foo();

作用域是分层的,内层作用域可以访问外层作用域的变量,反之不行。


块级作用域

块级作用域是对块语句而言的。

块语句就是大括号{}中间的语句,如if和switch条件语句或for和while循环语句,在ES6之前,块语句不会一个新的作用域。在块语句中定义的变量将保留在它们已经存在的作用域中。

if(true) {
  // if条件语句块不会创建新的作用域
  var str = 'Hello world'; // 在全局作用域中
}
console.log(str); // Hello world

ES6后,可以通过let和const声明变量,会产生块级作用域,所声明的变量在指定块的作用域外无法被访问。

if(true) {
  // if条件语句块会创建新的作用域
  let str = 'Hello world';
}
console.log(str); // 'str is not defined'


作用域与执行上下文

作用域和执行上下文这两个概念比较容易混淆,容易误认为是相同的概念,事实并不是。

JavaScript的执行分为两阶段,一是语法检查,二是执行:

  • 语法检查:

    • 词法分析
    • 语法分析
    • 作用域规则确定
  • 执行:

    • 创建执行上下文
    • 执行函数代码
    • 垃圾回收

从上面就能看出,作用域和执行上下文并不一样,作用域在函数定义的时候就已经确定了,不是在函数调用的时候确定的,而执行上下文是在函数执行前创建的。

作用域其实就是一张所有被声明的标识符(变量)的列表,里面没有值,就是定义了如何在某些位置存储变量,以及如何在稍后找到这些变量,然后代码执行的时候可以赋值给变量。要通过作用域相对应的执行上下文来获取变量的值。

同一作用域下,不同的调用会产生不同的执行上下文,继而产生不同的变量的值。所以,作用域变量中的值是在执行的过程中确定的,而作用域是在函数创建时就确定了。

如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文,再在其中寻找变量的值。

作用域与执行上下文的最大区别就是:

执行上下文是在运行时确定的,随时可能会变;作用域是在定义时就确定了,并且不会改变。

一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。

链接: https://fly63.com/article/detial/4792

ES6之块级作用域

在ES5中,只全局作用域和函数作用域。这会导致函数作用域覆盖了全局作用域;亦或者循环中的变量泄露为全局变量。用let命令新增了块级作用域,外层作用域无法获取到内层作用域,非常安全明了。

es6块级作用域

在JavaScript中使用var定义一个变量,无论是定义在全局作用域函数函数的局部作用域中,都会被提升到其作用域的顶部,这也是JavaScript定义变量的一个令人困惑的地方。由于es5没有像其它类C语言一样的块级作用域,因此es6增加了let定义变量,用来创建块级作用域。

Js作用域和执行上下文

作用域是在函数声明的时候就确定的一套变量访问规则,而执行上下文是函数执行时才产生的一系列变量的集合体。也就是说作用域定义了执行上下文中的变量的访问规则,执行上下文是在这个作用域规则的前提下执行代码的。

Js中的Function类型_设置函数的作用域

先声明一个name变量,然后声明一个person对象,person包含name和sayName属性。当直接在对象上进行方法的调用时:person.sayName(),函数的作用域遵循“谁调用就是谁”的原则,sayName的作用域(也就是this)指向的就是person。

ES5中模仿块级作用域

有一定JavaScript开发经验的人应该会熟悉下面这种立即执行函数的写法:不过即使不熟悉也没关系,这里我会讲解这种写法的含义。先来看下面这个更容易理解的示例:

作用域 CSS 回来了

几年前,消失的作用域 CSS,如今它回来了,而且比以前的版本要好得多。更好的是,W3C规范基本稳定,现在Chrome中已经有一个工作原型。我们只需要社区稍微关注一下,引诱其他浏览器构建它们的实现

Js静态作用域和动态作用域

静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域(变量)。 动态作用域–函数的作用域是在函数调用的时候才决定的

深入理解 JavaScript, 从作用域与作用域链开始

作用域是你的代码在运行时,某些特定部分中的变量,函数和对象的可访问性。换句话说,作用域决定了变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

不只是块级作用域,你不知道的let和const

ES6新增了两个重要的关键字let和const,相信大家都不陌生,但是包括我在内,在系统学习ES6之前也只使用到了【不存在变量提升】这个特性。let声明一个块级作用域的本地变量

理解Js的作用域和作用域链

作用域和作用域链在Javascript和很多其它的编程语言中都是一种基础概念。但很多Javascript开发者并不真正理解它们,但这些概念对掌握Javascript至关重要。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!