标签ES6下的文章

Jerry Bendy 发布于 08月30, 2016

【译】十个可以使用 ES6 代替的 Lodash 特性

Lodash 应该算是目前在 npm 上被依赖的最多的包了吧,但是如果你使用 ES6,也许你不再需要它。在这篇文章中,我们将尝试使用一些 ES6 的新特性来解决几种常见的问题。

1. Map, Filter, Reduce

这些方法使转换数据变得轻而易举,而且非常通用。我们可以使用 ES6 的箭头函数语法,帮助我们用更简短的方式代替 Lodash 的语法。

_.map([1, 2, 3], function(n) { return n * 3; });
// [3, 6, 9]
_.reduce([1, 2, 3], function(total, n) { return total + n; }, 0);
// 6
_.filter([1, 2, 3], function(n) { return n <= 2; });
// [1, 2]

// becomes

[1, 2, 3].map(n => n * 3);
[1, 2, 3].reduce((total, n) => total + n);
[1, 2, 3].filter(n => n <= 2);

不仅如此,如果你使用 ES6 的 polyfill,我们还可以使用 findsomeevery 以及 reduceRight 等方法。

2. Head & Tail

解构语法 允许我们轻而易举的获取一个列表的头部或尾部,不需要依赖任何函数。

_.head([1, 2, 3]);
// 1
_.tail([1, 2, 3]);
// [2, 3]

// becomes

const [head, ...tail] = [1, 2, 3];

也可以使用类似的方法达到 initiallast 的效果。

_.initial([1, 2, 3]);
// -> [1, 2]
_.last([1, 2, 3]);
// 3

// becomes

const [last, ...initial] = [1, 2, 3].reverse();

如果你介意 reverse 改变了原来的数组,还可以使用另一个解构将原数组复制一份。

const xs = [1, 2, 3];
const [last, ...initial] = [...xs].reverse();

3. Rest & Spread

restspread 函数允许我们定义可以接收可变数量参数的函数。使用 ES6 可以更完美的支持 rest 和 spread。

var say = _.rest(function(what, names) {
  var last = _.last(names);
  var initial = _.initial(names);
  var finalSeparator = (_.size(names) > 1 ? ', & ' : '');
  return what + ' ' + initial.join(', ') +
    finalSeparator + _.last(names);
});

say('hello', 'fred', 'barney', 'pebbles');
// "hello fred, barney, & pebbles"

// becomes

const say = (what, ...names) => {
  const [last, ...initial] = names.reverse();
  const finalSeparator = (names.length > 1 ? ', &' : '');
  return `${what} ${initial.join(', ')} ${finalSeparator} ${last}`;
};

say('hello', 'fred', 'barney', 'pebbles');
// "hello fred, barney, & pebbles"

4. Curry (柯里化)

Without a higher level language such as TypeScript or Flow, we can’t give our functions type signatures which makes currying quite difficult. When we receive curried functions it’s hard to know how many arguments have already been supplied and which we will need to provide next. With arrow functions we can define curried functions explicitly, making them easier to understand for other programmers.

function add(a, b) {
  return a + b;
}
var curriedAdd = _.curry(add);
var add2 = curriedAdd(2);
add2(1);
// 3

// becomes

const add = a => b => a + b;
const add2 = add(2);
add2(1);
// 3

These explicitly curried arrow functions are particularly important for debugging.

var lodashAdd = _.curry(function(a, b) {
  return a + b;
});
var add3 = lodashAdd(3);
console.log(add3.length)
// 0
console.log(add3);
//function wrapper() {
//  var length = arguments.length,
//  args = Array(length),
//  index = length;
//
//  while (index--) {
//    args[index] = arguments[index];
//  }…

// becomes

const es6Add = a => b => a + b;
const add3 = es6Add(3);
console.log(add3.length);
// 1
console.log(add3);
// function b => a + b

If we’re using a functional library like lodash/fp or ramda then we can also use arrows to remove the need for the auto-curry style.

_.map(_.prop('name'))(people);

// becomes

people.map(person => person.name);

5. Partial

Like with currying, we can use arrow functions to make partial application easy and explicit.

