什么是作用域
- 作用域是可访问变量的集合。
- 在
JavaScript
中, 对象和函数同样也是变量。 - 在
JavaScript
中, 作用域为可访问变量,对象,函数的集合。 - 分为 全局作用域/局部作用域(函数作用域与块级作用域)
全局作用域:
- 全局作用域贯穿整个
javascript
文档,在所有函数声明或者大括号之外定义的变量,都在全局作用域里。一旦你声明了一个全局变量,那么你在任何地方都可以使用它,包括函数内部。事实上,JavaScript
默认拥有一个全局对象window
,声明一个全局变量,就是为window
对象的同名属性赋值。 - 注:若变量在函数内部没有声明(未使用
var
关键字),该变量为全局变量。全局变量在页面关闭后销毁。
function zxxFn () {
zxxs = 2
}
var zxx = 1
console.log(window.zxx) // 1
// console.log(zxxs) 未定义
zxxFn() // 执行后zxxs成为全局变量
console.log(zxxs) // 2
局部作用域(函数作用域和块级作用域)
在JavaScript
中,任何定义在函数体内的变量或者函数都将处于函数作用域中,这些变量也无法被在函数外部使用。(闭包除外)
- 变量在函数内声明,变量属于局部作用域。
- 局部变量:只能在函数内部访问。
- 局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。
- 局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
局部作用域外部无法访问示例:
function zxxFn () {
var zxx = 'zxx is a great girl'
console.log(zxx) // zxx is a great girl
}
zxxFn() // zxx is a great girl
console.log(zxx)
// Uncaught ReferenceError: zxx is not defined 函数内部定义的局部变量外部无法访问
当函数体内局部变量和函数体外的变量重名
函数内部优先使用自己的变量。(部分知识涉及变量提升)
var zxx = 'zxx is a good girl'
function zxxFn () {
console.log(zxx)
var zxx = 'zxx is a great girl'
console.log(zxx)
zxx = 'very good'
console.log(zxx)
}
console.log(zxx)
zxxFn()
console.log(zxx)
// 依次输出 1. zxx is a good girl 2. undefined 3. zxx is a great girl
// 4. very good 5.zxx is a good girl
var zxx = 'zxx is a girl'
var zxxs = 'yes'
function zxxFn () {
console.log(zxx) // undefined
console.log(zxxs) // yes
zxx = 'very good'
zxxs = 'not'
console.log(zxx) // very good
console.log(zxxs) // not
var zxx = 'zxx is a great girl'
console.log(zxx) // zxx is a great girl
console.log(zxxs) // not
}
console.log(zxx) // zxx is a girl
console.log(zxxs) // yes
zxxFn()
console.log(zxx) // zxx is a girl
console.log(zxxs) // not
作用域链:
- 遍历嵌套作用域链的规则很简单:引擎从当前的执行作用域开始查找变量,如果找不到, 就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。
- 简单来讲,局部作用域(如函数作用域)可以访问到全局作用域中的变量和方法,而全局作用域不能访问局部作用域的变量和方法。
出现函数嵌套函数,则就会出现作用域链。
zxxAge = 'zxx is 18 years old'
function zxxFn () {
console.log(zxxAge)
var zxxAge = 'zxx is 8 years old'
var zxxSub = 'zxx is 25 years old'
console.log(zxxAge) // zxx is 8 years old
// console.log(zxxSub) // zxxSub is not defined
function zxxFnSub () {
var zxxSub = 'zxx is 18 years old'
console.log(zxxSub) // zxx is 18 years old
console.log(zxxAge) // zxxSub is not defined
}
zxxFnSub()
}
zxxFn()
如图:在执行变量打印的时候,会先在第一作用域寻找该变量是否存在,存在则使用该作用域的变量,不存在则向上寻找第二作用域,以此类推一直找到全局作用域。如果找不到则会抛出异常。
例:
function zxxFn (a) {
console.log(a + b)
}
var b = 2
zxxFn(2) // 4
对变量b查询无法在函数zxxFn内部,但可以在上一级作用域中完成(全局作用域)
注:代码中隐式的 a=2 操作可能很容易被你忽略掉。
比较下列两个代码function zxxFn (a) {
console.log(a + b) // b变量在任何相关的作用域都找不到,引擎就会抛出ReferenceError异常
b = a
}
zxxFn(2)
=================
function zxxFn (a) {
b = a
console.log(a + b)
}
zxxFn(2) // 4
函数被调用之前作用域链已经存在
zxxAge = 'zxx is 18 years old'
function zxxFn () {
console.log(zxxAge)
var zxxAge = 'zxx is 8 years old'
var zxxSub = 'zxx is 25 years old'
console.log(zxxAge) // zxx is 8 years old
// console.log(zxxSub) // zxxSub is not defined
function zxxFnSub () {
console.log(zxxSub) // zxx is 25 years old
console.log(zxxAge) // zxxSub is not defined
}
return zxxFnSub;
}
var zxx = zxxFn()
zxx()
上述代码,在函数被调用之前作用域链已经存在:- 全局作用域 ->
zxxFn
函数作用域 ->zxxFnSub
函数作用域 - 当执行【
zxx();
】时,由于其代指的是zxxFnSub
函数,此函数的作用域链在执行之前已经被定义为:全局作用域 ->zxxFn
函数作用域 ->zxxFnSub
函数作用域,所以,在执行【zxx();
】时,会根据已经存在的作用域链去寻找变量。
var zxx = 'zxx is a great girl'
function zxxFn() {
console.log(zxx)
}
function zxxFnSub() {
var zxx = 'zxx is 18 years old'
return zxxFn
}
var myZxx = zxxFnSub()
myZxx() // zxx is a great girl
上述代码,在函数被执行之前已经创建了两条作用域链:- 全局作用域 ->
zxxFn
函数作用域 - 全局作用域 ->
zxxFnSub
函数作用域
当执行【myZxx();
】时,myZxx
代指的zxxFn
函数,而zxxFn
函数的作用域链已经存在:全局作用域 -> zxxFn
函数作用域,所以,执行时会根据已经存在的作用域链去寻找。
最小特权原则:
- 也叫最小授权或最小暴露原则,这个原则是指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来。
- 比如某个模块或对象的 API 设计。这个原则可以延伸到如何选择作用域来包含变量和函数。如果所有变量和函数都在全局作用域中,当然可以在所有的内部嵌套作用域中访问到它们。
- 但这样会破坏前面提到的最小特权原则,因为可能会暴漏过多的变量或函数,而这些变量或函数本应该是私有的,正确的代码应该是可以阻止对这些变量或函数进行访问的。
例:
function doSomething (a) {
b = a + doSomethingElse(a * 2)
console.log(b * 3)
}
function doSomethingElse (a) {
return a - 1
}
var b
doSomething(2) // 15
在这个代码片段中,变量 b
和函数 doSomethingElse(..)
应该是 doSomething(..)
内部具体 实现的“私有”内容。给予外部作用域对 b
和 doSomethingElse(..)
的“访问权限”不仅没有必要,而且可能是“危险”的,因为它们可能被有意或无意地以非预期的方式使用, 从而导致超出了 doSomething(..)
的适用条件。更“合理”的设计会将这些私有的具体内 容隐藏在 doSomething(..)
内部,例如:function doSomething (a) {
function doSomethingElse (a) {
return a - 1
}
var b
b = a + doSomethingElse(a * 2)
console.log(b * 3)
}
doSomething(2) // 15
现在,b
和 doSomethingElse(..)
都无法从外部被访问,而只能被 doSomething(..)
所控制。 功能性和最终效果都没有受影响,但是设计上将具体内容私有化了,设计良好的软件都会 依此进行实现。