首页 > 其他分享 >es6必会之let && const

es6必会之let && const

时间:2022-10-07 23:04:36浏览次数:38  
标签:es6 const log let var console foo

关键词:

​let​​​ ,​​const​​​, ​​blocking scope​​​ ,​​temporal dead zone​​​,​​redeclaration​​​,​​reassignment​​​,​​immutable​​​,​​initializer​

一直以来都有用let和const,看似深入学习过,但其实没有真正完全理解,在模棱两可的用,之前在城西的一次面试就被问的哑口无言,所以我将通过mdn的资料针对性地学习​​let​​​ 和 ​​const​​,并且会写一些易懂的例子,也会涉及到一些规范里的内容。

  • block,statement和expression的区别是什么?
  • 为什么选择了'let'作为block-scoped variable declaration?
  • let和const不会像var一样绑定值到global 对象!
  • let和const不能像var一样同一个scope下声明多次!
  • let和const不会像var一样变量声明提升!
  • 循环中let/var与setTimeout的羁绊!
  • for循环无异步参与时,var,let效果相同
  • for循环有异步参与时,var异常,let创建块作用域
  • Array.prototype.forEach与setTimeout,独立作用域

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。

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:

{
StatementList

比起block statement syntax,更重要的是Block Scoping Rules: 1.var rule: 一种是全局定义,一种是函数局部定义。局部函数变量的访问会从作用域链由下往上找。但是这种老式的变量命名符不提倡再使用。

var x = 1;
{
var x = 2;
}
console.log(x); // 2

2.let && const rule:

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:

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
// eval(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:Cannot access 'foo' before initialization
}
}
test();

(foo + 55)中的foo在let foo初始化(正在赋值)的过程中访问,也就是在temporal dead zone期间进行访问,因此会抛出引用异常

function go(n) {
console.log(n);
for (let n of n.a) { // ReferenceError,Cannot access 'n' before initialization
console.log(n);
}
}
go({a: [1, 2, 3]});

n.a中的n在let n初始化(正在赋值)的过程中访问,也就是在temporal dead zone期间进行访问,因此会抛出引用异常

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​​就是代表常量,我才不管你是不是类的还是实例的。

for循环中let/var与setTimeout的羁绊!

无异步参与时,var,let效果相同

