Javascript概览
JavaScript 变量
var、let、const
var
var
语句用来在 JavaScript 中声明一个变量,该变量遵守以下规则:
- 作用域范围是函数作用域或全局作用域的。
- 不受暂存死区(TDZ)的限制。
-
它会在顶层对象(浏览器中是
window
,NodeJS中是global
)上以相同的名称创建一个全局属性。 - 是「可分配」的。
- 是「可声明」的。
函数作用域和全局作用域
当出现在全局作用域内时,var
创建一个全局变量。
另外它还会在window
(NodeJS中是global
)上创建一个具有相同名称的「全局属性」:
var city = "Florence"; console.log(window.city); // "Florence"
当在函数内部声明时,变量的作用域为该函数:
var city = "Florence"; function bubble() { var city = "Siena"; console.log(city); } bubble(); // "Siena" console.log(city); // "Florence"
var
声明会被提升:
function bubble() { city = "Siena"; console.log(city); var city; // hoists } bubble(); // "Siena"
意外的全局变量
在没有任何声明的情况下所分配的变量(无论是var
,let
还是const
)
在默认情况下会成为「全局变量」:
function bubble() { city = "Siena"; console.log(window.city); } bubble(); // "Siena"
为了消除这种行为,需要使用「严格模式」:
"use strict"; function bubble() { city = "Siena"; console.log(window.city); } bubble(); // ReferenceError: assignment to undeclared variable city
可重新分配和重新声明
任何用var
声明的变量都可以在以后进行「重新分配」或「重新声明」。
重新声明的例子:
function bubble() { var city = "Florence"; var city = "Siena"; console.log(city); } bubble(); // "Siena"
重新分配的例子:
function bubble() { var city = "Siena"; city = "Florence"; console.log(city); } bubble(); // "Florence"
let
let
语句在JavaScript
中声明一个变量,该变量遵守以下规则:
- 属于块作用域。
- 受到「暂存死区」的约束。
-
它不会在
window
(NodeJS中是global
)上创建任何全局属性。 - 是「可分配」的。
- 「不可重新声明」。
块作用域
用let
声明的变量不会在window
(NodeJS中是global
)上创建任何全局属性:
let city = "Florence"; console.log(window.city); // undefined
当在函数内部声明时,变量的作用域为该函数:
let city = "Florence"; function bubble() { let city = "Siena"; console.log(city); } bubble(); // "Siena" console.log(city); // "Florence"
当在「块」中声明时,变量的作用域为该块。以下是在块中使用的例子:
let city = "Florence"; { let city = "Siena"; console.log(city); // "Siena"; } console.log(city); // "Florence"
一个带有 if 块的例子:
let city = "Florence"; if (true) { let city = "Siena"; console.log(city); // "Siena"; } console.log(city); // "Florence"
相反,var 并不受到块的限制:
var city = "Florence"; { var city = "Siena"; console.log(city); // "Siena"; } console.log(window.city); // "Siena"
暂存死区
let
声明可能会被提升,但是「会产生暂存死区」:
function bubble() { city = "Siena"; console.log(city); // TDZ let city; } bubble(); // ReferenceError: can't access lexical declaration 'city' before initialization
暂存死区可防止在初始化之前访问let
声明。另外一个例子:
function bubble() { console.log(city); // TDZ let city = "Siena"; } bubble(); // ReferenceError: can't access lexical declaration 'city' before initialization
可以看到两个例子中产生的异常都是一样的:证明了「暂存死区」的出现。
可重新分配,不可重新声明
任何用let
声明的变量「都不能重新声明」。重新声明引发异常的例子:
function bubble() { let city = "Siena"; let city = "Florence"; console.log(city); } bubble(); // SyntaxError: redeclaration of let city
这是一个有效的重新分配的例子:
function bubble() { let city = "Siena"; city = "Florence"; console.log(city); } bubble(); // "Florence"
const
const
语句用来在 JavaScript 中声明一个变量,该变量遵守以下规则:
- 是属于块作用域的。
- 受到“暂存死区”的约束。
-
它不会在
window
上创建任何全局属性。 - 「不可重新分配」。
- 「不可重新声明」。
块作用域
用const
声明的变量不会在window
上创建任何全局属性:
const city = "Florence"; console.log(window.city); // undefined
当在函数内部声明时,变量的作用域为该函数:
const city = "Florence"; function bubble() { const city = "Siena"; console.log(city); } bubble(); // "Siena" console.log(city); // "Florence"
当在「块」中声明时,变量的作用域为该块。块语句{}
的例子:
const city = "Florence"; { const city = "Siena"; console.log(city); // "Siena"; } console.log(city); // "Florence"
在if
块中的例子:
const city = "Florence"; if (true) { const city = "Siena"; console.log(city); // "Siena"; } console.log(city); // "Florence"
暂存死区
const
声明可能会被提升,但是「会进入暂存死区」:
function bubble() { console.log(city); const city = "Siena"; } bubble(); // ReferenceError: can't access lexical declaration 'city' before initialization
不可重新分配,不可重新声明
用const
声明的任何变量「都不能重新声明,也不能重新分配」。
一个在重新声明时抛出异常的例子:
function bubble() { const city = "Siena"; const city = "Florence"; console.log(city); } bubble(); // SyntaxError: redeclaration of const city
重新分配的例子示例:
function bubble() { const city = "Siena"; city = "Florence"; console.log(city); } bubble(); // TypeError: invalid assignment to const 'city'
总结
块作用域 | 暂存死区 | 创建全局属性 | 可重新分配 | 可重新声明 | |
---|---|---|---|---|---|
var | ❌ | ❌ | ✅ | ✅ | ✅ |
let | ✅ | ✅ | ❌ | ✅ | ❌ |
const | ✅ | ✅ | ❌ | ❌ | ❌ |
ES6早期:普遍认为默认使用let来替代var,对于写保护的变量使用const
ES6普及后:普遍默认使用const,只有确实需要改变变量的值时使用let。 因为大部分变量的值在初始化后不应再改变,而预料之外的变量值的改变是许多bug的源头。 这样就可以在某种程度上实现代码的不可变,从而防止某些错误的发生。
自由变量与作用域链
当前作用域没有定义的变量,这成为 自由变量 。 自由变量的值如何得到 —— 向父级作用域寻找(注意:这种说法并不严谨,下文会重点解释)。 如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。 这种一层一层的关系,就是「作用域链」。
关于自由变量的值,上文提到要到父作用域中取,其实有时候这种解释会产生歧义。
var x = 10 function fn() { console.log(x) // 在这里定义函数,从这里向上找自由变量的定义 } function show(f) { var x = 20 (function() { f() //10,而不是20 })() } show(fn)
在fn
函数中,取自由变量x的值时,要到哪个作用域中取?
——要到创建fn
函数的那个作用域中取,无论fn
函数将在哪里调用。
其实这就是所谓的「静态作用域」。
作用域与执行上下文
许多开发人员经常混淆作用域和执行上下文的概念,误认为它们是相同的概念,但事实并非如此。
我们知道JavaScript属于解释型语言,JavaScript的执行分为解释和执行两个阶段, 这两个阶段所做的事并不一样:
-
解释阶段:
- 词法分析
- 语法分析
- 作用域规则确定
-
执行阶段:
- 创建执行上下文
- 执行函数代码
- 垃圾回收
JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,
而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。
执行上下文最明显的就是this
的指向是执行时确定的。
而作用域访问的变量是编写代码的结构确定的。
作用域和执行上下文之间最大的区别是:
- 执行上下文在运行时确定,随时可能改变;
- 作用域在定义时就确定,并且不会改变。
一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境 (函数从来就没有被调用过); 有可能有过,现在函数被调用完毕后,上下文环境被销毁了; 有可能同时存在一个或多个(闭包)。同一个作用域下, 不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
JavaScript 数组
JavaScript 函数基础
Javascript 运算符
JavaScript 流程控制
JavaScript 正则表达式
JavaScript 字符串函数
JavaScript DOM 基本操作