8. jquery和lodash

jquery和lodash(underscore)是js界非常重要和有用的类库。它们方便了js程序员的开发,大大推动了js语言的应用。当然我们不会在这里介绍它们的发展历史。

基本上利用jquery和lodash,在没有前端框架(React,Angular等)的情况下,就可以非常方便地写出功能非常丰富的前端页面了。在这个章节,我们将讨论去jquery化和应用lodash。

去jquery化

首先必须肯定jquery,真的是一个非常的不错的类库,而且对跨浏览器支持的非常好。在做React的项目之前,那时候做得是java web开发。在jsp和easyUI做页面开发的过程中,还是经常用到jquery,也深刻地感受到了jquery带来的方便。

在最开始做React项目的时候,我们也经常用到jquery的功能,但是后来,渐渐的发现对于React开发,并不需要jquery了。项目组也决定要在一段时间内将所有的jquery代码去掉。当时我也在开发过程中意识到了很多原本需要jquery的场景,在React开发中已经不需要了。

让我们先来总结下jquery的功能。然后对于在React开发过程中的去jquery,说说我个人的看法。

jQuery - Wikipedia jQuery is a cross-platform JavaScript library designed to simplify the client-side scripting of HTML.[3] jQuery is the most popular JavaScript library in use today, with installation on 65% of the top 10 million highest-trafficked sites on the Web.[4][5][6] jQuery is free, open-source software licensed under the MIT License.[2]
jQuery's syntax is designed to make it easier to navigate a document, select DOM elements, create animations, handle events, and develop Ajax applications. jQuery also provides capabilities for developers to create plug-ins on top of the JavaScript library. This enables developers to create abstractions for low-level interaction and animation, advanced effects and high-level, themeable widgets. The modular approach to the jQuery library allows the creation of powerful dynamic web pages and Web applications.

翻译: jQuery是一个跨平台JavaScript库,旨在简化客户端的HTML脚本。jQuery是目前最流行的JavaScript库,在Web上排名前1000万的最高投放网站的65%的网站中使用。jQuery是根据MIT许可证授权的免费开源软件。jQuery的语法设计使它更容易导航document,选择DOM元素,创建动画,处理事件和开发Ajax应用程序。 jQuery还为开发人员提供了在JavaScript库之上创建插件的功能。 这使开发人员能够创建低级交互和动画,高级效果和高级、可主题小部件。 jQuery库的模块化方法允许创建强大的动态网页和Web应用程序。

总结一下jquery的特点:

  1. 跨平台(跨浏览器);
  2. 导航document;
  3. 选择dom元素;
  4. 动画;
  5. 事件处理;
  6. ajax请求;

让我们看看react开发的特点:

1. 跨浏览器;

2. 基本不再需要导航document、进行元素的查找(对查找到的元素进行属性或者css的设置)和插入和隐藏节点。如果要做这些操作,也可以通过判断state和props是否改变来进行相应的操作。另外,现在我们需要更少的css代码:由于bootstrap等类库的流行。

3. 现代浏览器中的fetch取代了ajax;


fetch是基于es6的Promise实现的。对于Promise其实有很大的评论的,而且es7已经开始支持更直观的同步api(await async)。但是目前开发,我们还是会经常遇到Promise,各种类库都用到了promise。

4. react重新定义了事件,对于需要跨组件的数据交互(原本可以通过自定义事件来实现),我们也可以通过react的“Lifting State Up”原则或者redux的全局state管理来实现;

5. 动画这个东西的话,一般的网站,如果不是为了把效果做得很炫的话,基本不需要用到。如果真的要做的很炫,也有其他专门的类库。就像《单页SPA应用》书中作者说的,尽量使用功能单一的、面向特定功能的类库。

综上所述,我们在进行一般的react项目开发的时候,不需要用到jquery了。同时,我们还可以在github上看到一个项目You-Dont-Need-jQuery(oneuijs/You-Dont-Need-jQuery),下面的引用来自这个项目。

前端发展很快,现代浏览器原生 API 已经足够好用。我们并不需要为了操作 DOM、Event 等再学习一下 jQuery 的 API。同时由于 React、Angular、Vue 等框架的流行,直接操作 DOM 不再是好的模式,jQuery 使用场景大大减少。

lodash(underscore)的普及

