es6必會之let && const

關鍵詞:

letconstblocking scopetemporal dead zoneredeclarationreassignmentimmutableinitializerjava

一直以來都有用let和const,看似深刻學習過,但其實沒有真正徹底理解,在模棱兩可的用,以前在城西的一次面試就被問的啞口無言,因此我將經過mdn的資料針對性地學習letconst,而且會寫一些易懂的例子,也會涉及到一些規範裏的內容。es6

  • block,statement和expression的區別是什麼?
  • 爲何選擇了'let'做爲block-scoped variable declaration?
  • let和const不會像var同樣綁定值到global 對象!
  • let和const不能像var同樣同一個scope下聲明屢次!
  • let和const不會像var同樣變量聲明提高!

let

let聲明瞭一個block scope local variable,選擇性爲其賦值。面試

let x = 1;
if(x === 1) {
    let x = 2;
    console.log(x); // 2
}
console.log(x); // 1
複製代碼

let使變量的做用域限制於block,statement,或者expression。express

block,statement和expression的區別是什麼?

這對於做用域的判別頗有用。數組

  • block curly bracket,circle bracket等等。
  • statement js由各類語句組成,Control Flow,Function,Iterations等等。
  • expression 反作用,無反作用;可執行和關鍵詞。

不少東西自覺得會了,然而實際上可能只是蜻蜓點水,因此回過頭來將基礎撿起來,可能比學習高階技術更重要。安全

block
  • group zero or more statements
  • block 由 curly bracket包裹(彎曲的支架比花括號更形象)
  • blcok statement 在其餘語言裏一般叫作computed statement,之因此叫複合語句,由於JavaScript會一句一句執行,在block 裏能夠有多個statement,
var x = 1;
let y = 1;
if (true) {
  var x = 2;
  let y = 2;
}
console.log(x);
console.log(y);
複製代碼

這裏的block指的是{var x = 2; let y = 2;},注意:不包括if(true),由於它位於curly bracket以外。 Block Statement Syntax:bash

{
    StatementList
}
複製代碼

比起block statement syntax,更重要的是Block Scoping Rules: 1.var rule: 一種是全局定義,一種是函數局部定義。局部函數變量的訪問會從做用域鏈由下往上找。可是這種老式的變量命名符不提倡再使用。curl

var x = 1;
{
    var x = 2;
}
console.log(x); // 2
複製代碼

2.let && const rule:async

let x = 1;
{
    x = 2;
}
console.log(x); // 2
複製代碼
const x = 1;
{
   x = 2;
}
console.log(x); // TypeError: Assignment to constant variable.
複製代碼

在{ }中應該用var / let /const聲明x,而不是讓其變成全局變量致使與外部的x變量衝突。 3.function rule:ide

foo('outside'); // TypeError: foo is not a function
{
    function foo(location) {
        console.log('foo is called' + location);
    }
    foo('inside'); // foo is called inside
}
複製代碼

更準確一些的說法是,block statement阻止function declaration 被hoisted(變量提高)到做用域的頂部。這種定義方式與函數表達式相似。 函數表達式的變量提高規則是下面這樣:

foo('before'); // Uncaught TypeError: foo is not a function
var foo = function(location) {
    console.log('foo is called' + location);
}
foo('after'); // foo is called after
複製代碼

function塊做用域規則同上:

foo('before'); // TypeError: foo is not a function
{
    function foo(location) {
        console.log('foo is called' + location);
    }
}
foo('after'); // foo is called after
複製代碼

與函數表達式不會提高到var foo = function(){}同樣;{}內部定義的function,不會提高到{}以前。而這正是function的blocking statement rule。

statement
什麼是empty statement?
var array = [1, 2, 3];
for (i=0; i<array.length; array[i++] = 0) /* empty statement */;
console.log(array);
複製代碼
  • Javascript 應用就是由符合語法的多個statement組成的。
  • 一條statement可能跨越多行。
  • 多條statement也可能在一行中出現,每一句由一個分號標記。
  • statement能夠分爲Control flow,Declarations,Functions and classed,Iterations,Others

Control flow包括Block,break,continue,Empty,if...else,switch,throw,try...catch。 Declarations包括var,let,const。 Functions and classed包括function,function *,async function,return,class。 Iterations包括:do...while,for,for each...in,for...in,for...of,while。 Others包括:debugger,export,import,import.meta,label,with

