什么是作⽤域?⼏种常见的作⽤域详解
⼏乎所有编程语⾔就是在变量中存储值,并且能读取和修改此值。事实上,在变量中存储值和取出值的能⼒,给程序赋予了状态。
如果没有这样的概念,⼀个程序虽然可以执⾏⼀些任务,但是它们将会受到极⼤的限制⽽且不会⾮常有趣。
但是这些变量该存储在哪,⼜给如何读取?为了完成这个⽬标,需要制定⼀些规则,这个规则就是:作⽤域。
常见的作⽤域主要分为⼏个类型:全局作⽤域、函数作⽤域、块状作⽤域、动态作⽤域。
对象类型
global/window全局作⽤域
function函数作⽤域(局部作⽤域)
{}块状作⽤域
this动态作⽤域
如果⼀个 变量 或者其他表达式不在 “当前的作⽤域”,那么JavaScript机制会继续沿着作⽤域链上查直到全局作⽤域(global或浏览器中的window)如果不到将不可被使⽤。 作⽤域也可以根据代码层次分层,以便⼦作⽤域可以访问⽗作⽤域,通常是指沿着链式的作⽤域链查,⽽不能从⽗作⽤域引⽤⼦作⽤域中的变量和引⽤
全局作⽤域
变量在函数或者代码块{ }外定义,即为全局作⽤域。不过,在函数或者代码块{ }内未定义的变量也是拥有全局作⽤域的(不推荐)。
var carName = "Volvo";
//此处可以调⽤carName变量
function myFunction(){
//函数内可调⽤carName变量
}
上述代码中变量carName就是在函数外定义的,它是拥有全局作⽤域的。这个变量可以在任意地⽅被读取或者修改,当然如果变量在函数内没有声明(没有使⽤ var 关键字),该变量依然为全局变量。
//此处可以调⽤carName变量
function myFunction(){
carName = "Volvo";
eval是做什么的//函数内可调⽤carName变量
}
以上实例中 carName 在函数内,但是拥有全局作⽤域,它将作为 global 或者 window 的属性存在。
在函数内部或代码块中没有定义的变量实际上是作为 window/global 的属性存在,⽽不是全局变量。换句话说没有使⽤ var 定义的变量虽然拥有全局作⽤域,但是它是可以被 delete 的,⽽全局变量不可以。
函数作⽤域
在函数内部定义的变量,就是局部作⽤域。函数作⽤域内,对外是封闭的,从外层的作⽤域⽆法直接访问函数内部的作⽤域!
function bar(){
var testValue = 'inner';
}
console.log(testValue) //报错:ReferenceError:testValue is not defined
如果想读取函数内的变量,必须借助 return 或者闭包。
function bar(value){
var testValue = 'inner';
return testValue+value;
}
console.log(bar('fun')) //"innerfun"
这是借助return的⽅式,下⾯是闭包的⽅式:
function bar(value){
var testValue = 'inner';
var result = testValue+value;
function innser(){
return result
};
return innser();
}
console.log(bar('fun')) //"innerfun"
通俗的讲,return 是函数对外交流的出⼝,⽽ return 可以返回的是函数,根据作⽤域的规则,函数内部的⼦函数是可以获取函数作⽤域内的变量的。
说到这其实⼤家会想到嵌套函数的作⽤域问题,如果 inner 函数再嵌套函数呢?这就涉及到另⼀个概念:作⽤域链。
仔细观察上图,其实不难理解作⽤域链是什么,因为你可以按照原型链那样去理解。任何⼀个作⽤域链都是⼀个堆栈,⾸先先把全局作⽤域压⼊栈底,再按照函数的嵌套关系⼀次压⼊堆栈。在执⾏的时候就按照这个作⽤域链寻变量。
块状作⽤域
在其他编程语⾔中,块状作⽤域是很熟悉的概念,但是在JavaScript中不被⽀持,就像上述知识⼀样,除了全局作⽤域就是函数作⽤域,⼀直没有⾃⼰的块状作⽤域。在 ES6 中已经改变了这个现象,块状作⽤域得到普及。关于什么是块,只要认识
if(true){
let a = 1
console.log(a)
}
在这个代码中,if 后 { } 就是“块”,这个⾥⾯的变量就是拥有这个块状作⽤域,按照规则,{ }之外是⽆法访问这个变量的。ES6中的let。
动态作⽤域
在 JavaScript 中很多同学对 this 的指向时⽽清楚时⽽模糊,其实结合作⽤域会对 this 有⼀个清晰的理解。不妨先来看下这段代码:
window.a = 3
function test () {
console.log(this.a)
}
test.bind({ a : 2 })() //2
test() //3
在这⾥bind已经把作⽤域的范围进⾏了修改指向了{ a:2},⽽ this 指向的是当前作⽤域对象,是不是可以
清楚的理解了呢?
接下来我们再思考另⼀个问题:作⽤域是在代码编写的时候就已经决定了呢,还是在代码执⾏的过程中才决定的?
var carName = "Volvo";
//此处可调⽤ carName 变量
function myFunction(){
//函数内可调⽤ carName 变量
}
在看看这段代码,写代码的时候就知道 carName 就是全局作⽤域,函数内部的⽤ var 定义的变量就是函数作⽤域。这个也就是专业术语:词法作⽤域。
通俗的讲变量的作⽤域是在定义时决定⽽不是执⾏时决定,也就是说词法作⽤域取决于源码,通过静态分析就能确定,因此词法作⽤域也叫做静态作⽤域。
相反,只能在执⾏阶段才能决定变量的作⽤域,那就是动态作⽤域。
看看下⾯的代码是遵循了动态作⽤域还是静态作⽤域呢?
function foo(){
console.log(a) //2 (不是3!)
}
function bar(){
var a=3;
foo();
}
var a=2;
bar()
为什么会这样?
如果按照动态作⽤域分析:当 foo() 不能为 a 解析出⼀个变量引⽤时,它不会沿着嵌套的作⽤域链向上⾛⼀层,⽽是沿着调⽤栈向上⾛,以到 foo() 是 从何处 被调⽤的。因为 foo() 是从 bar() 中被调⽤的,它就会在 bar() 的作⽤域中检查变量,并且在这⾥到持有值 3 的a。
如果按照静态作⽤域分析:foo执⾏的时候没有到 a 这个变量,它会按照代码书写的顺序往上,也就是 foo 定义的外层,就到了 var a=2 ,⽽不是 foo 调⽤的 bar 内。
所以结果就是 2。
从这个⽰例可以看出 JavaScript 默认采⽤词法(静态)作⽤域,如果要开启动态作⽤域请借助 bind、with、eval 等。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论