结合你不知道的 JS.2nd 理解 var let const

371 阅读3分钟

Hoisting

Two kinds of Undefined Mess

typeof doesntExist;     // only input this line => "undefined"

typeof studentName;     // "undefined"
var studentName;

短语“not defined”似乎与英语中的单词“undefined”相同。但是这两个在JS中是不同的。

“Not defined”实际上是指“Not declared”,或者更确切地说是“undeclared”,在任何 lexically available 作用域内都没有相匹配的声明变量。相比之下,“undefined”实际上意味着发现(声明)了一个变量,但该变量此时没有其他值,因此它默认为“undefined”值。

Function hoisting(declaration vs. expression)

greeting();
// TypeError: greeting is not a function

var greeting = function greeting() {
    console.log("Hello!");
};

greeting() // output "Hello" if execute the program after expression statement (without the first line)  
-------------------------------------------------
greeting();
// Hello

function greeting() {
    console.log("Hello!");
};

函数提升(function hoisting)只适用于正规的函数声明, 不适用于表达式赋值声明.

“TypeError”表示我们试图用一个不允许的值执行某些操作。根据这里的JS环境,错误消息提示 "greeting”不是函数。

请注意,该错误不是“ReferenceError”。JS并没有告诉我们在作用域中找不到“greeting”作为标识符。

它告诉我们“greeting”被找到了,但在那一刻不包含函数引用。只有函数能被调用,因此尝试调用某些非函数值会导致错误(TypeError)。

在这里的表达式函数中, 用 var 声明的变量会在作用域的开头自动初始化为 undefined.

在第一行中, greeting 存在, 但它现在保存的是undefined值. 直到第四行, greeting 才被分配给函数引用.

一个“var”变量也被提升,然后自动初始化为“undefined”。直到在运行时,才会对该变量进行任何后续的“function”表达式赋值。

而(正规的)函数声明, 函数会被提升并自动初始化为函数值. 因此可以正常调用.

Variable hoisting(var)

console.log(greeting);
greeting = 'Hello';
// ReferenceError: greeting is not defined => actually not declared

---------------------------

console.log(greeting); // undefined => find the variable but the variable has default value 'undefined'
var greeting = 'Hello';

---------------------------

greeting = "Hello!";
console.log(greeting);
// Hello!

var greeting = "Howdy!";
console.log(greeting);
// Howdy!

提升(hoisting)的意思就好像是: lifiting - 就像把一个重物向上升,一直举到范围的顶端。通常的解释是,JS引擎实际上会在执行前""重写""该程序,因此看起来更像这样:

var greeting;           // hoisted declaration
greeting = "Hello!";    // the original line 1
console.log(greeting);  // Hello!
greeting = "Howdy!";    // `var` is gone!
console.log(greeting);  // Howdy! <- re-declaration

Variable && Function

此外,提升也意味着“function”声明整体上被提升到每个作用域的顶部。如下:

studentName = "Suzy";
greeting();
// Hello Suzy!

function greeting() {
    console.log(`Hello ${ studentName }!`);
}
var studentName;

提升的“规则”是先提升函数声明,然后在所有函数之后,立即提升变量。因此,这个程序可以被看作是被JS引擎这样''重新安排''的,如下所示:

function greeting() {
    console.log(`Hello ${ studentName }!`);
}
var studentName;

studentName = "Suzy";
greeting();
// Hello Suzy!

我们可以把这个程序看作是由JS引擎自上而下执行的。

JS引擎实际上并没有重新排列代码。它不能神奇地向前看并找到声明;要准确地找到它们以及程序中的所有范围边界,唯一的方法就是完全解析代码。(Compilation first, then execution)

What about let && const hoisting?

让我们试试跑几个程序吧:

console.log(greeting);
let greeting = 'hello';
// ReferenceError: Cannot access 'greeting' before initialization 
----------------------------

console.log(greeting);
const greeting = 'Hello';
// ReferenceError: Cannot access 'greeting' before initialization

这个错误消息很好地说明了问题所在:greeting存在于第1行,但尚未初始化,因此还不能使用它。让我们试试这个:

greeting = "hello";   // let's try to initialize it!

console.log(greeting);

let greeting;
// ReferenceError: Cannot access 'greeting' before initialization

---------------------------------------
greeting = "hello";   // let's try to initialize it!

console.log(greeting);

const greeting;
// Uncaught SyntaxError: Missing initializer in const declaration

我们会发现另一个细微差别是,Compiler在let 和 const程序中间添加了一条指令, 即初始化(initialization)。

打印的结果表明我们不能在初始化发生之前的任何时候使用这个变量。“const”和“let”都是如此。

