第03篇-前端知识之作用域与闭包

138 阅读5分钟

本人寻找武汉地区前端工作机会,有需要我搬砖的大佬欢迎骚扰,鄙人不甚感激,可以评论区留言讨论哦😲

前言

前面我们说过第02篇-前端知识之JavaScript原型和原型链,原型原型链、作用域与闭包、异步被称之为JavaScript的三座大山,今天本文就主要谈一谈作用域与闭包。

执行上下文

执行上下文其实很抽象,但是它又必然是一个无法忽略的知识点。首先我们先不考虑函数内部的执行,先从最简单的一段代码来说:

console.log(a,foo); // undefined  function foo ...
var a = 100;

foo("hello"); // 200,300,window,Arguments

function foo(message) {
  let b = 200;
  a = 300;
  console.log(b, a, this, arguments);
}

console.log(this); // window

let obj = { a: 10 };
foo.call(obj, "world"); // 200,300,{a:10},Arguments

先抛开函数执行不说,就单说全局,js引擎在第一行代码就知道a、foo的值,并且foo的不同执行方式也能确定不同的this。其实js在执行之前会先创建一个全局的执行环境,也就是全局执行上下文,这个看不见摸不着的东东会预先帮忙把变量定义、函数声明都准备好,这也就是为什么第一行打印就能知道a=undefinedfoo是一个函数咯。然后当全局里面不同形式函数执行的时候,this也不一样,也就是说只有函数执行才能确定this是谁。这一切都说明了内部有个机器人在默默无闻的做着一些准备工作。

当真正函数执行的时候,又会类似全局执行上下文一样,在函数内部创建一个函数执行上下文,其作用与全局类似,也会把这个函数内部的变量声明,函数声明先按一定的规则准备好,并且内部还凭空多出来了一个arguments对象,当然在函数执行的时候,根据不同的调用方式确定this是什么。

综上可以得出执行上下文正常来讲分:

  • 全局执行上下文 主要准备变量定义 函数声明 确定全局this,例如window
  • 函数执行上下文 主要准备变量定义 函数声明 arguments对象 确定调用this

还有一种eval执行上下文,用的比较少,自己可以查询资料。你可以简单理解为执行上下文就是我们代码运行起来的一个准备工作或者是一个运行环境。它看不见摸不着,如果你能用实际中的例子来对照一下,我很乐意听听。

作用域

代码的基本组成部分就是变量,变量又是存储数据的容器。而作用域就是一个变量能够被合理访问或者使用的范围。还是来先看一段代码

let hello = "world";

function bar() {
  let user = "xiaoming";
  console.log(user)

  // console.log(sex) // 报错

  function foo() {
    let sex = "男";
    console.log(sex, user, hello);
  }

  foo()
}

bar()

从中可以看到一个变量要访问到,那么其一定要在一个合法合理的区域内才行,并不是瞎访问的。从最内部的foo函数可以看到,它不仅可以访问自己内部的,还能一层一层往上寻找它内部没有定义的变量。

js中把作用域分为:

  • 全局作用域
  • 函数作用域
  • 块级作用域

上面说的向自己上级作用域寻找,直到找到全局作用域还是没找到,就会报错。正常情况下这种一层一层往上的关系,就是 作用域链。而那些个没有在自己作用域中定义的变量又称之为 自由变量。那么自由变量的查找真的就是一层一层的往上找吗?再看一段代码:

let a = 100;

function foo() {
  let a = 200;
  function baz() {
    console.log(a); // 200
  }
  baz();
  bar();
}

function bar() {
  console.log(a); // 100
}

foo();

可以看到 bar 并没有如愿以偿的输出200,而是输出100。因此可以看出自由变量的向上查找是往其定义的时候的那个作用域里面查找的。这点尤为重要!!!!

闭包

谈到闭包,就必然少不了函数。闭包的概念很晦涩难懂,并且它不是js这个语言特有的。只有通过两个例子

  • 1,函数作为参数传递
let a = 100

function foo(){
    console.log(a);
}
function bar(fn){
    let a = 200
    fn()
}
  • 2,函数作为返回值
function foo() {
  let a = 100;
  function bar() {
    console.log(a);
  }

  return bar;
}

const baz = foo()
baz()

我认为闭包是一种在其他作用域里面执行但是能记住他定义的作用域的上下文环境的函数表现。也有人说当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这就产生了闭包。

通常来讲闭包的常见应用有: iife模块化 高阶函数 函数柯里化等 异步回调保留内部变量等 包括一些流行库插件内部都大量使用了闭包。

this

this我认为是一个很神奇的东西,我们通过前面知道js在执行代码之前其实就对作用域及自由变量的查找规则就已经定义好咯。但是可能你还希望能有一个超能力者,能够在预先不知道是什么的变量吧,比如只有我们调用这个函数的时候才能确定其值。

this执行会有不同,主要集中在这几个场景中

  • 全局环境
  • 普通函数调用
  • 由call/apply/bind函数调用
  • 对象属性方法调用
  • 构造函数调用
  • 箭头函数

具体的看我之前写过的一篇文章浅谈JavaScript中this的指向问题及面试题

最后

最后想说的是作用域闭包的东西其实不太容易理解,如果需要全面了解的可以参考一下这位大佬写的: JavaScript深入之闭包