为了账号安全,请及时绑定邮箱和手机立即绑定

ES6常用方法解析

标签:
JavaScript

webp

图片发自简书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;

上面代码中,变量foovar命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量barlet命令声明,不会发生变量提升。这表示在声明它之前,变量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命令没有这个问题

constlet声明的常量及变量只在所在的块级作用域内有效,常量变量是不提升,同样存在暂时性死区,只能在声明的位置后面使用

letconst之间,建议优先使用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会直接返回该参数
如果该参数不是对象,则会先转成对象,然后返回
由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。

console.log(Object.assign(undefined)) // 报错console.log(Object.assign(null)) // 报错

如果undefinednull不在首参数,就不会报错

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

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,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


点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消