JS核心知识点梳理——上下文、作用域

223 阅读6分钟

引言

满满的干货,面试必bei系列,参考大量资料,并集合自己的理解以及相关的面试题,对JS核心知识点中的作用域、闭包进行了梳理。

上下文(execution context)

又叫执行环境,环境。

执行环境定义了变量或者环境有权访问的其他数据,据定了它们的各自行为 --高程

一个函数执行的时候,会产生一个属于自己的执行环境。环境里面有一个变量对象variable object(VO),OA里面存放着环境中定义的所有变量和函数

VO = 作用域链(scope chain)+ this + 各种定义的变量和函数(如果环境是函数的话还额外有arguments)

函数执行,环境产生被推入环境栈,函数执行完,环境出栈并被销毁(闭包例外),把控制权返回给之前的执行环境。

作用域

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。--你不知道的javascript

js中的作用域是静态作用域,静态作用域又叫做词法作用域,采用词法作用域的变量叫词法变量。词法变量有一个在编译时静态确定的作用域。词法变量的作用域可以是一个函数或一段代码,该变量在这段代码区域内可见(visibility);在这段区域以外该变量不可见(或无法访问)。词法作用域里,取变量的值时,会检查函数定义时的文本环境,捕捉函数定义时对该变量的绑定。

作用域就是执行的时候,给环境变量赋值的一种规则,这种规则在函数定义的时候就已经确定了,和运行无关

js只有全局作用域和函数作用域,没有块作用域。

由于没有块级作用域,所以我们使用立即执行函数模仿块级作用域

问一个比较傻波伊的问题,有对象作用域吗?答案是没有

var name = "The Window";
var object = { 
    name : "My Object",
    fn: function(){     
    console.log(name)     
  }
};
object.fn()
// 如果有的话应该输出My Object,这里输出的The Window 所以当函数执行的时候 只有两个上下文
fn作用域链
1 (全局变量对象)name:'the window' 、 obj:{...}
0(fn的活动对象)arguments

如果需要拿object里面的name 我们可以这样

var obj = {
	fn: function(){  
    	console.log(this.name) //走this定义
        //或者
        console.log(obj.name) //走全局作用域链查找
    }    
}

执行环境和作用域的关系

作用域是虚的,是一套规则

作用域链是实的,在执行环境的环境变量上,执行的时候的变量沿着作用域链逐级查找

先看一道经典的面试题 我们经常说,作用域看定义的时候,不要看执行的时候,这是为什么?

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar();   //1

全局环境定义foo的时候,栈空间开辟一片内存1存储它,同时创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部1的[[Scope]]属性中。

当调用foo函数时,会为函数foo创建一个执行环境,然后通过复制内部1的[[Scope]]属性中的对象构建起执行环境的作用域链。

此时foo的作用域链是这种的

foo作用域链
1 (全局变量对象)foo 、 bar 、 value:1
0(foo的活动对象)arguments

所以打印1,这个和bar里面的 value:2 其实没有任何关系

函数执行的完整过程

  1. 开辟一片栈空间,根据作用域生成上下文

  2. 形参赋值

  3. 变量提升

  4. 代码从上到下运行

  5. 执行完毕后,判断有没有闭包,没有的话上下文销毁

变量提升

我们把定一个变量的行为分为两个过程,声明和定义

var a = 1
//实际执行的是下面两步
var a 
a = 1

var的变量声明会提升,没有var就是全局变量,let没,const有变量提升 var的函数声明和赋值都提升

a //undefined 因为a的声明已经提升到最上面了
var a = 1

f() //alert 1
function f () {
    alert (1)
}

有几个特殊的地方虽然平时不会这么写,但是面试题会遇到:

  • 函数体中,return后面的代码不进行变量提升,但是return下面的代码要进行变量提升
  • 不管条件是否成立,都要进行变量提升;
  • 匿名函数不进行 变量提升;
  • 具名的立即执行函数 在函数体里无法改名字
  • 如果变量名字发生重复,那么不再重复声明,但是要重新定义;

垃圾收集机制(为什么闭包上下文不销毁?

怎么实现闭包上下文不销毁的?这个得从JS的垃圾收集机制开始讲。

js有两种垃圾收集机制,一种是引用计数(老版IE),还有一种是标记清除

引用计数(淘汰): 只要被引用就+1 ,引用它的变量又被赋值就-1(ie9之前的dom,bom)

存在问题:循环引用 比如dom引用js对象,JS对象(一般可以是闭包内绑定dom事件)又反过来引用dom,即使此时页面移除dom,dom也不会被回收,除非两个手动设置为null这就造成了内存泄漏

标记清除:垃圾收集器在运行时给内存中所有的变量加上标记,然后去掉环境中的变量以及被环境中变量引用的变量的标记(这也是为什么闭包存在的原用)。被标记的视为准备删除的变量.

正是因为外层上下文有内层上下文中某些东西的引用,所以内层上下文的标记不清除,在随后的垃圾收回操作中不被收回销毁。

面试题

客观来说上下文,作用域没啥独立的面试题,一般考的是变量提升,或者结合闭包,this来考。

变量提升的一些题目平时基本不会那么写代码,我觉得性价比很低。

搞清楚这段代码执行过程中发生了什么 这一部分基本就算过关了。

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar();   //1

总结

本文详细阐述了作用域和上下文以及他们的一些关系,并结合一个例子具体分析了一下。

此外,还稍微引入了一点闭包的内容。

闭包是啥,函数执行完,环境不被销毁就叫闭包,至于为什么不会被销毁,那肯定是还有人在用。这个道理应该是很简单的。

下篇文章将详细解说闭包和this。

如果大家觉得此文对你有帮助,点赞收藏加关注!