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就是表明常量,我才无论你是否是类的仍是实例的。

相关文章
相关标签/搜索