虽然随着ES6的普及,对array和object增加了很多实用的方法。但是,这只是lodash所提供的方法的冰山一角。对于我个人来说,比较看重lodash下面三个特点。

  1. 丰富的扩展函数
  2. 高性能
  3. 链式函数

让我们开始介绍lodash的使用。

首先当然还是安装

npm instaill lodash --save

让我们来看看lodash都提供了哪类函数

因为我们在平时开发过程中处理的最多的就是数组[]、对象{}和字符串。

js中的String对象已经提供了非常多的函数(当然es6也为数组和对象提供了更多的方法),所有,在这里,我们将关注Array、Collection和Array中的函数。

让我们首先看看lodash的函数长什么样的。map是我们经常用到的方法,让我们从这个大家都比较熟悉的方法来开始介绍lodash。

我们先展示大家都熟悉的map的用法,这段代码将原来数组中的每个元素翻倍了。

const doubleArray = [1, 3, 5].map(item=>2*item);

下面来看一下利用lodash的map

const doubleArray = _.map([1, 3, 5], item=>2*item);

我们可以看到lodash函数的基本样子都是以“_.xxx”开始的,然后将本来的对象放到了函数的第一个参数的位置,将原本函数需要传入的参数,放到了第二个参数的位置。

对于lodash中的map函数的基本形式如下:

_.map(collection, [iteratee=_.identity])。

我们可以从这个函数的第一个参数发现,lodash中的map支持的是collection类型,可能java程序员对诸如list array collection的概念会更加了解。一般在js的开发中,我们只涉及到数组和对象。collection,集合,就是一组元素的集合。数组和对象都是集合的实现。

数组可能会更好了解,因为它本来就是元素的有序集合。对于object,其实它是key-value的集合,集合中的一个元素就是一个key-value对。

让我们来看lodash文档上的map的例子。

function square(n) {
  return n * n;
}

_.map([4, 8], square);
// => [16, 64]

_.map({ 'a': 4, 'b': 8 }, square);
// => [16, 64] (iteration order is not guaranteed)

var users = [
  { 'user': 'barney' },
  { 'user': 'fred' }
];

// The `_.property` iteratee shorthand.
_.map(users, 'user');
// => ['barney', 'fred']

我们可以从最后一个map的例子看出,lodash中的map的第二个参数不仅支持函数类型,还支持属性(对象中的属性--key)。

下面让我们来看一个array类不提供,但lodash提供了的函数(太多这类函数了,我们只是随便找了一个)xor

Creates an array of unique values that is the symmetric difference of the given arrays. The order of result values is determined by the order they occur in the arrays.

创建一个由给定数组的对称差异组成的唯一值的数组,结果值(对称差异)在生成的数组中的顺序由它们在给定数组中出现的顺序确定。

这个函数就是一个初级的算法题,从两个数组中找出不同的元素。

让我们先看下这个函数的实例:

_.xor([2, 3, 1], [5, 2, 3]);
// [1, 5]

对于这个函数,我们会使用Array自带的函数来实现它。同时由于我对于算法的研究,已经是很久之前的事情了,那就让我们从网上找个比较好的方法。

在 JavaScript 中,如何求出两个数组的交集和差集?

让我们看这个知乎问题中尤雨溪大神(vue.js的作者)的回答(还需要一个unique操作):

let difference = a.concat(b).filter(v => !a.includes(v) || !b.includes(v));

这个算法的想法就是,将两个数组拼接起来,然后从新数组中过滤出既不在第一个数组也不再第二个数组的元素。这个算法用到了Array自带(ES7中)的includes这个遍历第一个数组,判断遍历到的元素是否存在于第二个数组中,如果存在,两个数组中都删除,将剩余的两个数组拼接成一个数组。这个解法真的是非常简洁了,一行代码就搞定。需要注意的是,这个算法并不会取出唯一的元素,即新数组中会有重复的元素。

Array.prototype.includes()

让我们通过下面的例子来看看两个方法的性能(下面的比较都不考虑最后需要的那个unique操作)。

import React, { Component, PropTypes } from 'react';
import _ from 'lodash';

class Example1 extends Component {

