做前端的,谁没写过几万行jQuery?

$(document).ready()开始,到$('.btn').click(),到$.ajax(),到各种链式调用和动画,jQuery曾经是每个前端开发者的标配。那时候找工作,不会jQuery都不好意思说自己是做前端的。

我也是写着jQuery长大的一代。从2010年开始用jQuery,写了五六年,自认为对jQuery了如指掌,各种插件、各种技巧、各种hack,玩得很溜。

但是2016年,我在一个项目中尝试用了Vue.js,然后……就再也回不去了。

今天就来聊聊,为什么用了Vue.js之后,我再也不想写jQuery了。

一、jQuery的那些痛

在用Vue之前,我其实没觉得jQuery有什么不好。毕竟用了这么多年,已经形成了肌肉记忆。但是回头看,jQuery的痛点其实很多,只是我们习惯了而已。

1. DOM操作太繁琐

jQuery的核心是DOM操作。要更新页面内容,你需要先选中元素,然后设置内容:

$('#username').text(user.name);
$('#user-avatar').attr('src', user.avatar);
$('#user-bio').html(user.bio);
$('.user-followers').text(user.followers + ' followers');

一个用户信息展示,就要写这么多行。如果数据变了,还要再写一遍更新逻辑。数据和视图是分离的,你需要手动把数据同步到DOM上。

而且,DOM操作多了,性能就成了问题。频繁的DOM操作会导致页面重排重绘,卡顿、闪烁都是常事。

2. 事件绑定和销毁麻烦

jQuery绑定事件很简单:$('.btn').click(handler)。但是在复杂的页面中,事件绑定和销毁就成了噩梦。

动态添加的元素,需要用事件委托:$(document).on('click', '.btn', handler)。页面切换时,需要手动解绑事件,否则会内存泄漏、事件重复绑定。

我曾经维护过一个项目,页面切换后按钮点击一次触发三次,查了半天才发现是之前的页面没有解绑事件,三次绑定叠加了。

3. 代码组织混乱

jQuery没有强制的代码组织方式,全靠开发者自觉。小项目还好,大项目中,代码很容易变成"面条代码"——HTML里混着JS,JS里拼着HTML字符串,逻辑和视图搅在一起,维护起来非常痛苦。

// 典型的jQuery代码:JS里拼HTML
var html = '<div class="user-card">' +
  '<img src="' + user.avatar + '">' +
  '<h3>' + user.name + '</h3>' +
  '<p>' + user.bio + '</p>' +
  '</div>';
$('#user-list').append(html);

拼字符串拼到怀疑人生,引号嵌套、转义、拼写错误,都是家常便饭。

4. 状态管理缺失

jQuery没有状态管理的概念。页面的状态(哪些数据加载了、哪些按钮被点击了、当前是哪个tab)都散落在各个地方,可能存在DOM的data属性里,可能存在全局变量里,可能存在隐藏域里。

状态多了之后,你根本不知道哪个状态是最新的,修改一个状态要改好几个地方,bug层出不穷。

5. 双向绑定要自己写

表单输入和数据同步,在jQuery里需要自己写:

$('#input-name').on('input', function() {
  user.name = $(this).val();
  $('#display-name').text($(this).val());
});

一个输入框就要写这么多,表单字段多了简直是灾难。

二、Vue.js的初体验

2016年初,我听说了Vue.js。那时候Vue还不算火,AngularJS和React是主流,Vue算是后起之秀。但是看了Vue的文档和demo之后,我被吸引了——它太简单、太优雅了。

我在一个小项目中尝试用了Vue,然后就被惊艳到了。

1. 数据驱动,告别DOM操作

Vue的核心理念是"数据驱动视图"。你只需要关心数据,视图会自动更新。

<div id="app">
  <div class="user-card">
    <img :src="user.avatar">
    <h3>{{ user.name }}</h3>
    <p>{{ user.bio }}</p>
    <p>{{ user.followers }} followers</p>
  </div>
</div>
new Vue({
  el: '#app',
  data: {
    user: {
      name: '张三',
      avatar: 'avatar.jpg',
      bio: '一个程序员',
      followers: 100
    }
  }
});

就这么简单!数据变了,视图自动更新。你不需要手动操作DOM,不需要选中元素、设置内容。你只需要修改this.user.name = '李四',页面上的名字就自动变了。

这种感觉太爽了,就像从"手动挡"换到了"自动挡"。

2. 组件化,代码组织清晰

Vue的组件化让代码组织变得非常清晰。每个组件都是一个独立的单元,有自己的模板、逻辑、样式,互不干扰。

<!-- user-card.vue -->
<template>
  <div class="user-card">
    <img :src="user.avatar">
    <h3>{{ user.name }}</h3>
    <p>{{ user.bio }}</p>
  </div>
</template>

<script>
export default {
  props: ['user']
}
</script>

<style scoped>
.user-card {
  padding: 20px;
  border: 1px solid #eee;
}
</style>

组件可以复用,可以嵌套,可以传参。一个复杂的页面,拆分成若干个组件,每个组件负责自己的部分,维护起来非常清晰。