var greet = function(greeting, name) {
  return greeting + ' ' + name;
};

var sayHelloTo = _.partial(greet, 'hello');
sayHelloTo('fred');
// "hello fred"

// becomes

const sayHelloTo = name => greet('hello', name);
sayHelloTo('fred');
// "hello fred"

It’s also possible to use rest parameters with the spread operator to partially apply variadic functions.

const sayHelloTo = (name, ...args) => greet('hello', name, ...args);
sayHelloTo('fred', 1, 2, 3);
// "hello fred"

6. Operators

Lodash comes with a number of functions that reimplement syntactical operators as functions, so that they can be passed to collection methods.

In most cases, arrow functions make them simple and short enough that we can define them inline instead.

_.eq(3, 3);
// true
_.add(10, 1);
// 11
_.map([1, 2, 3], function(n) {
  return _.multiply(n, 10);
});
// [10, 20, 30]
_.reduce([1, 2, 3], _.add);
// 6

// becomes

3 === 3
10 + 1
[1, 2, 3].map(n => n * 10);
[1, 2, 3].reduce((total, n) => total + n);

7. Paths

Many of Lodash’s functions take paths as strings or arrays. We can use arrow functions to create more reusable paths instead.

var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };

_.at(object, ['a[0].b.c', 'a[1]']);
// [3, 4]
_.at(['a', 'b', 'c'], 0, 2);
// ['a', 'c']

// becomes

[
  obj => obj.a[0].b.c,
  obj => obj.a[1]
].map(path => path(object));

[
  arr => arr[0],
  arr => arr[2]
].map(path => path(['a', 'b', 'c']));

Because these paths are “just functions”, we can compose them too.

const getFirstPerson = people => people[0];
const getPostCode = person => person.address.postcode;
const getFirstPostCode = people => getPostCode(getFirstPerson(people));

We can even make higher order paths that accept parameters.

const getFirstNPeople = n => people => people.slice(0, n);

const getFirst5People = getFirstNPeople(5);
const getFirst5PostCodes = people => getFirst5People(people).map(getPostCode);

8. Pick

The pick utility allows us to select the properties we want from a target object. We can achieve the same results using destructuring and shorthand object literals.

var object = { 'a': 1, 'b': '2', 'c': 3 };

return _.pick(object, ['a', 'c']);
// { a: 1, c: 3 }

// becomes

const { a, c } = { a: 1, b: 2, c: 3 };

return { a, c };

9. Constant, Identity, Noop

Lodash provides some utilities for creating simple functions with a specific behaviour.

_.constant({ 'a': 1 })();
// { a: 1 }
_.identity({ user: 'fred' });
// { user: 'fred' }
_.noop();
// undefined

We can define all of these functions inline using arrows.

const constant = x => () => x;
const identity = x => x;
const noop = () => undefined;

Or we could rewrite the example above as:

(() => ({ a: 1 }))();
// { a: 1 }
(x => x)({ user: 'fred' });
// { user: 'fred' }
(() => undefined)();
// undefined

10. Chaining & Flow

Lodash provides some functions for helping us write chained statements. In many cases the built-in collection methods return an array instance that can be directly chained, but in some cases where the method mutates the collection, this isn’t possible.

However, we can define the same transformations as an array of arrow functions.

_([1, 2, 3])
 .tap(function(array) {
   // Mutate input array.
   array.pop();
 })
 .reverse()
 .value();
// [2, 1]

// becomes

const pipeline = [
  array => { array.pop(); return array; },
  array => array.reverse()
];

pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);

This way, we don’t even have to think about the difference between tap and thru. Wrapping this reduction in a utility function makes a great general purpose tool.

const pipe = functions => data => {
  return functions.reduce(
    (value, func) => func(value),
    data
  );
};

const pipeline = pipe([
  x => x * 2,
  x => x / 3,
  x => x > 5,
  b => !b
]);

pipeline(5);
// true
pipeline(20);
// false

Conclusion

Lodash is still a great library and this article only offers a fresh perspective on how the evolved version of JavaScript is allowing us to solve some problems in situations where we would have previously relied on utility modules.

