Bootstrap

前端开发基础(2)ES6

ECMAScript 6(简称 ES6)是 JavaScript 的一个版本,也被称为 ECMAScript 2015。它是 JavaScript 的一个标准规范,提供了一系列新的语法和 API,用于帮助开发者更加轻松、高效地编写 JavaScript 代码。

一. let 和 const 命令

let 命令声明一个变量,可以修改。

const 命令声明一个常量,声明之后不允许修改,因此 const 一旦声明变量,就必须立即初始化,不能留到以后赋值。

const 实际上保证的,并不是变量的值不变,而是变量指向的内存地址所存储的数据不变。对基础数据类型,值就存储在变量指向的内存地址,因此相当于常量。对于引用数据类型,变量指向的内存地址,存储的只是一个指向实际数据的指针。const 只能保证这个指针是固定的,至于它指向的数据结构就不能控制了。所以 const 声明的数组和对象,可以增减元素和属性。

要点(和 var 的不同):

  • 不存在变量提升,变量一定要在声明之后才能使用。

  • 块级作用域:let 和 const 声明的变量只在它所在的代码块有效。因此,for 循环的计数器很适合使用 let 命令。

    const f = () => {
     	let x = 1; 			// 只在 f 函数内有效
     	if (true) {
       		let x = 2; 		// 只在 if 语句块内有效
       		const y = 'hello world !';
       		console.log(x); // 输出 2
    	}
    	console.log(x); 	// 输出 1
    	console.log(y);		// ReferenceError: y is not defined
    }
    f();
    

    在同一作用域下,不能重复声明同一个变量。

暂时性死区:是指在代码块中,在变量声明语句之前使用该变量会导致 ReferenceError 错误的现象。

在代码块中,如果使用 let 或 const 声明一个变量,那么这个变量会从开始执行该代码块的时间开始进入暂时性死区,直到声明语句完成为止。在这个时期内,如果使用该变量,将会触发 ReferenceError 错误。

var tmp = 123;

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

二. 解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于 undefined。

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

let [x = 1, y = x] = [2];	// x = 2, y = 2
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };	// bar = 'bbb', foo = 'aaa'

实际上,对象的解构赋值是let {bar: bar, foo: foo} = { foo: 'aaa', bar: 'bbb'};的简写形式。对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

// bar是匹配的模式,foo才是变量,真正被赋值的是变量foo
let { bar: foo } = { foo: 'aaa', bar: 'bbb' };		// foo = 'bbb'

三.扩展和新增

1. 字符串
  • 模板字符串:使用反引号(`)标记,变量名写在 ${}之中。
  • 字符串包含
let s = 'hello, world!';
s.includes('ll');			//true
s.startsWith('hello');		//true
s.endsWith('!');			//true
// 支持第二个参数,表示开始搜索的位置
  • replaceAll()

    • 之前,replace() 只能替换第一个匹配,要想全部替换,就需要用到正则表达式的g修饰符,这种情况就可以使用 replaceAll()
    let s = 'abbc';
    s.replace('b', '_');			// a_bc
    s.replace(/b/g, '_');			// a__c
    s.replaceAll('b', '_');			// a__c
    
    • replaceAll() 的第一个参数可以是字符串,也可以是正则表达式(必须带 g 修饰符,否则会报错)。
    • replaceAll() 的第二个参数表示替换的文本,也可以是一些特殊字符串
    // $&:匹配的字符串,即`b`本身
    'abbc'.replaceAll('b', '$&');		// abbc
    
    //$`:匹配结果前面的文本,对第一个b,指的是a,对第二个b,指的是ab
    'abbc'.replaceAll('b', '$`');		// aaabc
    
    // $':品牌结果后面的文本
    'abbc'.replaceAll('b', `b'`);		// abccc
    
    // $$:表示美元符号$
    ‘abbc’,replaceAll('b', '$$');		// a$$c
    
    // $1表示正则表达式的第一组匹配'ab',$2指正则匹配第二组匹配'bc'
    'abbc'.replaceAll(/(ab)(bc)/g, '$2$1');		// bcab
    
2. 数值
  • 数值分隔符:允许使用下划线 _ 作为分割符 123_40 === 12_340 === 1234 * 10;,但是,字符串转数值的函数不能使用数值分隔符parseInt('123_456') = 123;
3. 函数
  • 箭头函数:=>

  • ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

    • 参数默认值可以与解构赋值结合使用
    const fetch = (url, {body = '', method = 'GET', headers = {}}) => {
    	console.log(method);
    }
    
    fetch('http://example.com', {});	// GET
    fetch('http://example.com');		// 报错
    

    以上代码中,fetch() 的第二个参数必须是对象,且不能省略,这样才能获取默认参数值,否则会报错。如果结合函数参数的默认值,就可以省略第二个参数。

    const fetch = (url, {body = '', method = 'GET', headers = {}} = {}) => {
    	console.log(method);	
    }
    fetch('http://example.com');		// GET
    
  • rest 参数:使用 (…变量名)的形式来获取函数的剩余参数 const sortNumbers = ( ... numbers) => numbers.sort();

4. 数组
  • 扩展运算符:...,可以用于合并数组和函数调用。
const add = (x, y) => x + y;
add( ...[2, 3]);
  • includes():[1, 2, 3].includes(2);
  • arr.entries()、arr.keys()、arr.values():遍历数组
  • ES6 明确将数组中的空位转为 undefined
5. 对象
  • 属性简洁表示:当变量名和和属性名一样时可以省略。
const foo = 'abc';
const baz = { foo };	// { foo: 'abc' }
// 相当于const baz = { foo: foo };
  • 属性名表达式:当对象的属性名为参数时,使用 [ ]表达式
let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};
a['first word'];		// hello
a[lastWord];			// world
  • 解构赋值:浅拷贝
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
  • 扩展运算符:… let n = { ...{a: 2, b: 3} };
  • Object.assign():合并元对象到目标对象,浅拷贝