for (var i = 0; i<9 ;i++){
console.log(i); // 0, 1, 2, 3, 4, 5, 6, 7, 8
for (let i = 0; i<9 ;i++){
console.log(i); // 0, 1, 2, 3, 4, 5, 6, 7, 8

有异步参与时,var异常,let创建块作用域

for (var i = 0; i<9 ;i++){
setTimeout(()=>{
console.log(i);
})
}

输出了9次9。 这是因为var声明的i是全局变量,只声明了一次,当9次循环结束时,此时的i值为8,再次执行一次i++,i变为9,此时9个定时器开始打印,打印出的结果是全局的值为9为i变量。

  1. i从0加到8,创建了9个定时器
  2. i++,全局变量i变为9
  3. 9个定时器执行,打印9次全局变量9
for (let i = 0; i<9 ;i++){
setTimeout(()=>{
console.log(i);
})
}

依次从0到8输出。 这是因为let声明的i变量,会声明9次,当9次循环结束时,创建出9个块作用域,此时的i值为8,再执行一次++,i变为9,但是此时的i(9)与之前创建出的​​ setTimeout(()=>{console.log(0);})​​​ ​​ setTimeout(()=>{console.log(1);})​​​ ​​ setTimeout(()=>{console.log(2);})​​​ ​​ setTimeout(()=>{console.log(3);})​​​ ​​ setTimeout(()=>{console.log(4);})​​​ ​​ setTimeout(()=>{console.log(5);})​​​ ​​ setTimeout(()=>{console.log(6);})​​​ ​​ setTimeout(()=>{console.log(7);})​​​ ​​ setTimeout(()=>{console.log(8);})​​中的i(0),i(1),i(2),i(3),i(4),i(5),i(6),i(7),i(8)是完全不同的i,setTimeout中的i是独立的块作用域中的i。

用I don't know js中的一段话来讲就是:>let为每次迭代声明一次变量,后续迭代的初始值是上一次迭代的结束值。

Array.prototype.forEach与setTimeout

['foo','bar','baz'].forEach((_, i)=>{
setTimeout(()=>{console.log(i)})
})

依次输出0,1,2。 这是因为Array.prototype.forEach迭代器中的i,是传入的callback回调函数返回的,与let类似,会创建出3个独立的块所用域,​​ setTimeout(()=>{console.log(0);})​​​ ​​ setTimeout(()=>{console.log(1);})​​​ ​​ setTimeout(()=>{console.log(2);})​​ 。

所以使用Array.prototype.forEach时根本无需担心setTimeout的问题,没有let/var与setTimeout的困扰,在实际开发中是比较常见的场景。

注意:

  1. setTimeout(()=>{console.log(i)},i)中的延时参数i,var时是全局,let是块作用域
  2. 无论循环多少次,i从几开始,setTimeout延时参数i怎么变化,关键区分var与let即可解决所有问题
  • 微信公众号: 大大大前端

标签:es6,const,log,let,var,console,foo
From: https://blog.51cto.com/u_15725382/5735198

相关文章

  • 解决IntelliJ Idea与Tomcat10关于Servlet5.0不匹配的问题
    在学习Mybatis的时候,创建了一个JavaWeb程序来做试验。出现了以下错误,在网上查了很多,最后在网上发现是Idea里面的Serlvet4.0与Tomcat10不匹配的问题。jakarta.servlet.S......
  • jira项目笔记23-ts中 as const 使用
    概要typescript在开发过程中广泛被应用,typescript的断言特性更是重中之重,今天和大家来讨论一下asconst断言。代码和讨论我们首先来看一段代码,如下:leta:string="a......
  • js高级ES6解构语法
    <!DOCTYPEhtml><htmllang="en"><head> <metacharset="UTF-8"> <metahttp-equiv="X-UA-Compatible"content="IE=edge"> <metaname="viewport"content="......
  • 关于const在类中的一些总结
    const对象只能调用类的const方法,一般对象可以调用所有对外方法。 类的const方法实现内部:不能改变类的数据成员值,但是若有形参是此类的对象,且不是const型对象,那么通过这......
  • CompletableFuture事务问题
    前段时间写了关于CompletableFuture的使用博客,CompletableFuture使用方法详细说明和CompletableFuture的thenCompose使用具体说明。但在实际中使用的时候发现,Completabl......
  • Vue3移动端组件库Varlet源码主题阅读之一:本地启动服务时都做了什么
    本文为Varlet组件库源码主题阅读系列第一篇Vue开源的组件库有很多,基本各个大厂都会做一个,所以作为个人再重复造轮子其实意义也不是很大,但是笔者对于如何设计一个Vue组件......
  • CSharp: Singleton Pattern in donet core 3
     ///<summary>///单例模式SingletonPattern///geovindu,GeovinDuedit///</summary>publicclassDatabase{///<summary>......
  • org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for T
    在我本地运行项目成功且正常访问之后,需要把项目打成war包并上传到服务器中运行,但是服务器运行却是tomcat启动成功但是项目本身没有启动,且Catania的执行日志也比本地少了一......
  • 05.let和var(代码块)
    代码块使用{}来创建代码块,代码块可以用来对代码进行分组,  同一个代码中的代码,就是同一组代码,一个代码块中的代码要么都执行要么都不执行。let和var  在JS中,使用le......
  • [转]node.js 支持 ES6 模块化
    需检查是否满足以下条件:1、确保安装了v14.15.1或更高版本的node.js2、命令初始化 package.jsonnpminit-y3、在 package.json的根节点中添加 "type":"modu......