Living
Chaplin

Wayne Zheng

ES6实践(二)

2016-08-15 00:00
JavaScript

函数的扩展

ES6 对函数进行了扩展,新的函数支持定义默认值、扩展运算符、写法更加简洁的箭头函数等。

函数参数的默认值(3 种形式)

1.函数默认值

let fn = function (x = 1, y = 2) {
  return x + y;
};
fn(); // 3

// 代替函数体内判断的写法
let fn = function (x, y) {
  x = x || 1;
  y = y || 2;
  return x + y;
};
fn(); // 3

2.函数默认值——解构参数

可以通过解构变量来定义函数参数的默认值,减少参数的个数

let fn = function ({ x = 1, y = 2 }) {
  return x + y;
};
fn(); // 3

3.函数默认值与解构赋值结合

注意:前面这两种方法都存在一个问题,如果只使用第二个参数,就必须给第一个参数传入undefined来指示第一个参数的默认值,通过解构赋值和函数默认值结合的形式可以避免这样的问题。

// 既可以不传入参数,也可以指定参数调用
let fn = function ({ x = 1, y = 2 } = {}) {
  return x + y;
};
fn({ y: 2 }); // 3

实例:默认参数与解构赋值混合使用

let fn = function (x = 1, { y = 2, z = 3 } = {}) {
  return x + y + z;
};
fn(5); // 10

rest 参数与拓展符

ES6 也支持了函数 rest 形式的参数(使用者不需要考虑参数的个数),rest 参数必须跟在函数最后面,实例如下:

let fn = function (...values) {
  return values.join(" ");
};
fn("hello", "i'm", "waynezheng"); // hello i'm waynezheng

扩展运算符的灵活运用

// 数组合并
let arr = [...[1, 2, 3], ...[4, 5, 6]]; // [1, 2, 3, 4, 5, 6]

// 拆分字符串
let str = [..."hello"]; // ['h', 'e', 'l', 'l', 'o']

注意:实现了iterator接口的对象,都可以实用扩展运算符进行解构。

箭头函数

ES6 引入的箭头函数,使函数的定义方式更加简洁,同时也解决了this绑定的问题。

基本用法

let fn = (x) => x;

// 等价于
let fn = function (x) {
  return x;
};

解决this指向的问题

let fn = function () {
  this.name = "fn";

  setTimeout(() => {
    console.log(this.name); // fn
  }, 1000);
};
fn();

// 代替用that赋值this
let fn = function () {
  let that = this;

  setTimeout(function () {
    this.name = "fn";
    console.log(this.name); // undefined
    console.log(that.name); // fn
  }, 1000);
};
fn();

对象的扩展

ES6 让对象的定义更加简洁,同时引入了一些常见的第三方框架的功能,实例如下:

简洁的写法

1.返回对象简写

let fn = function () {
  let x, y;
  return { x, y };
};

// 等价于
let fn = function () {
  let x, y;
  return {
    x: x,
    y: y,
  };
};

2.方法简写

let obj = {
  sayName() {
    return "wynne";
  },
};

3.配合 import,export 的模块化输出简写

let foo = {
  sayName() {},
};

let bar = {
  sayName() {},
};
exports = { foo, bar };

// 等价于
exports = {
  foo: foo,
  bar: bar,
};

Object 上的扩展函数

1.严谨的值比较

let a = NaN;
let b = NaN;

// 旧的比较
a === b; // false
0 === -0; // true

Object.is(a, b); // true
Object.is(0, -0); // false

2.对象的浅复制

// 克隆一个对象
let obj = { a: 1 };
let clone = Object.assign({}, obj);

3.扩展默认属性(代替 jQuery 的 extend 功能)

const DEFAULT_PARAM = {
  url: "localhost",
  method: "get",
};

let getBooks = function (options) {
  let param = Object.assign({}, DEFAULT_PARAM, options);
};

getBooks({ data: { a: 1, b: 2 } }); // { url: 'localhost', method: 'get', data: {a: 1, b: 2} }

4.拓展已有对象

// 假设原有代码存在这样一个对象
let oldObj = function() {
    ... // 几乎不可维护的代码
}

oldObj.prototype = {
    method() {
        ...
    }
}

Object.assign(oldObj.prototype, {
    newMethod() {
        ...
    }
})

Promise 对象

Promise 是异步编程的解决方案,很简单的说,使用场景就是解决多层嵌套回调函数的问题(简称回调地狱),下面是一些实例:

解决回调地狱问题

有时候可能会遇到这么些需求,第三个请求的参数依赖于第二个请求的结果,第二个请求的参数依赖于第一个请求的结果,而这一过程只能在前端发起,那么可能会写出这样的代码:

$.get(url1, param, function (res) {
  let param = res.value;

  $.get(url2, param, function (res) {
    let param = res.value;

    $.get(url3, param, function (res) {
      // 到了第三层才是在编写业务逻辑
    });
  });
});