const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target 	// {a:1, b:2, c:3}
  • Object.keys(obj),Object.values(obj),Object.entries(obj):遍历对象
6. 运算符
  • 指数运算符 ** 2 ** 2 = 4;
  • 链判断运算符 ?. const firstName = message?.body?.user?.firstName || 'default';
  • Null 判断运算符 ?? const headerText = response.settings.headerText ?? 'Hello, world!';,只有运算符左侧的值为 null 或 undefined 时,才会返回右侧的值。和 || 类似。但是 || 使用时,左侧为 0、 false、 空字符串、NaN、null、undefined时都返回右侧的值,判断更为宽松。
7. Symbol
  • 基础数据类型,Symbol 值通过 Symbol() 函数生成,是一种类似字符串的数据类型。
const s = Symbol('foo');
s;				// Symbol(foo)
typeof s;		// 'symbol'
s.toString();	// 'Symbol(foo)'
s.description;	// 'foo' 
  • 表示独一无二的值,即使使用相同的参数调用 Symbol 函数,也认为是不同的值
const s1 = Symbol('foo');
const s2 = Symbol('foo');

console.log(s1 === s2); 	// false
  • 由于每一个 Symbol 值都是不相等的,所以 Symbol 常被用做属性名,来防止某一个键被改写或覆盖。注意,Symbol 值作为对象属性名时,不能用点运算符,而是需要放在方括号内
const name = Symbol('John');
let users = {
	Alice: 12,
	[name]: 13,
}
users;					// {Alice: 12, Symbol(John): 13}
users[name] = 15;		// {Alice: 12, Symbol(John): 15}	
8. Set
  • 类似数组,但是成员的值唯一,没有重复 let s = new Set();
  • 可以使用扩展运算符和 Set 去除数组中的重复成员
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];	// [3, 5, 2]
  • 增删查
let s = new Set();
s.add(1).add(2).add(2);
// 注意2被加入了两次

s.size // 2

s.has(2) // true
s.has(3) // false

s.delete(2) // true
s.has(2) // false
9. Map
  • 类似对象,本质上是键值对的集合。但是对象只能使用字符串当作键,而 Map 中,各种数据类型(包括对象 )都可以当作键。
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) 		// "content"

m.has(o)		// true
m.delete(o) 	// true
m.has(o) 		// false
  • 只有对同一个对象的引用,Map 结构才将其视为同一个键,主要针对的是引用数据类型作为键的情况。
const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined
// 表面上是相同的键,但实际上是两个不同的数组实例,内存地址不同

四. 异步编程

1. Promise 对象
  • Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

  • ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。并且,Promise 新建后就会立即执行。

    const fetchData = new Promise((resolve, reject) => {
    	console.log('Promise');	// Promise新建后立即执行,输出'Promise'
      	setTimeout(() => {		// 异步操作
          	const data = 'Some data';
          	if (data) {
            	resolve(data); 		// 异步操作成功时调用,使Promise的状态由pending转为resolved,并将异步操作的结果作为参数传递出去
          	} else {
            	reject('Error occurred'); 	// 异步操作失败时调用,使Promise的状态由pending转为rejected,并将错误信息作为参数传递出去
          }
        }, 2000);
    });
    

    Promise 构造函数接受一个函数作为参数,该函数的两个参数分别为 resolve 和 reject,这两个参数也都是函数,由 JavaScript 引擎提供,不需要自己写,分别指异步操作成功和失败后的回调函数。

  • Promise 实例生成之后,可以用 then 方法指定 resolve 状态的回调函数,用 catch 方法指定 reject 状态的回调函数。

    fetchData.then(data => {			// Promise状态变为resolved之后触发then方法
    	console.log('Data:', data);		// 输出 Data: Some data
    }).catch(err => {					// Promise状态变为rejected时触发catch方法
    	console.log('Error:', err);
    })
    

    一般来说,调用 resolve 或 reject 以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。

  • Promise 实例具有 then 方法,then 方法会返回一个新的 Promise 实例。因此,可以采用链式写法,then 方法后面再调用下一个 then 方法。

    fetchData.then(data => {
    	fetchData(data.result)
    }).then(comments => {
    	console.log("resolved: ", comments),
    }).catch(err => {
    	console.log("rejected: ", err)
    });
    

    catch 方法放在最后,会捕捉前面所有异步操作的错误。

2. Genetator 函数(用不到)

Generator 函数有两个特征。一是,function关键字与函数名之间有一个星号(*);二是,函数体内部使用 yield 表达式,定义不同的内部状态。

const fs = require('fs');

const readFile = fileName => {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (error, data) => {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

var co = require('co');	// co模块用于 Generator 函数的自动执行,返回一个Promise
co(gen).then(() => {
  console.log('Generator 函数执行完成');
});
3. async 函数

async 函数其实是 Generator 函数的语法糖,将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await 。而且 async 函数自带执行器,不需要 co 模块。

const asyncReadFile = async () => {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
};

asyncReadFile();

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数,async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。

五. Module 语法

1. export
const name = 'John';
const age = 18;
export {name, age};

// 还可以写成
export const name = 'John';
export const age = 18;

在一个文件或模块中,export 可以有多个。

2. export default
export default const user = {
	name: 'John',
	age: 18
}

export default 指默认输出,一个文件或模块中只能有一个。

3. import
import {name, age as year} from './index.js';  // export和import都可以使用as重命名

import user from './index.js';

通过 export 导出,导入时需要 {},通过 export default 导出则不需要。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;