TC39将一个作用域到变量自动初始化发生的这段时间称为:时间死区(Temporial dead zone)。TDZ是一个时间窗口,其中存在一个变量,但尚未初始化,因此无法以任何方式访问。

Where TDZ Started?

​ TDZ来源于 **const**.

在早期的ES6开发工作中,TC39决定“const”(和“let”)是否要提升到块的顶部。他们决定这些声明将被提升,类似于“var”的做法。

但是如果“let”和“const”提升到块的顶部(就像“var”提升到函数的顶部),为什么“let”和“const”不能像“var”那样自动初始化到“undefined”?

主要原因如下:

{
    // what should print here?
    console.log(studentName);

    // later

    const studentName = "Frank";

    //  Uncaught ReferenceError: Cannot access 'studentName' before initialization
}

假设“studentName”不仅被提升到这个块的顶部,而且被自动初始化为“undefined”。对于块的前半部分,可以观察到'studentName'具有'undefined'值。 一旦到达"const studentName = ..."语句, 我们就为"Frank" 赋值给"studentName"。

但是const只能被赋值一次. 所以我们不能自动地给"studentName"初始化为"undefined"(或任何其他值)。 但变量必须存在于整个作用域中。 因此我们不能在初始化发生之前的任何时候使用这个变量。

Visualized For All

Parse phase

functions

img

let && const

img

var

img

Execution phase

invoke functioins

img

output undefined

img

ReferrenceError

img

output declared variables after overwriting

img

var vs. let && const

Re- declaration

function func() {
    if (true) {
      	var myVar = 100;
        let myLet = 200;
        console.log(myVar); // 100
        console.log(myLet); // 200

    	var myVar = 300; 
    	console.log(myVar); // 300
    	let myLet = 400; // Uncaught SyntaxError: Identifier 'myLet' has already been declared
    }
}

func()

var 可被多次声明, let 不可被再声明.

var studentName = "Frank";

let studentName = "Suzy";
// Uncaught SyntaxError: Identifier 'studentName' has already been declared
--------------------------

let studentName = "Frank";

var studentName = "Suzy";
// Uncaught SyntaxError: Identifier 'studentName' has already been declared

只要包含 let 的两次声明, 都会报错

var studentName = "Frank";
console.log(studentName);
// Frank

var studentName;
console.log(studentName);   // ???  => Frank

// equal to the below
var studentName;
var studentName;    // clearly a pointless no-operation!

studentName = "Frank";
console.log(studentName);
// Frank
console.log(studentName);
// Frank

------------------------

var greeting;

function greeting() {
    console.log("Hello!");
}

// basically, a no-op
var greeting;

typeof greeting;        // "function"

var greeting = "Hello!";

typeof greeting;        // "string"

块作用域

var && let
function func() {
    if (true) {
      var myVar = 100;
      let myLet = 200;
    }
    console.log(myVar); // 100
    console.log(myLet); // ReferenceError: myLet is not defined
}
func()

var 没有块级作用域. 人们更习惯于块级作用域, 因此let出现!

function func() {
  let arr = [];
  for (let i=0; i < 3; i++) {
      arr.push(i);
  }
  console.log(arr); // [0,1,2]
  console.log(i); //Error: i not defined. block i 
}
func()

-----------------------------------------
function func() {
  let arr = [];
  for (var i=0; i < 3; i++) {
      arr.push(i);
  }
  console.log(arr); // [0,1,2]
  console.log(i); //3   global i
}
func()
varlet
ScopeFunction scopedBlock scoped
Multiple declaration in the same scopeNo issuesThrows error
Temporal dead zoneNoYes
Access outside loopYesNo
Declaration conflict with function paramsNoYes
Global scopeExists in global scope as well as become a property of the global object.Exists in global scope but doesn't become a property of the global object.
var && const
var myVar // OK
const myConst; //  SyntaxError: Missing initializer in const declaration => No blank declaration

---------------------------------------
    
var myVar = 100;
myVar = 200;
console.log(myVar) // 200

const myConst = 300;
console.log(myConst) // 300
myConst = 400;
console.log(myConst) // TypeError: Assignment to constant variable. => assign the value only once.

----------------------------------------

const me = {};
me.name = "Paul"; //OK
console.log(me.name); // Paul

const people=[];
people.push(1,2,3); //OK
console.log(people); // [1,2,3]

me = {{}}; //Error: read only   => only the identifier itself is read only. we can edit the properties of the object.
people = [1,2,3]; //Error: read only

REF:

You don't know JS Yet, 2nd (mainly ch5.md)

dev.to/lydiahallie… 变量提升动图

stackoverflow.com/questions/1…

自己之前通了个宵看了一遍原文档, 顺便记录了下来, 如有错误地方请不要吝惜指教.