js作用域
- 总览:
- 块级作用域
- 函数作用域
- 动态作用域
- 词法作用域
- 什么是作用域:
- 作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
- 块级作用域:
- 通常来说,将一个{}即视为一个块级作用域,即在此作用于中定义的变量只能在此作用域中定义。例如:
for (var i = 0; i < 3; i++) {
}
console.log(i)
//输出3
for(var i=0;i<3;i++){
var a="我就是"
}
console.log(a)
//输出"我就是"
- 很容易看到,js并不支持所谓块级作用域。不过此现象已将在es6中得到解决(let),本文暂不作多介绍,直接看代码吧。
for (var i = 0; i < 3; i++) {
let a = "我就是"
}
console.log(a)
//输出 a is not defined
- 用let声明的a变量此时已经无法在全局环境中被访问到
- 函数作用域
- 函数作用域本意在于在此函数内声明的变量只能该函数的作用域中被访问,但实际的编程语言不仅限于js在这一条中却并没有严格的支持此规范。
function a1() {
let a = 1;
function a2() {
console.log(a)
}
a2()
}
a1()
//输出 1
- 在这了即使是用let声明的变量,仍然可以被a2函数访问到。
- 动态作用域:
- 直接上代码吧
function a1() {
console.log(a)
}
function a2() {
var a = 1;
a1()
}
//a is not defined
- 当a1在a2函数中被调用时,虽然a已经被定义,但a1仍然无法访问到变量a的值。(后续补充…)
-
词法作用域(函数) lexical scope
- 词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变 。
- 看解释应该看不出来啥,直接分析代码吧
function a1() {
var a = 1;
function a2() {
console.log(a)
}
a2()
}
a1()
// 1
- 代码在执行时分函数解析和函数执行阶段。
- 在函数解析时会生成属于该函数的scope环境。
- 在函数执行时会生成该函数的词法作用域,并建立该函数[[scope]]作用域指向此函数scope所在词法作用域的词法作用域。(可能有点绕,稍等看下面的分析):
- 以上面的代码为例:
- js执行时先根据var声明的变量和声明式声明的函数建立自己的词法作用域,在浏览器时是window对象,在其他环境中是Global Object;命名window.ls。还有生成a1函数的scope环境,命名a1[[scope]]
- 接下来不难看出先执行的是a1函数的调用,此时生成a1函数的词法作用域a1.ls(其中包括a2函数的scope),并建立指向:a1.scope–>window.ls;
- 同理当a1函数执行到a2函数的调用部分时,先建立a2.ls,并建立关联a2.scope–>a1.ls。(形成作用域链条)
- 这样做的意义在于:当执行console.log(a)时,发现a不存在于a2函数的a2.ls中,将循着a2.scope–>a1.ls,在a1函数的词法作用域中寻找a,若找到则调用执行,若找不到则继续向上查找。直到找到window || Global Object 的词法作用域,若在找不到,即报错: a is not defined。(这种情况类似于js的基于原型链查找属性以及方法的处理方式)
- 此时有一个特殊情况:var a = new Function(“”,“console.log(a)”)
function a1() {
var a = 1;
var a2 = new Function("", "console.log(a)")
a2()
}
a1()
//报错 a id not defined
- 这种情况下a2函数被调用时,a2.scope将直接指向window.ls,因为a是存在于a1函数的词法作用域中,所以会造成报错。
- 出现错误还望指正