[TOC]
# let命令
## 块级作用域
lei用法与var类似,但所声明的变量只在let坐在的代码块内有效。
```
for(let i = 0; i < 10; i++){}
i // ReferenceError
```
```
var a = []
undefined
for(var i = 0; i<10;i++) {
a[i] = function(){
console.log(i)
}
}
a[6]() // 10
```
变量i是var声明的,在全局范围有效,i的值为最后一次输出的值。
如果用let,声明的变量仅在块级作用域有效,最后输出6
```
var a = []
undefined
for(let i = 0; i<10;i++) {
a[i] = function(){
console.log(i)
}
}
a[6]() // 6
```
## 不存在变量提升
```
console.log(foo) // ReferenceError
let foo = 2
```
这也意味着typeof不再是绝对安全的操作
```
typeof x // ReferenceError
let x
```
## 暂时性死区
只要块级作用域内存在let命令,它所声明的变量就不会受外部影响。
```
var tmp = '123'
if(true) {
tmp = 'abc' //ReferenceError
let tmp
}
```
上面代码中,存在全局变量`tmp`,但是块级作用域内`let`又声明了一个局部变量`tmp`,导致后者绑定这个块级作用域,所以在`let`声明变量前,对`tmp`赋值会报错。
ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用`let`命令声明变量之前,该变量都是不可用的。这在语法上,称为“**暂时性死区**”(temporal dead zone,简称 TDZ)。
<br>
有些“死区”比较隐蔽。
```
function bar(x = y, y = 2) {
return [x,y]
}
bar() //报错
```
调用bar会报错是因为参数x的默认值等于另一个参数y,此时y还没声明,属于“死区”。如果y的默认值是x,则不会报错。
```
function bar(x = 2, y = x) {
return [x,y]
}
bar() // [2, 2]
```
## 不允许重复声明
let不允许在相同作用域内重复声明同一个变量
```
function b() {
let a = 10 // 报错
var a = 10
}
b() // 报错
```
```
function b() {
let a = 10 // 报错
let a = 10
}
```
```
function bar(arg) {
let arg // 报错
}
```
```
function bar(arg) {
{
let arg // 不报错
}
}
```
# 块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
~~~javascript
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
~~~
第二种场景,用来计数的循环变量泄露为全局变量。
~~~javascript
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
~~~
## ES6 的块级作用域
`let`实际上为 JavaScript 新增了块级作用域。
~~~javascript
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
~~~
上面的函数有两个代码块,都声明了变量`n`,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用`var`定义变量`n`,最后输出的值才是 10。
ES6 允许块级作用域的任意嵌套。
~~~javascript
{{{{{let insane = 'Hello World'}}}}};
~~~
上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。
~~~javascript
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错
}}}};
~~~
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
~~~javascript
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
~~~
### 块级作用域与函数声明
函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
~~~javascript
// 情况一
if (true) {
function f() {}
}
// 情况二
try {
function f() {}
} catch(e) {
// ...
}
~~~
上面两种函数声明,根据 ES5 的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于`let`,在块级作用域之外不可引用。
~~~javascript
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
~~~
上面代码在 ES5 中运行,会得到“I am inside!”,因为在`if`内声明的函数`f`会被提升到函数头部,实际运行的代码如下。
~~~javascript
// ES5 环境
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());
~~~
ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于`let`,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢?
原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在[附录 B](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics)里面规定,浏览器的实现可以不遵守上面的规定,有自己的[行为方式](http://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6)。
* 允许在块级作用域内声明函数。
* 函数声明类似于`var`,即会提升到全局作用域或函数作用域的头部。
* 同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作`let`处理。
根据这三条规则,在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于`var`声明的变量。
~~~javascript
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
~~~
上面的代码在符合 ES6 的浏览器中,都会报错,因为实际运行的是下面的代码。
~~~javascript
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
~~~
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
~~~javascript
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}
// 函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
~~~
另外,还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
~~~javascript
// 不报错
'use strict';
if (true) {
function f() {}
}
// 报错
'use strict';
if (true)
function f() {}
~~~
<br>
# const命令
const用来声明变量。一旦声明,其值就不能改变。
```
const PI = 3.1415
PI // 3.1415
PI = 3
//TypeError: "PI" is read-only
```
const一旦声明变量,就必须立即初始化,不能以后赋值。
```
const foo;
// SyntaxError: missing = in const declaration
```
const的作用域与let相同,只在声明的块级作用域有效;声明的常亮不提升;存在暂时性死区;只能在声明后使用;不可重复声明。
对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只保证变量名指向的地址不变,不保证该地址的数据不变。
```
const foo = {}
foo.prop = 123
foo.prop // 123
foo= {} //TypeError
```
```
const a = []
a.push('hello') // 可执行
a.length = 0 // 可执行
a = ['aa'] // 报错
```
若想冻结对象,应该使用Object.freeze方法。
```
const foo = Object.freeze({})
```
除了冻结对象本身,对象的属性也应冻结。
## 跨模块常量
设置跨模块的变量
```
// constants.js
export const A = 1
export const B = 1
```
```
// test1.js
import * as constants from './constants.js'
console.log(constants.A)
```
```
// test2.js
import {A, B} from './contants'
console.log(A)
```
## 全局对象的属性
let、const和class命令声明的全局变量不属于全局对象的属性
```
var a = 1
window.a // 1
let b = 2
window.b // undefinded
```
# 参考资料
[ECMAScript 6 入门](http://es6.ruanyifeng.com/#docs/let#%E5%9D%97%E7%BA%A7%E4%BD%9C%E7%94%A8%E5%9F%9F)
- 第一部分 HTML
- meta
- meta标签
- HTML5
- 2.1 语义
- 2.2 通信
- 2.3 离线&存储
- 2.4 多媒体
- 2.5 3D,图像&效果
- 2.6 性能&集成
- 2.7 设备访问
- SEO
- Canvas
- 压缩图片
- 制作圆角矩形
- 全局属性
- 第二部分 CSS
- CSS原理
- 层叠上下文(stacking context)
- 外边距合并
- 块状格式化上下文(BFC)
- 盒模型
- important
- 样式继承
- 层叠
- 属性值处理流程
- 分辨率
- 视口
- CSS API
- grid(未完成)
- flex
- 选择器
- 3D
- Matrix
- AT规则
- line-height 和 vertical-align
- CSS技术
- 居中
- 响应式布局
- 兼容性
- 移动端适配方案
- CSS应用
- CSS Modules(未完成)
- 分层
- 面向对象CSS(未完成)
- 布局
- 三列布局
- 单列等宽,其他多列自适应均匀
- 多列等高
- 圣杯布局
- 双飞翼布局
- 瀑布流
- 1px问题
- 适配iPhoneX
- 横屏适配
- 图片模糊问题
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 内存空间
- 作用域
- 执行上下文栈
- 变量对象
- 作用域链
- this
- 类型转换
- 闭包(未完成)
- 原型、面向对象
- class和extend
- 继承
- new
- DOM
- Event Loop
- 垃圾回收机制
- 内存泄漏
- 数值存储
- 连等赋值
- 基本类型
- 堆栈溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍历对象属性
- 宽度、高度
- performance
- 位运算
- tostring( ) 与 valueOf( )方法
- JavaScript技术
- 错误
- 异常处理
- 存储
- Cookie与Session
- ES6(未完成)
- Babel转码
- let和const命令
- 变量的解构赋值
- 字符串的扩展
- 正则的扩展
- 数值的扩展
- 数组的扩展
- 函数的扩展
- 对象的扩展
- Symbol
- Set 和 Map 数据结构
- proxy
- Reflect
- module
- AJAX
- ES5
- 严格模式
- JSON
- 数组方法
- 对象方法
- 函数方法
- 服务端推送(未完成)
- JavaScript应用
- 复杂判断
- 3D 全景图
- 重载
- 上传(未完成)
- 上传方式
- 文件格式
- 渲染大量数据
- 图片裁剪
- 斐波那契数列
- 编码
- 数组去重
- 浅拷贝、深拷贝
- instanceof
- 模拟 new
- 防抖
- 节流
- 数组扁平化
- sleep函数
- 模拟bind
- 柯里化
- 零碎知识点
- 第四部分 进阶
- 计算机原理
- 数据结构(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 搜索算法
- 动态规划
- 二叉树
- 浏览器
- 浏览器结构
- 浏览器工作原理
- HTML解析
- CSS解析
- 渲染树构建
- 布局(Layout)
- 渲染
- 浏览器输入 URL 后发生了什么
- 跨域
- 缓存机制
- reflow(回流)和repaint(重绘)
- 渲染层合并
- 编译(未完成)
- Babel
- 设计模式(未完成)
- 函数式编程(未完成)
- 正则表达式(未完成)
- 性能
- 性能分析
- 性能指标
- 首屏加载
- 优化
- 浏览器层面
- HTTP层面
- 代码层面
- 构建层面
- 移动端首屏优化
- 服务器层面
- bigpipe
- 构建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack优化
- Webpack原理
- 实现loader
- 实现plugin
- tapable
- Webpack打包后代码
- rollup.js
- parcel
- 模块化
- ESM
- 安全
- XSS
- CSRF
- 点击劫持
- 中间人攻击
- 密码存储
- 测试(未完成)
- 单元测试
- E2E测试
- 框架测试
- 样式回归测试
- 异步测试
- 自动化测试
- PWA
- PWA官网
- web app manifest
- service worker
- app install banners
- 调试PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 饿了么整理
- 样式
- 技巧
- Vue音乐播放器
- Vue源码
- Virtual Dom
- computed原理
- 数组绑定原理
- 双向绑定
- nextTick
- keep-alive
- 导航守卫
- 组件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 动画(未完成)
- 异常监控、收集(未完成)
- 数据采集
- Sentry
- 贝塞尔曲线
- 视频
- 服务端渲染
- 服务端渲染的利与弊
- Vue SSR
- React SSR
- 客户端
- 离线包
- 第五部分 网络
- 五层协议
- TCP
- UDP
- HTTP
- 方法
- 首部
- 状态码
- 持久连接
- TLS
- content-type
- Redirect
- CSP
- 请求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服务端
- Linux
- Linux命令
- 权限
- XAMPP
- Node.js
- 安装
- Node模块化
- 设置环境变量
- Node的event loop
- 进程
- 全局对象
- 异步IO与事件驱动
- 文件系统
- Node错误处理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服务
- 负载均衡
- 获取用户IP
- 解决跨域
- 适配PC与移动环境
- 简单的访问限制
- 页面内容修改
- 图片处理
- 合并请求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自动化(未完成)
- docker
- 创建CLI
- 持续集成
- 持续交付
- 持续部署
- Jenkins
- 部署与发布
- 远程登录服务器
- 增强服务器安全等级
- 搭建 Nodejs 生产环境
- 配置 Nginx 实现反向代理
- 管理域名解析
- 配置 PM2 一键部署
- 发布上线
- 部署HTTPS
- Node 应用
- 爬虫(未完成)
- 例子
- 反爬虫
- 中间件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源码
- app.js
- config.js
- 消息队列
- RPC
- 性能优化
- 第七部分 总结
- Web服务器
- 目录结构
- 依赖
- 功能
- 代码片段
- 整理
- 知识清单、博客
- 项目、组件、库
- Node代码
- 面试必考
- 91算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码