  xor = (a, b) => {
    return a.concat(b).filter(v => !a.includes(v) || !b.includes(v));
  }
  render() {
    // const a = [2, 3, 11, 3, 5, 7, 88,  3, 5, 7,9, 12, 43, 1, 11, 3, 12, 43, 1, 11, 5, 7, 88, 9, 12, 43, 5 ,34, 3,4,34,34,343 ];
    // const b = [5, 2, 3, 2, 34, 3, 5, 88, 9, 126, 3, 11, 3, 5, 7, 61,7, 34, 3, 7, 88, 9, 12, 43, 1,34, 54,54,54,5,45,45,33];
    const a = [2, 3, 1];
    const b = [5, 2, 3];
    let t0;
    let t1;
    t0 = performance.now();
    for (var i = 0; i < 1000; i++) {
      this.xor(a, b);
    }
    t1 = performance.now();
    const xor1 = <p>{`Call this.xor, take ${t1-t0} milliseconds.`}</p>

    t0 = performance.now();
    for (var i = 0; i < 1000; i++) {
      _.xor(a, b);
    }
    t1 = performance.now();
    const xor2 = <p>{`Call _.xor, take, take ${t1-t0} milliseconds.`}</p>
    return <div>
      {xor1}
      {xor2}
    </div>
  }
}
export default Example1

在这个例子中,我们performance.now()来获取当前时间,重复执行两个方法各1000次来计算需要的时间。

我们发现lodash的方法竟然要比网上找来的简洁的算法要慢,不管刷新这个页面几次,基本上都是lodash的方法比网上找来的算法慢。那么lodash库说好的高性能呢?!

基于以前写论文做实验的经验,让我们修改某些参数来看算法的性能,那么修改什么好呢?上面的例子中其实就两个地方可以修改:

1. 循环的次数,这个参数只会让两个算法的时间分别线性的增长。

2. 数组的长度,对了,我们可以试试,在“大数据”的情况下,哪个算法性能更好。

让我们对Example1中的数组a,b,用那两个元素多的。

这时候,我们发现lodash的方法已经比网上找来的算法快了,不管刷新多少次,基本都是这个情况。lodash的提供的方法终于显示出它的高性能了。注意如果在网上的算法要达到lodash提供的xor函数的全部功能,我们还需要在得到的结果外包一个求数组唯一元素的方法。如果包了这个函数,那么lodash的性能优势更凸显了。

基本上除了Array的map,forEach,filter这种最基本的函数,我会直接使用js自己的函数外,对于比较复杂的逻辑或者需要两个以上基本函数才能实现的场景,我就会考虑是否lodash已经有了这个功能或者用lodash的几个函数来实现。如果这几个函数可以使用链式的形式,那么我就会用lodash的链式函数来实现该功能。

对于Example1中的网上找来的算法,我们将其改写成lodash函数的形式。

xor = (a, b) => {
  return _.filter(_.concat(a, b), v => !_.includes(a, v) || !_.includes(b, v));
}

lodash的链式函数:

Creates a lodash wrapper instance that wraps value with explicit method chain sequences enabled. The result of such sequences must be unwrapped with _#value.

翻译: 通过启用显式方法链序列来创建一个lodash封装器实例。 这些序列的结果必须用_#value来进行介封。让我们看lodash提供的基本实例。我们将其改成ES6的形式。

const users = [
  { 'user': 'barney',  'age': 36 },
  { 'user': 'fred',    'age': 40 },
  { 'user': 'pebbles', 'age': 1 }
];

const youngest = _
  .chain(users)
  .sortBy('age')
  .map((o)=>`${o.user} is ${o.age}`)
  .head()
  .value();
// => 'pebbles is 1'

我们可以看出链式函数的启动需要借助_.chain函数,它接受一个对象,之后的方法都可以用".xxx"来表示,最后需要用".value()"函数来获取最终的结果。

如果不用链式函数,让我们看看,这个操作是多么的复杂。

const users = [
  { 'user': 'barney',  'age': 36 },
  { 'user': 'fred',    'age': 40 },
  { 'user': 'pebbles', 'age': 1 }
];

const sortedUsers = _.sortBy(users, 'age');
const mappedUsers = _.map(sortedUsers, (o)=>`${o.user} is ${o.age}`);
const result = _.head(mappedUsers);
// => 'pebbles is 1'

当然我们也可以去掉中间变量,使用一行代码来实现,但是这样的函数看起来很费时费力。

_.head(_.map(_.sortBy(users, 'age'), (o)=>`${o.user} is ${o.age}`))