Don’t disregard it, but instead—next time you reach for an abstraction—think about whether a simple function would do instead!


via sitePoint

阅读全文 »

Jerry Bendy 发布于 08月30, 2016

【译】React on ES6+

本文由冰翼博客翻译自 babelJs.io 原文作者: Steven Luscher (Github

在重新设计 Instagram Web 的最近一年里,我们享受到很多使用 ES6+ 新特性写 React 组件的好处。下面我整理了一些可以方便写 React 应用语言特性,相信这会使工作变得更轻松愉快。

迄今为止我想在写组件时最能直观看到的变化就是 ES6+ 类的使用。关于 ES6 的类定义语言可 参考这里。现在,我们可以写一个继承自 React.Component 的类来取代 React.createClass 的写法。

class Photo extends React.Component {
  render() {
    return <img alt={this.props.caption} src={this.props.src} />;
  }
}

当然,你会发现一些细小的变化 —— 在定义类的时候可以使用一些更简短的语法:

// The ES5 way
var Photo = React.createClass({
  handleDoubleTap: function(e) { … },
  render: function() { … },
});


// The ES6+ way
class Photo extends React.Component {
  handleDoubleTap(e) { … }
  render() { … }
}

尤其是我们去掉了两个圆括号和一个分号,每一个方法的声明我们都省略了一个冒号、一个 function 关键字和一个逗号。

几乎所有的生命周期方法,(componentWillMount 除外)都可以使用新的类语法定义。componentWillMount 需要写在组件的初始化代码中。

// The ES5 way
var EmbedModal = React.createClass({
  componentWillMount: function() { … },
});


// The ES6+ way
class EmbedModal extends React.Component {
  constructor(props) {
    super(props);
    // 这里可以进行一些组件初始化的工作,componentWillMount 也移到这里执行
  }
}

属性初始化

在 ES6+ 类的世界中,propTypes 以及组件的默认值都可以作为类本身的静态属性存在。类似的,组件中初始化 state 也可以使用 ES7 的 属性初始化

// The ES5 way
var Video = React.createClass({
  getDefaultProps: function() {
    return {
      autoPlay: false,
      maxLoops: 10,
    };
  },
  getInitialState: function() {
    return {
      loopsRemaining: this.props.maxLoops,
    };
  },
  propTypes: {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
  },
});


// The ES6+ way
class Video extends React.Component {
  static defaultProps = {
    autoPlay: false,
    maxLoops: 10,
  }
  static propTypes = {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
  }
  state = {
    loopsRemaining: this.props.maxLoops,
  }
}

ES7 的属性初始化代码将会在类的构造函数中被加载,在这里 this 指向类对象本身,所以在初始化 state 的代码中可以直接使用 this.props。尤其是我们不再需要使用一个 getter 的函数来定义 prop 的默认值和 state 对象。

箭头函数

React.createClass 会执行一些额外的绑定工作以确保在组件内部 this 能够指向组件本身。

// 自动绑定
var PostInfo = React.createClass({
  handleOptionsButtonClick: function(e) {
    // 这里, 'this' 指向组件本身
    this.setState({showOptionsModal: true});
  },
});

当我们使用 ES6+ 的类语法时就不会再需要 React.createClass 提供的这种辅助绑定,下面可以看到我们将要自己手动执行绑定的过程:

// 在任何需要的地方都需要手动绑定
class PostInfo extends React.Component {
  constructor(props) {
    super(props);
    // 手动绑定方法到组件实例
    this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
  }
  handleOptionsButtonClick(e) {
    // 需要确保 this 指向组件实例
    this.setState({showOptionsModal: true});
  }
}

幸运的时,结合两个 ES6+ 的新特性 —— 箭头函数语法 和属性初始化语法将会使这种绑定变得轻而易举:

class PostInfo extends React.Component {
  handleOptionsButtonClick = (e) => {
    this.setState({showOptionsModal: true});
  }
}

ES6 的箭头函数内部没有自己独立的 this,所以就使用了外层的 this,加上 ES7 的属性初始化语法中的 this 总是指向类实例本身,所以函数内的 this 自然也就指向了类实例。可以 点击这里 查看它是如何工作的。

*注:如果上面的短网址打不开的话可以 点击这里 打开 babel 的 REPL 环境,并在左侧输入以下内容,右侧即可显示编译后的内容:

class PostInfo extends React.Component {
  handleOptionsButtonClick = (e) => {
   this.setState({showOptionsModal: true});
  }
}

动态属性名 & 模板字符串

其中一个 对象字面量增强 的特性使我们有可以分配一个分离的属性名,在此之前我们可能会像下面这样定义 state 的一部分:

var Form = React.createClass({
  onChange: function(inputName, e) {
    var stateToSet = {};
    stateToSet[inputName + 'Value'] = e.target.value;
    this.setState(stateToSet);
  },
});

现在我们可以在写一个对象字面量的时候直接使用一个 Javascript 表达式来表示对象的 key。这里我们使用一个 模板字符串 来表示新 state 的 key:

class Form extends React.Component {
  onChange(inputName, e) {
    this.setState({
      [`${inputName}Value`]: e.target.value,
    });
  }
}

解构 & 属性展开

通常,当我们组合组件时,我们可能会想从父组件中传递很多 prop 到子组件,但不一定是全部。基于 ES6+ 的 解构赋值语法 和 JSX 的 属性展开语法 ,现在我们可以更随意的去写:

class AutoloadingPostsGrid extends React.Component {
  render() {
    var {
      className,
      ...others,  // 包含 this.props 的全部,除了 className 
    } = this.props;
    return (
      <div className={className}>
        <PostsGrid {...others} />
        <button onClick={this.handleLoadMoreClick}>Load more</button>
      </div>
    );
  }
}

我们也可以结合 JSX 属性展开语法和普通属性,利用一个简单的优先规则实现覆盖和默认。 This element will acquire the className “override” even if there exists a className property in this.props:

<div {...this.props} className="override">
  …
</div>

This element will regularly have the className “base” unless there exists a className property in this.props to override it:

<div className="base" {...this.props}>
  …
</div>

感谢阅读

希望你可以享受更多 ES6+ 新特性带来的写 React 组件的乐趣。感谢所有为这篇文章做过贡献的人,更要感谢 Babel 的团队让我们可以在今天用到这么多未来的新特性。

阅读全文 »

Jerry Bendy 发布于 08月29, 2016

6 种方法在 React 中绑定 javascript 的 this 关键字(ES6/ES7)

Javascript 中的 this 关键字对很多 JS 开发者来说都是令人疑惑、头痛东西,很多时候往往搞不清楚某个 this 究竟指的是谁,尤其是在多层回调嵌套的情况下,OH GOD!!

It’s trivial for some other code to rebind the context of the function you’re working with―using the new keyword and some of the methods that are built onto Functon.prototype. This introduces an entire class of confusing scenarios and often you’ll see callback driven code scattered with calls to .bind(this).

问题

因为 React 使用 this 关键字在内部引用组件上下文,也就带来了关于 this 的一些困惑。你很可以见过或写过下面这样的代码:

this.setState({ loading: true });

fetch('/').then(function loaded() {
  this.setState({ loading: false });
});

这段代码会返回一个 TypeError,因为 this.setState is not a function。这是因为在 promise 的回调中,函数执行的上下文已经改变,this 指向了错误的对象(这时 this 指回调函数本身)。下面我们来看下如何避免这种问题的发生。

解决方案

有一些可选的解决方案已经被使用了很多年,还有一些是仅适用于 React 的,甚至有些方案在浏览器环境下是不能使用的。我们先来看一下。

1. 为 this 创建别名

这个应该算是被使用最多的一种方法了吧。在函数的顶部为 this 创建一个别名,并在组件内部通过这个别名访问 this

var component = this;
component.setState({ loading: true });

fetch('/').then(function loaded() {
  component.setState({ loading: false });
});

这种方法很轻量,并且易于理解。使用一个有意义的别名可以很轻松的使用上下文 this

2. bind this

第二种方法是运行时在回调函数上绑定我们自己的上下文环境。

this.setState({ loading: true });

fetch('/').then(function loaded() {
  this.setState({ loading: false });
}.bind(this));

从 ES5 开始,所有 Javascript 函数都有一个bind 方法,可以允许在函数执行的时候重新为函数内部绑定一个 this。一旦函数绑定了上下文 this 就不可以被重写,也就意味着绑定的上下文环境总是正确的。

这种方式对于其它语言的开发者来说可能很难理解,尤其在有多层函数嵌套时使用 bind,这时候就只能凭借自己的理解和记忆来记住究竟哪个 this 指的哪个对象。

3. React 组件方法

React 可以使用 createClass 创建一个组件类对象,组件类对象里的方法会自动绑定到组件的上下文环境,所以你可以直接在这里使用 this。这样可以允许把回调直接写在组件里。

React.createClass({
  componentWillMount: function() {
    this.setState({ loading: true });

    fetch('/').then(this.loaded);
  },
  loaded: function loaded() {
    this.setState({ loading: false });
  }
});

如果你的组件内部不需要做太多工作的话,想必这会是一种非常优雅的方案。这能允许你使用命名的方法、可以使你的代码更加扁平化,并且忘记 this 上下文这回事。事实上,如果你尝试在组件方法中使用 .bind(this), React 将会警告你正在做一件不必要的工作。

bind(): You are binding a component method to the component. React does this for you automatically in a high-performance way, so you can safely remove this call.

很重要的是,这种方式并不适用于 ES6 类语法的创建的 React 组件。 如果你正在使用 ES6 的类语法创建 React 组件,可以尝试下后面的方法。

4. ES2015(ES6)箭头语法

ES2015 中新加入了箭头语法用来更方便的写函数表达式。箭头语法除了可以以更简短的方式写函数外,还有一些很重要的特性,例如箭头语法创建的函数体中没有 this 而总是使用外部的 this 对象。

this.setState({ loading: true });

fetch('/').then(() => {
  this.setState({ loading: false });
});

无论多少层的函数嵌套,箭头函数中总是使用正确的 this 上下文。

很不幸的是,这样我们将无法命名我们的函数。这会使调试变得更困难,调用栈中将会显示这是一个 (anonymous function) (匿名函数)。

如果你使用 Babel 转换 ES6 的代码到 ES5,你将会发现一些比较有趣的现象。

  • 有些情况下编译器会通过变量名推理出函数名
  • 编译器是通过为 this 创建别名的方法来保持上下文
const loaded = () => {
  this.setState({ loading: false });
};

// 将会被编译为

var _this = this;
var loaded = function loaded() {
  _this.setState({ loading: false });
};

5. ES2016 (ES7)的绑定语法

这是一个 ES7 的提案,ES2016(ES7)的绑定语法,使用两个冒号(::)作为新的操作符。绑定操作符要求左侧是一个值,并且右侧是需要处理的函数,使用这种语法相当于把双冒号左侧的数值绑定到右侧处理函数的 this 上下文中。

下面通过 map 来举个简单的例子:

function map(f) {
  var mapped = new Array(this.length);

  for(var i = 0; i < this.length; i++) {
    mapped[i] = f(this[i], i);  
  }

  return mapped;
}

不同于 lodash,我们不需要在参数中传递数据,这使 map 函数看起来更像是数据的一个成员方法。

[1, 2, 3]::map(x => x * 2)
// [2, 4, 6]

是不是曾经像下面这下写过?

[].map.call(someNodeList, myFn);
// or
Array.from(someNodeList).map(myFn);

ES7 绑定操作符允许你直接在类数组结构上使用 map 函数。

someNodeList::map(myFn);

我们也可以在 React 组件中使用这种语法:

this.setState({ loading: true });

fetch('/').then(this::() => {
  this.setState({ loading: false });
});

可能我会是第一个站出来承认这种语法会让人觉得恐惧的人。

了解这个操作符会觉得很有趣,虽然它不是专为这种使用场景而生的。它解决了很多由于.bind(this)产生的缺点(事实上 babel 最终还是会将它编译成 .bind(this)),在解决很多层的嵌套代码中 this 的问题时可以放心的使用这种方式。当然这可能会使用其它开发人员有些困惑。

React component context probably isn’t the future of the bind operator, but if you are interested take a look at some of the great projects where it’s being used to great effect (such as mori-ext).

6. 函数指定

有些函数可以在执行时手动为它指定上下文的 this,例如 map,它接受的最后一个参数将会作为回调函数内的 this

items.map(function(x) {
  return <a onClick={this.clicked}>x</a>;
}, this);

虽然这可以解决问题,但却不存在通用性。因为大部分的函数是不能接受重新指定的 this 的。

总结

上面我们说了一些上下文中使用正确的 this 的方法。如果担心性能问题,为 this 创建别名将会是最快的方法(由于箭头函数在编译后与创建别名相同,所以使用 ES6 的箭头函数也是很好的选择)。当然,也许直到你的界面有上万个组件也许都不会看到这种性能差别,也许到那时 this 的问题也不会成为真正的瓶颈。

If you’re more concerned about debugging, then use one of the options that allows you to write named functions, preferably component methods as they’ll handle some performance concerns for you too.

At Astral Dynamics, we’ve found a reasonable compromise between mostly using named component methods and arrow functions, but only when we write very short inline functions that won’t cause issues with stack traces. This allows us to write components that are clear to debug, without losing the terse nature of arrow functions when they really count.

Of course, this is mostly subjective and you might find that you prefer to baffle your colleagues with arrow functions and bind syntax. After all, who doesn’t love reading through a codebase to find this?

this.setState({ loading: false });

fetch('/')
  .then((loaded = this::() => {
    var component = this;
    return this::(() =>
      this::component.setState({ loaded: false });
    }).bind(React);
  }.bind(null)));

