zhangdizhangdi

作用域、闭包

作用域和执行上下文之间最大的区别是:

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

作用域

编译原理

    1. 分词/词法分析(Tokenizing/Lexing)

      这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。
      例如,考虑程序var a = 2;。这段程序通常会被分解成 为下面这些词法单元:var、a、=、2 、;。

    1. 解析/语法分析(Parsing)

      这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法 结构的树。
      这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。

    1. 代码生成

      将 AST 转换为可执行代码的过程称被称为代码生成。

定义

作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域

词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。

分类

  1. 全局作用域
  2. 函数作用域
  3. 块级作用域
参考

执行上下文

分类

  • 全局执行上下文
  • 函数执行上下文
  • Eval函数执行上下文

阶段

  • 创建
    • 确定 this 的值,也被称为 This Binding
    • LexicalEnvironment(词法环境) 组件被创建
      • 函数声明和变量(let、const)绑定
      • let和const定义的变量,保持 uninitialized(未初始化状态)
    • VariableEnvironment(变量环境) 组件被创建
      • 变量(var)绑定
      • 被赋值为undefined
  • 执行
    • 变量赋值
    • 代码执行
  • 回收

属性

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this
参考

提升

变量和赋值语句一起书写,在js引擎解析时,会将其拆成声明和赋值2部分,声明置顶,赋值保留在原来位置

  1. 函数声明会提升, 函数表达式不会被提升
  2. 变量声明也会置顶
  3. 函数会先被提升,然后才是变量(函数是一等公民)
  4. 声明过的变量不会重复声明,但出现在后面的函数声明还是可以覆盖前面的
  5. IIFE(立即执行函数表达式)函数名不允许更改
  6. let 进行的声明,不会在块作用域中进行提升,声明的代码被运行之前,声明并不“存在”
js
if (!('a' in window)) {
  var a = 12
}
// console.log(a)       undefined
js
// console.log(a)       undefined
var a = 'window'
function fn() {
  // console.log(a)     undefined
  var a = 'function'
}
fn()
// console.log(a)       window
js
// console.log(a)       undefined
var a = 'window'
function fn() {
  // console.log(a)     window
  a = 'function'
}
fn()
// console.log(a)       function
js
console.log(a)
a = 'window'
function fn() {
  console.log(a)
  a = 'function'
}
fn()
console.log(a)

// Uncaught ReferenceError: a is not defined
js
var a = 'window'
function fn() {
  // console.log(a)    undefined
  if (!a) {
    var a = 'function'
  }
  // console.log(a)    function
}
fn()
js
var a = 1,
  b = 10,
  c = 100
function fn(a) {
  a = 0
  var b = 0
  c = 0
}
fn(a)
console.log(a)
console.log(b)
console.log(c)

// 1
// 10
// 0
js

`var a = b = 'func'` 相当于
b='func'
var a = b

-----

// console.log(a, b)       undefined undefined
var a = 'wina',
  b = 'winb'
function foo() {
  //  console.log(a, b)    undefined "winb"
  var a = (b = 'func')
  //  console.log(a, b)    "func" "func"
}
foo()
// console.log(a, b)       "wina" "func"
js
var a = 10
;(function () {
  // console.log(a)            undefined
  a = 5
  // console.log(window.a)     10
  var a = 20
  // console.log(a)            20
})()

var b = {
  a,
  c: b,
}
// console.log(b.c)            undefined

重名

js
// console.log(a)  function
var a = 100
// console.log(a)  100
function a() {
  console.log(a)
}
// console.log(a)  100
js
function a() {}
var a
console.log(typeof a) // function
js
// console.log(fn)      fn -> 3
function fn() {
  console.log(1)
}

// console.log(fn)      fn -> 3
function fn() {
  console.log(2)
}

// console.log(fn)      fn -> 3
var fn = '100'

// console.log(fn)      100
function fn() {
  console.log(3)
}

// console.log(fn)      100

参数

js
var a = 1
function foo(a) {
  // console.log(a)   1
  var a = 3
  // console.log(a)   3
}
foo(a)
// console.log(a)      1
js
var a = 1
function foo(a) {
  // console.log(a)   1
  a = 3
  // console.log(a)   3
}
foo(a)
// console.log(a)      1
js
var a = 1
function foo(a, b) {
  console.log(a) // 1
  a = 2
  arguments[0] = 3
  var a
  console.log(a, this.a, b) // 3, 1, undefined
}
foo(a)

立即执行函数

js
var a = 'oa'
var b = 'ob'
var c = 'oc'
var d = 'od'
;(function (a, b, c) {
  // console.log(a)    oa
  // console.log(b)    ob
  // console.log(c)    undefined
  // console.log(d)    od
  a = 'hello'
  var b = 'world'
  c = '!'
  d = 'id'
  // console.log(a)    hello
  // console.log(b)    world
  // console.log(c)    !
  // console.log(c)    id
})(a, b)
// console.log(a)      oa
// console.log(b)      ob
// console.log(c)      oc
// console.log(c)      id
js
var a = 10
;(function c() {})()
// console.log(c)      Uncaught ReferenceError: c is not defined
js
// console.log(b)  undefined
var b = 10
// console.log(b)  10
;(function b() {
  // console.log(b)  function
  b = 20
  // console.log(b)  function
})()
参考

闭包

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。
换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
—— MDN

函数在定义时的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的词法作用域。
无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或者任何其他的异步(或同步)任务中,只要使用了回调函数,实际上就是在使用闭包。

—— 《你不知道的JS【上】》

特点:
  1. 闭包可以访问当前函数以外的变量
  2. 即使外部函数已经返回,闭包仍能访问外部函数定义的变量
  3. 闭包可以更新外部变量的值
js
var n = 10
function fn() {
  var n = 20
  function f() {
    n++
    console.log(n)
  }
  f()
  return f
}

var x = fn()
x()
x()
console.log(n)

// 21 22 23 10
js
let a = 0,
  b = 0
function fn(a) {
  fn = function fn2(b) {
    console.log(a, b)
    console.log(++a + b)
  }
  console.log('a', a++)
}
fn(1)
fn(2)

// a, 1

// 2, 2
// 5
js
var data = []

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i)
  }
}

data[0]()
data[1]()
data[2]()

// 3 3 3

// console.log(data)
// Array [function () {
//   console.log(i)
// }, function () {
//   console.log(i)
// }, function () {
//   console.log(i)
// }]
js
var result = []
var a = 3
var total = 0

function foo(a) {
  for (var i = 0; i < 3; i++) {
    result[i] = function () {
      total += i * a
      console.log(total)
    }
  }
}

foo(1)
result[0]() // 3
result[1]() // 6
result[2]() // 9
js
function fun(n, o) {
  console.log(o)
  return {
    fun: function (m) {
      return fun(m, n)
    },
  }
}

var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)

// undefined
// 0
// 0
// 0

var b = fun(0).fun(1).fun(2).fun(3)

// undefined
// 0
// 1
// 2

var c = fun(0).fun(1)
c.fun(2)
c.fun(3)

// undefined
// 0
// 1
// 1
参考