Jade Dungeon

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"
意外的全局变量

在没有任何声明的情况下所分配的变量(无论是varlet还是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 基本操作

图示