文章目录
- 写在前面
- 1.var关键字
- 1.1 没有块级作用域的概念,有全局作用域、函数作用域的概念
- 1.2 存在变量提升
- 1.3 全局作用域用var声明的变量会挂载到window对象上
- 1.4 同一作用域中允许重复声明
- 1.5 不初始化值默认为undefined
- 2.let关键字
- 2.1 有块级作用域的概念
- 2.2 不存在变量提升
- 2.3 同一作用域内不允许重复声明
- *2.4 暂时性死区(dead zone)
- 3.const关键字
- 3.1 创建后必须立即初始化,不能留到以后赋值
- *3.2 创建常量
- 3.2.1 es5实现创建常量
- 3.2.1 实现const创建的引用类型为常量
写在前面
该格式的文字为个人理解,纯大白话
文章标题前加* 代表该知识点为不好理解或容易遗漏的知识点
1.var关键字
1.1 没有块级作用域的概念,有全局作用域、函数作用域的概念
例1:
//Global Scope
{
var a = 10;
}
console.log(a); //10
在大括号内部声明a,在大括号外部仍然可以访问,由此可以看出var声明的变量不存在块级作用域(Block Scope)的概念
例2:
//Global Scope
var a = 10;
function checkscope(){
//Local Scope
var b = 20;
console.log(a); //10
console.log(b); //20
}
checkscope();
console.log(b); //ReferenceError: b is not defined
上面代码中,在 Global Scope 中用 var 声明了 a,在 checkscope 函数中的 Local Scope(本地作用域、函数作用域)中打印出了 10,但是在 Global Scope 中打印的变量 b 报错了。
在全局中声明了a,存在全局作用域中,函数(checkscope)内部也可以访问。在函数(checkscope)内部声明的变量,仅存在与函数内部作用域中,函数外无法访问
1.2 存在变量提升
例3:
//Global Scope
console.log(a); //undefined
var a = 10;
checkscope();
function checkscope(){
//Local Scope
console.log(a); //undefined
var a;
}
先打印了a,然后用var声明变量a,打印结果为undefined。
变量提升是因为js需要经理编译和执行阶段。js在编译阶段的时候,会搜集所有的变量声明并且提前声明变量
1.3 全局作用域用var声明的变量会挂载到window对象上
//Global Scope
var a = 10;
console.log(a); //10
console.log(window.a); //10
console.log(this.a); //10
上面代码中,打印出了 3 个 10,访问 a 和 window.a 或是 this.a 都是等价的。
举个例子:比如我要访问 location 对象,使用 location 可以访问,使用 window.location 也可以访问,只不过 window 对象可以省略不写,就像 new Array( ) 和 new window.Array( ) 是等价的。
1.4 同一作用域中允许重复声明
//Global Scope
var a = 10;
var a = 20;
console.log(a); //20
checkscope();
function checkscope(){
//Local Scope
var b = 10;
var b = 20;
console.log(b); //20
}
上面代码中,在 Global Scope 中声明了 2 次 a,以最后一次声明有效,打印为 20。同理,在 Local Scope 也是一样的。
1.5 不初始化值默认为undefined
//Global Scope
var a;
console.log(a); //undefined
在 Global Scope 中用 var 声明了 a,但没有初始化值,它的值默认为 undefined
这里是 undefined 是 undefined 类型,而不是字符串。
2.let关键字
2.1 有块级作用域的概念
{
//Block Scope
let a = 10;
}
console.log(a); //ReferenceError: a is not defined
在大括号内声明的变量,在大括号外无法被访问
2.2 不存在变量提升
console.log(a); // Uncaught ReferenceError: a is not defined
let a = 10;
上面代码中,打印 a 报错:无法在初始化之前访问。说明不存在变量提升。
2.3 同一作用域内不允许重复声明
{
//Block Scope
let A;
var A; //SyntaxError: Identifier 'A' has already been declared
}
{
//Block Scope
var A;
let A; //SyntaxError: Identifier 'A' has already been declared
}
{
//Block Scope
let A;
let A; //SyntaxError: Identifier 'A' has already been declared
}
*2.4 暂时性死区(dead zone)
在作用域内第一行到使用let、const声明语句之间,存在暂时性死区,访问该变量就会报错。
ECMAScript2015文档中的解释:
当程序的控制流程在新的作用域(module、function 或 block 作用域)进行实例化时,在此作用域中用 let/const 声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区。
个人理解:let/const 命令会使区块形成封闭的作用域,在此作用域中,let/const* 声明的变量会先在作用域中被创建,一直到变量被声明之前,该变量都是不可用的,访问就会报错。在语法上称为 “暂时性死区”( temporal dead zone,简称 TDZ)
if (true) {
//暂时性死区(TDZ)开始
console.log(a); //ReferenceError: Cannot access 'a' before initialization
let a; //暂时性死区(TDZ)结束
console.log(a); //undefined
a = 123;
console.log(a); //123
}
例4:
{
//Block Scope
console.log(a); //ReferenceError: Cannot access 'a' before initialization
let a = 20;
}
专属报错:初始化之前无法访问
区别:
这里的暂时性死区存在于块级作用域中,若在全局作用域中,则报错‘is not defined’
3.const关键字
const除了具有以上let中的四种特性(存在块级作用域、不存在变量提升、同一作用域内不允许重复声明、存在暂时性死区)之外,还有以下特点:
3.1 创建后必须立即初始化,不能留到以后赋值
用 const 声明的变量 a 没有进行初始化,所以报错。
*3.2 创建常量
const创建常量的特点:
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
对于简单类型的数据(数值、字符串、布尔值)而言,值就保存在变量指向的内存地址中,因此等同于常量。但对于复合类型的数据(主要是对象和数组)而言,变量指向的内存地址保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,这完全不能控制。
例5:
const 创建的变量的内存地址不可改变,而对于简单的数据类型,值就存在于变量指向的内存地址中,所有a的值不可以改变
例6:
对于复杂的数据类型,内存地址中保存的是一个指针,const只能保证这个指针是固定的,指针指向的数据结构是可以改变的
3.2.1 es5实现创建常量
Object.defineProperty
三个参数:
- 属性所在的对象
- 属性的名字
- 一个描述符对象:
- configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为false。
- enumerable:表示能否通过for in循环访问属性,默认值为false
- writable:表示能否修改属性的值。默认值为false。
- value:包含这个属性的数据值。默认值为undefined。
实现:
Object.defineProperty(window,"args",{
value: ' this is es5',
writable: false
})
报错:常量变量赋值
3.2.1 实现const创建的引用类型为常量
const obj={
id:1,
age:18,
name:'aaa'
}
obj.age=19 // cosnt 创建一个对象,对象中的属性可以被改变
追问1:如何使对象中的属性不可改变
解决:使用Object.freeze(),冻结对象,一个被冻结的对象再也不能被修改
const obj2={
id:2,
name:'bbb',
age:20,
food:['banana','apple']
}
Object.freeze(obj2)
obj2.age=21 //被Object.freeze()冻结后,不可以改变
obj2.foods[1]='pear' //可以改变 freeze只能冻结根层 嵌套引用类型需要嵌套递归
解决:实现创建引用类型
思路:使用递归,将每一层的引用类型冰冻,实现创建引用类型的常量。
function deepFreeze(obj) {
Object.freeze(obj);
(Object.keys(obj) || []).forEach((key) => {
let innerObj = obj[key]
if (typeof innerObj === 'object') {
deepFreeze(innerObj);
}
}
)
}
const tempObj = {
id: 23,
name: '1',
food: ['banana', 'apple']
}
deepFreeze(tempObj)