1、什么是作用域?
(1)广义:可访问变量、函数、对象的集合,决定代码区域中变量和其他资源的可见性。
(2)狭义:所有编程语言最基本的功能就是存储变量的的值,并且在之后能够访问和修稿它,这种访问或者修改变量的值得能力给程序带来了“状态”,如果没有状态,程序的灵活性会大大降低,在程序中如何存储变量,已经变量的访问,需要一套设计良好的规则,我们称这套规则为作用域。
2、有哪几种作用域?
(1)全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域(缺点:操作命名空间的污染)。
- 最外层函数和变量拥有全局作用域
- 所有未定义直接声明的变量拥有全局作用域
- window的所有属性拥有全局作用域
(2)函数作用域(局部作用域):在函数内部定义的变量或者函数。 作用域是分层的,内部作用域可以访问外部的作用域,反之不行。
(3)块级作用域:可以使用let和const进行声明(let、const和var的区别)
- 不存在变量提升
- 不允许重复声明
- 存在暂时性死区
- 形成块级作用域
PS1:const定义之后不能修改,针对简单类型(原始类型)来说。变量保存在内存的栈中,是值保存,const定于的值是不能修改的。对于复合类型来说,变量保存在内存的栈中是一个指针,这个指针指向堆。const定义之后,只要这个指针不改变,指针的值是可以改变的。
PS2:ES6定义变量的几种方式,let、const、var、class、import、function
3、词法作用域
作用域分为:词法作用域、动态作用域。js只用词法作用域,this的访问让js看起来像动态作用域。 定义在词法阶段的作用域我们称之为词法作用域。词法作用域是由你在写代码时将变量和块级作用域写在哪决定的。
(1)欺骗词法作用域
eval:包含一个或者多个声明的“代码字符串”来进行演算,修改词法作用域(运行时)。 with:通过将一个对象的引用当做作用域来处理,将对象的属性当做标识符,从而创建一个新的词法作用域(运行时)
(2)欺骗词法作用域的副作用
严格模式禁止 引擎编译时不会对作用域查询进行优化,导致运行变慢。
4、作用域链
在js中一切都是对象,函数和对象一样,拥有可以使用代码访问的变量和只能javascript引擎访问的内部变量,其中有一个内部变量[[scope]],该属性包含了函数被创建时的作用域中对象的集合,我们称这个集合为作用域链,定义了那些函数能被作用域访问。
- 自由变量:在当前作用域没有定义的变量叫自由变量。
- 作用域链:自用变量在当前作用域没有找到,会向父级作用进行查找,如果父级域没有,会一级一级向上查找,直到全局作用域,如果还是没有找到,变量值为undefined,这种查询形成的一层一层关系叫做作用域链。
- 自由变量的取值:创建函数的作用域中查找取值,而不是调用函数的作用域。
5、执行上下文
在javaScript中,代码执行并非一行一行执行,而是一段一段分析执行(在javaScript中一段一段可执行代码分为三类:全局可执行代码,函数可执行代码,eval可执行代码)。当执行一段代码之前,会进入一个“准备工作”。这个“准备工作”就是执行上下文。在执行时会创建大量的执行上下文,需要使用执行上下文栈来对执行上下文进行管理。
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
代码一运行过程:ECStack执行上下文栈
ECStack.push(全局上下文)
ECStack.push(checkscope上下文)
ECStack.push(f上下文)
ECStack.pop(f上下文)
ECStack.pop(checkscope上下文)
ECStack.pop(全局上下文)
6、作用域和执行上下文
javaScrip属于解释型语言,js的执行分为解析和执行
(1)解析
- 分词/词法分析:生成词法单元。
- 解析/语法分析:根据词法单元生成,抽象语法书(AST)。
- 生成可执行代码、作用域规则确认。
(2)执行
- 创建指向上下文
- 执行代码
- 垃圾回收
作用域是代码解析阶段就已经确定,而不是在函数调用时,执行上下文时函数执行前被创建的,最明显的就是this的指向是运行时确认的。而作用域变量访问的变量是在代码结构确认就已经确认了的。
(3)区别
执行上下文时时运行前确认,作用域是解析时就已经确认。 执行上文文可能随便改变,作用域不会。 一个作用域可能包含若干执行上下文。 做一个作用域下,不同的调用会产生不用的执行上行文环境。随之产生不同的变量。