es6 使用技巧 [tips]

原文地址

1 var VS let/const

在JavaScript中除了var类型外,我们现在还可以使用letconst两种新类型进行变量声明了。与var不同,letconst类型会按照定义的顺序产生变量,而var在作用域任意位置定义都可。

//var 的例子
var snack = 'Meow Mix';

function getFood(food) {
    if (food) {
        var snack = 'Friskies';
        return snack;
    }
    return snack;
}

getFood(false); // undefined
//我们改为使用let 代替 var
let snack = 'Meow Mix';

function getFood(food) {
    if (food) {
        let snack = 'Friskies';
        return snack;
    }
    return snack;
}

getFood(false); // 'Meow Mix'

当我在使用let替换var重构代码时,我们需要更加小心了。因为类型作用域的问题会导致很多意想不到的麻烦。

Note: letconst类型会限定自身的有效作用域,因此,当在定义变量之前引用所定义的变量时会产生引用错误

console.log(x);

let x = 'hi'; // ReferenceError: x is not defined

最佳实践
使用var声明变量当对代码进行重构时,需要仔细判断其作用范围。因此,对于新的项目,建议使用let声明变量,使用const声明不允许改变的常量。

2 使用块作用域替换IIFES

使用立即执行函数表达式的一种方式是将其包含在小括号当中。在ES6中,我们可以创建一个非基于函数作用域形式的块级作用域。

(function () {
    var food = 'Meow Mix';
}());

console.log(food); // Reference Error
//ES6
{
    let food = 'Meow Mix';
}

console.log(food); // Reference Error

3 箭头函数

通常我们使用嵌套函数是想要在词法作用域中保留this

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(function (character) {
        return this.name + character; // Cannot read property 'name' of undefined
    });
};

最常见的解决方式是通过赋值变量保留上级作用域的this

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    var that = this; // Store the context of this
    return arr.map(function (character) {
        return that.name + character;
    });
};

我们也可以通过合适的方式传递this

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(function (character) {
        return this.name + character;
    }, this);
};

bind也是一个好办法

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(function (character) {
        return this.name + character;
    }.bind(this));
};

最后使用箭头函数,this的词法值不再隐藏而会自动绑定。我们重写上述例子

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(character => this.name + character);
};

最佳实践 无论何时你需要保持this的值,使用箭头函数。

当仅仅需要通过函数方法返回一个值时,使用箭头函数也更加简洁。

var squares = arr.map(function (x) { return x * x }); // Function Expression
const arr = [1, 2, 3, 4, 5];
const squares = arr.map(x => x * x); // Arrow Function for terser implementation

最佳实践 尽可能的使用箭头函数作为函数表达式。

4 字符串

在ES6中,标准库变得更加丰富。其中有更多可以应用在字符串方面的方法。比如:.includes().repeat()

.includes()

var string = 'food';
var substring = 'foo';

console.log(string.indexOf(substring) > -1);

与通过是否返回-1判断是否包含字符串相比我们可以使用.includes()返回boolean类型更简单的进行判断。

const string = 'food';
const substring = 'foo';

console.log(string.includes(substring)); // true

.repeat()

//ES5
function repeat(string, count) {
    var strings = [];
    while(strings.length < count) {
        strings.push(string);
    }
    return strings.join('');
}
//ES6
// String.repeat(numberOfRepetitions)
'meow'.repeat(3); // 'meowmeowmeow'

模板字符串

使用模板字符串,我们可以不需要特殊说明直接构建含有特殊字符的字符串。

var text = "This string contains \"double quotes\" which are escaped.";
let text = `This string contains "double quotes" which are escaped.`;

模板字符串也支持字符串+变量值这一类型的变量插入操作。

//ES5
var name = 'Tiger';
var age = 13;

console.log('My cat is named ' + name + ' and is ' + age + ' years old.');

//ES6
//Much simpler:
const name = 'Tiger';
const age = 13;

console.log(`My cat is named ${name} and is ${age} years old.`);

在ES5中,创建多行字符串,我们需要:

var text = (
    'cat\n' +
    'dog\n' +
    'nickelodeon'
);

//Or:

var text = [
    'cat',
    'dog',
    'nickelodeon'
].join('\n');

在ES6当中,模板字符串不需要特殊说明,默认保存多行字符串。

let text = ( `cat
  dog
  nickelodeon`
);

同时,在模板字符串中我们可以添加表达式:

let today = new Date();
let text = `The time and date is ${today.toLocaleString()}`;

5 重构

重构方法允许我们使用更方便的方法,从数组或者对象中获取特定的属性或元素,并将它们存储在变量中。

重构数组

var arr = [1, 2, 3, 4];
var a = arr[0];
var b = arr[1];
var c = arr[2];
var d = arr[3];
let [a, b, c, d] = [1, 2, 3, 4];

console.log(a); // 1
console.log(b); // 2

重构对象

var luke = { occupation: 'jedi', father: 'anakin' };
var occupation = luke.occupation; // 'jedi'
var father = luke.father; // 'anakin'
let luke = { occupation: 'jedi', father: 'anakin' };
let {occupation, father} = luke;

console.log(occupation); // 'jedi'
console.log(father); // 'anakin'

模块

ES6之前,我们使用Browserify等库在客户端创建模块,并且使用Node.jsrequire库引用。在ES6中我们可以直接使用所有类型的模块(AMD 和 CommenJS)。

使用CommonJS方式Export

