作用域
作用域控制变量的访问范围、声明周期。分为全局作用域和局部作用域。局部作用域可以访问全局作用域的变量,但反过来不行。 在javascipt中,变量的作用域有全局作用域、函数作用域和块级作用域。
一、全局作用域
以下两种方式声明的都是全局变量,可以直接在全局作用域中使用。
var a = 10;
function test() {
console.log(a);
}
window.test(); // 10
省略var关键字声明的变量是全局变量.
function test() {
a = 10;
console.log(window.a);
}
test(); // 10
所有的全局变量都挂载到window对象上,可以通过window.变量名访问,其中还包含一些浏览器内置的全局变量,如window.onload、window.setTimeout、window.alert等。
console.log(window.innerHeight); // 窗口高度
console.log(window.innerWidth); // 窗口宽度
console.log(window.location); // 页面地址
二、函数作用域
var a = 10;
function test() {
var b = 100;
console.log(a,b); // 10,100
}
console.log(b); // ReferenceError: b is not defined
三、块级作用域 在es6之前使用var 声明的变量只有函数作用域和全局作用域,而es6中引入了let和const关键字可用于声明块级作用域的变量,它使用{}来定义一个块级作用域,例如for(){}、if(){}、switch(){}等。 块级作用域中声明的变量只能在该作用域中使用,且不存在变量提升,解决了变量覆盖的问题。
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
} // 0 1 2 3 4
if(false){
let a = 10;
}
{
let a = 20;
console.log(a); // 20
}
console.log(a); // ReferenceError: name is not defined
四、函数和变量的声明提升 使用var关键字声明的变量和函数会进行提升,变量声明提升,函数声明整体提升。
console.log(c); // undefined
console.log(f()); // fn
var c = 20;
function f(){
return 'fn'
}
五、如何访问函数作用域中的变量?
- 保存为全局变量
function fn(){
window.a = 10;
}
- 函数返回值
function fn2(){
var a = 10;
return a;
}
- 闭包
function fn3(){
var a = 10;
return function(){
return a;
}
}
var func = fn3();
console.log(func()); // 10
作用域链
作用域链是由一系列变量对象组成的链条,它决定了变量的查找方式。当在某个作用域中访问一个变量时,会先在当前作用域中查找,如果没有找到,则逐级向上查找,直到全局作用域。
var a = 10;
function test() {
var b = 100;
function inner() {
console.log(a,b); // 10,100
}
inner();
}
test();
- 首先,在test()函数中,声明了变量b,它位于test()函数的作用域中。
- 然后,在test()函数中调用了inner()函数,inner()函数位于test()函数的作用域中。
- 进入inner()函数,首先查找变量a,它位于inner()函数的作用域中,找到了a,返回10。
- 然后查找变量b,它位于inner()函数的作用域中,找到了b,返回100。
- 最后,打印a和b的值,结果为10和100。
词法作用域和动态作用域
在javascipt中,变量的作用域采用的是词法作用域(静态作用域),即变量的声明的位置决定了它的作用域,与之相反的是动态作用域即变量的作用域是由运行时环境决定的.
var count = 10
function fn4(){
var count = 20;
fn(5)
}
function fn5(){
console.log(count);
}
console.log();// 10
上面的例子如果是静态作用域输出10,如果是动态作用域输出20。
在javascript中,this指向的作用域就是动态的,它总是指向函数的直接调用者。
// this的执行是动态的
const student = {
name: 'Tom',
sayName: function(){
console.log(this.name);
}
}
const teacher = {
name: 'Jane',
}
console.log(student.sayName()); // Tom
console.log(student.sayName.call(teacher)); // Jane
拓展
对于下面的代码你有几种方式得到正常的输出结果?
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // 5 5 5 5 5
}, 0);
}
- 自执行函数(IIFE),将每次循环的i保存到函数作用域中
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(function () {
console.log(i); // 5 5 5 5 5
}, 0);
})(i);
}
- 使用函数参数,将每次循环的i保存到函数作用域中
for (var i = 0; i < 5; i++) {
timeout(i);
}
function timeout(i) {
setTimeout(() => {
console.log(i)
}, 0);
};
- 使用let关键字声明变量,将每次循环的i保存到块级作用域中
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // 0 1 2 3 4
}, 0);
}
- 使用async
function timeout(time) {
return new Promise(function(resolve, reject){
setTimeout(() => {
resolve();
}, time);
})
}
async function fn() {
for (var i = 0; i < 5; i++) {
await timeout(0);
console.log(i); // 0 1 2 3 4
}
}
fn();
- 使用setTimeout的第三个参数, 本质上也是将循环过程中的i保存到函数作用域中。
for (var i = 0; i < 5; i++) {
setTimeout((i) => {
console.log(i); // 1 2 3 4 5
}, 0, i);
}