ES6 常用特性与案例

前言

看书学习React && 手动实现书中代后的一周,内心对于ES6、React自身的一些特性很混淆,不知道代码中哪一些是ES6的语法和特性,哪些是React自身的语法, 哪些又是JavaScript自带的用法。

学习参考资料
给React初学者的10分钟ES6教程
深入理解ES6
ECMAScript 6 入门
JavaScript 标准参考教程 - 语法专题 - ECMAScript 6 介绍

ES6

声明变量的关键字:var、let 和 const

JavaScript中,我们通常说的作用域是函数作用域,使用var声明的变量,无论是在代码的哪个地方声明的,都会提升到当前作用域的最顶部,这种行为叫做变量提升(Hoisting)。
也就是说,如果在函数内部声明的变量,都会被提升到该函数开头,而在全局声明的变量,就会提升到全局作用域的顶部。
没有声明和声明后没有赋值是不一样

在ES5中,使用 var 去声明变量,var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

//ES5
console.log(myVar); // prints out undefined
var myVar = 10;

在ES6中,使用 letconst 声明变量,并且必须提前声明变量才可以使用。

//ES6
console.log(myVar); // ReferenceError myVar is not defined
let myVar = 10;

在ES5 中只有全局作用域和函数作用域,没有块级作用域。而 let则实际上为JavaScript新增了块级作用域。用let声明的变量,只在let 命令所在的代码块内有效。

Note:

  • const的作用域与let命令相同:只在声明所在的块级作用域内有效。
  • const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
  • const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
  • 当前使用块级绑定的最佳实践是:默认使用const,只在确实需要改变变量值时使用let

暂时性死区

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}
let & const.png
let x = 10;
const y = 20;
x = 25;  //OK
y = 30; // TypeError: Assignment to constant variable.

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加letconst命令,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

ES6函数

ES6中的构造函数Function新增了支持默认参数和不定参数。

函数的默认参数(ES6 语法糖)

ES5 中,我们给函数传参,然后在函数体内设置默认值。

function a(num, callback) {
    num = num || 6;
    callback = callback || function (data) { console.log('ES5:', data) };
    callback(num * num);
}

a() //ES5: 36

a(10, function (data) {
    console.log(data * 10);  //1000, 传参输出新数值
})

ES6中,我们可以使用新的默认值写法。这种写法可以让函数体内部的代码更加简洁优雅。

function b(num = 6, callback = function (data) { console.log('ES6:', data) }) {
    callback(num * num);
}

b() //ES6: 36
b(10, function (data) { console.log(data * 10) }); //1000, 传参输出新数值

修改默认参数对arguments的影响

arguments对象是什么?
准确一点来说它是一个类数组对象,它存在函数内部,它将当前函数的所有参数组成了一个类数组对象。

  function e(x, y) {
      console.log(arguments); //[Arguments] { '0': 1, '1': 2 }
      console.log(arguments.length); //2
  }
e(1,2)

当函数带有默认值的时候,arguments对象是读取不到默认参数的值。

function f(x = 1, y = 2) {
    console.log(arguments); 
}
f()  //[Arguments] {}
f(4, 5)  //[Arguments] { '0': 4, '1': 5 }

在ES5的非严格模式下,一开始输入的参数是1,那么可以获取到arguments[0] (表示第一个参数)全等于num,修改num=2后,arguments[0] 也能更新到2。

function c(num){
    console.log(num === arguments[0]) //true
    num = 2 //修改参数默认值
    console.log(num === arguments[0]) //true
  }
  c(1);

在ES5的严格模式,这个不知道为什么在create-react-app 创建的工程下不生效,都是true

  "use strict"; //严格模式
  function d(num) {
    console.log(num === arguments[0]); // true
    num = 2;
    console.log(num === arguments[0]); // true????
  }
  d(1);
console中使用严格模式.png

在ES6环境下,默认值对arguments的影响和ES5严格模式是同样的标准。