什麼是Expressions?
  • expression指的是任何能夠解析爲value的代碼單元。
  • 2種有效的expression:有side effect的,例如x = 7;某些狀況下執行而且解析爲值,例如3 + 4。
  • 還有2種分類,一類是執行後爲number,string,boolean型的;一類是關鍵詞類型,這種類型又分爲Primary expression和Left-hand-side expressions。

Primary expressions Basic keywords and general expressions in JavaScript,例如this,grouping operator.

  • this 當前對象的引用,2種調用對象方法的方式this['propertyName'],this.propertyName
function validate(obj, lowval, hival) {
  if ((obj.value < lowval) || (obj.value > hival))
    console.log('Invalid Value!');
}
<p>Enter a number between 18 and 99:</p>
<input type="text" name="age" size=3 onChange="validate(this, 18, 99);">
複製代碼

上面的例子中,this指代input這個DOM對象,它因爲具備屬性value,所以能夠調用validate函數,而且每次輸入值發生變化時都觸發onChange回調。

  • Grouping operator ()
var a = 1;
var b = 2;
var c = 3;
// default precedence
a + b * c     // 7
// evaluated by default like this
(a + b) * c   // 9
複製代碼

Left-hand-side expressions 左值是賦值的目標,例如new,super,Spread operator。 new 建立一個用戶自定義object類型

var objectName = new objectType([param1, param2, ..., paramN]);
複製代碼

super 調用當前object的父object上的函數,在class中經常使用。

super([arguments]); // 調用parent constructor
super.functionOnParent([arguments]); // 調用parent上的方法
複製代碼

Spread operator 容許表達式被展開,能夠是函數參數處展開,也能夠是數組迭代處展開。 數組某處插入數組元素。

var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];
複製代碼

一個完整數組做爲參數傳入函數

function f(x,y,z){}
var args = [0,1,2];
f(...args);
複製代碼

經過對block,statement,expression的回顧。咱們發現,其實塊做用域不只僅是curly bracket,{}。在for(){}for(key in object)for(item of array)等等的()內,其實也屬於塊做用域,不只僅是if else的{},for{}中的{}纔算是塊做用域,let都有效。

let a = 1;
for(let a = 2; a<3; a++){
    console.log(a);
};
console.log(a);
// 2 1
複製代碼
let key = 'hello world';
for(let key in {foo:1,bar:2}){
    console.log(key);
}
console.log(key);
// foo bar hello world
複製代碼

如果不用let,會將全局的key override,因此在for系列的循環控制語句中使用let頗有必要。

let key = 'hello world';
for(key in {foo:1,bar:2}){
    console.log(key);
}
console.log(key);
// foo bar bar
複製代碼

for(item of array)中也同樣。

let item = 4;
for(let item of [1,2,3]){
	console.log(item);
}
console.log(item);
// 1 2 3 4
複製代碼
let item = 4;
for(item of [1,2,3]){
	console.log(item);
}
console.log(item);
// 1 2 3 3
複製代碼

使用let之後,井水不犯河水,不用擔憂改寫全局中的同名變量,可是必定要明確,let不只僅做用於{},()也一樣做用。

爲何選擇了'let'做爲block-scoped variable declaration?

能夠看這個stack overflow上的question:Why was the name 'let' chosen for block-scoped variable declarations in JavaScript?。 有兩點比較重要:

  1. 參考了scala,F#等語言裏比variable用做更高級抽象的let;
  2. 一個頗有趣的解釋:let myPet = 'dog', let my pet be a dog。

let和const不會像var同樣綁定值到global 對象!

衆所周知,var會綁定變量到global對象(不必定是window,global,還多是Vue instance),可是let和const不會。

var foo = 1;
let bar = 2;
const baz = 3;
console.log(this.foo, this.bar, this.baz); //1 undefined undefined 
複製代碼

let和const不能像var同樣同一個scope下聲明屢次!

let foo = 1;
let foo = 2; // Uncaught SyntaxError: Identifier 'foo' has already been declared
複製代碼
const foo = 1;
const foo = 2; // Uncaught SyntaxError: Identifier 'foo' has already been declared
複製代碼
var foo = 1;
var foo = 2; // everything is ok
複製代碼

let和const不會像var同樣變量聲明提高!

緣由是:const,let存在temporal dead zone!

