Webcourse/10-ES6/04-ES6:变量、函数扩展.md
2018-04-05 11:01:33 +08:00

578 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## ES6 的变量声明
ES6 中新增了 let 和 const 来定义变量:
- `var`ES5 和 ES6中定义**全局变量**是variable的简写
- `let`:定义**局部变量**,替代 var。
- `const`:定义**常量**(定义后,不可修改)。
### var全局变量
看下面的代码:
```javascript
{
var a = 1;
}
console.log(a); //这里的 a指的是 区块 里的 a
```
上方代码是可以输出结果的,输出结果为 1。因为 var 是全局声明的,所以,即使是在区块里声明,但仍然在全局起作用。
再来看下面这段代码:
```javascript
var a = 1;
{
var a = 2;
}
console.log(a); //这里的 a指的是 区块 里的 a
```
上方代码的输出结果为 2 ,因为 var 是全局声明的。
**总结:**
用 var 定义的全部变量,有时候会污染整个 js 的作用域。
### let局部变量
```
var a = 2;
{
let a = 3;
}
console.log(a);
```
上方代码的输出结果为 2。用 let 声明的变量,只在局部(块级作用域内)起作用。
let是防止数据污染我们来看下面这个例子
用 var 声明变量:(可以打印结果,说明循环体外的变量 i 被污染了)
```
for (var i = 0; i < 10; i++) {
console.log('循环体中:' + i);
}
console.log('循环体外:' + i);
```
用let声明变量不能打印结果
```
for (let i = 0; i < 10; i++) {
console.log('循环体中:' + i);
}
console.log('循环体外:' + i);
```
**总结:**我们要习惯用 let 声明减少var声明带来的**污染全局空间**
为了进一步说明 let 不会带来污染,需要说明的是:当我们定义了`let a = 1`时,如果我们在同一个作用域内继续定义`let a = 2`,是会报错的。
### const声明常量
在程序开发中,有些变量是希望声明后,在业务层就不再发生变化,此时可以用 const 来定义。
举例:
```
const name = 'smyhvae'; //定义常量
```
## 变量的解构赋值
ES6允许我们通过数组或者对象的方式对一组变量进行赋值这被称为解构。
解构赋值在实际开发中可以大量减少我们的代码量,并且让程序结构更清晰。
### 数组的解构赋值
**举例:**
通常情况下,我们在为一组变量赋值时,一般是这样写:
```javascript
let a = 0;
let b = 1;
let c = 2;
```
现在我们可以通过数组解构的方式进行赋值:
```javascript
let [a, b, c] = [1, 2, 3];
```
二者的效果是一样的。
**解构的默认值:**
在解构赋值时,是允许使用默认值的。举例如下:
```javascript
{
//一个变量时
let [foo = true] = [];
console.log(foo); //输出结果true
}
{
//两个变量时
let [a, b] = ['生命壹号'] //a 赋值为生命壹号。b没有赋值
console.log(a + ',' + b); //输出结果:生命壹号,undefined
}
{
//两个变量时
let [a, b = 'smyhvae'] = ['生命壹号'] //a 赋值为生命壹号。b 采用默认值 smyhvae
console.log(a + ',' + b); //输出结果:生命壹号,smyhvae
}
```
`undefined`和`null`的区别:
如果我们在赋值时,采用的是 `undefined`或者`null`,那会有什么区别呢?
```javascript
{
let [a, b = 'smyhvae'] = ['生命壹号', undefined]; //b 虽然被赋值为 undefined但是 b 会采用默认值
console.log(a + ',' + b); //输出结果:生命壹号,smyhvae
}
{
let [a, b = 'smyhvae'] = ['生命壹号', null]; //b 被赋值为 null
console.log(a + ',' + b); //输出结果:生命壹号,null
}
```
上方代码分析:
- undefined相当于什么都没有此时 b 采用默认值。
- null相当于有值但值为 null。
### 对象的解构赋值
我们同样可以针对对象,进行结构赋值。
**举例如下:**
```
let { foo, bar } = { bar: '我是 bar 的值', foo: '我是 foo 的值' };
console.log(foo + ',' + bar); //输出结果:我是键 foo 的值,我是键 bar 的值
```
上方代码可以看出,对象的解构与数组的结构,有一个重要的区别:**数组**的元素是按次序排列的,变量的取值由它的**位置**决定;而**对象的属性没有次序**,是**根据键来取值**的。
**圆括号的使用**
如果变量 foo 在解构之前就已经定义了,此时你再去解构,就会出现问题。下面是错误的代码,编译会报错:
```javascript
let foo = 'haha';
{ foo } = { foo: 'smyhvae' };
console.log(foo);
```
要解决报错,只要在解构的语句外边,加一个圆括号即可:
```javascript
let foo = 'haha';
({ foo } = { foo: 'smyhvae' });
console.log(foo); //输出结果smyhvae
```
### 字符串解构
字符串也可以解构,这是因为,此时字符串被转换成了一个类似数组的对象。举例如下:
```javascript
const [a, b, c, d] = 'smyhvae';
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log(typeof a); //输出结果string
```
输出结果:
![](http://img.smyhvae.com/20180304_1626.png)
## 扩展运算符和 rest 运算符
- 扩展运算符的格式为`...`
- rest运算符的格式为`...变量名`
### 扩展运算符
有了ES6当我们在定义一个方法但是不确定其参数的个数时我们就可以用**扩展运算符**作为参数。
以前,我们在定义方法时,参数要确定个数,如下:(程序会报错)
```javascript
function fn(a, b, c) {
console.log(a);
console.log(b);
console.log(c);
console.log(d);
}
fn(1, 2, 3);
```
上方代码中,因为方法的参数是三个,但使用时是用到了四个参数,所以会报错:
![](http://img.smyhvae.com/20180304_1638.png)
现在,我们有了扩展运算符,就不用担心报错的问题了。代码可以这样写:
```javascript
function fn(...arg) { //当不确定方法的参数时,可以使用扩展运算符
console.log(arg[0]);
console.log(arg[1]);
console.log(arg[2]);
console.log(arg[3]);
}
fn(1, 2, 3); //方法中定义了四个参数但只引用了三个参数ES6 中并不会报错。
```
![](http://img.smyhvae.com/20180304_1650.png)
上方代码中注意arg参数之后不能再加别的参数否则编译报错。
**举例:**数组赋值的问题
我们来分析一段代码:(将数组 arr1 赋值给 arr2
```javascript
let arr1 = ['www', 'smyhvae', 'com'];
let arr2 = arr1; // 将 arr1 赋值给 arr2其实是让 arr2 指向 arr1 的内存地址
console.log('arr1:' + arr1);
console.log('arr2:' + arr2);
console.log('---------------------');
arr2.push('你懂得'); //往arr2 里添加一部分内容
console.log('arr1:' + arr1);
console.log('arr2:' + arr2);
```
运行结果:
![](http://img.smyhvae.com/20180304_1950.png)
上方代码中,我们往往 arr2 里添加了`你懂的`却发现arr1 里也有这个内容。原因是:`let arr2 = arr1;`其实是让 arr2 指向 arr1 的地址。也就是说,二者指向的是同一个内存地址。
如果不想让 arr1 和 arr2 指向同一个内存地址,我们可以借助扩展运算符来做:
```javascript
let arr1 = ['www', 'smyhvae', 'com'];
let arr2 = [...arr1]; //arr2 会重新开辟内存地址
console.log('arr1:' + arr1);
console.log('arr2:' + arr2);
console.log('---------------------');
arr2.push('你懂得'); //往arr2 里添加一部分内容
console.log('arr1:' + arr1);
console.log('arr2:' + arr2);
```
运行结果:
![](http://img.smyhvae.com/20180304_1951.png)
我们明白了这个例子,就可以避免开发中的很多业务逻辑上的 bug。
### `rest` 运算符
`rest` 在英文中指的是**剩余部分**(不是指休息)。我们来举个例子,理解剩余部分的含义:
```javascript
function fn(first, second, ...arg) {
console.log(arg.length);
}
fn(0, 1, 2, 3, 4, 5, 6); //调用函数后,输出结果为 5
```
上方代码的输出结果为 5。 调用`fn()`时,里面有七个参数,而`arg`指的是剩下的部分(因为除去了`firt`和`second`)。
从上方例子中可以看出,`rest`运算符适用于:知道前面的一部分参数的数量,但对于后面剩余的参数数量未知的情况。
## for ... of 循环
ES6 中,如果我们要遍历一个数组,可以这样做:
```
let arr1 = [1, 2, 3, 4, 5];
for (let value of arr1) {
console.log(value);
}
```
输出结果:
![](http://img.smyhvae.com/20180304_2016.png)
for…of 的循环可以避免我们开拓内存空间增加代码运行效率所以建议大家在以后的工作中使用for…of循环。
注意,上面的数组中,`for ... of`获取的是数组里的值;`for ... in`获取的是index索引值。
### Map对象的遍历
`for ... of`既可以遍历数组也可以遍历Map对象。
## 模板字符串
我们以前让字符串进行拼接的时候,是这样做的:(传统写法的字符串拼接)
```javascript
var name = 'smyhvae';
var age = '26';
console.log('name:'+name+',age:'+age); //传统写法
```
这种写法,比较繁琐,而且容易出错。
现在有了 ES6 语法,字符串拼接可以这样写:
```javascript
var name = 'smyhvae';
var age = '26';
console.log('name:'+name+',age:'+age); //传统写法
console.log(`name:${name},age:${age}`); //ES6 写法
```
注意上方代码中倒数第二行用的是单引号最后一行用的是反引号在tab键的上方
参考链接:
- [ES6的rest参数和扩展运算符](https://segmentfault.com/a/1190000010222698)
## 箭头函数
需要说明的是ES6中函数新增了很多特性。例如
- 参数默认值
- 扩展运算符
- rest参数
- 箭头函数
- this绑定
- 尾调用
这一段,我们来讲一下箭头函数。
定义和调用函数:(传统写法)
```javascript
function fn1(a, b) {
return a + b;
}
console.log(fn1(1, 2)); //输出结果3
```
定义和调用函数ES6中的写法
```
var fn2 = (a, b) => a + b;
console.log(fn2(1, 2)); //输出结果3
```
上方代码中,箭头后面的内容,就相当于 return 的内容(**返回值**)。
在箭头函数中,如果方法体内有两句话,那就需要在方法体外边加上{}括号。如下:
```javascript
var fn2 = (a, b) => {
console.log('haha');
return a + b;
};
console.log(fn2(1, 2)); //输出结果3
```
从上面的箭头函数中,我们可以很清晰地找到函数名、参数名、方法体。
### 参数默认值
当然,在 ES6 中定义方法时,我们还可以给方法里的参数加一个**默认值**(缺省值):
- 方法被调用时,如果没有给参数赋值,那就是用默认值;
- 方法被调用时,如果给参数赋值了新的值,那就用新的值。
如下:
```javascript
var fn2 = (a, b = 5) => {
console.log('haha');
return a + b;
};
console.log(fn2(1)); //第二个参数使用默认值 5。输出结果6
console.log(fn2(1, 8)); //输出结果9
```
需要提醒的是:**默认值的后面,不能再有没有默认值的变量**。比如`(a,b,c)`这三个参数如果我给b设置了默认值那么就一定要给c设置默认值。
另外,我们来看下面这段代码:
```javascript
let x = 'smyh';
function fn(x, y = x) {
console.log(x, y);
}
fn('vae');
```
注意第二行代码我们给y赋值为`x`,这里的`x`是第一个参数,并不是第一行代码里定义的`x`。打印结果:`vae vae`。
如果我把第一个参数改一下,改成:
20180312_2017.png
此时打印结果是:`vae smyh`。
### this的指向
ES5中this指向的是函数被调用的对象而ES6的箭头函数中this指向的是函数被定义时。
所以说箭头韩注重一定要注意this的指向。
## 模块化
**模块化的意义**
比如说当我需要用到jQuery库时我会把jQuery.js文件引入到我自己代码的最前面当我需要用到vue框架时我会把vue.js文件引入到我自己代码的最前面。
可是如果有20个这样的文件就会产生20次http请求。这种做法的性能肯定是不能接受的。
如果把20个文件直接写在一个文件里肯定是不方便**维护**的。可如果写成20个文件这20个文件又不好排序。这就是一个很矛盾的事情于是模块化就诞生了。
**模块化历程**commonJS、AMD规范RequireJS、CMD规范SeaJSimport & export
**export**
静态化:必须在顶部,不能使用条件语句,自动采用严格模式。(静态化有利于性能以及代码的稳定性)
更多 ES6 的语法,本文会陆续更新。
## 参考链接:
- [阮一峰 | ECMAScript 6 入门](http://es6.ruanyifeng.com/)
## 我的公众号
想学习<font color=#0000ff>**代码之外的软技能**</font>?不妨关注我的微信公众号:**生命团队**id`vitateam`)。
扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:
![](http://img.smyhvae.com/2016040102.jpg)