默认参数表达式

参数不仅可以设置默认值为字符串,数字,数组或者对象,还可以是一个函数。

const add = () => 10;
function g(num = add()) {
    console.log(num);
}
g();  //10

无命名参数

当传入的参数是一个对象,不是一个具体的参数名,则是无命名参数。

const demo = (object) => console.log(object.a + object.b);

let obj = {
    a: 1,
    b: 2
}
demo(obj);

不定参数

不定参数使用...(展开运算符)的参数就是不定参数,它表示一个数组.
限制:...参数必须放在所有的参数末尾,不能用于对象的setter方法中。

const add_num = (...arr) => {
    console.log(a + b);
}

let a = 1, b = 2;
add_num(a, b)  //3
// 使用带有不定参数的函数时,参数的声明是要和函数体中使用的具体参数名称一致
// 错误的写法
let obj = {
    set add(...arr){}
}

箭头函数(ES6 语法糖)

箭头函数的声明

// ES5 声明函数的写法
var sum = function(a, b) {
    return a + b;
}

ES6 箭头函数声明注意事项:

  • 如果箭头函数不需要参数 或者 需要多个参数,就要使用圆括号代表参数部分
  • 如果箭头函数有且仅有一个参数,可以省略圆括号
  • 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return 语句返回
  • 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
// ES6 函数写法
// 如果箭头函数不需要参数 或者 需要多个参数,就要使用圆括号代表参数部分
const f = () => 5;
const sum = (a, b) => { return a + b }
// 如果箭头函数有且仅有一个参数,可以省略圆括号
const print = name => console.log(name);
// 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return 语句返回。
const sum2 = (num1, num2) => { return num1 + num2; }
//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
var getTempItem = id => ({ id: id, name: "Temp" });
箭头函数和普通函数的区别:
  • 箭头函数没有 this, 函数内部的this 来自于父级最近的非箭头函数, 并且不能改变 this 的指向。(还有一种描述:函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。
  • 箭头函数没有 super
  • 箭头函数没有 arguments
  • 箭头函数没有 new.targert 绑定
  • 不可以当作构造函数,也就是说,不可以使用 new命令,会报错
  • 没有原型
  • 不支持重复的命名
箭头函数.png

对象

对象字面量语法扩展

在ES6 中,可以用更少的代码表示对象的键值对。

属性初始值简写

//ES5
function a(id) {
    return {
        id: id
    };
}

// ES6
const a6 = (id) => ({id})
str = "Hello"
number = 20

//ES5
obj = {
    str: str,
    number: number
}

// ES6
obj6 = {str, number}

对象方法简写

// ES5
const obj = {
    id: 1,
    printId: function () {
        console.log(this.id)
    }
}

// ES6
const obj = {
    id: 1,
    printId() {
        console.log(this.id)
    }
}

解构 (Destructuring)

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。

解构分类:

  • 数组解构
  • 对象解构
  • 字符串解构

数组解构赋值

从数组中提取值,然后按照对应的位置,对变量赋值。

//ES6
let [a, b, c] = [1, 2, 3];

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
如果解构不成功,变量的值就等于undefined。

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

let [foo] = [];  // foo undefined
let [bar, foo] = [1]; // foo undefined

另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。

// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

解构赋值允许指定默认值。

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null

对象的解构赋值

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: x, last: y } = obj;
x // 'hello'
y // 'world'

这实际上说明,对象的解构赋值是下面形式的简写.

let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
对象的解构也可以指定默认值。

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

函数参数的解构赋值

解构是将对象或者数组中的元素一个个提取出来,而赋值是给元素赋值,解构赋值的作用就是给对象或者数组的元素赋值。

在react的父子组件传递参数过程中,就使用到了解构赋值。

class Parent extends React.Component {
      render() {
        const {a = 3, b = 3} = this.props
        return 

{a}-{b}

} } ReactDOM.render( , document.getElementById('root') );