而且,scoped样式让CSS只在当前组件内生效,再也不用担心样式冲突、类名冲突了。

3. 双向绑定,表单so easy

Vue的v-model让表单双向绑定变得异常简单:

<input v-model="user.name" type="text">
<p>你好,{{ user.name }}</p>

就这一行,输入框和数据就双向绑定了。输入框内容变了,数据自动更新;数据变了,输入框内容也自动更新。不需要写任何事件监听和同步逻辑。

表单字段多了之后,这个优势更加明显。一个复杂的表单,在jQuery里可能要写几百行DOM操作和事件绑定,在Vue里可能只需要一个v-model就搞定了。

4. 指令系统,声明式操作

Vue的指令系统让DOM操作变得声明式、直观:

  • v-if / v-show:条件渲染
  • v-for:列表渲染
  • v-on(简写@):事件绑定
  • v-bind(简写:):属性绑定
  • v-model:双向绑定
<ul>
  <li v-for="item in list" :key="item.id">
    {{ item.name }}
    <button @click="removeItem(item.id)">删除</button>
  </li>
</ul>
<p v-if="list.length === 0">暂无数据</p>

列表渲染、条件渲染、事件绑定,都在模板里声明式地写,一目了然。不需要在JS里循环拼HTML字符串,不需要手动绑定事件。

5. 生命周期钩子,逻辑清晰

Vue的生命周期钩子让组件的初始化、更新、销毁逻辑变得清晰:

export default {
  data() {
    return { list: [] }
  },
  created() {
    // 组件创建后,加载数据
    this.fetchData();
  },
  mounted() {
    // DOM挂载后,可以操作DOM
  },
  beforeDestroy() {
    // 组件销毁前,清理定时器、解绑事件
  },
  methods: {
    fetchData() {
      // 加载数据
    }
  }
}

什么时候加载数据、什么时候操作DOM、什么时候清理资源,都有对应的钩子,逻辑非常清晰。再也不用担心事件没解绑、定时器没清理导致的内存泄漏了。

三、实际项目中的对比

说了这么多概念,来看看实际项目中,jQuery和Vue的代码量对比。

场景:一个用户列表页面

功能:加载用户列表、展示用户卡片、点击删除按钮删除用户、搜索过滤。

jQuery版本(简化版):

$(function() {
  var list = [];
  var keyword = '';

  // 加载数据
  function loadData() {
    $.getJSON('/api/users', function(data) {
      list = data;
      renderList();
    });
  }

  // 渲染列表
  function renderList() {
    var $list = $('#user-list');
    $list.empty();

    var filtered = list.filter(function(user) {
      return user.name.indexOf(keyword) > -1;
    });

    if (filtered.length === 0) {
      $list.append('<p class="empty">暂无数据</p>');
      return;
    }

    filtered.forEach(function(user) {
      var html = '<div class="user-card" data-id="' + user.id + '">' +
        '<img src="' + user.avatar + '">' +
        '<h3>' + user.name + '</h3>' +
        '<p>' + user.bio + '</p>' +
        '<button class="btn-delete">删除</button>' +
        '</div>';
      $list.append(html);
    });
  }

  // 搜索
  $('#search').on('input', function() {
    keyword = $(this).val();
    renderList();
  });

  // 删除(事件委托)
  $('#user-list').on('click', '.btn-delete', function() {
    var id = $(this).closest('.user-card').data('id');
    if (confirm('确定删除吗?')) {
      $.ajax({
        url: '/api/users/' + id,
        type: 'DELETE',
        success: function() {
          list = list.filter(function(user) {
            return user.id !== id;
          });
          renderList();
        }
      });
    }
  });

  loadData();
});

Vue版本(简化版):

<template>
  <div>
    <input v-model="keyword" type="text" placeholder="搜索">
    <div class="user-list">
      <div v-for="user in filteredList" :key="user.id" class="user-card">
        <img :src="user.avatar">
        <h3>{{ user.name }}</h3>
        <p>{{ user.bio }}</p>
        <button @click="removeUser(user.id)">删除</button>
      </div>
      <p v-if="filteredList.length === 0" class="empty">暂无数据</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: [],
      keyword: ''
    }
  },
  computed: {
    filteredList() {
      return this.list.filter(user => user.name.includes(this.keyword));
    }
  },
  created() {
    this.fetchData();
  },
  methods: {
    fetchData() {
      fetch('/api/users')
        .then(res => res.json())
        .then(data => { this.list = data; });
    },
    removeUser(id) {
      if (confirm('确定删除吗?')) {
        fetch('/api/users/' + id, { method: 'DELETE' })
          .then(() => {
            this.list = this.list.filter(user => user.id !== id);
          });
      }
    }
  }
}
</script>

对比一下:

  • jQuery版本:JS里拼HTML字符串,事件委托,手动渲染,数据和视图分离
  • Vue版本:模板声明式渲染,计算属性自动过滤,数据驱动视图,逻辑清晰

而且,Vue版本的可读性和可维护性远高于jQuery版本。如果要加功能(比如分页、排序),Vue版本只需要加几个属性和方法,jQuery版本可能要大改。

