1. var声明的变量会挂载在window上,而let和const声明的变量不会
let / const允许把变量的作用域限制在块级域中;var 申明变量要么是全局的,挂载在window上,要么是函数级的,限制在函数作用域内,而无法是块级的。
function fn(){
var username = '1';
console.log(username);
}
fn();
console.log(username); // 报错 变量存在函数作用域内。
2. var声明变量存在变量提升,let和const不存在变量提升
console.log(mom); // undefined 虽然开始没有声明mom但是,默认赋值undefined。
var mom = '我是妈妈';
// 整个过程相当于
var mom;
console.log(mom);
mom = '我是妈妈';
console.log(mom); // error 报错
let mom = '我是妈妈'; // let和const不存在变量提升
// var 存在变量提升,而 let,const(后面会提及)声明的变量却不存在变量提升,所以用 let 定义的变量一定要在声明后再使用,否则会报错。
参照后文中暂存死区部分。
3. let和const声明形成块作用域
- var:只有全局作用域和函数作用域概念,没有块级作用域的概念。但是会把{}内也假称为块作用域。
- let / const:只有块级作用域的概念 ,由 { } 包括起来,if语句和for语句里面的{ }也属于块级作用域。
- 块级作用域可以嵌套。 内部的作用域可以访问到外层作用域
4. 同一作用域下let和const不能声明同名变量,而var可以
for (var i = 0; i < 3; i++) {
console.log(i); // 0 1 2
}
console.log(i); // 3 var声明了变量之后,就一直存在,for循环结束后,他还在。
var i;
console.log(i); // 依然还是3
for (var i = 0; i < 3; i++) { // 可以继续声明i,并重新赋值
console.log(i); // 0 1 2
}
let son = '这是son';
let son; // error 变量已经声明过了
// 但是
let letClass = function(){
let name='xiaoming';
if(true){
let name='saucxs';
console.log(name);
}
console.log(name);
}
letClass();
// 上面的结果说明了let只在{}内使用。
// 先let后var
let subClass = function(){
let name='xiaoming';
if(true){
var name='saucxs'; // error
console.log(name);
}
console.log(name);
}
subClass();
// var 是函数级作用域,相当于这个函数作用域中有let和var声明的两个name的变量了
// var 作用于整个 subClass,不仅仅是if(if只是个块级作用域,无法限制var) ,和let冲突了,let不能重复声明,already been declared=已经被声明
// 先var后let 可以正常运行,块级作用域内let覆盖了var定义的变量
let subClass = function(){
var name='xiaoming';
if(true){
let name='saucxs';
console.log(name);
}
console.log(name);
}
subClass();
5. const和let区别
const一旦声明必须赋值, 必须赋值,必须赋值!
不能使用null占位;声明后不能再修改 ;如果声明的是复合类型数据,可以修改其属性。具体参照后文中const的补充部分。
暂存死区
ES6明确规定,如果区块中存在let命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。所以在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
var monkey = '111';
{
console.log(monkey); // 111 块级作用域对于var没效果,会自动查找上面的变量
var monkey = '222';
}
console.log(monkey); // 222 块级作用域中修改了变量,生效了
let monkey = '111';
{
console.log(monkey); // error let直接封闭作用域,这里的变量,不能向上找了,向下又找不到
let monkey = '222';
}
console.log(monkey);
let monkey = '111';
{
let monkey = '222';
console.log(monkey); // 222 222这个let只在当前作用域内生效
}
console.log(monkey); // 111
let a = 'outside';
if(true) {
console.log(a); //Uncaught ReferenceError: a is not defined
let a = "inside";
}
// 当前作用域顶部到该变量声明位置中间的部分,都是该let变量的死区,在死区中,禁止访问该变量。由此,我们给出结论,let声明的变量存在变量提升, 但是由于死区我们无法在声明前访问这个变量。
// “暂时性死区”也意味着typeof不再是一个百分之百安全的操作,因为会使typeof报错。
{
typeof name; //ReferenceError
let name;
}
// 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。在代码块中,使用let命令声明变量之前,该变量都是不可用的,这在语法上称为“暂时性死亡”。
常量const的补充
除了let以外,ES6还引入了cons,const 和 let 的作用域是一致的,不同的是 const 变量一旦被赋值,就不能再改变了,但是这并不意味着使用 const 声明的变量本身不可变,只是说它不可被再次赋值了,而且const 声明的变量必须经过初始化。
- 声明常量
const a = 1; // 常量在声明的时候必须被初始化
a = 2; // error 常量不能被修改
const xiaoming = { // 这是一个引用类型的常量 里面的内容可以修改
age: 14,
name: 'xiaoming',
};
// 注意,修改其属性值是没问题的,不能修改引用
xiaoming = { age: 13, } // 报错
xiaoming.age = 22; // const只会保证这个引用常量的地址不发生改变,修改里面的值不会改变地址。
console.log(xiaoming); // 此处age变成了22
xiaoming = {}; // 报错
const ARR = [];
ARR.push(1); // 可以改变数组中的元素。
- Question1: 解决引用类型的常量可以被修改的问题
// 使用Object.freeze()
const xiaoming = {
age: 14,
name: 'xiaoming',
};
Object.freeze(xiaoming);
xiaoming.age = 22;
console.log(xiaoming); // 虽然不会报错,但是没有任何修改
- Question2: ES6之前怎么声明常量
// 使用Object.defineProperty();
var CST = {};
Object.defineProperty(CST, 'BASE_NAME',{
value: 'xiaoming', // BASE_NAME = 'xiaoming'
writable: false, // 该对象,不可被修改
});
// 上面这样只是不能修改里面的属性。但是可以加属性 例如: CST.a = 2; 就可以加进去
Object.seal(CST); // 只有freeze一半的功能,不允许拓展,但是可以修改内部的值
ES6之前声明常量 (实现freeze效果)
1.遍历属性和方法
2.修改遍历到的属性和描述
3.Object.seal()
Object.defineProperty(Object, 'freezePolyfill', {
// 相当于 Object.freezePolyfill = function(){} 这是自己定义一个函数
value: function (obj) {
var i;
for(i in obj){
// for(i in obj) 不光会遍历到obj下的属性,还会遍历到obj._proto_下的属性。 hasOwnProperty确保遍历到的只是obj下的属性
/*
var obj1 = {a:1 , b :2,};
var obj2 = Object.create(obj1); // 把obj1作为原型创建obj2,此时obj1的属性就会作为obj2._proto_ 保存
obj2.c = 3; // obj2 = { c: 3, d : 4, _proto : {a : 1, b : 2,} }
obj2.d = 4;
for(let i in obj2){ document.body.innerHTML += (i + ': ' + obj2[i] + '<br>'); } // 此处会遍历出所有a,b,c,d
*/
if(obj.hasOwnProperty(i)){
Object.defineProperty(obj, i, {
writable: false,
});
}
}
Object.seal(obj);
}
});
const xiaoming = {
age: 14,
name: 'xiaoming',
};
Object.freezePolyfill(xiaoming); // 此时 这个引用类的常量内部的属性都不能修改了
相关问题
Question: 生成十个按钮,每个点击的时候弹出1 - 10
var i = 0;
for (i = 1; i <= 10; i++) {
// 一个自运行的函数 (function (i))(i) 相当于(function (i))(1) (function (i))(2) (function (i))(3) ...
// 这个函数里面的i是独立的作用域,是最后(i)传进来的
(function (i) {
var btn = document.createElement('button');
btn.innerText = i;
btn.onclick = function () {
alert(i);
};
document.body.appendChild(btn);
})(i)
}
// 去掉自运行的函数,结果会生成1-10个按钮,但是弹出来的永远是11
var i = 0;
for (i = 1; i <= 10; i++) {
var btn = document.createElement('button');
// 这个i会遵循1-10的循环,所以生成按钮内容都是1-10
btn.innerText = i;
// 这里的i并不参与循环,相当于给每个按钮绑定一个事件。此时alert里面的值没确定。每次点击按钮的时候,alert(i)就会向上去找i,此时i已经结束循环了,变成11
btn.onclick = function () {
alert(i);
};
document.body.appendChild(btn);
}
// 用let就和其他java等语言一样,这个i只作用于当前for循环的作用域内,不需要自运行函数
for (let i = 1; i <= 10; i++) {
var btn = document.createElement('button');
btn.innerText = i;
btn.onclick = function () {
alert(i);
};
document.body.appendChild(btn);
}
此问题详细解释,参考另一篇文章