via sitepoint

阅读全文 »

Jerry Bendy 发布于 07月30, 2016

【分享】ES6 你可能不知道的事 - 基础篇

ES6,或许应该叫 ES2015(2015 年 6 月正式发布),对于大多数前端同学都不陌生。

首先这篇文章不是工具书,不会去过多谈概念,而是想聊聊关于每个特性你可能不知道的事,希望能为各位同学正确使用ES6,提供一些指导。

对于 ES6,有些同学已经在项目中有过深入使用了,有些则刚刚开始认识他,但不论你是属于哪一类,相信这篇文章都有适合你的部分。针对文章中的问题或不同意见,欢迎随时拍砖、指正。

正文

Let + Const

这个大概是开始了解 ES6 后,我们第一个感觉自己完全明白并兴致勃勃的开始使用的特性。

以如下方式使用的同学请举下手?

// 定义常量
const REG_GET_INPUT = /^\d{1,3}$/;

// 定义配置项
let config = {
  isDev : false,
  pubDir: './admin/'
}

// 引入 gulp
let gulp    = require('gulp');

// 引入gulp相关插件
let concat  = require('gulp-concat');
let uglify  = require('gulp-uglify');
let cssnano = require('gulp-cssnano');

很多人看完概念之后,第一印象都是:“const 是表示不可变的值,而 let 则是用来替换原来的 var 的。”

