那些年听说的作用域
- 全局作用域
- 函数作用域
- 块作用域
- 词法作用域
- 动态作用域
- 全局作用域
- 作用域链
作用域
作用域(英文:scope)是据名称来查找变量的一套规则,可以把作用域通俗理解为一个封闭的空间,这个空间是封闭的,不会对外部产生影响,外部空间不能访问内部空间,但是内部空间可以访问将其包裹在内的外部空间。
说白了就是一门语言如果声明的变量都放在全局,程序规模小还行如果规模一大肯定就不行了。所以就会采用各种方案来确定函数的作用域。
静态作用域与动态作用域
静态作用域(static scope) 与 词法作用域(lexical scope)
其实就是指的词法作用域,所谓静态作用域,也就是说在程序编译期通过对源代码的词法分析就可以确定某个标识符属于哪个作用域、作用域的嵌套关系(作用域链),在书写源代码时这些关系就已经确立了。
词法分析是编译中不可或缺的一环。
// 静态作用域: var a = 10; function fn() { var b = 1; console.log(a + b); } fn(); // 11
动态作用域(dynamic scope)
动态作用域是在运行时确定的,词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用,其作用域链是基于运行时的调用栈的。
js语言中的变现为this 也就是上下文环境
function say() { debugger console.log('我的家乡:' + this.name) } var china = { name: '中国', say, beijing :{ name: '北京', say } } setInterval(() => Math.random() > 0.5 ? china.say() : china.beijing.say() , 1000)
因为 this 是指向的是函数运行时所在的环境,也就是说只有到了执行时才能确定。
扩展
其实动态与静态的问题在每种语言中都存在,
比如:
- C++ 动态联编与静态联编 - 虚函数
- Java 动态编译与动态加载
函数作用域 与 块级作用域
下面我们细致的说一下函数作用域和块级作用域。
对于JS这种函数式语言,函数是一等公民,甚至有人想过用函数解决所有问题。
所以我们首先说说静态作用域的基础函数作用域。
函数作用域:指在函数内声明的所有变量在函数体内始终是可见的,可以在整个函数的范围内使用及复用。
var a = 'a' function f1() { var b = 'b' function f2() { var c = 'c' function f3() { if(true) { var d = 'd' } console.log(a, b, c, d) debugger } f3() } f2() } f1()
作用域链
ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链
块级作用域
块作用域是一个用来对之前的最小授权原则进行扩展的工具,将代码从在函数中隐藏信息扩展为在块中隐藏信息。
为了与其他主流语言靠近,块级作用域是ES6推出了let、const实现块级作用域。
var a = 'a' function f1() { var b = 'b' function f2() { var c = 'c' function f3() { if(true) { debugger let d = 'd' // var 改为 let } debugger console.log(a, b, c, d) } f3() } f2() } f1()
上面只是将变量d改为使用let声明
但是运行结果就发生了变化
发生的原因就是使用var声明时,变量d的作用域在函数内。
而使用let声明时,作用域只在if代码块内。
面试攻略
- 这道题其实是一个基础题,没有一个人不回答。回答的关键是在描述的系统性上面。比如你硬说词法作用域和动态作用域组成了JS的作用域体系就很奇怪。不在一个维度的描述会让人觉得描述不够系统和全面。