作用域
什么是作用域?
作用域,按字面的意思就是作用的区域。在 JS 代码中无非就是在特定区域(函数、对象)内是否可读(访问)和写(增改查)。
在W3C上 作用域的定义:指的是 有权访问的变量集合。
JS作用域
因为 JS 拥有函数作用域:每个函数创建一个新的作用域。
作用域决定了这些变量的可访问性(可见性)。
函数内部定义的变量从函数外部是不可访问的(不可见的)。
所以作用域分为
全局作用域
局部作用域
全局变量
变量在函数外定义,即为全局变量。
全局变量有 全局作用域: 网页中所有脚本和函数均可使用。
全局变量是全局对象的一个属性。
var carName = " Volvo";
// 此处可调用 carName 变量
function myFunction() {
// 函数内可调用 carName 变量
}
var truevar = 1; // 声明一个不可删除的全局变量
fakevar = 2; // 创建全局对象的一个可删除的属性
this.fakevar2 = 3; // 同上
delete truevar // => false:变量没有被删除
delete fakevar // => true:变量被删除
delete this.fakevar2 // => true:变量被删除
局部作用域
在 JavaScript 函数中声明的变量,会成为函数的局部变量。
局部变量的作用域是局部的:只能在函数内部访问它们。
// 此处的代码不能使用 carName 变量
function myFunction() {
var carName = "porsche";
// 此处的代码能使用 carName 变量
}
作用域链
一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。
但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
var a = 1;
function fn(){
console.log(a)
}
function fn1(fn){
var a = 2;
(function(){
fn() // 1
})()
}
fn1(fn)
作用域的判定
- script 标签中
在 script 块中所有的变量和函数都是全局变量、全局函数。
多个script之间:自上而下,将上一个变量读到仓库,执行完后
再进行到下一个script,上一个script中的变量值仍存在仓库中
- 在函数中
由里到外(局部到全局找变量值)
{}
在浏览器中如何读取 JS 代码
浏览器读取 HTML 文件时,遇到 <script>
标签时会运行“JS解析器”。
第一步:进行预解析
在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var
和 function
声明的变量在内存中进行提前声明或者定义。然后在从上到下去执行代码 ,预解析会把变量和函数的声明在代码执行之前执行完成。
- 注意
变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作
console.log(a) // undefined
var a = 1
相当于
// 所有的变量,在正式运行代码之前,都提前赋了一个值:未定义
var a
console.log(a) // undiefined
a = 1
函数提升 就是把所有的函数声明提升到当前作用域的最前面 不调用函数
fn1()
console.log(c)
console.log(b)
console.log(a)
function fn1(){
var a = b = c = 1
console.log(a)
console.log(b)
console.log(c)
}
相当于
// 所有的函数,在正式运行代码之前,都是整个函数块
function fn1(){
var a
a = b = c = 1
console.log(a)
console.log(b)
console.log(c)
}
fn1() // 1 1 1
console.log(c) // 1
console.log(b) // 1
console.log(a) // not defined
注意
-
遇到重名的:只留一个
-
变量和函数重名了,就只留下函数
第二步:读取代码
-
从上到下,逐行解析代码!
-
注意局部作用域的整体解读!
-
表达式可以修改预解析的值!
-
注意变量提升
-
for 循环和 if 语句不是局部作用域
var a=[]
// 声明全局变量 i=0 ,i++ 不断对 i 进行赋值 i=10 时不符合条件停止执行
for(var i = 0;i<10;i++){
var q = i; // 声明全局变量 q = i
a[i]=function(){console.log(q)}
// i = 10 不执行,存储的内存引用为 q = 9
}
a[0]() // i = 10 q = 9
不过,在 es6 中新增了 let 命令声明变量,用法和 var 类似,不过 let 所声明的变量,只在 let 命令所在的代码块有效果,for 循环的计数器中就很适合 let 命令
var a=[];
for(let i = 0;i<10;i++){
let q = i;
a[i]=function(){console.log(q)}
}
a[6]() // 6
// 这里的 i 只在本轮循环有效果,每次循环的 i 其实都是一个新的变量
- 循环中的闭包
当 console.log
被调用的时候,匿名函数保持对外部变量 i 的引用,此时for
循环已经结束, i 的值被修改成了 10。
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i); // 10次10
}, 1000);
}
为了正确的获得循环序号,最好使用自执行匿名函数。
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
//外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。
//当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。
注意
表达式:= + - * / % ++ -- ! 参数……
let 声明的变量仅在块级作用域有效