试想,如果其中一个函数错误,程序就会中断,因此还需要给每个请求加上 error 的回调函数,写出来的代码嵌套了一层又一层。看看用 Promise 来书写这段代码是怎么样的:

// 假设get函数内部实现了promise
$.get(url1, param)
  .then(function (res) {
    let param = res.value;
    return $.get(url2, param); // 返回promise对象
  })
  .then(function (res) {
    let param = res.value;
    return $.get(url3, param); // 返回promise对象
  })
  .then(function (res) {
    // 业务逻辑编写
  })
  .catch(function (error) {
    // 统一处理回调发生的错误
  });

封装函数支持 promise

编写一个 promise 的方法也很简单,下面上实例:

// 以$.ajax为例
function ajaxGet(url, param) {
  return new Promise((resolve, reject) => {
    $.ajax(
      url,
      param,
      function (res) {
        if (res.state == 200) {
          resolve(res);
        } else {
          reject("请求不成功!");
        }
      },
      function (error) {
        reject(error);
      }
    );
  });
}

// 使用
ajaxGet(url, param)
  .then((res) => {
    // 业务逻辑,处理resolve
  })
  .catch((err) => {
    // 处理reject
    console.log(err);
  });
// 这样也可以
ajaxGet(url, param).then(
  (res) => {
    // 业务逻辑,处理resolve
  },
  (err) => {
    // 处理reject
  }
);

// 只要返回的是promise对象,则可以做链式调用
ajaxGet(url, param)
  .then((res) => {
    // 业务逻辑,处理resolve
    return ajaxGet(url2, res);
  })
  .then((res) => {
    // 上面的ajaxGet函数的处理
  })
  .catch((err) => {
    // 处理reject
    console.log(err);
  });

包装多个 promise

Promise.all()支持将多个Promise实例,包装成一个新的Promise,使用场景也很广泛。比如需要多个异步操作返回数据后进行处理时:

let promises = [1, 2, 4, 5].map((post_id) => {
  return ajaxGet("post/", { id: post_id });
});

Promise.all(promises)
  .then((resultArray) => {
    // 对所有数据做处理
  })
  .catch((error) => {
    // 处理第一个出现reject的情况
  });

数据降级

合理的利用catch方法,可以使数据有更好的处理方式。比如说,某一个模块数据的加载,由于第一次请求服务器抖动了,请求失败,这时可以降级从缓存中取得数据;要是缓存不小心也挂了,可以从localStorage中获取缓存的数据,例子如下:

function getDataFromlocalStorage() {
  if (window.localStorage && window.localStorage["DATA"]) {
    return new Promise.resolve(window.localStorage["DATA"]);
  } else {
    return new Promise.reject();
  }
}

function getDataFromCache() {
  return getDataCache(url);
}

function parseData(data) {
  return JSON.parse(data);
}

function getData() {
  let promise = getDataFromServer(url)
    .then(parseData) // 正常业务逻辑
    .catch(function () {
      // 请求失败,从服务器缓存取数据
      return getDataFromCache().then(parseData);
    })
    .catch(function () {
      // 服务器缓存取数据失败,从localstorage取数据
      return getDataFromlocalStorage().then(parseData);
    })
    .catch(function () {
      // 没办法了,什么数据都没有,只能处理异常了
    });
}

Class

ES6 的Class可以视为创建类的语法糖(但本质上实现方式是不一样的),Class的使用会比以往的prototype直观得多:

1.类的基本使用方法

class Person {
  static is_live = true; // 静态属性

  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  get name() {
    return this.name;
  }

  set name(name) {
    this.name = name;
  }

  sayName() {
    alert(this.name);
  }

  static eat() {
    alert("eat done");
  }
}

let person = new Person("wynne", 23);
person.sayName(); // wynne
Person.eat(); // eat done

2.类的继承

class Man extends Person {
  constructor(name, age, sex) {
    super(name, age);
    this.sex = sex;
  }

  get name() {
    return this.name;
  }

  set name(name) {
    this.name = name;
  }

  sayName() {
    alert(this.name);
  }
}

模块化

在 ES6 还没有标准化之前,模块化有 AMD 和 CMD 两种方式,AMD 使用于浏览器端,CMD 使用于服务端,相对应的类库有 requirejs 和 common.js,但是有了 ES6 的模块,当然是使用 ES6 的。

export 命令

export可以输出常量,变量,函数,对象,使用方式很简单:

export var firstName = 'Michael';
export const A = 1;
export function multiply(x, y) {
    return x * y;
};
export {
    name: 'wynne'
};
export class q {
  constructor() {
    this.es6 = 'hello';
  }
}

import 命令

import可以将上面的export输出的导入到指定文件中使用,方式也很简单:

import { es6 } from "./someModule";
import { lastName as surname } from "./profile"; // 取别名

该系列完结啦!(如有变动会更新) 如有错误请帮我指出下,感激不尽~