在ES6中,我们有多种方式传递出模块。通常使用命名传递的形式

export let name = 'David';
export let age  = 25;

也可以使用基于对象列表的形式传出

function sumTwo(a, b) {
    return a + b;
}

function sumThree(a, b, c) {
    return a + b + c;
}

export { sumTwo, sumThree };

当然,简单的使用export关键字传出方法、对象和值。

export function sumTwo(a, b) {
    return a + b;
}

export function sumThree(a, b, c) {
    return a + b + c;
}

最后,还有export default绑定的方式

function sumTwo(a, b) {
    return a + b;
}

function sumThree(a, b, c) {
    return a + b + c;
}

let api = {
    sumTwo,
    sumThree
};

export default api;

最佳实践: 在模块文件的最后使用export default方法。这么做可以让人很清楚明白传递的是什么方法、对象,并且可以避免费时判断各值的命名。CommonJS最常见做法为每次只传递出一个值或对象。坚持这种模式,我们可以使代码更易读,并且允许我们插入到CommonJS或ES6模块中

ES6的import

ES6为我们提供了许多导入模块的方法,我们可以使用其导入整文件。

import 'underscore';

需要提醒简单的整文件导入,会执行整个文件内的所有代码

与python相似我们可以基于命名导入模块:

import { sumTwo, sumThree } from 'math/addition';

我们也可以重命名导入的模块

import {
    sumTwo as addTwoNumbers,
    sumThree as sumThreeNumbers
} from 'math/addition';

除此之外,可以使用*引入所有模块(命名空间导入)

import * as util from 'math/addition';

最后,我们可以通过像从模块中引入一系列值

import * as additionUtil from 'math/addition';
const { sumTwo, sumThree } = additionUtil;

当引入默认对象时,我们可以指定特定方法进行引用。

import React from 'react';
const { Component, PropTypes } = React;

//简单方法
import React, { Component, PropTypes } from 'react';

注意: 导出的值是绑定不是引用。因此,改变模块内被绑定的变量将影响导出模块内的值。因此,避免更改导出这些值的公共接口。

参数

在ES5中,对于函数的默认值、未知参数个数和命名参数,我们有不同的做法。在ES6中,我们可以通过更加简洁的语法完成上述内容。

默认函数值

// ES5
function addTwoNumbers(x, y) {
    x = x || 0;
    y = y || 0;
    return x + y;
}

// ES6
// 我们可以直接在函数中设定参数默认值
function addTwoNumbers(x=0, y=0) {
    return x + y;
}

addTwoNumbers(2, 4); // 6
addTwoNumbers(2); // 2
addTwoNumbers(); // 0

其他参数

在ES5,我们通过如下方式处理参数数目不明确的情况:

function logArguments() {
    for (var i=0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}

通过使用其他操作符,我们可以直接传递未知数目的参数。

function logArguments(...args) {
    for (let arg of args) {
        console.log(arg);
    }
}

命名参数

ES5中处理命名参数,通过使用继承自JQuery的选择对象模式。

function initializeCanvas(options) {
    var height = options.height || 600;
    var width  = options.width  || 400;
    var lineStroke = options.lineStroke || 'black';
}

我们可以实现相同的功能,通过使用解构作为一个函数的形式参数:

function initializeCanvas(
    { height=600, width=400, lineStroke='black'}) {
        // Use variables height, width, lineStroke here
    }

如果希望得到让全部值可选,我们可以解构一个空对象。

function initializeCanvas(
    { height=600, width=400, lineStroke='black'} = {}) {
        // ...
    }

展开操作符

在ES5中,我们可以通过对Math.max方法使用apply得到数组中的最大值。

Math.max.apply(null, [-1, 100, 9001, -32]); // 9001

在ES6中,我们可以使用展开操作符通过一个数组作为参数传递到函数中。

Math.max(...[-1, 100, 9001, -32]); // 9001

可以通过通过展开操作符得到简单方式连接数组。

let cities = ['San Francisco', 'Los Angeles'];
let places = ['Miami', ...cities, 'Chicago']; // ['Miami', 'San Francisco', 'Los Angeles', 'Chicago']

在ES6之前,我们通过创建构造函数实现类,并且增加属性和扩展原型链。

function Person(name, age, gender) {
    this.name   = name;
    this.age    = age;
    this.gender = gender;
}

Person.prototype.incrementAge = function () {
    return this.age += 1;
};

然后基于父类创建扩展类。

function Personal(name, age, gender, occupation, hobby) {
    Person.call(this, name, age, gender);
    this.occupation = occupation;
    this.hobby = hobby;
}

Personal.prototype = Object.create(Person.prototype);
Personal.prototype.constructor = Personal;
Personal.prototype.incrementAge = function () {
    Person.prototype.incrementAge.call(this);
    this.age += 20;
    console.log(this.age);
};

ES6提供了丰富的语法糖来直接创建类:

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

    incrementAge() {
      this.age += 1;
    }
}

并且,可以直接通过extends关键字扩展类

class Personal extends Person {
    constructor(name, age, gender, occupation, hobby) {
        super(name, age, gender);
        this.occupation = occupation;
        this.hobby = hobby;
    }

    incrementAge() {
        super.incrementAge();
        this.age += 20;
        console.log(this.age);
    }
}

最佳实践

ES6的语法掩盖了如何创建和扩展原型链的工作,对于初学者来说这是一个能够让我们编写更清晰代码的特性。

添加新评论

top