执行环境
在了解作用域前,先了解一下执行环境,也叫执行上下文。每个函数都有一个自己的执行环境,书中(javascript高级程序设计)说明
每个执行环境都有一个 与之关联的变量对象(variable object)。
我们不妨将这个执行环境看做这个对象,它里面包含了在函数中定义的变量、函数等。函数定义时,会生成该函数的执行环境。当函数被执行时,该执行环境(该对象)被推入环境栈中,执行完毕时,该对象会被弹出销毁。然后执行环境会改为当前栈顶的对象。
默认的环境变量是全局环境,与之关联的对象就是window。局部环境的关联对象开始只有一个变量-arguments
作用域和作用域链
作用域控制着变量与参数的可见性及生命周期。关于执行环境和作用域,书中没有明确指出作用域和执行环境的异同。执行环境应该指的是上下文环境,和作用域还是有点区别的。例如,函数中,作用域用于定义函数时,上下文环境还包括调用函数时的环境。
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。当函数执行时,函数内部访问的变量会首先在当前作用域(即作用域链的前端)寻找,如果找不到,则会依次向后寻找,直到找到变量,否则会报错。举个例子:
var color = "red"
function getSelfColor() {
var color = "yellow"
console.log(color)
}
function getColor() {
console.log(color)
}
getSelfColor()//yellow
getColor()//red
-
在该例子默认的全局环境是window,在该环境中定义了color变量和getSelfColor、getColor两个函数,当getSelfColor和getColor函数被调用时, 会创建该函数环境关联对象的一个作用域链
window
- color
-
getSelfColor
color
- getColor
当getSelfColor执行时,该函数的关联对象推入栈顶,里面定义了color变量,当调用console.log()函数时,优先在当前环境中查找变量,color为yellow。
当getSelfColor执行时,里面没有color变量,当调用console.log()函数时,优先在当前环境中查找变量,因为并没有找到,所以沿作用域链向后查找,color为red。
this
js中的this是个很关键的值,它与函数的执行环境有关。this的值在函数定义时是无法确定的,只有当函数执行时,才能确定this。
window.name = "llx"
var a = {
name: 'A',
fn: function() {
console.log(this.name)
}
}
a.fn()
a.fn.call({name: 'B'})
var fn1 = a.fn
fn1()
//结果:
//A
//B
//llx
js没有块级作用域
js中没有块级作用域,只有函数和全局作用域。即在{}中定义的变量会被添加到当前作用域中,当{}内部的代码被执行完毕以后,并不会销毁变量,因为变量所在的环境的代码并没有被执行完。举例:
if (true) {
var color = "red"
}
console.log(color)//red
此处的{}里面的定义的变量可以被外部访问到,因为里面的部分并不会单独构建新的作用域。
变量提升
在当前作用域的变量定义会被提升到当前环境下代码运行的最前面,这就是javascript的变量提升。举例:
console.log(color)
var color = "red"
concole.log(color)
//结果
//undefined
//red
第一行的打印并没有报错,就是因为javascript的变量提升机制,代码实际的解释执行是这样的:
var color
console.log(color)
var color = "red"
concole.log(color)
闭包
闭包是指有权访问另一个函数作用域中的变量的函数,闭包的常见形式是在一个函数内部创建另一个函数,一般用来包裹的外层函数是匿名函数。但是闭包并不是指外层的匿名函数。看一个闭包的小例子:
for (var i = 0; i < 10; i ++) {
(function(i) {
var a = document.createElement('a')
a.innerHTML = i + '<br>'
a.style.cursor = "pointer"
a.addEventListener('click', function(e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
})(i)
}
//例子为创建10个a标签,并且为其添加点击事件,输出a标签的序号
可以看到在上面的例子中,for循环的内部添加一个自执行的函数,并传入变量i,在为a标签绑定事件时,该函数可以获取到上层作用域的变量i,然后打印。
闭包主要用于封装变量,收敛权限。
function isFirstLoad() {
var _list = []
return function (id) {
if (_list.indexOf(id) >= 0) {
return false
} else {
_list.push(id)
return true
}
}
}
//使用闭包
var firstLoad = isFirstLoad()
console.log(firstLoad(10))
console.log(firstLoad(10))
console.log(firstLoad(20))
//结果
//true
//false
//true