让我们对比下这使用链式函数和不使用链式函数的实现。

_.chain(users).sortBy('age').map((o)=>`${o.user} is ${o.age}`).head().value();
_.head(_.map(_.sortBy(users, 'age'), (o)=>`${o.user} is ${o.age}`));

用链式函数实现,所有的其它函数就像一条链子一样,一个接着一个,非常清晰。当然这种清晰的形式,肯定是需要一点点性能代价的。让我们来看下本章最后的一个实例。

import React, { Component, PropTypes } from 'react';
import _ from 'lodash';

class Example1 extends Component {

  xor = (a, b) => {
    return a.concat(b).filter(v => !a.includes(v) || !b.includes(v));
  }

  xor1 = (a, b) => {
    return _.filter(_.concat(a, b), v => !_.includes(a, v) || !_.includes(b, v));
  }

  xor2 = (a, b) => {
    return _.chain(a).concat(b).filter(v => !_.includes(a, v) || !_.includes(b, v)).value();
  }

  render() {
    const a = [2, 3, 11, 3, 5, 7, 88,  3, 5, 7,9, 12, 43, 1, 11, 3, 12, 43, 1, 11, 5, 7, 88, 9, 12, 43, 5 ,34, 3,4,34,34,343 ];
    const b = [5, 2, 3, 2, 34, 3, 5, 88, 9, 126, 3, 11, 3, 5, 7, 61,7, 34, 3, 7, 88, 9, 12, 43, 1,34, 54,54,54,5,45,45,33];
    // const a = [2, 3, 1];
    // const b = [5, 2, 3];
    let t0;
    let t1;
    console.log(_.xor(a, b));
    console.log(this.xor(a, b));
    console.log(this.xor1(a, b));
    console.log(this.xor2(a, b));
    t0 = performance.now();
    for (var i = 0; i < 10000; i++) {
      _.xor(a, b);
    }
    t1 = performance.now();
    const xor0 = <p>{`Call _.xor, take ${t1-t0} milliseconds.`}</p>

    t0 = performance.now();
    for (var i = 0; i < 10000; i++) {
      this.xor(a, b);
    }
    t1 = performance.now();
    const xor = <p>{`Call this.xor, take ${t1-t0} milliseconds.`}</p>

    t0 = performance.now();
    for (var i = 0; i < 10000; i++) {
      this.xor1(a, b);
    }
    t1 = performance.now();
    const xor1 = <p>{`Call this.xor1, take ${t1-t0} milliseconds.`}</p>

    t0 = performance.now();
    for (var i = 0; i < 10000; i++) {
      this.xor2(a, b);
    }
    t1 = performance.now();
    const xor2 = <p>{`Call this.xor2, take, take ${t1-t0} milliseconds.`}</p>
    return <div>
      {xor0}
      {xor}
      {xor1}
      {xor2}
    </div>
  }
}

export default Example1

图12.4展示了实例中4个方法的性能,我们将循环次数增加到了10000次,因为在1000次的情况下,会有一些不稳定的情况出现。而在10000次的时候,能减少偶尔事件造成的数据不稳定因素。

在四个方法中,_.xor就是lodash提供的默认方法,this.xor使用Array自带的函数实现的方法,this.xor1是用lodash提供的对应于Array自带的函数来实现的方法,this.xor2是用链式函数的形式来实现的方法。四个方法的性能是从上到下依次变慢。

我们可以从这个实例中得到一些使用lodash进行日常开发的小建议:

1. 如果lodash提供了对应的方法,那么使用lodash的方法

2. 如果对性能要求特别高的情况下,即函数的执行时间成为了影响系统性能的重要原因是,我们使用js自带函数

3. 如果对性能要求不是很高的情况下(其实谷歌浏览器的V8引擎,已经大大提升了js的性能,现在在开发中,影响系统性能的并不是一些基本逻辑如何实现,而是前后台交互等因素),我们使用lodash提供的方法。

2.3两点是在lodash不提供对应方法的情况下

4. 如果用lodash实现一个逻辑需要用到非常多的函数,一层一层嵌套,那么可以使用链式函数的形式。

总之,lodash是一个非常好的函数库,当然jquery也是。lodash已经在非常多的js开源项目中被使用到。希望读者也能因为使用它而更方便日常的开发。

编辑于 2016-12-31

文章被以下专栏收录