所以就会出现上面代码中的样子;一段代码中出现大量的 let,只有部分常量用 const 去做定义,这样的使用方式是错误的。

你可能不知道的事

const 的定义是不可重新赋值的值,与不可变的值(immutable value)不同;const 定义的 Object,在定义之后仍可以修改其属性。

所以其实他的使用场景很广,包括常量、配置项以及引用的组件、定义的 “大部分” 中间变量等,都应该以const做定义。反之就 let 而言,他的使用场景应该是相对较少的,我们只会在 loop(for,while 循环)及少量必须重定义的变量上用到他。

猜想:就执行效率而言,const 由于不可以重新赋值的特性,所以可以做更多语法静态分析方面的优化,从而有更高的执行效率。

所以上面代码中,所有使用 let 的部分,其实都应该是用 const 的。

Template Strings(字符串模板)

字符串模板是我刚接触ES6时最喜欢的特性之一,他语法简洁,语义明确,而且很好的解决了之前字符串拼接麻烦的问题。

因为他并不是 “必须” 的,而且原有的字符串拼接思维根深蒂固,导致我们很容易忽视掉他。

使用实例

我们先来看看他的一般使用场景:

const start = 'hi all';

const getName = () => {
  return 'jelly';
};

const conf = {
  fav: 'Coding'
};

