js中有三种方式可以声明变量:var、let、const
可以从以下方面来区分他们。
一、作用域
-
在函数外
var globalVar = "我是全局变量"; // var会声明全局变量
let letVar = "我不是window属性"; // let声明的不会声明全局变量
const constVar = "我也不是"; // const声明的也不会// 全局变量自动成为window的属性
console.log(window.globalVar); // "我是全局变量"
console.log(window.letVar); // undefined
console.log(window.constVar); // undefined -
在函数内
function test() {
if (true) {
var x = 10; // 函数作用域
let y = 20;
const z = 30; // let/const - 块级作用域
}
console.log(x); // 10 - 在函数内可访问
console.log(y); // ReferenceError: y is not defined
console.log(z); // ReferenceError: z is not defined
}
二、变量提升
变量提升就是JavaScript 在编译阶段就把所有变量声明记录进作用域,但赋值操作仍留在原地的一种机制。
声明"提前",赋值"不动"。
// var a = 5;提到了前面
console.log(a); // undefined (不会报错)
var a = 5;
// let/const 编译时已知道有个变量 b,但进入 TDZ(暂时性死区),
// 在 let/const b 语句执行完之前都不能读/写。
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;
三、重新赋值
var 和 let 可以重新赋值,const 不可以重新赋值,但可以改属性
var name = "Alice";
name = "Bob"; // ✅
let age = 25;
age = 26; // ✅
const PI = 3.14; //const 变量必须在声明时赋值
PI = 3.14159; // TypeError: Assignment to constant variable
const person = { name: "Alice" };
person = { name: "Bob" }; // ❌ 不能重新赋值
person.name = "Bob"; // ✅ 可以修改属性
const numbers = [1, 2, 3];
numbers = [5, 6, 7]; // ❌ 不能重新赋值
numbers.push(4); // ✅ 可以修改数组内容
// 冻结对象(浅冻结)
const obj = { name: 'Alice', info: { age: 18 } };
Object.freeze(obj); // 第一层冻结
obj.name = 'Bob'; // 静默失败(严格模式会抛错)
obj.info.age = 20; // ✅ freeze 只冻结浅层
四、重复声明
var可以重复声明,let和const不能
var count = 1;
var count = 2; // ✅ 不会报错
let total = 10;
let total = 20; // ❌ SyntaxError: Identifier 'total' has already been declared
const MAX = 100;
const MAX = 200; // ❌ SyntaxError: Identifier 'MAX' has already been declared
var count = 1;
var count = 2;
console.log(count);
// 同一块作用域里多次 var 声明会被合并。等价于============ >>
var count; // 提升后只声明一次
count = 1; // 第一次赋值
count = 2; // 第二次赋值
console.log(count); // 2
五、总结
-
如果值不需要改变 用
constconst API_URL = "https://api.example.com";
const container = document.getElementById('container'); -
需要重新赋值时使用
letlet counter = 0;
counter = 1; // 需要改变值for (let i = 0; i < 10; i++) {
// 循环变量需要改变
} -
容易引起错误的
varfor (var i = 0; i < 5; i++) {
console.log(i); // 输出 0、1、2、3、4。同步执行
}for (var i = 0; i < 5; i++) { // var 是函数作用域,所有回调都引用同一个 i
setTimeout(function() { // 循环会立即执行完毕,i 的值从 0 增加到 5
console.log(i); // setTimeout 的回调是异步的,会在 100ms 后执行
}, 100); // 当回调函数执行时,循环早已结束,此时的 i 已经是 5
} // 所以输出 5个5。for (let i = 0; i < 5; i++) { // let 是块作用域,每次循环迭代都会创建一个新的 i 变量
setTimeout(function() { // 这就形成了 5 个独立的闭包
console.log(i); // 输出 0、1、2、3、4
}, 100);
}// var 通过立即执行函数来创建闭包,实现同样的效果
for (var i=0; i<5; i++){
function(j){ // t=0ms: 循环开始,创建5个定时器
setTimeout(function(){ // t=0ms: 循环结束
console.log(j); // t=100ms: 5个定时器同时触发,打印 0 1 2 3 4
}, 100);
}(i)
}
六、跨js文件的变量
index.html 中有一个按钮
<button onclick="dayin()">点击</button>
<!-- type="module" 是使用 import 的关键 -->
<script type="module" src="script.js"></script>
script.js 从 test.js 导入变量
import { url } from './test.js'
// type="module",这个函数默认不会添加到全局 window 对象
// 需要通过 window.dayin = dayin 显式暴露给全局作用域
window.dayin = dayin;
function dayin() {
alert(url);
}
test.js中的变量需要导出
// export 导出 url 变量,使其他模块可以导入使用
export var url = "yang";