JavaScript的ES6~ES13核心知识点
一. let/const
1.1. let/const 基本使用
在 ES5 中我们声明变量都是使用的 var 关键字,从 ES6 开始新增了两个关键字可以声明变量:let、const
let、const 在其他编程语言中都是有的,所以也并不是新鲜的关键字;
但是 let、const 确确实实给 JavaScript 带来一些不一样的东西;
let 关键字:
- 从直观的角度来说,let 和 var 是没有太大的区别的,都是用于声明一个变量
1 | let foo = "foo"; |
const 关键字:
const 关键字是 constant 的单词的缩写,表示常量、衡量的意思;
它表示保存的数据一旦被赋值,就不能被修改;
1 | const foo = "foo"; |
但是如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容:
1 | const obj = { name: "why", age: 18 }; |
另外 let、const 不允许重复声明变量:
1 | let name = "why"; |
1.2. let/const 作用域提升
let、const 和 var 的另一个重要区别是作用域提升。
我们知道 var 声明的变量是会进行作用域提升的:
1 | console.log(foo); // undefined |
但是如果我们使用 let 声明的变量,在声明之前访问会报错:
1 | console.log(foo); // ReferenceError: Cannot access 'foo' before initializationlet |
那么是不是意味着 foo 变量只有在代码执行阶段才会创建的呢?
事实上并不是这样的,我们可以看一下 ECMA262 对 let 和 const 的描述;
这些变量会被创建在包含他们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值;
从上面我们可以看出,在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的。
那么变量已经有了,但是不能被访问,是不是一种作用域的提升呢?
事实上维基百科并没有对作用域提升有严格的概念解释,那么我们自己从字面量上理解;
作用域提升:在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升;
在这里,它虽然被创建出来了,但是不能被访问,我认为不能称之为作用域提升;
1.3. window 对象添加属性
我们知道,在全局通过 var 来声明一个变量,事实上会在 window 上添加一个属性:
1 | var foo = "foo"; |
但是 let、const 是不会给 window 上添加任何属性的:
1 | let foo = "foo"; |
那么我们可能会想这个变量是保存在哪里呢?
也就是说我们声明的变量和环境记录是被添加到变量环境中的:
但是标准有没有规定这个对象是 window 对象或者其他对象呢?
其实并没有,那么 JS 引擎在解析的时候,其实会有自己的实现,比如 v8 中其实是通过 VariableMap 的一个 hashmap 来实现它们的存储的。
那么 window 对象呢?而 window 对象是早期的 GO 对象,在最新的实现中其实是浏览器添加的全局对象,并且一直保持了 window 和 var 之间值的相等性;
1.4. let/const 块级作用域
1.4.1. 理解块级作用域
在我们前面的学习中,JavaScript 只会形成两个作用域:全局作用域和函数作用域。
ES5 中放到一个代码中定义的变量,外面是可以访问的:
1 | { |
在 ES6 中新增了块级作用域,并且通过 let、const、function、class 声明的标识符是具备块级作用域的限制的:
1 | { |
但是我们会发现函数拥有块级作用域,但是外面依然是可以访问的:
- 这是因为引擎会对函数的声明进行特殊的处理,允许像 var 那样进行提升;
1.4.2. 块级作用域应用
那么增加了这个块级作用域后,它有什么作用呢?
if 的块级作用域
1 | if (true) { |
switch、case 的块级作用域:
1 | var color = "red"; |
for 循环的块级作用域:
1 | for (var i = 0; i < 10; i++) { |
1.4.3. 块级作用域场景
我来看一个实际的案例:获取多个按钮监听点击
1 | <button>按钮1</button> |
如果我们通过 var 来实现,会有问题:
1 | const btns = document.getElementsByTagName("button"); |
var 问题的解决方案:
1 | const btns = document.getElementsByTagName("button"); |
使用 let 或者 const 来实现:
1 | const btns = document.getElementsByTagName("button"); |
1.5. let、const 暂时性死区
在 ES6 中,我们还有一个概念称之为暂时性死区:
它表达的意思是在一个代码中,使用 let、const 声明的变量,在声明之前,变量都是不可以访问的;
我们将这种现象称之为 temporal dead zone(暂时性死区,TDZ);
1 | var foo = "foo"; |
1.6. var、let、const 的选择
那么在开发中,我们到底应该选择使用哪一种方式来定义我们的变量呢?
对于 var 的使用:
我们需要明白一个事实,var 所表现出来的特殊性:比如作用域提升、window 全局对象、没有块级作用域等都是一些历史遗留问题;
其实是 JavaScript 在设计之初的一种语言缺陷;
当然目前市场上也在利用这种缺陷出一系列的面试题,来考察大家对 JavaScript 语言本身以及底层的理解;
但是在实际工作中,我们可以使用最新的规范来编写,也就是不再使用 var 来定义变量了;
对于 let、const:
对于 let 和 const 来说,是目前开发中推荐使用的;
我们会有限推荐使用 const,这样可以保证数据的安全性不会被随意的篡改;
只有当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用 let;
这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范;
二. 字符串模板
2.1. 字符串模板基本使用
在 ES6 之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的(ugly)。
1 | const name = "why"; |
ES6 允许我们使用字符串模板来嵌入 JS 的变量或者表达式来进行拼接:
首先,我们会使用 `` 符号来编写字符串,称之为模板字符串;
其次,在模板字符串中,我们可以通过 ${expression} 来嵌入动态的内容;
1 | const name = "why"; |
甚至是我们可以进行函数的调用:
1 | function foo() { |
2.2. 标签模板字符串使用
模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)。
我们一起来看一个普通的 JavaScript 的函数:
1 | function foo(...args) { |
如果我们使用标签模板字符串,并且在调用的时候插入其他的变量:
模板字符串被拆分了;
第一个元素是数组,是被模块字符串拆分的字符串组合;
后面的元素是一个个模块字符串传入的内容;
1 | function foo(...args) { |
在 styled component 中,就是通过这种方式来解析模块字符串,最终生成我们想要的样式的:
三. 函数知识补充
3.1. 函数的默认参数
在 ES6 之前,我们编写的函数参数是没有默认值的,所以我们在编写函数时,如果有下面的需求:
传入了参数,那么使用传入的参数;
没有传入参数,那么使用一个默认值;
通常我们会进行如下方式的实现:
1 | function foo(x, y) { |
其实这种做法是有弊端的,如果我们传入 null、0、空字符串,那么会认为需要使用默认值:
1 | foo(null, ""); // 20, 30 |
而在 ES6 中,我们允许给函数一个默认值:
1 | function foo(x = 20, y = 30) { |
我们使用 ES6 默认值的写法不仅仅可以避免上面的问题,而且代码的阅读性也会更强:
- 其实 ES6 的默认值转化后是如下的代码:
1 | function foo() { |
默认值也可以和解构一起来使用:
1 | // 写法一: |
另外参数的默认值我们通常会将其放到最后(在很多语言中,如果不放到最后其实会报错的):
- 但是 JavaScript 允许不将其放到最后,但是意味着还是会按照顺序来匹配;
1 | function foo(x = 20, y, z) { |
另外默认值会改变函数的 length 的个数,默认值以及后面的参数都不计算在 length 之内了:
1 | function foo(x = 10, y, z) { |
3.2. 参数的作用域?
3.3. 函数的剩余参数
ES6 中引用了 rest parameter,可以将不定数量的参数放入到一个数组中:
- 如果最后一个参数是
...
为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
1 | function foo(m, n, ...args) { |
那么剩余参数和 arguments 有什么区别呢?
剩余参数只包含那些没有对应形参的实参,而
arguments
对象包含了传给函数的所有实参;arguments
对象不是一个真正的数组,而 rest 参数是一个真正的数组,可以进行数组的所有操作;arguments 是早期的 ECMAScript 中为了方便去获取所有的参数提供的一个数据结构,而 rest 参数是 ES6 中提供并且希望以此来替代 arguments 的;
剩余参数必须放到最后一个位置,否则会报错:
1 | function foo(m, ...args, n) { |
3.4. 箭头函数的补充
在前面我们已经学习了箭头函数的用法,这里进行一些补充:
- 箭头函数是没有显示原型的,所以不能作为构造函数,使用 new 来创建对象;
1 | const foo = () => { |
所以下面的代码会报错:
1 | // TypeError: foo is not a constructor |
四. ES6 小知识点
4.1. 展开语法
展开语法(Spread syntax):
可以在函数调用/数组构造时,将数组表达式或者 string 在语法层面展开;
还可以在构造字面量对象时, 将对象表达式按 key-value 的方式展开;
在函数调用时使用:
1 | // 在调用函数时展开数组 |
在数组构造时使用:
1 | const newNames = [...names, ...name]; |
在构建对象字面量时,也可以使用展开运算符,这个是在 ES2018(ES9)中添加的新特性:
1 | const newObj = { ...obj, height: 1.88, ...names }; |
展开语法是一种浅拷贝:
1 | const obj = { friend: { name: "kobe" } }; |
4.2. 数值的表示
在 ES6 中规范了二进制和八进制的写法:
1 | const num1 = 100; |
文章转载于coderwhy | JavaScript 高级系列(十三) - ES6~ES13-ES6 核心知识点