// 模板
const msg = `${start}, my name is ${getName()}, ${conf.fav} is my favourite`;

你可能不知道的事

// 1. 与引号混用
const wantToSay = `I'm a "tbfed"`;

// 2. 支持多行文本
const slogan = 
`
I have a dream today!
`;

// 比较适合写HTML
const resultTpl = 
`
  <section>
    <div>...</div>
  </section>
`;

Enhanced Object Literals(增强的对象字面量)

增强的对象字面量是 ES6 中的升华功能,他设计了很多简写,这些简写不但保留了明确的语义,还减少了我们多余的代码量。

当他的使用成为一个习惯时,我们会看到自己代码变得更为优雅。

你可能不知道的事

const _bookNum = 4;

const basicConfig = {
  level: 5
}

const config = {
  // 直接指定原型对象
  __proto__: basicConfig,

  // 属性简写
  _bookNum,

  // 方法简写
  getBookNum() {
    return this.bookNum;
  }
}

Arrows and Lexical This(箭头函数)

箭头函数是ES6中的一个新的语法特性,他的用法简单,形态优雅,备受人们青睐。

大多数同学初识这个特性时,更多的仅仅用它作为函数定义的简写,这其实就有些屈才了。

// 未使用箭头函数的写法
{
  ...

  addOptions: function (options) {

    var self = this;

    options.forEach(function(name, opts){

      self[name] = self.addChild(name, opts);

    });

  } 
}

