1.let 和 const
变量提升:
在声明变量或者函数时,被声明的变量和函数会被提升到函数最顶部;
但是如果声明的变量或者函数被初始化了,则会失去变量提升;
示例代码:
param2 = "第二个参数" console.log(param1); // undefined console.log(param2); // 第二个参数 var param1 = "第一个参数"; var param2;
对于变量 "param1":
它被声明且初始化了,所以失去了变量提升,在 "console.log(param1);" 语句访问 "param1" 时则会得到 "undefined" 的结果无法得到初始化的结果,但是不会报错,在 "console.log(param1);" 之前对param1进行赋值,则会在后面 的console语句中打印出你新赋的值;
对于变量 "param2":
它只是被声明了,所以直接打印则会得到 "undefined" 的结果,因为它本身就没有值,同样在 "console.log(param2);" 语句之前对他初始化,则会在console语句中得到你初始化的值;与"param1" 相比,访问 "param1" 时无法获取到它的初始化的值,但是 "param1" 在访问它的语句之前进行赋值则可以访问它的新值;"param2" 可以在任何访问它之前的语句进行初始化,并且访问的结果就是 "param2" 初始化的值;
let 命令
使用let 声明变量只会在它所在的块级区域生效,并且生效的区域是从这一条声明语句向下延伸到这一个块级区域结束;let声明的变量也不会出现变量提升;
let命令不允许对同一变量的重复声明;
示例代码:
console.log(b); // undefined
{ console.log(a); // Cannot access 'a' before initialization console.log(b); // undefined c = 3; console.log(c); // 3 let a = 1; var b = "这是一条字符串"; var c; console.log(a); // 1 console.log(b); // 这是一条字符串 console.log(c); // 3 } console.log(a);// a is not defined console.log(b); // 这是一条字符串 console.log(c); // 3
对于变量 "a":
两个花括号"{}"之间的区域是使用 "let" 声明的变量 "a" 所在的块级作用域,但是 "a" 能被调用生效的区域只是从 "let a = 1;" 这条声明语句一直向下直到结尾的花括号 "}" 处,在这个范围内,a才是可以被使用的;而从开头的花括号 "{" 一直到 "let a = 1;" 之间这个无法访问到 "a" 的区域叫做 "暂时性死区";
第一条"console.log(a);" 语句提示 “不能在a初始化之前访问”,
第二条 "console.log(a);" 语句打印 '1' ,
第三条 花括号外的 "console.log(a);" 语句打印 "a未定义";
对于变 "b":
它被声明并且初始化了,在console语句中访问得到的值是 "undefined",只有在访问 "b" 的语句之前对 "b" 进行重新赋值,才能拿到 "b" 中的值;并且在花括号之外的区域也能访问到它,同样,只能在花括号包裹的整个区域的 "下方" 才能访问到;
对于变量 "c":
它与 "b" 的区别则如 上方 "变量提升" 部分所讲的一样;
const 命令
const命令与let命令一样是块级作用域,具有暂时性死区,不能对同一变量进行重复声明;
const声明的变量必须初始化,该变量的值无法更改,const定义的变量时一个只读变量;
const的本质是控制变量指向的内存地址所保存的数据不得改动,对于简单变量("字符串","布尔值","数值"),其值就保存在变量指向的存贮地址,所以这类变量是无法重新赋值的,成为一个常量;而对于复杂变量(通常是指对象与数组)变量指向的内存地址保存的是一个指向了实际数据的指针,const能保证的是这个指针指向的地址不变,却不能保证这个数据的结构不发生改变;
示例代码:
1 { 2 const b; // Missing initializer in const declaration 3 const a = "变量a"; 4 const obj = {}; 5 const arr = []; 6 7 console.log(a); // 变量a 8 console.log(obj); // {} 9 console.log(arr); // [] 10 11 obj = { age: 1 }; // Assignment to constant variable. 12 arr = ["数据"]; // Assignment to constant variable. 13 14 obj.name = "张三"; 15 arr.push("一个数据"); 16 console.log(obj); // {name:"张三"} 17 console.log(arr); // ["一个数据"] 18 19 a = "另一个变量"; // Assignment to constant variable. 20 }
对变量 "a":
const初始化后赋值为 "变量a" ,第5行console语句可以访问到初始化的值,第12行代码对简单变量进行赋值则抛出一个错误:"对const类型的变量赋值";
对于变量 "b":
const声明的变量必须初始化;
对于复杂变量 "arr","obj":
第11,12行是将整个变量的指针指向了另一个地址,所以会抛出错误,而14,15行代码则是对原对象(数组)进行数据结构上的改变,obj新增一个属性,arr新增一个数据,他们的指针依旧指向原本指向的地址,只是其中的数据的结构发生了变化所以可以在第16,17行代码中打印出结果;
冻结对象
使用Object.freeze(); 方法可以将一个对象冻结使它无法添加或者修改自己的属性,但是如果是对象数据中嵌套着一个对象属性,这个嵌套的对象是不受影响的,它任然可以对自己的属性进行增删改操作;
这时则需要考虑使用递归来将整个对象进行冻结,从而真正使这个对象变成一个常量;
在第一个代码块中,obj2.obj3是不受 Object.freeze(); 影响的,将第二个代码块中的 freezeAll 方法加入进来后,obj2的全部属性以及嵌套着的对象的属性也被冻结,从而将整个obj2变成了一个常量;
示例代码:
1 { 2 let obj1 = { name: "李四" }; 3 Object.freeze(obj1); 4 console.log(obj1); // {name: "李四"} 5 obj1.name = "张三"; 6 console.log(obj1); // {name: "李四"} 7 8 let obj2 = { age: 14, obj3: { name: "王五" } }; 9 Object.freeze(obj2); 10 obj2.obj3.age = 23; 11 obj2.obj3.name = "张三"; 12 console.log(obj2.obj3); // {name: "张三", age: 23} 13 }
1 { 2 function freezeAll(obj) { 3 Object.freeze(obj); 4 Object.keys(obj).forEach(attr => { 5 if (typeof obj[attr] === "object") { 6 freezeAll(obj[attr]); 7 } 8 }) 9 } 10 freezeAll(obj2); 11 console.log(obj2.obj3); // {name: "张三", age: 23} 12 obj2.obj3.name = "新的名字"; 13 console.log(obj2.obj3); // {name: "张三", age: 23} 14 }