作用域的延伸

简介: 从变量、函数的声明提升开始,到作用域、作用域链的重新理解。带你重新回顾作用域引发的一系列问题探讨,再一次巩固js作用域基础。

网络异常,图片无法展示
|

从变量、函数的声明提升开始,到作用域、作用域链的重新理解。带你重新回顾作用域引发的一系列问题探讨,再一次巩固js作用域基础。


声明提升


变量声明提升


使用var关键字声明的变量会在代码执行之前被声明(但不会赋值--undefined)

console.log(a);  //undefined
var a = 'xxx'


函数声明提升


使用函数声明式创建的函数在代码执行之前就被创建,可在函数声明前调用,但是使用函数表达式创建的函数,不会被声明提前(not defined)


fun1()   // 111
function fun1() {
  console.log(111)
}
fun2()   // fun2 is not a function
var fun2 = function () {
  console.log(222)
}


作用域


作用域:一个变量作用的范围区域。

作用域分为全局作用域、函数作用域。

作用:隔离变量,在不同的作用与中定义相同的变量而不相互影响。


全局作用域


全局作用域在页面打开时创建,关闭时销毁。

全局作用域中有一个全局对象window(代表一个浏览器窗口,由浏览器创建)可直接使用。


创建的变量会作为全局的属性保存(任意部分都可以访问),创建函数的时候会作为全局的方法保存。


函数作用域


函数在定义时确定函数的作用域,由函数声明的位置决定,执行完成之后函数作用域销毁。每调用一个函数都会创建一个函数作用域,之间相互独立。


函数作用域中可以访问全局作用域变量,但是全局变量无法访问函数作用域内的变量。在函数作用域中,操作一个变量,如果该变量在当前作用域不存在,就会往上级找,直到全局作用域。


var e = 0
    fun1()   // 0
function fun1() {
  var a = 1
  console.log(e)
}
console.log(a)  // ReferenceError: a is not defined


在函数作用域中,不使用var声明变量都会变成全局变量


var e = 0
function fun1() {
  console.log(e)
  e = 1
}
fun1()  // 0
console.log(e) // 1


函数中的形参就相当于在函数中定义了变量


var e = 0
fun1()  // undefined
function fun1(e) {
  console.log(e)
}


作用域其他分类


作用域还可以分为:静态作用域、动态作用域。


静态作用域:又称词法作用域,采用词法作用域的变量叫词法变量。词法变量在词法作用域内可见,在词法作用域外不可见。静态作用域由函数定义位置决定,js中的作用域就是静态作用域(词法作用域)。


function bar() {
    var a = 1000
  }


动态作用域:采用动态作用域的变量叫动态变量。在动态作用域执行时间内,该变量一直存在;代码段执行结束,该变量消失。由函数运行被调用时的位置决定。


function fn() {
  console.log(x)
}
function show() {
  var x = 20
  fn()
}


调用show()时,它会调用fn()。在执行期间,fn()被show()调用代表了一种动态关系。


作用域链


定义:存在作用域嵌套,嵌套的作用域是由内向外的一个结构。

作用:查找变量。


当代码在一个环境中执行时,会创建变量对象的一个作用域链。通过作用域链保证有权访问的所有变量和函数,进行有序访问。

作用域链在函数定义时已经确定,作用域链是和函数定义时的位置相关的。

例1:

var x = 10
function fn() {
  console.log(x)
}
function show() {
  var x = 20
  fn()
}
show()  // 10


例2:


var x = 10
function show1() {
  var x = 20
  function fn() {
    console.log(x)
  }
  fn()
}
show1()  // 20


理解: 函数作用域在定义时就已经确定,所以在例1中,函数fn的父级作用域是window。当fn内不存在x时,会向其父级找寻x,父级存在x变量。所以输出10。

例2中,fn的父级环境是show1函数,所以到show1中找x,输出20。


执行环境与执行环境栈


作用域是静态的,只要函数定义好就会一直存在且不变化;执行环境是动态的调用函数时创建,函数调用结束自动销毁。

执行上下文分为:全局执行上下文、函数执行上下文。这里执行上下文又称为执行环境。在代码运行时会创建相应的执行环境。


全局执行环境


在执行全局代码之前,将window确定为全局执行环境。 然后对全局数据进行预处理:全局变量添加为window属性、function声明全局函数添加为window方法,this指向window。


函数执行环境


函数运行时,创建对应的函数执行环境对象。对局部数据进行预处理。


执行环境栈


在全局代码执行之前,js引擎就会创建一个栈来存储管理所有执行环境的对象。

在全局执行环境确定后,会将全局执行环境添加到栈中。

在函数执行环境创建后,将函数执行环境添加入栈。

函数执行完成后,将函数执行环境从栈中移除。

所有代码执行完成后,栈中会只剩下window全局执行环境。


函数的运行流程


