图片发自简书App
const和let
解构赋值
模板字符串
函数
扩展对象
import和export
Promise
async与await
一:const和let
let
ES6的块级作用域
let
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5}
上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。
变量提升
let
不像var
那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
console.log(foo); // 输出undefinedconsole.log(bar); // 报错ReferenceErrorvar foo = 2;let bar = 2;
上面代码中,变量foo
用var
命令声明,会发生变量提升,即脚本开始运行时,变量foo
已经存在了,但是没有值,所以会输出undefined
。变量bar
用let
命令声明,不会发生变量提升。这表示在声明它之前,变量bar
是不存在的,这时如果用到它,就会抛出一个错误。
const命令
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415; PI // 3.1415PI = 3;// TypeError: Assignment to constant variable.
const
声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo;// SyntaxError: Missing initializer in const declaration
const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
if (true) { const MAX = 5; } MAX // Uncaught ReferenceError: MAX is not defined
const
声明的常量,也与let
一样不可重复声明。
var message = "Hello!";let age = 25;// 以下两行都会报错const message = "Goodbye!";const age = 30;
总结
const
声明一个只读常量,一旦声明,常量的值就不能改变
const num1 = { a:12}; num1.a=1; //可改变常量的属性值,但无法更改常量值
let用来声明变量,变量值可随意更改
{ let a = 12; var b = 1; console.log(a); a=a+12; console.log(a) }
var
命令存在变量提升效用,let
命令没有这个问题
const
与let
声明的常量及变量只在所在的块级作用域内有效,常量变量是不提升,同样存在暂时性死区,只能在声明的位置后面使用
在let
和const
之间,建议优先使用const
,尤其是在全局环境,不应该设置变量,只应设置常量
二:结构赋值
基本用法
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
以前,为变量赋值,只能直接指定值。
var a = 1;var b = 2;var c = 3;
ES6允许写成下面这样。
var [a, b, c] = [1, 2, 3];
也就是说ES6 里允许给数组进行赋值
let [a, b, c] = [1, 2, 3];console.log([a,b,c])// [1, 2, 3]
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值.
默认值
var [foo = true] = []; foo // true[x, y = 'b'] = ['a']; // x='a', y='b'[x, y = 'b'] = ['a', undefined]; // x='a', y='b'
ES6内部使用严格相等运算符(===
),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined
,默认值是不会生效的。
var [x = 1] = [undefined]; x // 1var [x = 1] = [null]; x // null
上面代码中,如果一个数组成员是null
,默认值就不会生效,因为null
不严格等于undefined
。
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() { console.log('aaa'); }let [x = f()] = [1];
上面代码中,因为x
能取到值,所以函数f
根本不会执行。上面的代码其实等价于下面的代码。
对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
var { foo, bar } = { foo: "aaa", bar: "bbb" }; foo // "aaa"bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var { bar, foo } = { foo: "aaa", bar: "bbb" }; foo // "aaa"bar // "bbb"var { baz } = { foo: "aaa", bar: "bbb" }; baz // undefined
上面代码的第一个例子,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。第二个例子的变量没有对应的同名属性,导致取不到值,最后等于undefined
。
如果变量名与属性名不一致,必须写成下面这样。
var { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa"let obj = { first: 'hello', last: 'world' };let { first: f, last: l } = obj; f // 'hello'l // 'world'
这实际上说明,对象的解构赋值是下面形式的简写(参见《对象的扩展》一章)。
var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
var { foo: baz } = { foo: "aaa", bar: "bbb" }; baz // "aaa"foo // error: foo is not defined
上面代码中,foo
是匹配的模式,baz
才是变量。真正被赋值的是变量baz
,而不是模式foo
。
三:模版字符串
传统的JavaScript语言,输出模板通常是这样写的。
$('#result').append( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!');
上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。
$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
// 普通字符串`In JavaScript '\n' is a line-feed.`// 多行字符串`In JavaScript this is not legal.`console.log(`string text line 1 string text line 2`);// 字符串中嵌入变量var name = "Bob", time = "today";`Hello ${name}, how are you ${time}?`
上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
var greeting = `\`Yo\` World!`;
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
$('#list').html(`<ul> <li>first</li> <li>second</li></ul>`);
上面代码中,所有模板字符串的空格和换行,都是被保留的,比如<ul>
标签前面会有一个换行。如果你不想要这个换行,可以使用trim
方法消除它。
$('#list').html(`<ul> <li>first</li> <li>second</li></ul>`.trim());
模板字符串中嵌入变量,需要将变量名写在${}
之中。
function authorize(user, action) { if (!user.hasPrivilege(action)) { throw new Error( // 传统写法为 // 'User ' // + user.name // + ' is not authorized to do ' // + action // + '.' `User ${user.name} is not authorized to do ${action}.`); } }
大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。
var x = 1;var y = 2;`${x} + ${y} = ${x + y}`// "1 + 2 = 3"`${x} + ${y * 2} = ${x + y * 2}`// "1 + 4 = 5"var obj = {x: 1, y: 2};`${obj.x + obj.y}`// 3
模板字符串之中还能调用函数。
function fn() { return "Hello World"; }`foo ${fn()} bar`// foo Hello World bar
如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString
方法。
如果模板字符串中的变量没有声明,将报错。
// 变量place没有声明var msg = `Hello, ${place}`;// 报错
由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。
`Hello ${'World'}`// "Hello World"
模板字符串甚至还能嵌套。
const tmpl = addrs => ` <table> ${addrs.map(addr => ` <tr><td>${addr.first}</td></tr> <tr><td>${addr.last}</td></tr> `).join('')} </table>`;
上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。
const data = [ { first: '<Jane>', last: 'Bond' }, { first: 'Lars', last: '<Croft>' }, ]; console.log(tmpl(data)); // <table>// // <tr><td><Jane></td></tr>// <tr><td>Bond</td></tr>// // <tr><td>Lars</td></tr>// <tr><td><Croft></td></tr>// // </table>
如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。
// 写法一let str = 'return ' + '`Hello ${name}!`';let func = new Function('name', str); func('Jack') // "Hello Jack!"// 写法二let str = '(name) => `Hello ${name}!`';let func = eval.call(null, str); func('Jack') // "Hello Jack!"
四:函数&箭头函数
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function actions(num = 200) { console.log(num) } actions() //200actions(300) //300
两种默认写法的对比:
function Fend1({w=0,y=0}={}){ return [w,y] }function Fend2({w,y}={w:0,y:0}){ return [w,y] }console.log(Fend1()) // [0, 0]console.log(Fend2()) // [0, 0]console.log(Fend1({w:3,y:8})) // [3, 8]console.log(Fend2({w:3,y:8})) // [3, 8]console.log(Fend1({w:2})) //[2, 0]console.log(Fend2({w:2})) //[2, undefined]console.log(Fend1({})) //[0, 0]console.log(Fend2({})) //[undefined, undefined]console.log(Fend1({y:8})) //[0, 8]console.log(Fend2({y:8})) //[undefined, 8]
建议使用第一种方式设置默认值,防止参数为undefined
箭头函数
ES6允许使用“箭头”(=>
)定义函数。
var f = v => v;
上面的箭头函数等同于:
var f = function(v) { return v; };
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 5;// 等同于var f = function () { return 5 };var sum = (num1, num2) => num1 + num2;// 等同于var sum = function(num1, num2) { return num1 + num2; };
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return
语句返回。
var sum = (num1, num2) => { return num1 + num2; }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
var getTempItem = id => ({ id: id, name: "Temp" });
箭头函数可以与变量解构结合使用。
const full = ({ first, last }) => first + ' ' + last;// 等同于function full(person) { return person.first + ' ' + person.last; }
箭头函数使得表达更加简洁。
const isEven = n => n % 2 == 0;const square = n => n * n;
五:扩展对象
ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值
var ser1 = (name,age) => ({name,age})//es6箭头函数写法var ser2 = (name,age) => ({name:name,age:age})console.log(ser1(12,"45"))console.log(ser2(12,"45"))
除了属性外,方法也可以简写
var people = {name : 'Lux', senGet (){ console.log(this.name) } } people.senGet();// Lux
此方法同样适用于返回值
var getPoint = ()=>{ const x = 1; const y = 2; return {x,y} }console.log(getPoint())//{x: 1, y: 2}
ES6 对象提供了Object.assign()这个方法来实现浅复制Object.assign()
可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象
const object1 = {a:12,b:13};const object2 = {b:16,c:15};const object3 = {d:17,e:14};const object4 = {f:19,e:18};const copy = Object.assign(object1,object2,object3,object4);//依次传入所浅复制的对象console.log(copy)//{a: 12, b: 16, c: 15, d: 17, e: 18, …}
如果只有一个参数,Object.assign
会直接返回该参数
如果该参数不是对象,则会先转成对象,然后返回
由于undefined
和null
无法转成对象,所以如果它们作为参数,就会报错。
console.log(Object.assign(undefined)) // 报错console.log(Object.assign(null)) // 报错
如果undefined
和null
不在首参数,就不会报错
let obj = {a: 1};console.log(Object.assign(obj, undefined) === obj )// trueconsole.log(Object.assign(obj, null) === obj )// true
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错
但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
const v1 = 'abc';const v2 = true;const v3 = 10;const objs = Object.assign({}, v1, v2, v3);console.log(objs); //{0: "a", 1: "b", 2: "c"}
只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性
注意点:
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
对于Object.assign这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加
Object.assign可以用来处理数组,但是会把数组视为对象。(用于数组合并去重)
Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制
六:import和export
JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来ES6 模块不是对象,需要注意this
的限制。ES6 模块之中,顶层的this
指向undefined
,即不应该在顶层代码使用this
模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能
封装对外暴露的接口:
// utils.jsexport let counter = 3;export function incCounter() {....};
当文档中有且只有一个默认函数时可使用
export default function incCounter() {....};
二者不能同时出现在一个文件中
引入封装好的模块接口:
模块之中,可以使用import
命令加载其他模块(.js
后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export
命令输出对外接口。import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs
模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高
ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致。
<script type="modules"> import utils from "./utils.js";</script>
七:Promise函数
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
Promise
对象有以下两个特点:
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果
Promise
也有一些缺点:
无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
Promise 异步操作
// 创建一个promise实例const promise = new Promise(function(resolve, reject) { // ... some code let se =12; if (se == 12){ //状态成立的时候 resolve(se); } else {//状态失败的时候 reject(se +1); } });
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数
promise.then(function(value) { // success console.log(value) }, function(error) { // failure console.log(error) });
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用,其中,第二个函数是可选的,不一定要提供
八:async与await
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
async
函数对 Generator 函数的改进,体现在以下四点:
内置执行器,Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器
更好的语义,async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
更广的适用性,async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖
async函数返回一个 Promise 对象,可以使用then方法添加回调函数:
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); }async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50);
由于async
函数返回的是Promise
对象,可以作为await命令的参数。所以,上面的例子也可以写成下面的形式。
async function timeout(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); }async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50);
async
函数有多种使用形式:
// 函数声明async function foo() {}// 函数表达式const foo = async function () {};// 对象的方法let obj = { async foo() {} }; obj.foo().then(...)// Class 的方法class Storage { constructor() { this.cachePromise = caches.open('avatars'); } async getAvatar(name) { const cache = await this.cachePromise; return cache.match(`/avatars/${name}.jpg`); } }const storage = new Storage(); storage.getAvatar('jake').then(…);
// 箭头函数const foo = async () => {};async函数的语法:async函数返回一个 Promise 对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。async function f() { return 'hello world'; } f().then(v => console.log(v))// "hello world"
上面代码中,函数f内部return命令返回的值,会被then方法回调函数接收到async
函数的难点是错误处理机制:
async函数返回一个 Promise 对象,async函数内部return语句返回的值,会成为then方法回调函数的参数
async function sfs() { try { await new Promise(function (resolve, reject) { throw new Error('出错了'); }); } catch(e) { } return await('hello world'); }console.log(sfs()) sfs().then(v=>console.log(v)).catch(v=>console.log(v))
作者:程序员小哥哥
链接:https://www.jianshu.com/p/13959879c85d