- 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); // 输出undefined
console.log(bar); // 报错ReferenceError
var foo = 2;
let bar = 2;
上面代码中,变量foo
用var
命令声明,会发生变量提升,即脚本开始运行时,变量foo
已经存在了,但是没有值,所以会输出undefined
。变量bar
用let
命令声明,不会发生变量提升。这表示在声明它之前,变量bar
是不存在的,这时如果用到它,就会抛出一个错误。
const命令
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 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 // 1
var [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() //200
actions(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 )// true
console.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.js
export 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))
网友评论