// 使用箭头函数后的写法
{
  ...

  addOptions: function (options) {

    options.forEach((name, opts) => {

      this[name] = this.addChild(name, opts);

    });

  } 
}

可以注意到上下两段代码的区别。

在未使用箭头函数前,我们在过程函数中使用父级 this,需要将其显式缓存到另一个中间变量中,因为过程函数有独立的 this 变量,会覆盖父级;使用箭头函数后,不但简写了一个过程函数( forEach 的参数),还省略掉了 this 的中间变量的定义。

原因:箭头函数没有独立执行上下文( this ),所以其内部引用 this 对象会直接访问父级。

插播:原来我们定义这个中间变量还有一个有趣的现象,就是明明千奇百怪,例如 self, that, me, _that, _me, Self...,快站出来说说你用过哪个,还是哪几个~

当然,从这块我们也可以看出,箭头函数是无法替代全部 function 的使用场景的,例如我们需要有独立 this 的函数。

另注:作者原文的评论中有人提到 forEach 方法中是可以不用写 that = this 这样的语句的,因为 forEach 可以接受可选的第二个参数用于重新指定 callback 中的 this 指向。即,使用 options.forEach( callback, this ) 即可。具体可参见MDN上的说明

你可能不知道的事

  • 箭头函数不但没有独立 this,他也没有独立的 arguments,所以如果需要取不定参的时候,要么使用 function,要么用 ES6 的另一个新特性 rest(具体在 rest 中会有详解)。
  • 箭头函数语法很灵活,在只有一个参数或者只有一句表达式做方法体时,可以省略相应括号。
// 完整写法
const getOptions = (name, key) => {
  ...
}

// 省略参数括号
const getOptions = key => {
  ... 
}

// 省略参数和方法体括号
const getOptions = key => console.log(key);

// 无参数或方法体,括号不能省略
const noop = () => {};

有个简单小栗子,这一灵活的语法在写连续的Promise链式调用时,可以使代码更加优雅

gitPromise
  .then(() => git.add())
  .then(() => git.commit())
  .then(() => git.log())
  .then((msg) => {
      ...
  })
  .then(() => git.push())
  .catch((err) => {
      utils.error(err);
  });

Destructuring(解构)

解构这个特性可以简单解读为分别定义,用于一次定义多个变量,常常用于分解方法返回对象为多个变量,分别使用。

