全局作用域是最外层的作用域。
// 全局变量
var globalVar = "全局变量";
let globalLet = "全局let";
const GLOBAL_CONST = "全局常量";
// 在任何地方都可以访问
function showGlobal() {
console.log(globalVar); // "全局变量"
}
// 全局变量成为window的属性(浏览器环境)
console.log(window.globalVar); // "全局变量"
console.log(window.globalLet); // undefined(let不会)
// 不推荐:污染全局命名空间
var name = "东巴文";
var age = 1;
// 推荐:使用命名空间或模块
const App = {
name: "东巴文",
age: 1
};
// 或使用IIFE
(function() {
var name = "东巴文";
var age = 1;
})();
// 使用模块
// module.js
export const name = "东巴文";
export const age = 1;
// 使用块级作用域
{
let name = "东巴文";
const age = 1;
}
// 使用函数作用域
function init() {
var name = "东巴文";
var age = 1;
}
函数内部创建的作用域。
function outer() {
var message = "函数作用域";
function inner() {
console.log(message); // 可以访问外层变量
}
inner();
console.log(message); // "函数作用域"
}
outer();
// console.log(message); // ReferenceError
function test() {
if (true) {
var x = 1; // 函数作用域
}
console.log(x); // 1(可以在if外访问)
}
test();
function test() {
console.log(x); // undefined(提升但未赋值)
var x = 1;
console.log(x); // 1
}
// 等价于
function test() {
var x; // 声明提升
console.log(x); // undefined
x = 1; // 赋值不提升
console.log(x); // 1
}
function test() {
console.log(foo()); // "foo"(函数声明提升)
function foo() {
return "foo";
}
}
// 函数表达式不会提升
function test2() {
// bar(); // TypeError
var bar = function() {
return "bar";
};
}
ES6引入的let和const具有块级作用域。
{
let x = 1;
const Y = 2;
var z = 3;
}
// console.log(x); // ReferenceError
// console.log(Y); // ReferenceError
console.log(z); // 3(var没有块级作用域)
// if语句
if (true) {
let blockScoped = "块级作用域";
}
// console.log(blockScoped); // ReferenceError
// for循环
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0 1 2
}
// console.log(i); // ReferenceError
// for循环使用var的问题
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 3 3 3
}
{
// 暂时性死区开始
// console.log(x); // ReferenceError
let x = 1; // 暂时性死区结束
console.log(x); // 1
}
// typeof也会报错
{
// typeof x; // ReferenceError
let x = 1;
}
| 特性 | var | let | 东巴文建议 |
|---|---|---|---|
| 作用域 | 函数 | 块 | - |
| 提升 | 是 | 是(但TDZ) | - |
| 重复声明 | 允许 | 不允许 | 用let |
| 全局属性 | 是 | 否 | - |
| 重新赋值 | 可以 | 可以 | - |
作用域链决定了变量的查找顺序。
let global = "全局";
function outer() {
let outer = "外层";
function inner() {
let inner = "内层";
console.log(inner); // 在当前作用域找到
console.log(outer); // 在外层作用域找到
console.log(global); // 在全局作用域找到
}
inner();
}
outer();
┌─────────────────────────────────────┐
│ 全局作用域 │
│ global = "全局" │
│ ┌─────────────────────────────┐ │
│ │ outer函数作用域 │ │
│ │ outer = "外层" │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ inner函数作用域 │ │ │
│ │ │ inner = "内层" │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
let x = "全局";
function outer() {
let x = "外层"; // 遮蔽全局x
function inner() {
let x = "内层"; // 遮蔽外层x
console.log(x); // "内层"
}
inner();
console.log(x); // "外层"
}
outer();
console.log(x); // "全局"
JavaScript使用词法作用域(静态作用域)。
let x = 10;
function foo() {
console.log(x);
}
function bar() {
let x = 20;
foo(); // 输出10,不是20
}
bar(); // 10
function createPrinter() {
let message = "东巴文";
return function() {
console.log(message); // 词法作用域:定义时确定
};
}
const printer = createPrinter();
printer(); // "东巴文"(即使createPrinter已执行完毕)
// JavaScript是词法作用域
let name = "全局";
function greet() {
console.log(name); // 使用定义时的作用域
}
function run() {
let name = "局部";
greet(); // 输出"全局",不是"局部"
}
run(); // "全局"
// 如果是动态作用域(如Bash),会输出"局部"
闭包是函数和其词法环境的组合。
function createCounter() {
let count = 0; // 私有变量
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// count变量被"封闭"在闭包中
// 1. 函数嵌套
// 2. 内部函数引用外部函数的变量
// 3. 内部函数被返回或传递到外部
function outer() {
let outerVar = "外部变量";
function inner() {
console.log(outerVar); // 引用外部变量
}
return inner; // 返回内部函数
}
const closure = outer();
closure(); // "外部变量"
function createGreeter(greeting) {
return function(name) {
console.log(`${greeting}, ${name}!`);
};
}
const sayHello = createGreeter("你好");
const sayHi = createGreeter("嗨");
sayHello("东巴文"); // 你好, 东巴文!
sayHi("JavaScript"); // 嗨, JavaScript!
// greeting被保存在闭包中
闭包在实际开发中有很多应用场景。
function createUser(name) {
let _name = name; // 私有变量
return {
getName() {
return _name;
},
setName(newName) {
_name = newName;
}
};
}
const user = createUser("东巴文");
console.log(user.getName()); // "东巴文"
user.setName("JavaScript");
console.log(user.getName()); // "JavaScript"
// _name无法直接访问
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
function setupButtons() {
const buttons = document.querySelectorAll("button");
buttons.forEach((button, index) => {
button.addEventListener("click", function() {
console.log(`按钮${index}被点击`);
});
});
}
// 柯里化
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
// 防抖
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 节流
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
const module = (function() {
let privateVar = "私有变量";
function privateMethod() {
console.log("私有方法");
}
return {
publicVar: "公共变量",
publicMethod() {
console.log(privateVar);
privateMethod();
}
};
})();
module.publicMethod(); // 可以访问
// module.privateVar; // 无法访问
闭包使用不当可能导致问题。
// 闭包会保持对外部变量的引用
function createHeavyClosures() {
const largeData = new Array(1000000).fill("data");
return function() {
console.log(largeData.length); // largeData不会被回收
};
}
// 解决:不需要时设为null
function createBetterClosure() {
let largeData = new Array(1000000).fill("data");
return function() {
console.log(largeData.length);
largeData = null; // 允许垃圾回收
};
}
// 问题:使用var
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i); // 4 4 4
}, 100);
}
// 解决方案1:使用let
for (let i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i); // 1 2 3
}, 100);
}
// 解决方案2:使用IIFE
for (var i = 1; i <= 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 1 2 3
}, 100);
})(i);
}
// 解决方案3:使用forEach
[1, 2, 3].forEach(function(i) {
setTimeout(function() {
console.log(i); // 1 2 3
}, 100);
});
// 不推荐:每次调用都创建新闭包
function process(data) {
const processItem = function(item) {
// 处理逻辑
};
return data.map(processItem);
}
// 推荐:函数定义在外部
function processItem(item) {
// 处理逻辑
}
function process(data) {
return data.map(processItem);
}
IIFE(Immediately Invoked Function Expression)是立即执行的函数表达式。
// 标准形式
(function() {
console.log("立即执行");
})();
// 另一种形式
(function() {
console.log("立即执行");
}());
// 箭头函数形式
(() => {
console.log("立即执行");
})();
(function(name) {
console.log(`你好,${name}!`);
})("东巴文");
// 返回值
const result = (function(a, b) {
return a + b;
})(1, 2);
console.log(result); // 3
// 创建独立作用域
(function() {
var private = "私有变量";
// 不会污染全局
})();
// 模块化
const module = (function() {
let private = "私有";
return {
get() {
return private;
}
};
})();
// 解决循环问题
for (var i = 1; i <= 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 100);
})(i);
}
// IIFE
(function() {
let private = "私有";
})();
// 块级作用域替代
{
let private = "私有";
}
// 模块替代
// module.js
const private = "私有";
export const get = () => private;
掌握了作用域与闭包后,让我们继续学习:
东巴文(db-w.cn) - 让编程学习更简单
🔒 东巴文寄语:闭包是JavaScript最强大的特性之一,理解闭包对于编写高质量代码至关重要。掌握作用域链和闭包,让你的代码更加优雅和强大。在 db-w.cn,我们帮你深入理解每一个概念!