四、Vue的坑和注意事项

当然,Vue也不是完美的,用的过程中也踩了一些坑。

1. 响应式的限制

Vue 2.x的响应式是基于Object.defineProperty的,有一些限制:

  • 不能检测对象属性的添加和删除(需要用Vue.set
  • 不能检测数组的直接修改(如arr[0] = newValue,需要用Vue.setsplice

这些限制在Vue 3.x中用Proxy解决了,但是2016年用的是Vue 1.x/2.x,需要注意。

2. 组件通信

组件之间的通信,在简单场景下用props和事件就够了,但是复杂场景下(如跨层级通信、兄弟组件通信)会比较麻烦。2016年的时候,Vuex还不算成熟,大型应用的状态管理需要自己摸索。

3. 学习曲线

Vue虽然比Angular简单,但是比jQuery还是有学习曲线的。需要理解数据驱动、组件化、生命周期、计算属性、指令等概念。对于习惯了jQuery命令式编程的开发者,需要转变思维方式。

4. 生态还不够成熟

2016年的时候,Vue的生态还不如React和Angular成熟。第三方组件库、插件、工具链都还在发展中。很多东西需要自己造轮子。

但是这些坑,相比Vue带来的便利,都是可以接受的。而且Vue的文档非常好,社区也很活跃,遇到问题基本都能找到解决方案。

五、为什么回不去jQuery了

用了Vue之后,再回去写jQuery,感觉就像从现代社会回到了原始社会。

  • 写一个列表渲染,第一反应是v-for,而不是for循环拼HTML
  • 写一个表单输入,第一反应是v-model,而不是on('input')手动同步
  • 写一个条件显示,第一反应是v-if,而不是show()/hide()
  • 写一个组件,第一反应是拆分成.vue文件,而不是在一个大JS文件里写一堆函数

jQuery的命令式编程,需要你告诉浏览器"做什么"和"怎么做";Vue的声明式编程,你只需要告诉浏览器"我要什么",剩下的交给框架。

这种思维方式的转变,一旦习惯了,就再也回不去了。就像用惯了自动挡的车,再开手动挡会觉得累;用惯了智能手机,再用功能机会觉得不方便。

当然,jQuery也不是一无是处。在一些简单的页面(如营销页、活动页)中,jQuery还是很方便的,引入一个JS文件就能用,不需要构建工具、不需要模块化。但是在中大型项目中,Vue(或React、Angular)的优势是碾压级的。

六、2016年的前端框架选择

2016年,前端框架三足鼎立:AngularJS(Angular 1.x)、React、Vue.js。

  • AngularJS:老牌框架,功能强大,但是学习曲线陡,性能有问题,而且Angular 2即将发布(不兼容1.x),很多人在观望。
  • React:Facebook出品,组件化+虚拟DOM,生态强大,但是需要学习JSX、Flux/Redux等概念,入门门槛不低。
  • Vue.js:尤雨溪出品,简单易学,文档友好,渐进式框架(可以只用到一部分功能),性能优秀,但是生态还在发展中。

我选择Vue的原因:

  1. 简单:看了一遍文档就能上手,不需要学习太多新概念
  2. 灵活:可以从简单的用法开始,逐步深入,不需要一上来就用全套工具链
  3. 文档好:Vue的中文文档非常友好,例子丰富,遇到问题查文档基本能解决
  4. 性能好:虚拟DOM+异步更新,性能不输React
  5. 中国人开发:尤雨溪是中国人,社区氛围好,中文资料多

当然,这只是我个人的选择。React和Angular也各有优势,选择哪个框架要看项目需求、团队情况、个人偏好。但是无论选哪个,都比纯jQuery开发效率高得多。

总结

用了Vue.js之后,我再也不想写jQuery了。原因很简单:

  1. 数据驱动视图:告别繁琐的DOM操作,只关心数据
  2. 组件化:代码组织清晰,可复用,易维护
  3. 双向绑定:表单处理so easy
  4. 声明式模板:直观、易读、易维护
  5. 生命周期钩子:逻辑清晰,资源管理方便
  6. 开发效率高:同样的功能,代码量更少,bug更少

jQuery的痛点:DOM操作繁琐、事件绑定/销毁麻烦、代码组织混乱、状态管理缺失、双向绑定要自己写。

Vue的坑:响应式限制、组件通信、学习曲线、生态不够成熟(2016年)。但是这些坑相比Vue带来的便利,都是可以接受的。

2016年,前端框架三足鼎立(AngularJS、React、Vue),我选择Vue是因为它简单、灵活、文档好、性能好、中国人开发。当然,选择哪个框架要看具体情况,但是无论选哪个,都比纯jQuery强。

最后,如果你还在用jQuery写中大型项目,我强烈建议你试试Vue(或React、Angular)。一开始可能不习惯,但是用了之后,你会发现前端开发原来可以这么优雅。然后,你也会和我一样,再也回不去jQuery了。

技术在进步,我们也要跟着进步。不要让"习惯"成为你学习新技术的障碍。走出舒适区,你会发现更大的世界。