var a = 10
function fn(x) {
  var a = 100,
    c = 300
  b = 20
  console.log(a, b, c, x)
  function bar(x) {
    var a = 1000,
      d = 4000
    console.log(a, b, c, x)
  }
  bar(1)
}
fn(10)  
// 100  20  300  10
// 1000  20  300  1
复制代码


首先,确定全局作用域和函数作用域。

网络异常,图片无法展示
|
其次,依次确定相应的执行环境,要时刻谨记执行环境是动态的。

fn函数代码执行之前创建全局执行环境,记录全局变量。

网络异常,图片无法展示
|

执行函数fn时,创建fn函数执行环境。在fn函数内部存在b未定义变量,b会变成全局变量。执行bar函数时,创建bar函数执行环境。此时的各执行环境如下图:

网络异常,图片无法展示
|

运行fn函数,输出a、b、c、x。当前作用域对应的执行环境中,创建变量对象的相应作用域链。a存在输出100,c存在输出300,x存在输出10。b不存在于当前执法环境中,通过作用域链向其上一级作用域寻找,即全局作用域。在全局作用域对应的全局执行环境中,存在b变量输出20(此过程称为“变量解析”)。


注意: 此时输出语句在b定义之后,当执行完b = 20时,b变量已经存在于全局执行环境中,所以输出时b存在且值为20。如果b = 20与输出语句互换,则输出语句中b未定义(全局执行环境中无b)。


在bar函数中,输出a、b、c、x。当前作用域对应的执行环境中,a存在输出1000,x存在输出1。b不存在,向其父级fn作用域寻找,fn执行环境中无b。再向上一级作用域中寻找,即全局作用域。在全局执行环境中,存在b变量,且值为20,所以输出的b为20。c不存在当前作用域,所以向其父级fn函数作用域中寻找c,存在且值为300,所以c输出300。


执行环境栈的流程


代码执行时,会先创建一个执行环境栈。当执行流进入一个函数时,函数的执行环境就会被压入执行栈中,函数执行完成之后,会将其环境弹出。


1、 在上例中,先进入全局执行环境,将全局执行环境压入执行栈中。

2、 执行fn,将fn执行环境压入栈中。

3、 fn中执行bar,将bar压入执行环境栈中。



网络异常,图片无法展示
|

4、 当执行完bar后,bar函数执行环境从栈中移除。

5、 执行完fn后,fn的执行环境从栈中移除。

6、 执行完成后,只剩全局执行环境存在于栈中。

7、 当页面关闭,全局执行环境移除,执行环境栈销毁。

目录
相关文章
|
6月前
|
自然语言处理
如何在箭头函数中访问非封闭作用域中的变量?
【2月更文挑战第20天】【2月更文挑战第63篇】如何在箭头函数中访问非封闭作用域中的变量?
48 1
|
3月前
|
存储 安全 编译器
【C深度解剖】const关键字
【C深度解剖】const关键字
|
16天前
|
存储 JavaScript 前端开发
块级作用域和函数作用域的区别在哪些方面会对性能产生影响?
【10月更文挑战第29天】块级作用域和函数作用域在变量查找效率、内存管理、闭包、代码执行顺序以及作用域链维护等方面的区别,都会在不同程度上对性能产生影响。在实际开发中,需要根据具体的代码逻辑、应用场景和性能需求,合理地选择和运用这两种作用域,以达到最佳的性能和代码质量平衡。
|
28天前
|
存储
局部对象和全局对象之间的区别是什么
【10月更文挑战第19天】局部对象和全局对象之间的区别是什么
36 2
|
5月前
|
C++
C++对C的改进和拓展\引用
C++对C的改进和拓展\引用
24 0
|
6月前
|
JavaScript 前端开发
js开发:请解释什么是作用域(scope),并说明全局作用域、局部作用域和块级作用域的区别。
JavaScript中的作用域规定了变量和函数的可见性与生命周期。全局作用域适用于整个脚本,变量可通过全局对象访问,可能导致命名冲突和内存占用。局部作用域限于函数内部,每次调用创建新作用域,执行完毕后销毁。ES6引入的块级作用域通过`let`和`const`实现,变量仅在其代码块内有效,并有暂时性死区。作用域机制有助于代码组织和变量管理。
46 1
|
6月前
|
存储 缓存 Java
探秘闭包:隐藏在函数背后的小秘密(下)
探秘闭包:隐藏在函数背后的小秘密(下)
探秘闭包:隐藏在函数背后的小秘密(下)
|
6月前
|
缓存 自然语言处理 前端开发
探秘闭包:隐藏在函数背后的小秘密(上)
探秘闭包:隐藏在函数背后的小秘密(上)
探秘闭包:隐藏在函数背后的小秘密(上)
|
6月前
|
自然语言处理 JavaScript 前端开发
JavaScript开发基础问题:如何理解闭包及其作用?
JavaScript开发基础问题:如何理解闭包及其作用?
60 5
|
传感器 编译器
__weak关键字:程序模块相互独立的大杀器
__weak关键字:程序模块相互独立的大杀器
76 0