所以不能let ,const賦值前使用變量。

在說變量提高以前,先了解一個概念,Temporal Dead Zone,指的是從block建立到初始化完成之間的時間。用var不會存在Temporal Dead Zone,由於用var聲明的變量,初始值當即默認賦予undefined,不會像let這樣,存在Temporal Dead Zone,不會當即爲其賦undefined,因此會報ReferenceError錯誤。

function do_something() {
    console.log(bar); // undefined
    console.log(foo); // ReferenceRrror
    var bar = 1;
    let foo = 2;
}
複製代碼

正是因爲let存在temporal dead zone,沒有當即爲變量賦初始值爲undefined,因此typeof的結果爲ReferenceRrror。

console.log(typeof undeclaredVariable); // undefined
console.log(typeof i);// ReferenceError,存在temporal dead zone
let i = 10;
複製代碼

var,let,const都會變量提高,可是僅僅是對var foo;let foo;const foo的提高,而不是var foo = 1;let foo =1;const foo = 1;總體提高!

let不會當即爲變量賦undefined初值是好是壞呢?固然是好事!這樣將變量的管理更加精細,避免引用重名變量覆蓋後出現bug還發現不了的狀況。

還有兩個temporal dead zone的狀況:

function test(){
   var foo = 33;
   if (true) {
      let foo = (foo + 55); // ReferenceError,let foo 存在temporal dead zone
   }
}
test();
複製代碼
function go(n) {
  console.log(n);
  for (let n of n.a) { // ReferenceError,let n 存在temporal dead zone
    console.log(n);
  }
}
go({a: [1, 2, 3]});
複製代碼

const

其實在let模塊已經寫了不少關於const的內容,因此在這裏就寫一些const特有的特性。

  • const也是block-scoped的,和用let定義的變量相似。
  • 不能夠修改變量值,也就是不能夠reassignment,並非immutable
  • 不能夠從新定義
  • const foo = [value],value能夠是function,而let也能夠!
  • 必須爲const賦一個初值且存在temporal dead zone,比let更加嚴格!
const foo = 1;
{
    const foo =2;
}
複製代碼
const foo = 1;
foo = 2; // Uncaught TypeError: Assignment to constant variable.
複製代碼
const foo = 1;
const foo = 2; // Uncaught SyntaxError: Identifier 'foo' has already been declared
複製代碼

let定義的變量賦值function會有什麼錯誤提示呢?

let foo = function(){
    console.log('foo');
}
foo();// foo
複製代碼

不會報錯,可是由於let能夠reassignment,因此不如const更加安全,由於通常來講,咱們建立一個函數之後,不太會再去覆蓋這個函數。

const不能夠reassignment,並非immutable什麼意思?

immutable指的是變量的值徹底不可改變,例如'hi',{foo:1,bar:2},若這個字符串和對象是immutable的,那麼'hi'徹底不能被修改,並且對象{foo:1,bar:2}也徹底不能修改,也就是說它的屬性foo和bar值都不能修改,可是const只是約束了reassignment,沒有約束mutable。

下面這種寫法是徹底OK的:

const obj = {
    foo: 1,
    bar: 2,
}
obj.foo = 3;
console.log(obj); // {foo: 3,bar:2}
複製代碼

cosnt不賦初值有什麼報錯?

cosnt foo;// Uncaught SyntaxError: Missing initializer in const declaration
複製代碼

假設修改了原型鏈上的屬性會怎樣?

const foo = 'foo';
foo.length = 5; // return 5
console.log(foo.length); // 3
複製代碼

咱們能夠看出,const仍是很包容的,即便你試圖修改原型鏈上的屬性,也不會報錯,他只是一笑而過,而且這種修改不會生效。

const真的很嚴格!

類型 是否必須賦值 是否存在temporal dead zone 是否支持redeclaration 是否支持reassignment
var
let
const

2018年12月18日01:45更新

es6的const和java的final之間的對比

關於const不支持reassignment這一點,java中的關鍵字final與之對應,表示這個常量只能賦值一次。 可是java多了一個類常量的概念,所謂類常量,指的其實就是一個常量在類的多個方法中有調用, 也就是static final foo = "foo";這樣的形式。 在js中就沒有這種講究了,const就是表明常量,我才無論你是否是類的仍是實例的。

相關文章
相關標籤/搜索