1. 作用域
作用域定义了一个区域,在这个区域内可以访问某些变量和函数。JavaScript 中的作用域有两种类型:
- 全局作用域(Global Scope) :全局作用域中的变量可以在整个程序中访问。
- 局部作用域(Local Scope) :局部作用域中的变量只能在特定的代码块或函数内部访问。
2. 作用域链(Scope Chain)
作用域链是用来查找变量的机制。当你访问一个变量时,JavaScript 引擎会沿着作用域链从当前作用域开始查找,直到全局作用域。每个作用域都有一个与外部作用域连接的链条,直到最外层的全局作用域。
let a = 'global';
function outer() {
let b = 'outer';
function inner() {
let c = 'inner';
console.log(a, b, c);
}
inner();
}
outer(); // 输出: global outer inner
在 inner 函数中,a 和 b 从外部作用域(outer 和全局作用域)中查找。
3. 变量提升(Hoisting)
JavaScript 中的变量声明(使用 var)和函数声明会被提升到当前作用域的顶部,但变量的初始化会留在原来的位置。let 和 const 声明不会被提升。
console.log(x); // 输出: undefined
var x = 5;
function test() {
console.log(x); // 输出: undefined
var x = 10;
}
test();
4. 闭包(Closures)
闭包允许函数访问外部函数的变量,即使外部函数已经执行完毕。闭包非常强大,它可以保持对外部函数变量的引用
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const increment = outer();
increment(); // 输出: 1
increment(); // 输出: 2
increment(); // 输出: 3
inner 函数是一个闭包,它记住了 outer 函数的作用域,因此每次调用 increment() 时,count 变量的值会累加。
5. 块级作用域(Block Scope)
JavaScript 中使用 let 和 const 声明的变量具有块级作用域。也就是说,变量只在 {} 内有效,而 var 声明的变量具有函数作用域。
if (true) {
let a = 1; // 块级作用域
var b = 2; // 函数作用域
}
console.log(a); // 会报错,因为 `a` 在块级作用域内
console.log(b); // 输出 2,因为 `b` 在函数作用域内
6. 箭头函数与作用域
箭头函数与普通函数不同,它的 this 是 静态绑定 的,也就是说,箭头函数的 this 继承自定义时的外部作用域。而普通函数的 this 会根据调用时的上下文来确定。
var name = 'Amaly'
const obj = {
name: 'Alice',
greet: function() {
setTimeout(() => {
console.log(this.name); // `this` 会指向 `obj`
}, 1000);
},
next: function() {
setTimeout(function () {
console.log(this.name); // `this` 会指向 `window`
}, 1000);
}
};
obj.greet(); // 输出: Alice
obj.next(); // 输出: Amaly
8. let 和 const 的作用域
let 和 const 被引入 JavaScript ES6,它们具有块级作用域。在 if 语句、for 循环等块级代码块中使用 let 和 const 时,它们的作用域仅限于当前块。
for (let i = 0; i < 3; i++) {
console.log(i); // 输出: 0, 1, 2
}
console.log(i); // 会报错,`i` 只在 for 循环块内有效
9. this 和作用域
this 的值由函数调用时的上下文决定,而不是由作用域决定。this 在不同的场景中会有所不同:
- 在 全局作用域 或 普通函数调用 中,
this指向全局对象(在浏览器中是window,在严格模式下是undefined)。 - 在 方法调用 中,
this指向调用方法的对象。 - 在 构造函数调用 中,
this指向新创建的实例对象。 - 在 箭头函数 中,
this继承自定义时的外部作用域,不会根据调用时的上下文改变。 举例说明
console.log(this); // 在浏览器中输出: window
function normalFunction() {
console.log(this); // 在浏览器中输出: window
}
normalFunction();
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name); // `this` 指向 obj,输出: Alice
}
};
obj.greet();
const greetFunc = obj.greet;
greetFunc(); // 输出: undefined (因为 this 指向了 global/window)
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 输出: Alice
const obj = {
name: 'Alice',
greet: function() {
setTimeout(() => {
console.log(this.name); // `this` 指向外部 `greet` 方法的 `obj`,输出: Alice
}, 1000);
}
};
obj.greet();
10. 总结
- 作用域 决定了变量和函数的可见性,作用域链通过查找外部作用域来决定变量的访问顺序。
- 闭包 允许函数记住和访问其定义时的作用域,即使外部函数已经执行完毕。
- 非箭头函数**
this** 的值由函数调用时的上下文决定,与作用域无关。 let和const具有块级作用域,能够避免var可能导致的作用域问题。
练习题
- 全局作用域与函数作用域
var a = 10;
function outer() {
var b = 20;
function inner() {
var c = 30;
console.log(a, b, c);
}
inner();
}
outer();
inner 函数执行时,console.log(a, b, c) 会输出什么?请解释为什么。
let和const的块级作用域
if (true) {
let a = 1;
const b = 2;
var c = 3;
}
console.log(a); // 会报错还是输出什么?
console.log(b); // 会报错还是输出什么?
console.log(c); // 输出什么?
问题:解释 a 和 b 为什么会报错,而 c 不会报错?并说明 let 和 const 的块级作用域的区别。
- 闭包和作用域链
function createCounter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 输出什么?
counter(); // 输出什么?
counter(); // 输出什么?
解释为什么 count 的值会持续增加。counter 函数是如何记住 count 变量的?
this和作用域
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};
const greetFunc = obj.greet;
greetFunc();
问题:obj.greet() 和 greetFunc() 的 this 指向有什么不同?分别输出什么?为什么?
- 箭头函数与作用域
const obj = {
name: 'Alice',
greet: function() {
setTimeout(() => {
console.log(this.name); // `this` 会指向什么?
}, 1000);
}
};
obj.greet();
问题:this 的值会指向谁?请解释为什么?
- 函数作用域中的变量提升
console.log(x); // 输出什么?
var x = 5;
function test() {
console.log(x); // 输出什么?
var x = 10;
}
test();
问题:解释变量提升的行为,为什么 x 在 test 函数中的输出是 undefined?
- 闭包与定时器
function createFunctions() {
const result = [];
for (var i = 0; i < 3; i++) {
result[i] = function() {
console.log(i);
};
}
return result;
}
const funcs = createFunctions();
funcs[0](); // 输出什么?
funcs[1](); // 输出什么?
funcs[2](); // 输出什么?
问题:为什么输出的是 3, 3, 3,而不是 0, 1, 2?如何修改代码使其输出 0, 1, 2?
this在箭头函数中的行为
const obj = {
name: 'Alice',
greet: () => {
console.log(this.name);
}
};
obj.greet();
问题:obj.greet() 输出什么?为什么 this 在箭头函数中指向 window 或 undefined?
- 块级作用域与函数作用域
function test() {
if (true) {
let a = 'Hello';
var b = 'World';
}
console.log(a); // 输出什么?
console.log(b); // 输出什么?
}
test();
问题:解释为什么 a 会报错,而 b 不会报错。
this和构造函数
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 输出什么?
问题:this 在构造函数中的指向是什么?为什么 alice.name 能正确输出 Alice?