👨面试官的三个问题
本文采用口语化的表达,弱化代码的表现,尽可能最大化地体现面试的内容
面试开始,现在问你三个问题,看看你的基础水平
什么是执行上下文
一
执行上下文是 js 代码运行时的环境,决定了代码执行的顺序,以及变量和函数生命周期、访问权限。
二
执行上下文,还分为全局执行上下文,和函数执行上下文
当 js 代码执行时,会首先创建全局执行上下文,这个上下文在整个代码执行周期都会存在。全局上下文中,存放了很多全局变量,window,document,location,history,Math,Object,Function,String 等等
每当函数被调用,就会创建函数执行上下文。在函数执行上下文中,存放着函数的参数以及函数内变量。等函数执行完毕,函数执行上下文就会被销毁,其中存放的变量也会跟着销毁。
三
每个执行上下文,里面都有:
- 变量环境,用来存放 var 变量,以及 function 变量
- 词法环境,用来存放 let 以及 const 变量
- outer 指针,用来指父级作用域。
- this 指针
父级作用域是词法作用域,是由函数声明的位置决定的,而不是函数调用的位置,所以父级作用域不一定是上一个执行上下文
四
执行上下文在内存中是以栈的形式存放的,代码执行的第一个执行上下文是全局上下文,被创建的时候被压入栈底。执行到函数的时候,创建函数执行上下文,并将该函数执行上下文压入栈中。等函数执行完成,函数执行上下文就会被弹出栈。并将控制权交还给上一个执行上下文
什么是 this
this 其实是一个模仿面向对象的一个概念,在面向对象中,this 表示方法所处的实例对象。而在 js 中,this这个变量在不同情况下有不同的含义:
- 在全局作用域中,this 指向 window,或者 global;严格模式下,this 为 undefined
- function函数调用的时候,this 指向 window,或者 global;严格模式下,this 也为 undefined
- 对象方法方式调用 function 的时候,this 指向调用它的对象
- 对 function 执行 new 操作的时候,this 指向 function 返回的实例对象
- 对 function 执行 bind,apply,call 的操作时,this 指向方法绑定的对象
- 对于箭头函数,里面的 this 指向父级作用域中的 this 对象
- 在 class 中的函数,this 的指向和 function,剪头函数的 this 规则是一致的
- 当 function 作为事件的回调函数,其中 this 指向触发事件的 DOM 元素
箭头函数中的 this 指向不能修改,但是 function 函数中 this 指向可以修改。且不同的修改方式优先级不同,下面是优先级从低到高的方式
- 普通调用方式
- 通过对象方法调用方式
- bing、apply、call 强制指定
- 构造函数调用
什么是作用域
作用域用来描述变量和函数的可访问范围
作用域有全局作用域,函数作用域,局部作用域之分。
在全局上下文中声明的变量,就拥有全局作用域;在代码的任意位置都可以访问。在函数中声明的变量,就拥有函数作用域;在函数内部可以访问,在函数外部不可访问。
在 ES6 之前,只有这两种作用域,所以在 if,while,for 等内部声明的变量都拥有函数作用域,及在{ }
外部也可以访问这些变量。在 ES6 之后,有了 let,const 变量,就支持了局部作用域。即{ }
外部不可以访问{}
内部变量。
作用域之间可以相互嵌套,代码在查找变量的时候,会优先从当前作用域中查找,然后再去外部作用域查找,直到全局作用域为止。这个查找线路就是作用域链
作用域之间的嵌套关系,在代码编写时候就已经决定了。即一个函数的外部作用域不是函数调用的位置决定的,而是函数声明的位置决定的。所以作用域又叫词法作用域,或者静态作用域。
举个例子:在全局作用域中声明函数 A,又在函数 A 中声明了变量 varA,以及函数 B,并且返回函数 B。在函数 B 的内部有对 varA 的访问。这时候执行函数 A 就会得到函数 B,那么请问,执行函数 B 时,访问 varA 过程是什么?
//全局作用域
var varA = '1'
function A(){
var varA = '2';
function B(){
console.log(varA);
}
return B;
}
var funcB = A();
funcB(); // 2
首先在函数 B 的作用域中查找 varA,如果没有找到,就会往父级作用域,也就是函数 A 的作用域里找,找到 varA 就会停止查找,并且返回 varA 值。
在执行上下文那里,我们知道,在函数执行完毕后,对应的执行上下文是会被销毁的,而函数 B 还能访问函数 A 里面的varA,这说明函数 A 的执行上下文并没有被销毁。更严格地说没有被完全销毁。
这是因为函数 B 的执行上下文中有一个对它的父级执行上下文--outer,每个执行上下文中都有一个这样的 outer,这也是为什么调用的位置千变万化,还是记得自己的父级作用域是谁,靠的就是这个 outer 变量。
JS 引擎在执行的时候,就发现函数 B 的内部有对 varA 的访问,JS 引擎就会保留 varA,而函数 A 执行上下文中其他的没有被引用的变量全部都要销毁掉,比如 this,arguments 等等
那保留下来用来存放 varA 的内存空间,叫做函数 A 的部分执行上下文,也叫做函数 A 的闭包。
转载自:https://juejin.cn/post/7387305065801334794