相同点
var
、let
、const
三者都可以声明变量。变量可以看作盒子,变量名就是盒子名称,值是放在盒子里的东西。
// var 声明变量,初始值可选
var name;
var name = "Lucy";
// let 声明变量,初始值可选
let age;
let age = 12;
// const 声明常量,必须要赋初始值
const city = "Beijing";
// 如果给常量重新赋值会报错
const city = "Shanghai"; // Uncaught SyntaxError: Identifier 'city' has already been declared
// 如果常量的值是对象(数组),不可以修改常量指向的引用,但是可以修改引用的值
const cities = ["Beijing"];
cities[0] = ["Shanghai"]; // "Shanghai"
差异
var
、let
、const
三者的差异:
注:
暂时性死区:Temporal dead zone(TDZ)
全局属性:是否会被添加到 window
或 globalThis
等对象中
暂时性死区
从一个代码块的开始直到代码执行到声明变量的行之前,let
或 const
声明的变量都处于“暂时性死区中。简单理解:let
或 const
只能先声明再访问。如下面的代码:
// 访问 person 变量
// 变量 person 使用 let 声明,声明不会提升,因此此处访问会报错
console.log(person); // ReferenceError: person is not defined
// 声明 person
let person = {
name: "Lucy"
};
同理,使用 const
声明变量,如果在声明前使用,表现与 let
一致。var
声明的全局变量会进行变量提升:
console.log(person); // undefined
var person = {
name: "Lucy"
};
全局属性
var
声明的变量会被添加到全局对象中,可以使用 window
和 globalThis
访问,let
和 const
声明的全局变量则不会添加到全局对象中。
var name = "Lucy";
console.log(window.name); // "Lucy"
console.log(globalThis.name); // "Lucy"
const age = 12;
console.log(window.age); // undefined
console.log(globalThis.age); // undefined
let gender = "female";
console.log(window.gender); // undefined
console.log(globalThis.gender); // undefined
小练习
以下代码输出结果是什么?
const obj = { prop: 0 };
obj.prop = obj.prop + 1;
console.log(obj.prop); // 1. 打印结果是什么?
obj = {}; // 2. 执行结果是什么?
答案:1 处输出 1, 2 处报错: “Uncaught TypeError: Assignment to constant variable”。这与 const
的特性有关,const
仅仅意味着变量名和值的绑定是不可变的,但是它的值可以是可变的,如 1 处 的值 { prop: 0 }
就可以更改。因此,我们可以得出结论:1 处 obj
的值是对象,可以改变 obj
的属性,然而不能重新给 obj
赋新的值(2 处)。
作用域
作用域是当前的执行上下文,值和表达式在其中“可见”或可被访问。
静态作用域和动态作用域
作用域分为静态作用域(又称为词法作用域)和动态作用域:
JavaScript 作用域
JavaScript 的作用域可以分为以下四种:
接下来,我们分析一下各种作用域的特点。
全局作用域
JavaScript 变量作用域是嵌套的,它们形成树:
全局作用域中的变量称为全局变量,可以在任何作用域内访问。有两种全局变量:
const
、let
和 class
声明的变量。在最顶级由 var
和 function
声明后创建的变量
全局对象可以通过 globalThis
和 window
访问,它可以对全局对象变量进行增删改查
全局属性 globalThis
包含全局的 this
值,类似于全局对象。globalThis
提供了一个标准的方式来获取不同环境下的全局 this
对象(也就是全局对象自身),它可以在任何环境下使用。
下面的代码可以帮助我们更好理解 globalThis
和两种全局变量:
<script>
const declarativeVariable = 'd';
var objectVariable = 'o';
</script>
<script>
// 所有脚本共享同样的顶级作用域
console.log(declarativeVariable); // 'd'
console.log(objectVariable); // 'o'
// 不是所有变量都会创建为全局对象的属性
// 此处最顶层 const 创建的是全局声明变量,不是全局对象,因此 window 对象下访问为 undefined
console.log(window.declarativeVariable); // undefined
console.log(window.objectVariable); // 'o'
// globalThis 同 window 表现一致
console.log(globalThis.declarativeVariable); // undefined
console.log(globalThis.objectVariable); // 'o'
</script>
观察以上的代码,就能明白为什么最顶层 const 创建的变量,使用 window 访问依然是 undefined。
函数作用域
函数内声明的变量,只能在函数作用域范围内访问。
function scope() {
var address = "Beijing";
}
console.log(address); // Uncaught ReferenceError: address is not defined
块级作用域
作用域对变量来说,可以简单理解为程序能够访问到变量的范围,超过作用域的就无法访问。
let
和 const
let
、const
支持块级作用域。如果在代码块 {...}
中使用 let
或 const
声明一个变量,那么这个变量只在该代码块中可见。
{
// 作用域 A,可以访问变量 x
const x = 0;
console.log(x); // 0
{
// 作用域 B,可以访问 x、y
const y = 1;
console.log(x); // 0
console.log(y); // 1
}
}
// 作用域 A 外,不能访问 x、y
console.log(x); // 报错:Uncaught ReferenceError: x is not defined
我们分析一下上面的代码:
每个变量可访问的范围是它所在的作用域以及该作用域所嵌套的外部作用域。let
和 const
声明的变量是块级的,因此它们作用域始终在块中。同一作用域内,不允许声明同名变量。如下示例代码用 const 声明同名变量:
{
const x = 1;
const x = 2; // 报错:Uncaught SyntaxError: Identifier 'x' has already been declared
}
不同作用域下可以使用同名变量:
{
const x = 1;
{
const x = 2;
}
}
class
class
声明的类也支持块级作用域。如下面代码,在全局作用域中声明了 Animal
:
class Animal {}
console.log(Animal); // class Animal {}
如果在代码块 {...}
中创建 Animal
,此时在代码块外部就无法访问 Animal
了。
{
class Animal {}
console.log(Animal) // class Animal {}
}
console.log(Animal); // Uncaught ReferenceError: Animal is not defined
模块作用域
每个 ECMAScript 模块(ES6 Modules)都有自己的作用域,因此,在顶级模块中声明的变量不是全局的。如下图所示:
总结
作用域是值或者表达式的可访问范围。分为静态作用域(词法作用域)和动态作用域,JavaScript 采用的是静态作用域。其中,分为四种不同的作用域:全局作用域、函数作用域、块级作用域以及模块作用域。
除此之外,文中还提到了各种声明与作用域的关系,下面总结一下各种声明的异同。我们从以下四个方面来看各种声明的异同:
变量提升和上文提到触发时间有关。我们知道,var
和 function
声明的变量可以在声明前访问,这就是因为变量提升的缘故。
当 JavaScript 引擎执行代码时,创建了全局执行上下文,它有两个阶段:
在创建阶段,JavaScript 引擎将 var
和 function
声明移到了顶层,这就是 JavaScript 的变量提升。
var
关键字
我们先看一段代码:
console.log(counter); // undefined
var counter = 1;
在这段代码中,我们在声明前访问 counter
变量,并未报错。这是变量提升的缘故。
function
提升
和 var
一样,函数声明也会提升:
let x = 20,
y = 10;
let result = add(x, y);
console.log(result); // 30
function add(a, b) {
return a + b;
}
除此之外,let
关键字、函数表达式、箭头函数等均不会变量提升。
处理相同的变量名或者函数名
代码中出现相同的变量或者函数怎么办?我们知道 var
声明的同名变量,后者会覆盖前者,如果是函数呢?先看下面的例子:
let x = 20,
y = 10;
function add(a, b) {
return a + b;
}
let result = add(x, y);
console.log("result " + result); // "result 60"
function add(a) {
return a + 40;
}
let result1 = add(x);
console.log("result1 " + result); // "result1 60"
从上面的结果可以看到,两个同名函数 add,定义在后面的函数覆盖了前面的函数,因此 result
和 result1
的结果都是执行后面的函数后返回的结果。
因此,代码中出现同名的变量名或者函数名,都是后者覆盖前者。
小练习
下面的代码输出的结果是什么?
let x = 20,
y = 10;
let result = add(x);
console.log("result1 " + result); // "result1 60"
var add = function(a, b) {
return a + b;
}
function add(a) {
return a + 40;
}
这里考察的是同名变量和函数的提升,后面的会覆盖前面的,因此执行的是最后一个函数 add
。
阅读量:2011
点赞量:0
收藏量:0