【js 笔记】读阮一峰老师 es6 入门笔记 —— 第一章
鉴于最近用 vuejs 框架开发项目,其中有很多涉及到 es6 语法不太理解所以便认真地读了一下这本书。
地址:http://es6.ruanyifeng.com/#README
第一章:let ,const 命令以及块级作用域
es6 新增了 let 和 const 这两个变量的声明关键字,这样大大的强化了 js 变量的合理程度以及修补了很多es6 版本前出现的bug。他们有着以下的特性:
一:let 的声明
1.1 用let 声明的变量会将变量绑定到声明时所属的语句块中,并且语句块外部不可访问
例如下面这个例子:
for (let i = 0; i < 10; i++) { // ... } console.log(i); // ReferenceError: i is not defined
在es6 版本以前,循环条件内的变量如果用var 声明则循环结束后还可以在外部环境中访问,而是用let 声明不会。在每一次循环中let 都会被重新声明一次并且在本轮循环中有效
在for 循环中还有一个特别的地方,条件块和执行块都是一个独立的块作用域因此下面的例子也能正常运行
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
即使是声明了同名变量但因为是在不同的块作用域中声明,所以不会报错,但我觉得为了阅读代码有更好的体验还是尽量别用同名变量
1.2 用let 声明的变量不存在变量提升
变量提升一直是js 的一个通病,好听一点是叫做“特征”,这不仅颠覆了编程语言执行顺序还会带来一些不可思议的问题,如下:
// var 的情况 console.log(foo); // 输出undefined var foo = 2; // let 的情况 console.log(bar); // 报错ReferenceError let bar = 2;
第一种用var 声明的变量在代码执行前就已经存在变量的提升可以拆解为如下步骤:
// var 的情况 var foo; console.log(foo); // 输出undefined foo = 2;
所以才会输出 undefined
而let 的出现改变了这个“特征”,使js 不存在变量声明,假若在声明变量语句前调用变量则会报错
1.3 暂时性死区
在一个语句块中用let 声明一个变量,这个变量便会绑定在这个语句块中不受外部变量影响,例子:
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面例子中因为tmp 是在if 语句块中声明所以tmp 便绑定在了if 语句块中,语句块中又会重新判断一次块内语句声明的合理性,而tmp 的调用时在声明前所以报错
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。
1.4 不允许重复声明
用let 声明的变量不能同名,否则报错,另外在函数内部用let 声明的变量也不可以与参数同名
二:块级作用域
2.1 没有作用域的日子
es5 没有块级作用域的概念,这样导致了内层变量覆盖外层变量 以及 用来计数的循环变量泄露为全局变量
内层变量覆盖外层变量的例子:
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
上面说过var 声明的变量会被提升,所以上面的例子可以拆解成这样:
var tmp = new Date(); function f() { var tmp; console.log(tmp); if (false) { tmp = 'hello world'; } } f(); // undefined
因为变量提升 tmp 未被赋值就被调用(函数的后期也没有为tmp 赋值),所以导致函数输出为undefined
用来计数的循环变量泄露为全局变量例子:
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
上面也说过,用let 在循环条件内部声明变量的好处就是不会将变量泄露到全局作用域,而用var 则会。es5的解决办法是用闭包模拟块作用域迫使循环条件内部的变量不泄露
2.2 es6 作用域的来袭
先看个例子:
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
函数体内部定义了一个块作用域,其上级作用域就是函数体,当在if 语句作用域外调用 变量n 的时候是不会调用到if 语句内部的变量的只会调用会输出语句所属语句块中的对应变量。
假如用var 声明便会这样:
function f1() { var n = 5; if (true) { var n = 10; } console.log(n); } f1(); // 输出10
因为变量的提升影响了输出结果,分解:
function f1() { var n; n = 5; if (true) { n = 10; } // 判断条件成立 变量n 被重新赋值为10 console.log(n); } f1(); // 故输出10
2.3 块作用域可嵌套,在不同的块作用域声明同名变量不会报错
{{{{ let insane = 'Hello World'; {let insane = 'Hello World'} }}}}; // 没问题
块作用域的出现使原来的 iife(自执行函数)可以停止使用了
2.4 块级内部声明函数可能会因为浏览器的差异导致不可思议的效果
es5 规则中函数只能在全局作用域中声明,不能再局部作用域声明,但是浏览器并没有遵循这个原则为了兼容以前的旧代码这种声明是正常的
而es6 有了块作用域的概念,明确指明可以在块级作用域中声明函数,例子:
function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }());
在es5 中运行会输出 I am inside! 因为函数声明的提升,而在es6 中调用理论会输出 I am outside!,而实际上浏览器执行会直接报错那是因为为了减轻老代码不兼容的问题浏览器可以不按规则办事,具体如下:
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。
所以在支持es6 的浏览器执行上面例子语句其实会执行如下语句
// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f; if (false) { f = function() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
考虑到浏览器的差异,所以尽量少在块作用域中使用函数声明而改成用函数表达式。
另外ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
// 不报错 'use strict'; if (true) { function f() {} } // 报错 'use strict'; if (true) function f() {}
2.5 do 表达式
块作用域没有返回值,如果在块作用域前面加上一个 do 关键字可以使块作用域有返回值,在chrome 上没有实验出来只能贴下阮一峰老师的实例代码了
let x = do { let t = f(); t * t + 1; };
x 会得到块作用域的返回值(尽管不知道哪里才是返回的值)
三:const(constant ) 声明
3.1 const 声明的变量是一个常量,声明之后常量的值不可改变而且一旦声明就必须要初始化
声明常量后再次赋值的错误:
const PI = 3.1415; console.log(PI); // 3.1415 PI = 3; // 报错 TypeError: Assignment to constant variable.
声明常量但不赋初始值的错误:
const foo; // SyntaxError: Missing initializer in const declaration
3.2 const 声明的常量和 let 声明的变量一样:
1、都会绑定在声明的语句块中,在语句块外调用会报错
2、不存在变量提升,存在暂时性死区,只能在声明的位置后面使用
3、不能重复声明
3.3 const 声明的常量是一个基础类型,那么它保证这个基础类型的值不变;如果const 声明的常量是一个引用类型,那么它保证这个引用的指针不变
对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const
只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。例如我们改变对象中某个键的对应值,在常量声明下是可以继续运行的不会报错。
如果想完全冻结对象可以用使用 Object.freeze()