函数参数默认值与解构赋值默认值结合使用

// 这个只使用了解构赋值默认值,没有使用函数参数默认值
function foo ({x, y=5}){
  console.log(x, y);
}

foo({}); // undefined 5
foo({x: 1})  // 1  5
foo({x: 1, y: 2}); //1  2
foo() // Cannot destructure property `x` of 'undefined' or 'null'.
// 使用了参数默认值,然后是解构赋值默认值
// 如果没有提供参数,函数foo的参数默认为一个空对象。
function foo ({x, y=5} = {}){
  console.log(x, y);
}
foo() // undefined 5

function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')
// 报错

上面代码中,如果函数fetch的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。

function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

字符串的解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

解构的用途

  1. 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
  1. 从函数返回多个值
    函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();
  1. 函数参数的定义 和 数参数的默认值
    解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
function f([x=100, y, z]) { 
  console.log(x)
  console.log(y)
  console.log(z)
 }
f([ , 2, 3]);  // 100    2   3 
  1. 遍历 Map 结构
    任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world
  1. 输入模块的指定方法
    加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
import { SourceMapConsumer, SourceNode } from './xxx.js'

模块的定义

模块是自动运行在严格模式下并且没有办法退出运行的JavaScript代码。
模块可以是函数、数据、类,需要指定导出的模块名,才能被其他模块访问。

//数据模块
const obj = { a: 1 }
//函数模块
const sum = (a, b) => {
    return a + b
}
//类模块
class My extends React.Components {

}

模块的导出

给数据、函数、类添加一个export,就能导出模块。一个配置型的JavaScript文件中,你可能会封装多种函数,然后给每个函数加上一个export关键字,就能在其他文件访问到。

  //数据模块
    export const obj = {a: 1}
    //函数模块
    export const sum = (a, b) => {
      return a + b
    }
    //类模块
    export class My extends React.Components {
    
    }

模块的引用

在另外的js文件中,我们可以引用上面定义的模块。使用import关键字,导入分2种情况,一种是导入指定的模块,另外一种是导入全部模块。

导入指定的模块

 //导入obj数据,My类
import {obj, My} from './xx.js'
    
//使用
console.log(obj, My)

导入全部模块

//导入全部模块
import * as all from './xx.js'

//使用
console.log(all.obj, all.sun(1, 2), all.My)

默认模块的使用

如果给我们的模块加上default关键字,那么该js文件默认只导出该模块,你还需要把大括号去掉。

//默认模块的定义
function sum(a, b) {
    return a + b
}
export default sum

//导入默认模块
import sum from './xx.js'

模块的使用限制

不能在语句和函数之内使用import关键字,只能在模块顶部使用。

修改模块导入和导出名

  1. 在模块导出的时候修改
function sum(a, b) {
    return a + b
}
export { sum as add }

import { add } from './xx.js'
add(1, 2)
  1. 在模块导入的时候修改
function sum(a, b) {
   return a + b
}
export sum

import { sum as add } from './xx.js'
add(1, 2)

无绑定导入

当你的模块没有可导出模块,全都是定义的全局变量的时候,你可以使用无绑定导入。
模块

let a = 1
const PI = 3.1314

无绑定导入:

import './xx.js'
console.log(a, PI)
模块内容.png

模板字符串 (ES6 语法糖)

模板字符串(template string)是增强版的字符串,即可以当作普通字符串使用,也可以在字符串中嵌入变量。它用反引号(`)标识。

// 普通字符串
console.log(`In JavaScript '\n' is a line-feed.`);

// 多行字符串
console.log(`In JavaScript this is
 not legal.`);

// 字符串中嵌入变量
var name = "Bob", time = "today";
console.log(`Hello ${name}, how are you ${time}?`);

var x = 1;
var y = 2;
console.log(`${ x } + ${ y } = ${ x + y}`) 
// "1 + 2 = 3"

你可能感兴趣的:(ES6 常用特性与案例)