使用过ES6的同学应该或多或少接触过这个特性,但是你可能不知道它如下几个用法:

你可能不知道的事

const bookSet = ['UED', 'TB fed', 'Not find'];
const bookCollection = () => {
  return {book1: 'UED', book2: 'TB fed'};
};

// 1. 解构也可以设置默认值
const {book1, book3 = 'Not find'} = bookCollection();

// 2. 解构数组时候是可以跳过其中某几项的
const [book1,,book3] = bookSet;  // book1 = 'UED', book3 = 'Not find'

// 3. 解构可以取到指定对象的任何属性,包括它包含的方法
const {length: setLength} = bookSet;  // setLength = 3

Rest + Spread

Rest 和 Spread 主要是应用 ... 运算符,完成值的聚合和分解。

你可能不知道的事

// 1. rest 得到的是一个真正的数组而不是一个伪数组
const getOptions = function(...args){
  console.log(args.join); // function
};

// 2. rest 可以配合箭头函数使用,达到取得所有参数的目的
const getOptions = (...args) => {
  console.log(args); // array
};

// 3. spread 可以用于解构时,聚合所得的值
const [opt1, ...opts] = ['one', 'two', 'three', 'four'];

// 4. spread 可以用于数组定义
const opts = ['one', 'two', 'three', 'four'];
const config = ['other', ...opts];

Classes

ES6 中实现的一个语法糖,用于简化基于原型集成实现类定义的场景。

虽然有很多人不太喜欢这个特性,认为它作为一个简单增强扩展,并没有其他语言 class 应有的特点。 但是就我自己观点来看,还是感觉这样一种写法确实比原有的原型继承的写法语义更清晰、明确,而且语法更简单。

同样,可能有些用法是你之前容易忽略掉的,在此做个补充。

你可能不知道的事

// 1. 静态变量
// ES6 的类定义实现了静态方法的定义,但静态变量呢?
// 可以用如下方式实现: 
class TbFedMembers{
  static get HuaChen(){
    return 'jelly';
  }
}
TbFedMembers.HuaChen; // "化辰"

// 2. 私有属性(私有属性有多种实现方式,只谈及其中一种)
// 闭包
const TbFedMembers = (() => {
  const HuaChen = 'jelly';

  return class{
    getOneMemberName(){
      return HuaChen;
    }
  };
})();

Promises

Promise 不只是一个对象、一个语法,他更是一种异步编程方式的变化 相信使用过 ES6 的同学都已经开始尝试了 Promise,甚至在不支持ES6的时候,已经开始使用一些基于 Promise 思想的开源框架。

那么我们之前用 Promise 究竟用的对么?有什么需要注意的点呢?

你可能不知道的事


// 1. 多个异步任务同时执行用 Promise.all,顺序执行使用链式调用
// Promise.all
Promise
  .all([jsBuildPromise, cssBuildPromise])
  .then(() => {
    ...
  });

// chain
jsBuildPromise
  .then(() => cssBuildPromise)
  .then(() => {
    ...
  });


// 2. Promise 的链式调用需要每一个过程返回一个 Promise 对象才能保证顺序执行
gitPromise
  .then(() => git.add())  // 正确,箭头函数简写
  .then(() => {
    git.commit(); // 错误,函数返回 undefined,会立即执行下一过程
  })
  .then(() => {
    return git.log(); // 正确
  });


// 3. Promise 需要调用 catch 方法来捕获错误,而且过程内的错误不会阻塞后续代码执行
new Promise(() => {
  f;  // not define error !
})
.catch((err) => {
  console.log(err)  // show 'f is not define'
});
console.log('error test');  // 此行可以被正常执行

结语

基础篇主要是讲了我们最常用的一些特性,后续如果大家感兴趣,还可以再来个 “进阶篇”,最后,希望文章中的部分内容可以对大家理解和使用 ES6 有所帮助。

参考资料

文章分享自淘宝FED团队化辰的文章, 原文地址:http://taobaofed.org/blog/2016/07/22/es6-basics/

感谢作者的分享!

阅读全文 »