筽然wwW看到了h4f3曾经,vim 保存并退出的网止h4f3可是怎么com登不上了?

中文翻译可能有些偏差(不是我翻的)。区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。&br&&br&事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。&br&&br&同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。&br&&br&如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个点子一直没时间做,那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用的异步状态变化很有帮助。
中文翻译可能有些偏差(不是我翻的)。区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。 事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触…
&figure&&img src=&https://pic2.zhimg.com/v2-f83af28dbd4b58cf9bc4_b.jpg& data-rawwidth=&1280& data-rawheight=&866& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-f83af28dbd4b58cf9bc4_r.jpg&&&/figure&&blockquote&&p&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&详解 Vue & Vuex 实践&/a&翻译自&a href=&https://link.zhihu.com/?target=https%3A//medium.com/%40bradfmd/vue-vuex-getting-started-f78c03d9f65%23.e1zv0af2g& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Vue + Vuex — Getting started&/a&,承接自&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&2017 Vue.js 2快速入门指南&/a&,从属于&a href=&https://link.zhihu.com/?target=https%3A//github.com/wxyyxc1992/Web-Frontend-Introduction-And-Engineering-Practices& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Web 前端入门与工程实践&/a&。&/p&&/blockquote&&p&随着应用复杂度的增加,我们需要考虑如何进行应用的状态管理,将业务逻辑与界面交互相剥离,详细讨论参考笔者的&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&2016-我的前端之路:工具化与工程化&/a&。Vue 为我们提供了方便的组件内状态管理的机制,下面这个例子就是常见的获取列表数据然后渲染到界面中:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import axios from 'axios'
export default {
name: 'projects',
data: function () {
projects: []
methods: {
loadProjects: function () {
axios.get('/secured/projects').then((response) =& {
this.projects = response.data
}, (err) =& {
console.log(err)
mounted: function () {
this.loadProjects()
&/code&&/pre&&/div&&p&在 template 中我们可以方便的访问项目列表并且进行过滤、排序等操作,不过如果我们在另一个列表中也需要来展示相同的数据信息,继续按照这种方式实现的话我们不得不重新加载一遍数据。更麻烦的是如果用户在本地修改了某个列表数据,那么如何同步两个组件中的列表信息会是个头疼的问题。Vue 官方推荐使用&a href=&https://link.zhihu.com/?target=https%3A//github.com/vuejs/vuex& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Vuex&/a&,类似于 Redux 的集中式状态管理工具来辅助解决这个问题。&/p&&h1&何谓 Vuex?&/h1&&p&根据 &a href=&https://link.zhihu.com/?target=http%3A//vuex.vuejs.org/en/intro.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Vuex&/a& 文档中的描述,Vuex 是适用于 Vue.js 应用的状态管理库,为应用中的所有组件提供集中式的状态存储与操作,保证了所有状态以可预测的方式进行修改。&/p&&p&Vuex 中 Store 的模板化定义如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
actions: {
mutations: {
getters: {
modules: {
export default store
&/code&&/pre&&/div&&p&上述代码中包含了定义 Vuex Store 时关键的 5 个属性:&/p&&ul&&li&&p&state: state 定义了应用状态的数据结构,同样可以在这里设置默认的初始状态。&/p&&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&state: {
projects: [],
userProfile: {}
&/code&&/pre&&/div&&ul&&li&&p&actions:Actions 即是定义提交触发更改信息的描述,常见的例子有从服务端获取数据,在数据获取完成后会调用store.commit()来调用更改 Store 中的状态。可以在组件中使用dispatch来发出 Actions。&/p&&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&actions: {
LOAD_PROJECT_LIST: function ({ commit }) {
axios.get('/secured/projects').then((response) =& {
commit('SET_PROJECT_LIST', { list: response.data })
}, (err) =& {
console.log(err)
&/code&&/pre&&/div&&ul&&li&&p&mutations: 调用 mutations 是唯一允许更新应用状态的地方。&/p&&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&mutations: {
SET_PROJECT_LIST: (state, { list }) =& {
state.projects = list
&/code&&/pre&&/div&&ul&&li&&p&getters: Getters 允许组件从 Store 中获取数据,譬如我们可以从 Store 中的 projectList 中筛选出已完成的项目列表:&/p&&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&getters: {
completedProjects: state =& {
return state.projects.filter(project =& project.completed).length
&/code&&/pre&&/div&&ul&&li&&p&modules: modules 对象允许将单一的 Store 拆分为多个 Store 的同时保存在单一的状态树中。随着应用复杂度的增加,这种拆分能够更好地组织代码,更多细节参考&a href=&https://link.zhihu.com/?target=http%3A//vuex.vuejs.org/en/modules.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&。&/p&&/li&&/ul&&h1&Example&/h1&&p&在理解了 Vuex 的基础概念之后,我们会创建一个真正的应用来熟悉整个使用流程。该应用承接自&a href=&https://link.zhihu.com/?target=https%3A//medium.com/%40bradfmd/vue-js-setting-up-auth0-6eb26cbbc48a& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这个博客&/a&,在准备好基础项目之后,我们需要将 vuex 引入项目中:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&$ yarn add vuex
&/code&&/pre&&/div&&p&该步骤完成之后,我们需要在 src 目录下创建名为 store 的目录来存放状态管理相关代码,首先创建 index.js:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
const store = new Vuex.Store({
actions: {
mutations: {
getters: {
export default store
&/code&&/pre&&/div&&p&然后在 main.js 文件中我们需要将该 Store 实例添加到构造的 Vue 实例中:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import store from './store'
/* eslint-disable no-new */
template: `
&navbar /&
&section class=&section&&
&div class=&container is-fluid&&
&router-view&&/router-view&
&/section&
components: {
}).$mount('#app')
&/code&&/pre&&/div&&p&然后,我们需要去完善 Store 定义:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
const store = new Vuex.Store({
projects: []
actions: {
LOAD_PROJECT_LIST: function ({ commit }) {
axios.get('/secured/projects').then((response) =& {
commit('SET_PROJECT_LIST', { list: response.data })
}, (err) =& {
console.log(err)
mutations: {
SET_PROJECT_LIST: (state, { list }) =& {
state.projects = list
getters: {
openProjects: state =& {
return state.projects.filter(project =& !project.completed)
export default store
&/code&&/pre&&/div&&p&在本项目中,我们将原本存放在组件内的项目数组移动到 Store 中,并且将所有关于状态的改变都通过 Action 进行而不是直接修改:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// /src/components/projectList.vue
&template lang=&html&&
&div class=&&&
&table class=&table&&
&th&Project Name&/th&
&th&Assigned To&/th&
&th&Priority&/th&
&th&Completed&/th&
&tr v-for=&item in projects&&
&td&{{item.name}}&/td&
&td&{{item.assignedTo}}&/td&
&td&{{item.priority}}&/td&
&td&&i v-if=&item.completed& class=&fa fa-check&&&/i&&/td&
&/template&
import { mapState } from 'vuex'
export default {
name: 'projectList',
computed: mapState([
'projects'
&style lang=&css&&
&/code&&/pre&&/div&&p&这个模板还是十分直观,我们通过computed对象来访问 Store 中的状态信息。值得一提的是这里的mapState函数,这里用的是简写,完整的话可以直接访问 Store 对象:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&computed: {
projects () {
return this.$store.state.projects
&/code&&/pre&&/div&&p&mapState 是 Vuex 提供的简化数据访问的辅助函数。我们视线回到 project.vue 容器组件,在该组件中调用this.$store.dispatch('LOAD_PROJECT_LIST)来触发从服务端中加载项目列表:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&template lang=&html&&
&div id=&projects&&
&div class=&columns&&
&div class=&column is-half&&
&div class=&notification&&
Project List
&project-list /&
&/template&
import projectList from '../components/projectList'
export default {
name: 'projects',
components: {
projectList
mounted: function () {
this.$store.dispatch('LOAD_PROJECT_LIST')
&/code&&/pre&&/div&&p&当我们启动应用时,Vuex 状态管理容器会自动在数据获取之后渲染整个项目列表。现在我们需要添加新的 Action 与 Mutation 来创建新的项目:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// under actions:
ADD_NEW_PROJECT: function ({ commit }) {
axios.post('/secured/projects').then((response) =& {
commit('ADD_PROJECT', { project: response.data })
}, (err) =& {
console.log(err)
// under mutations:
ADD_PROJECT: (state, { project }) =& {
state.projects.push(project)
&/code&&/pre&&/div&&p&然后我们创建一个简单的用于添加新的项目的组件 addProject.vue:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&template lang=&html&&
&button type=&button& class=&button& @click=&addProject()&&Add New Project&/button&
&/template&
export default {
name: 'addProject',
methods: {
addProject () {
this.$store.dispatch('ADD_NEW_PROJECT')
&/code&&/pre&&/div&&p&该组件会派发某个 Action 来添加组件,我们需要将该组件引入到 projects.vue 中:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&template lang=&html&&
&div id=&projects&&
&div class=&columns&&
&div class=&column is-half&&
&div class=&notification&&
Project List
&project-list /&
&add-project /&
&/template&
import projectList from '../components/projectList'
import addProject from '../components/addProject'
export default {
name: 'projects',
components: {
projectList,
addProject
mounted: function () {
this.$store.dispatch('LOAD_PROJECT_LIST')
&/code&&/pre&&/div&&p&重新运行下该应用会看到服务端返回的创建成功的提示,现在我们添加另一个功能,就是允许用户将某个项目设置为已完成。我们现在添加新的组件 completeToggle.vue:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&template lang=&html&&
&button type=&button& class=&button&
@click=&toggle(item)&&
&i class=&fa fa-undo& v-if=&item.completed&&&/i&
&i class=&fa fa-check-circle& v-else&&/i&
&/template&
export default {
name: 'completeToggle',
props: ['item'],
methods: {
toggle (item) {
this.$store.dispatch('TOGGLE_COMPLETED', { item: item })
&/code&&/pre&&/div&&p&该组件会展示一个用于切换项目是否完成的按钮,我们通过 Props 传入具体的项目信息然后通过触发 TOGGLE_COMPLETED Action 来使服务端进行相对应的更新与相应:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// actions
TOGGLE_COMPLETED: function ({ commit, state }, { item }) {
axios.put('/secured/projects/' + item.id, item).then((response) =& {
commit('UPDATE_PROJECT', { item: response.data })
}, (err) =& {
console.log(err)
// mutations
UPDATE_PROJECT: (state, { item }) =& {
let idx = state.projects.map(p =& p.id).indexOf(item.id)
state.projects.splice(idx, 1, item)
&/code&&/pre&&/div&&p&UPDATE_PROJECT 会触发项目列表移除对应的项目并且将服务端返回的数据重新添加到数组中:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&app.put('/secured/projects/:id', function (req, res) {
let project = data.filter(function (p) { return p.id == req.params.id })
if (project.length & 0) {
project[0].completed = !project[0].completed
res.status(201).json(project[0])
res.sendStatus(404)
&/code&&/pre&&/div&&p&最后一步就是将 completeToggle 组件引入到 projectList 组件中,然后将其添加到列表中:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// new column in table
&td&&complete-toggle :item=&item& /&&/td&
// be sure import and add to the components object
&/code&&/pre&&/div&&h1&再谈引入状态管理的意义&/h1&&p&现在我们的应用已经具备了基本的特性,这里我们再度回顾下文首的讨论,为什么我们需要大费周章的引入外部状态管理,将业务逻辑切分到组件外。譬如这里我们需要另一个组件来展示项目的统计信息,譬如项目的总数或者已完成项目的数目。我们肯定要避免重复地从服务端抓取数据,而是所谓的 Single Source Of Truth。这里我们添加新的 projectStatus.vue 组件来展示项目的统计信息:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&template lang=&html&&
&article class=&message&&
&div class=&message-header&&
&p&Project Status:&/p&
&div class=&message-body&&
&div class=&control&&
&span class=&tag is-info&&Number of projects: {{projectCount}}&/span&
&div class=&control&&
&span class=&tag is-success&&Completed: {{completedProjects}}&/span&
&/article&
&/template&
import { mapGetters } from 'vuex'
export default {
name: 'projectStatus',
computed: {
...mapGetters([
'completedProjects',
'projectCount'
&/code&&/pre&&/div&&p&该组件会展示项目的总数与已完成项目的总数,上面我们使用了maoGetters辅助函数来减少冗余代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&getters: {
completedProjects: state =& {
return state.projects.filter(project =&project.completed).length
projectCount: state =& {
return state.projects.length
&/code&&/pre&&/div&&p&最后我们将该统计信息添加到项目列表中,效果图示如下:&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-276a3bc29b68f766daa828ef_b.jpg& data-rawwidth=&480& data-rawheight=&354& class=&origin_image zh-lightbox-thumb& width=&480& data-original=&https://pic4.zhimg.com/v2-276a3bc29b68f766daa828ef_r.jpg&&&/figure&
翻译自,承接自,从属于。随着应用复杂度的增加,我们需要考虑如何进行应用的状态管理,将业务逻辑与界面交互相剥离,详细讨论参考笔者的
&figure&&img src=&https://pic1.zhimg.com/v2-0feff0e84adb80c739aa3e_b.jpg& data-rawwidth=&775& data-rawheight=&503& class=&origin_image zh-lightbox-thumb& width=&775& data-original=&https://pic1.zhimg.com/v2-0feff0e84adb80c739aa3e_r.jpg&&&/figure&&h2&前言&/h2&&p&上周有幸和淘宝前端团队的七念老师做了一些NodeJS方面上的交流(实际情况其实是他电话面试了我╮(╯-╰)╭),我们主要聊到了我参与维护的一个线上NodeJS服务,关于它的现状和当下的不足。他向我提出的一些问题带给了我很大启发,尽管回答的不是很好。问题大意是,对于你意识到的这些不足,你将尝试怎样去改进它们?甚至,如果给你一个机会来重新设计这个系统服务,你将如何做?相比现在有什么的改进?&/p&&p&为什么说这些问题对我产生了启发,是因为这些问题是我不曾考虑过的。或者说考虑过,但没有这么严肃的考虑过。这里的“严肃”指的是具体到线上,细节,容灾容错等方面。而在电话之后我重新尝试回答这些问题的过程中又收获了不少新的知识。&/p&&p&这篇文章与以往的文章不同,并不是阐述某一个问题的最佳解决方案,也不会落实到具体的代码上。而是分享在探寻答案过程中收获的心得、留下的困惑还有一点个人的经验。至于这些能否拿来回答最初的那些问题我没有十足的把握,也许能,但肯定不是最佳答案。因为后端架构实在一个很有深度的话题,也是一个极其成熟的技术方向。即使有了理论方面的积累,面对千变万化的业务需求难免还是灵活的对方案进行改进,而无论是理论还是实践经验都是我欠缺的。&/p&&p&这段话本来应该是写在结尾,感觉顺嘴也就挂在了开头。&/p&&p&最后,本文的部分内容和图片参考自图书Node.js design patterns的第七章内容Scalability and Architectural Patterns。其实书中该章中的大部分内容也并非原创,但是它做了很好的汇总和迁移,具体我会在之后说明。所以如有雷同,不是巧合。&/p&&h2&正文&/h2&&p&一个怎样的后端服务才能算得上优秀?或者放低身段说合格?再把这个问题翻译翻译,优秀或者合格的标准是什么?&/p&&p&假设现在需要你用NodeJS搭建一个http服务,我猜测你会借助express框架用不到10行的代码完成这项工作。不能说这么做是错的,但这样简易的程序是脆弱的:一旦部署上线之后,可能瞬间就被大量涌入的请求击垮,更不要提各种潜在的漏洞危险。退一步说,即使线上程序经过了这一关考验,如果你要更新程序怎么办?不得不让用户中断访问一段时间?&/p&&p&在我看来,后端服务务必要满足两条特性:&/p&&ul&&li&能容错(Fault tolerant)&/li&&li&可扩展(Scalability)&/li&&/ul&&p&当然还有一些其他特性也很重要,比如程序要健壮,接口设计要友好,程序修改起来要灵活等等。但容错性和拓展性才是正常运行的基本保障,至少保证了你的服务是可用的,永远是可用的。而无论实现服务的代码如何优雅,它都是为业务服务的,一旦用户无法访问你的服务了,再优美的代码也无济于事。所以接下来的问题就是,我们后端程序的架构如何的设计以保证满足这两条特性呢?&/p&&p&首先我们说说拓展性(Scalability)。&/p&&p&按照书中的说法,拓展性划分为三类,如下图所示:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-0feff0e84adb80c739aa3e_b.jpg& data-rawwidth=&775& data-rawheight=&503& class=&origin_image zh-lightbox-thumb& width=&775& data-original=&https://pic1.zhimg.com/v2-0feff0e84adb80c739aa3e_r.jpg&&&/figure&&br&&ul&&li&X轴方向:纯粹的对服务实例进行拓展,例如为了响应更多的请求&/li&&li&y轴方向:为服务添加新的功能,功能性拓展&/li&&li&z轴方向:按照业务数据对服务进行拓展(这里没搞懂,不知道这么说是否准确)&/li&&/ul&&p&而通常实际的拓展过程中多维度是同时进行的,例如增添了新的功能也就意味着有跟多的流量进入,也就是意味着需要增加新的服务实例。&/p&&h3&实例拓展&/h3&&p&我们先谈第一类X轴拓展,增加服务的实例。增加服务实例也分为两类,横向拓展(horizontal scaling)和纵向拓展(vertical scaling),横向表示利用更多的机器,纵向表示在同一台机器上挖掘它的潜力。但其实横向和纵向两者解决问题的思路的差异并不大。&/p&&p&从小到大,先说纵向拓展。&/p&&p&我们都知道NodeJS程序是以单进程形式运行,32位机器上最多也只有1GB内存的实用权限(在64位机器上最大的内存权限扩大到1.7GB)。而目前绝大部分线上服务器的CPU都是多核并且至少16GB起,如此以来Node程序便无法充分发挥机器的潜力。同时NodeJS自己也意识到了这一点,所以它允许程序创建多个子进程用于运行多个实例。&/p&&p&具体技术细节涉及到Cluster模块,详情可以查看NodeJS相关文档: &a href=&https://link.zhihu.com/?target=https%3A//nodejs.org/api/cluster.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Node.js v7.5.0 Documentation&/a&&/p&&p&下图就是对以上所说多进程模式原理的图解:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-bf1dffb664_b.jpg& data-rawwidth=&846& data-rawheight=&533& class=&origin_image zh-lightbox-thumb& width=&846& data-original=&https://pic3.zhimg.com/v2-bf1dffb664_r.jpg&&&/figure&&br&&p&简单来说,首先我们有一个主进程master,但master主进程并不实际的处理业务逻辑,但除了业务逻辑以外事情它都做:它是manager,负责启动子进程,管理子进程(如果子进程挂了要及时重启),它也扮演router,也就是对该程序的访问请求首先到达主进程,再由主进程分配请求给子进程worker。而子进程才负责处理业务逻辑。&/p&&p&在这个机制下有两条细节需要我们定夺如何处理。&/p&&p&如何把外界的请求平均的分配给不同的worker处理?这里的平均不是指数量上的平均(因为单条请求处理的工作量可能不同),而是既不能让某个子进程太闲,也不能让某个子进程太忙,保证它们始终处于工作的状态即可。这也是我们常说的负载均衡(load-balancing)。 默认情况下Cluster模块采用的是round robin负载均衡算法,说白了就是依次按顺序把请求派给列表上的子进程,派到结尾之后又重头开始。&/p&&p&这个算法只能保证每个子进程收到的请求个数是平均的,和随机算法类似。但如果某个子进程遇到问题,处理变得迟缓了,而后续的请求又源源不断的分配过来,那么这个子进程的压力就大了,这就略显不公了。除此之外我们还要考虑到超时,重做等机制的建立。所以主进程master作为路由时不仅仅是转发请求,还要能智能的分配请求。&/p&&p&另一个问题是状态共享问题,假如某个用户第一次访问该服务时是分配给了线程A上的实例A处理,并且用户在这个实例上进行了登陆,而没有过几秒钟之后当用户第二次访问时分配给了线程B上的实例B处理,如果此时用户在A上的登陆状态没有共享给其他实例的话,那么用户不得不重新登陆一次,这样的用户体验是无法接受的。如下图所示&/p&&figure&&img src=&https://pic2.zhimg.com/v2-cf6f1a826e5dde15dd97_b.jpg& data-rawwidth=&980& data-rawheight=&589& class=&origin_image zh-lightbox-thumb& width=&980& data-original=&https://pic2.zhimg.com/v2-cf6f1a826e5dde15dd97_r.jpg&&&/figure&&br&&p&这个问题的解决办法是把状态进行共享:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-3bbffde84453aaca6aeaaf4eff735be5_b.jpg& data-rawwidth=&1056& data-rawheight=&634& class=&origin_image zh-lightbox-thumb& width=&1056& data-original=&https://pic1.zhimg.com/v2-3bbffde84453aaca6aeaaf4eff735be5_r.jpg&&&/figure&&br&&p&也可以新增一个模块用于记录用户第一次访问的实例,并在之后当用户访问服务时始终访问该实例&/p&&figure&&img src=&https://pic4.zhimg.com/v2-52f70d0fd3d50e29afcda7_b.jpg& data-rawwidth=&1018& data-rawheight=&815& class=&origin_image zh-lightbox-thumb& width=&1018& data-original=&https://pic4.zhimg.com/v2-52f70d0fd3d50e29afcda7_r.jpg&&&/figure&&br&&p&主进程-子进程的模式思路不仅适用于纵向拓展,还适用于横向拓展。当单台机器已经无法满足你需求的时候,你可以把单实例子进程的概念拓展为单台机器:我们将在多台机器上部署多个进行实例,用户的访问请求也并非直接到达它们,而是先到达前方的代理机器,它也是负责负载均衡的机器,负责将请求转发给部署了应用实例的机器。这样的模式我们也通常称为反向代理模式:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-0a65b6feeeb4_b.jpg& data-rawwidth=&669& data-rawheight=&424& class=&origin_image zh-lightbox-thumb& width=&669& data-original=&https://pic4.zhimg.com/v2-0a65b6feeeb4_r.jpg&&&/figure&&br&&br&&p&我们仍然能对这个模式持续改进,例如动态的启动或者关闭机器上的实例用于节省资源,甚至想办法移除负载平衡这一环节用于提高通讯的效率。在这里就不延伸开了去了,具体可以参考Node.js design patterns这本书中的内容。&/p&&p&&strong&最后在这里要说一件很重要的事情&/strong&。上面说的负载平衡也好,反向代理也好,都不是新的技术。相反,都是非常非常成熟,有着相当多经验积累的技术。然而为什么我们接触起来却感觉如此的新鲜和陌生?我想原因大概是NodeJS程序员大多是由前端工程师转化而来,而大家此前都只专注于前端代码而很少接触后端知识。然而如果你从入行开始就是一个Java程序员或者运维工程师,相信你对这一切早就耳熟能详并且手到擒来。&/p&&p&几年前看到过一篇文章,(很可惜现在找不到了,如果有哪位同学知道篇文章的麻烦告知一下谢谢),记录的是一位技术人员针对网站访问量增大而做的一系列技术改进。文章的后半部分我记不得了,但是前半部分遇到的问题和改进的思路和我们是一模一样的:请求骤增,增加实例机器和解决session共享问题。&strong&我想说的是,虽然NodeJS是新技术,但是我们解决问题的思路和方案可以来自传统软件行业,并且它们在这方面比我们有经验的多。所以我们在学习NodeJS,在寻找一些问题的解决方案时,不要局限于NodeJS本身,而是应该开阔眼界,跨语言包容的去汲取知识。&/strong&&/p&&h3&功能拓展&/h3&&p&你也许会问新增功能有什么难点?每个程序员的日常就是不断的进行功能迭代。但在这里我们希望解决一个问题,就是既然我们无法保证功能不会出错,那我们有没有办法保证当一个功能出错之后不会影响整个程序的正常运行?这也是我们所说的容错性。&/p&&p&道理都懂,我们都明白程序需要容错,所以try/catch是从编码上解决这个问题。但问题是try/catch不是万能的,万无一失的程序也是不存在的,所以我们要换个思路解决这个问题,我们允许程序出错,但是要及时把错误隔离,并且不再影响程序的运行。这个就要从架构上解决这个问题。例如使用微服务(Microservices)架构。&/p&&p&在介绍微服务架构之前,我们要了解其它架构为什么没法满足我们的要求。例如我们常用的单体(monolithic)架构。单体架构这个词你可能不熟悉,但几乎我们每天都在和它打交道,大部分的后端服务都归属于单体架构,对它的解释我翻译Martin Fowler的描述:&/p&&blockquote&&p&企业级应用通常分为三个部分:用户界面(包含运行在用户浏览器上的html页面和javascript脚本),数据库(通常是包含许多表的关系数据库),和服务端应用。服务端应用将会处理http请求,执行业务逻辑,从数据库中取得数据,生成html视图返回给浏览器。这样的服务端应用就被称为单体(monolith)——单个具有逻辑性的执行过程。任何针对系统的修改都会导致重新构建和部署一个新版本的服务端应用。&/p&&/blockquote&&p&(注:以上这段描述摘自Martin Fowler的文章&a href=&https://link.zhihu.com/?target=https%3A//martinfowler.com/articles/microservices.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Microservices&/a&,我认为这是对微架构描述最全面的文章,如果想对这一小节做更深入的了解可以把这篇文章细读。 这也是我读到的Martin Fowler所写的文章中最通俗的文章。个人认为Martin Fowler的文章读起来比较晦涩,John Resig紧随其后)&/p&&p&单体架构是一种很自然的搭建应用的方式,它符合我们对业务处理流程的认知。但单体应用也存在问题:任何一处,无论大小的修改都会导致整个应用被重新构建和重新部署。随着应用规模和复杂性的不断增大,参与维护的人数增多,每一轮迭代修改的模块增多,对上线来说是极大的考验,对于内部单个模块的拓展也是极为不利的。例如当图片压缩请求剧增时,需要新增图片压缩模块的实例,但实际上不得不扩展整个单体应用的实例。&/p&&p&微服务架构解决的就是这一系列问题。顾名思义,微服务架构下软件是由多个独立的服务组成。这些服务相互独立互不干预。以拆分上面所说的单体应用为例,我们可以把处理HTTP请求的模块和负责数据库读写的模块分离出来成为独立的服务,这两个模块从功能上看是没有任何交集。这样的好处就是,我们可以独立的部署,拓展,修改这些服务。例如应用需要添加新的接口时,我们只需要修改处理HTTP请求的服务,只公开这部分代码给修改者,只上线这部分服务,拓展时也只需要新添这部分服务的实例。&/p&&p&微服务和我们通常编写的模块(以文件为单位,以命名空间为单位)相比更加独立,更像是一个五脏俱全的“小应用”,如果你读完了我之前推荐的Martin Fowler关于微服务的文章的话,你会对这点更深有感触:微服务除了在运维上独立以外,它还可以拥有独立的数据库,还应该配备独立的团队维护。它甚至可以允许使用其他的语言进行开发,只要对外接口正常即可。&/p&&p&当然微服务也存在不足,例如如何将诸多的微服务在大型架构中组织起来,如何提高不同服务之间的通信效率都是需要在实际工作中解决的问题。&/p&&p&微服务说到底还是解耦思想的实践。从这个意义上来说,React下的Flux架构某种意义上也属于微服务。如果你了解Flux的起源的话,Flux架构其实来源于后端的CQRS,即Command Query Responsibility Segregation,命令与查询职责分离,也就是将数据的读操作和写操作分离开。这么设计的理由有很多,举例说一点:在许多业务场景中,数据的读和写的次数是不平衡,可能上千次的读操作才对应一次写操作,比如机票余票信息的查询和更新。所以把读和写操作分开能够有针对性的分别优化它们。例如提高程序的scalability,scalability意味着我们能够在部署程序时,给读操作和写操作部署不同数量的线上实例来满足实际的需求。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-669a481ea9e36d19917cb4_b.jpg& data-rawwidth=&800& data-rawheight=&584& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic3.zhimg.com/v2-669a481ea9e36d19917cb4_r.jpg&&&/figure&&br&&br&&p&如果你也有Unity编程经验的话会对解耦更有感触,在Unity中我们已经不能称之为解耦,而是自治,这是Unity的设计模式。举个例子,屏幕上少则可能有十几个游戏元素,例如玩家、敌人还有子弹。你必须为它们编写“死亡”的规则,“诞生”的规则,交互的规则。因为你根本无法预料玩家在何时何种位置发射出子弹,也无法预料子弹何时在什么位置碰撞上什么状态敌人。所以你只能让它们在规则下自由发挥。这和微服务有异曲同工之妙:独立,隔离,自治。&/p&&h3&总结&/h3&&p&实话实说,这篇文章里没有干货,全都是舶来品。但舶来品不是一个贬义词,它是我们学习知识和解决问题的第一手材料。我还是想重申一遍,在后端领域来说Node.js是一个新人,我们应该学习前辈的经验。借用许多年前奔驰广告的一句话:经典是对经典的继承,经典是对经典的背叛。只有站在前人的肩膀上,我们才有可能创新,看的更远。&/p&
前言上周有幸和淘宝前端团队的七念老师做了一些NodeJS方面上的交流(实际情况其实是他电话面试了我╮(╯-╰)╭),我们主要聊到了我参与维护的一个线上NodeJS服务,关于它的现状和当下的不足。他向我提出的一些问题带给了我很大启发,尽管回答的不是很好。…
&blockquote&注:2.0 已经有&a href=&https://link.zhihu.com/?target=http%3A//cn.vuejs.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&中文文档&/a& 。如果对自己英文有信心,也可以直接阅读&a href=&https://link.zhihu.com/?target=https%3A//vuejs.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&英文文档&/a&。&br&此指南仅供参考,请根据自身实际情况灵活调整。&br&欢迎转载,请注明出处。&/blockquote&&h2&起步&/h2&&p&1. 扎实的 JavaScript / HTML / CSS 基本功。这是前置条件。&br&&/p&&p&2. 通读官方教程 (guide) 的基础篇。不要用任何构建工具,就只用最简单的 &script&,把教程里的例子模仿一遍,理解用法。&b&不推荐上来就直接用 vue-cli 构建项目,尤其是如果没有 Node/Webpack 基础。&/b&&/p&&p&3. 照着官网上的示例,自己想一些类似的例子,模仿着实现来练手,加深理解。&/p&&p&4. 阅读官方教程进阶篇的前半部分,到『自定义指令 (Custom Directive) 』为止。着重理解 Vue 的响应式机制和组件生命周期。『渲染函数(Render Function)』如果理解吃力可以先跳过。&/p&&p&5. 阅读教程里关于路由和状态管理的章节,然后根据需要学习 vue-router 和 vuex。同样的,先不要管构建工具,以跟着文档里的例子理解用法为主。&br&&/p&&p&&br&&/p&&p&6. 走完基础文档后,如果你对于基于 Node 的前端工程化不熟悉,就需要补课了。下面这些严格来说并不是 Vue 本身的内容,也不涵盖所有的前端工程化知识,但对于大型的 Vue 工程是前置条件,也是合格的『前端工程师』应当具备的知识。&/p&&h2&前端生态/工程化&/h2&&p&1. 了解 JavaScript 背后的规范,ECMAScript 的历史和目前的规范制定方式。学习 ES2015/16 的新特性,理解 ES2015 modules,适当关注&a href=&https://link.zhihu.com/?target=https%3A//github.com/tc39/proposals& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&还未成为标准的提案&/a&。&/p&&p&2. 学习命令行的使用。建议用 Mac。&/p&&p&3. 学习 Node.js 基础。&b&建议使用 &a href=&https://link.zhihu.com/?target=https%3A//github.com/creationix/nvm& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&nvm&/a& 这样的工具来管理机器上的 Node 版本,并且将 npm 的 registry 注册表配置为&a href=&https://link.zhihu.com/?target=https%3A//npm.taobao.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&淘宝的镜像源&/a&。&/b&至少要了解 npm 的常用命令,npm scripts 如何使用,语义化版本号规则,CommonJS 模块规范(了解它和 ES2015 Modules 的异同),Node 包的解析规则,以及 Node 的常用 API。应当做到可以自己写一些基本的命令行程序。注意最新版本的 Node (6+) 已经支持绝大部分 ES2015 的特性,可以借此巩固 ES2015。&/p&&p&4. 了解如何使用 / 配置 Babel 来将 ES2015 编译到 ES5 用于浏览器环境。&/p&&p&5. 学习 Webpack。Webpack 是一个极其强大同时也复杂的工具,作为起步,理解它的『一切皆模块』的思想,并基本了解其常用配置选项和 loader 的概念/使用方法即可,比如如何搭配 Webpack 使用 Babel。学习 Webpack 的一个挑战在于其本身文档的混乱,建议多搜索搜索,应该还是有质量不错的第三方教程的。英文好的建议阅读 &a href=&https://link.zhihu.com/?target=https%3A//webpack.js.org/get-started/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Webpack 2.0 的文档&/a&,比起 1.0 有极大的改善,但需要注意&a href=&https://link.zhihu.com/?target=https%3A//webpack.js.org/how-to/upgrade-from-webpack-1/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&和 1.0 的不兼容之处&/a&。&/p&&h2&Vue 进阶&/h2&&p&1. 有了 Node 和 Webpack 的基础,可以通过 vue-cli 来搭建基于 Webpack ,并且支持单文件组件的项目了。建议用 webpack-simple 这个模板开始,并阅读官方教程进阶篇剩余的内容以及 &a href=&https://link.zhihu.com/?target=http%3A//vue-loader.vuejs.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&vue-loader 的文档&/a&,了解一些进阶配置。有兴趣的可以自己亲手从零开始搭一个项目加深理解。&/p&&p&2. 根据 &a href=&https://link.zhihu.com/?target=https%3A//github.com/vuejs/vue-hackernews-2.0& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&例子&/a& 尝试在 Webpack 模板基础上整合 vue-router 和 vuex&/p&&p&&br&&/p&&p&3. 深入理解 Virtual DOM 和『渲染函数 (Render Functions)』这一章节(可选择性使用 JSX),理解模板和渲染函数之间的对应关系,了解其使用方法和适用场景。&/p&&p&4. (可选)根据需求,了解服务端渲染的使用(需要配合 Node 服务器开发的知识)。其实更重要的是理解它所解决的问题并搞清楚你是否需要它。&/p&&p&&br&&/p&&p&5. 阅读开源的 Vue 应用、组件、插件源码,自己尝试编写开源的 Vue 组件、插件。&/p&&p&6. 参考 &a href=&https://link.zhihu.com/?target=https%3A//github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md%23development-setup& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&贡献指南&/a& 阅读 Vue 的源码,理解内部实现细节。(需要了解 &a href=&https://link.zhihu.com/?target=https%3A//flowtype.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Flow&/a&)&/p&&p&7. 参与 Vue GitHub issue 的定位 -& 贡献 PR -& 加入核心团队 -& 升任 CTO -& 迎娶白富美...(误&/p&
注:2.0 已经有 。如果对自己英文有信心,也可以直接阅读。 此指南仅供参考,请根据自身实际情况灵活调整。 欢迎转载,请注明出处。起步1. 扎实的 JavaScript / HTML / CSS 基本功。这是前置条件。 2. 通读官方教程 (guide) 的基础篇。不要…
&figure&&img src=&https://pic2.zhimg.com/v2-0e892f7f9a0ad46130f9e_b.jpg& data-rawwidth=&1358& data-rawheight=&826& class=&origin_image zh-lightbox-thumb& width=&1358& data-original=&https://pic2.zhimg.com/v2-0e892f7f9a0ad46130f9e_r.jpg&&&/figure&&blockquote&&p&原文链接:&a href=&https://link.zhihu.com/?target=https%3A//medium.freecodecamp.com/javascript-modules-part-2-module-bundling-6& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&JavaScript Modules Part 2: Module Bundling&/a&&/p&&p&作者:&a href=&https://link.zhihu.com/?target=https%3A//medium.freecodecamp.com/%40preethikasireddy& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Preethi Kasireddy&/a&&/p&&/blockquote&&p&在&a href=&http://zhuanlan.zhihu.com/p/& class=&internal&&上一篇教程&/a&里我们已经讨论过什么是模块,为什么要使用模块以及多种实现模块化的方式。&/p&&p&这次,我们会聊一聊什么是模块打包,为什么要打包模块,模块打包的方式工具,还有它当前在Web开发中的运用。&/p&&h2&什么是模块打包?&/h2&&p&粗俗一点来讲,模块打包就是把一小坨一小坨的代码粘成一大坨。&/p&&p&实际操作起来的时候当然还需要关注一些细节。&/p&&h2&为什么要打包模块?&/h2&&p&一般来讲,我们用模块化组织代码的时候,都会把模块划分在不同的文件和文件夹里,也可能会包含一些诸如React和Underscore一类的第三方库。&/p&&p&而后,所有的这些模块都需要通过&script&标签引入到你的HTML文件中,然后用户在访问你网页的时候它才能正常显示和工作。每个独立的&script&标签都意味着,它们要被浏览器分别一个个地加载。&/p&&p&这就有可能导致页面载入时间过长。&/p&&p&为了解决这个问题,我们就需要进行模块打包,把所有的模块合并到一个或几个文件中,以此来减少HTTP请求数。这也可以被称作是从开发到上线前的构建环节。&/p&&p&还有一种提升加载速度的做法叫做代码压缩(混淆)。其实就是去除代码中不必要的空格、注释、换行符一类的字符,来保证在不影响代码正常工作的情况下压缩其体积。&/p&&p&更小的文件体积也就意味着更短的加载时间。要是你仔细对比过带有 .min后缀的例如 jquery.min.js和jquery.js的话,应该会发现压缩版的文件相较之下要小很多。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-60a0bef5aebbbcf32c0d80e_b.jpg& data-rawwidth=&340& data-rawheight=&77& class=&content_image& width=&340&&&/figure&&br&&p&Gulp和Grunt一类的构建工具可以很方便地解决上述的需求,在开发的时候通过模块来组织代码,上线时再合并压缩提供给浏览器。&/p&&h2&打包模块的方法有哪些?&/h2&&p&如果你的代码是通过之前介绍过的模块模式来组织的,合并和压缩它们其实就只是把一些原生的JS代码合在一起而已。&/p&&p&但如果你使用的是一些浏览器原生不支持的模块系统(例如CommonJS 或 AMD,以及ES6 模块的支持现在也不完整),你就需要使用一些专门的构建工具来把它们转换成浏览器支持的代码。这类工具就是我们最近经常听说的Browserify, RequireJS, Webpack等等模块化构建、模块化加载工具了。&/p&&p&为了实现模块化构建或载入的功能,这类工具提供许多诸如在你改动源代码后自动重新构建(文件监听)等一系列的功能。&/p&&p&下面我们就一起来看一些实际的例子吧:&/p&&h2&打包 CommonJS&/h2&&p&在上一篇教程中我们了解到, CommonJS是同步载入模块的,这对浏览器来说不是很理想。其实下面介绍的模块化构建工具Browserify在上一篇也提到过。它是一个专门用来打包CommonJS模块以便在浏览器里运行的构建工具。&/p&&p&举个例子,假如你在 main.js 文件中引入了一个用来计算平均数的功能模块:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&myDependency&/span& &span class=&o&&=&/span& &span class=&nx&&require&/span&&span class=&p&&(&/span&&span class=&s1&&'myDependency'&/span&&span class=&p&&);&/span&
&span class=&kd&&var&/span& &span class=&nx&&myGrades&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&mi&&93&/span&&span class=&p&&,&/span& &span class=&mi&&95&/span&&span class=&p&&,&/span& &span class=&mi&&88&/span&&span class=&p&&,&/span& &span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&mi&&91&/span&&span class=&p&&];&/span&
&span class=&kd&&var&/span& &span class=&nx&&myAverageGrade&/span& &span class=&o&&=&/span& &span class=&nx&&myDependency&/span&&span class=&p&&.&/span&&span class=&nx&&average&/span&&span class=&p&&(&/span&&span class=&nx&&myGrades&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&在这个示例中,我们只有一个名为 myDependency 的模块依赖。通过下面的命令,Browserify会依次把main.js里引入的所有模块一同打包到一个名为 bundle.js 的文件里:&/p&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&browserify main.js -o bundle.js
&/code&&/pre&&/div&&p&Browserify 首先会通过抽象语法树(&a href=&https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Abstract_syntax_tree& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AST&/a&)来解析代码中的每一个 require 语句,在分析完所有模块的依赖和结构之后,就会把所有的代码合并到一个文件中。然后你在HTML文件里引入一个bundle.js就够啦。&/p&&p&多个文件和多个依赖也只需要再稍微配置一下就能正常工作了。&/p&&p&之后你也可以使用一些例如Minify-JS的工具来压缩代码。&/p&&h2&打包 AMD&/h2&&p&假若你使用的是AMD,你会需要一些例如RequireJS 或 Curl的AMD加载器。模块化加载工具可以在你的应用中按需加载模块代码。&/p&&p&需要再次提醒一下,AMD 和 CommonJS 的最主要区别是AMD是异步加载模块的。这也就意味着你不是必须把所有的代码打包到一个文件里,模块加载不影响后续语句执行,逐步加载的的模块也不会导致页面阻塞无法响应。&/p&&p&不过在实际应用中,为了避免用户过多的请求对服务器造成压力。大多数的开发者还是选择用RequireJS optimizer, &a href=&https://link.zhihu.com/?target=http%3A//requirejs.org/docs/optimization.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&r.js&/a&一类的构建工具来合并和压缩AMD的模块。&/p&&p&总的来说,AMD 和 CommonJS 在构建中最大的区别是,在开发过程中,采用AMD的应用直到正式上线发布之前都不需要构建。&/p&&p&要是你对CommonJS vs. AMD的讨论感兴趣,可以看这一篇&a href=&https://link.zhihu.com/?target=http%3A//tomdale.net/2012/01/amd-is-not-the-answer/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AMD is Not the Answer&/a&&/p&&h2&Webpack&/h2&&p&Webpack 是新推出的构建工具里最受欢迎的。它兼容CommonJS, AMD, ES6各类规范。&/p&&p&也许你会质疑,我们已经有这么多诸如Browserify 或 RequireJS 的工具了,为什么还需要 Webpack 呢?究其原因之一,Webpack 提供许多例如 code splitting(代码分割) 的有用功能,它可以把你的代码分割成一个个的 chunk 然后按需加载优化性能。&/p&&p&举个例子,要是你的Web应用中的一些代码只在很少的情况下才会被用到,把它们全都打包到一个文件里是很低效的做法。所以我们就需要 code splitting 这样的功能来实现按需加载。而不是把那些很少人才会用到的代码一股脑儿全都下载到客户端去。&/p&&p&code splitting 只是 Webpack 提供的众多强大功能之一。当然,网上也为这些模块化构建工具吵得不可开交。你要是感兴趣的话也可以在下面这些地方观摩一下:&/p&&ul&&li&&a href=&https://link.zhihu.com/?target=https%3A//gist.github.com/substack/68f8d502be42d5cd4942& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&gist.github.com/substac&/span&&span class=&invisible&&k/68f8d502be42d5cd4942&/span&&span class=&ellipsis&&&/span&&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=http%3A//mattdesl.svbtle.com/browserify-vs-webpack& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Browserify vs. Webpack&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=http%3A//blog.namangoel.com/browserify-vs-webpack-js-drama& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Browserify VS Webpack&/a&&/li&&/ul&&h2&ES6 模块&/h2&&p&看他们吵够了的话,接下来我就要介绍一下ES6模块了。假如你采用ES6模块,在不远的将来对那些构建工具的需求可能会小一些。首先我们还是看看ES6模块是怎么加载的吧。&/p&&p&ES6模块和CommonJS, AMD一类规范最主要的区别是,当你载入一个模块时,载入的操作实际实在编译时执行的——也就是在代码执行之前。所以去掉那些不必要的exports导出语句可以优化我们应用的性能。&/p&&p&有一个经常会被问到的问题:去除exports和冗余代码消除(UglifyJS一类工具执行后的效果)之间有什么区别?&/p&&p&答案是这个要具体情况具体分析,感兴趣的话可以上Github看这个Repo:&a href=&https://link.zhihu.com/?target=https%3A//github.com/rollup/rollup& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Rollup’s wiki&/a&&/p&&p&让ES6模块与冗余代码消除(Dead code elimination)不同的是一种叫做tree shaking的技术。Tree shaking其实恰好是冗余代码消除的反向操作。它只加载你需要调用的代码,而不是删掉不会被执行的代码。我们还是用一个具体的例子说明吧:&/p&&p&假设我们有如下一个使用ES6语法,名为 utils.js 的函数:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&export&/span& &span class=&kd&&function&/span& &span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&iterator&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nb&&Array&/span&&span class=&p&&.&/span&&span class=&nx&&isArray&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span& &span class=&o&&&&/span& &span class=&nx&&collection&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span&&span class=&o&&++&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&[&/span&&span class=&nx&&i&/span&&span class=&p&&],&/span& &span class=&nx&&i&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&key&/span& &span class=&k&&in&/span& &span class=&nx&&collection&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&[&/span&&span class=&nx&&key&/span&&span class=&p&&],&/span& &span class=&nx&&key&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&kr&&export&/span& &span class=&kd&&function&/span& &span class=&nx&&filter&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&test&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&filtered&/span& &span class=&o&&=&/span& &span class=&p&&[];&/span&
&span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&test&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&nx&&filtered&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&});&/span&
&span class=&k&&return&/span& &span class=&nx&&filtered&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&kr&&export&/span& &span class=&kd&&function&/span& &span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&iterator&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&mapped&/span& &span class=&o&&=&/span& &span class=&p&&[];&/span&
&span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&value&/span&&span class=&p&&,&/span& &span class=&nx&&key&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&mapped&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&value&/span&&span class=&p&&));&/span&
&span class=&p&&});&/span&
&span class=&k&&return&/span& &span class=&nx&&mapped&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&kr&&export&/span& &span class=&kd&&function&/span& &span class=&nx&&reduce&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&iterator&/span&&span class=&p&&,&/span& &span class=&nx&&accumulator&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&startingValueMissing&/span& &span class=&o&&=&/span& &span class=&nx&&accumulator&/span& &span class=&o&&===&/span& &span class=&kc&&undefined&/span&&span class=&p&&;&/span&
&span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&nx&&startingValueMissing&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&accumulator&/span& &span class=&o&&=&/span& &span class=&nx&&item&/span&&span class=&p&&;&/span&
&span class=&nx&&startingValueMissing&/span& &span class=&o&&=&/span& &span class=&kc&&false&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&nx&&accumulator&/span& &span class=&o&&=&/span& &span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&accumulator&/span&&span class=&p&&,&/span& &span class=&nx&&item&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&});&/span&
&span class=&k&&return&/span& &span class=&nx&&accumulator&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&现在我们也不清楚到底需要这个函数的哪些功能,所以先全部引入到 main.js 中:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&//main.js&/span&
&span class=&kr&&import&/span& &span class=&o&&*&/span& &span class=&nx&&as&/span& &span class=&nx&&Utils&/span& &span class=&nx&&from&/span& &span class=&s1&&'./utils.js'&/span&&span class=&p&&;&/span&
&/code&&/pre&&/div&&p&之后我们再调用一下 each 函数:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&//main.js&/span&
&span class=&kr&&import&/span& &span class=&o&&*&/span& &span class=&nx&&as&/span& &span class=&nx&&Utils&/span& &span class=&nx&&from&/span& &span class=&s1&&'./utils.js'&/span&&span class=&p&&;&/span&
&span class=&nx&&Utils&/span&&span class=&p&&.&/span&&span class=&nx&&each&/span&&span class=&p&&([&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&,&/span& &span class=&mi&&3&/span&&span class=&p&&],&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&x&/span&&span class=&p&&)&/span& &span class=&p&&{&/span& &span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&x&/span&&span class=&p&&)&/span& &span class=&p&&});&/span&
&/code&&/pre&&/div&&p&通过 &tree shaken& 之后的 main.js 看起来就像下面这样:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//treeshake.js
function each(collection, iterator) {
if (Array.isArray(collection)) {
for (var i = 0; i & collection. i++) {
iterator(collection[i], i, collection);
for (var key in collection) {
iterator(collection[key], key, collection);
each([1, 2, 3], function(x) { console.log(x) });
&/code&&/pre&&/div&&p&注意到这里只导出了我们调用过的 each 方法。&/p&&p&再如果我们只调用 filter 方法的话:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&//main.js&/span&
&span class=&kr&&import&/span& &span class=&o&&*&/span& &span class=&nx&&as&/span& &span class=&nx&&Utils&/span& &span class=&nx&&from&/span& &span class=&s1&&'./utils.js'&/span&&span class=&p&&;&/span&
&span class=&nx&&Utils&/span&&span class=&p&&.&/span&&span class=&nx&&filter&/span&&span class=&p&&([&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&,&/span& &span class=&mi&&3&/span&&span class=&p&&],&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&x&/span&&span class=&p&&)&/span& &span class=&p&&{&/span& &span class=&k&&return&/span& &span class=&nx&&x&/span& &span class=&o&&===&/span& &span class=&mi&&2&/span& &span class=&p&&});&/span&
&/code&&/pre&&/div&&p&&Tree shaken& 之后就会变成这样:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&iterator&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nb&&Array&/span&&span class=&p&&.&/span&&span class=&nx&&isArray&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span& &span class=&o&&&&/span& &span class=&nx&&collection&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span&&span class=&o&&++&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&[&/span&&span class=&nx&&i&/span&&span class=&p&&],&/span& &span class=&nx&&i&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&key&/span& &span class=&k&&in&/span& &span class=&nx&&collection&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&[&/span&&span class=&nx&&key&/span&&span class=&p&&],&/span& &span class=&nx&&key&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&p&&};&/span&
&span class=&kd&&function&/span& &span class=&nx&&filter&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&test&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&filtered&/span& &span class=&o&&=&/span& &span class=&p&&[];&/span&
&span class=&c1&&//注意在filter中调用了each,所以两个方法都会被引入&/span&
&span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&test&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&nx&&filtered&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&});&/span&
&span class=&k&&return&/span& &span class=&nx&&filtered&/span&&span class=&p&&;&/span&
&span class=&p&&};&/span&
&span class=&nx&&filter&/span&&span class=&p&&([&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&,&/span& &span class=&mi&&3&/span&&span class=&p&&],&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&x&/span&&span class=&p&&)&/span& &span class=&p&&{&/span& &span class=&k&&return&/span& &span class=&nx&&x&/span& &span class=&o&&===&/span& &span class=&mi&&2&/span& &span class=&p&&});&/span&
&/code&&/pre&&/div&&p&很神奇不是么?&/p&&p&你也可以自己在Rollup.js的实时预览编辑器里做做试验:&a href=&https://link.zhihu.com/?target=http%3A//rollupjs.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&live demo and editor&/a&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-c15de2bbc52eff93e1cceaca_b.jpg& data-rawwidth=&939& data-rawheight=&653& class=&origin_image zh-lightbox-thumb& width=&939& data-original=&https://pic4.zhimg.com/v2-c15de2bbc52eff93e1cceaca_r.jpg&&&/figure&&h2&构建ES6模块&/h2&&p&现在我们已经了解到ES6模块载入的与众不同了,但我们还没有聊到底该怎么构建ES6模块。&/p&&p&因为浏览器对ES6模块的原生支持还不够完善,所以现阶段还需要我们做一些补充工作。&/p&&p&让ES6模块在浏览器中顺利运行的常用方法有以下几种:&/p&&p&1.使用语法编译器(Babel或Traceur)来把ES6语法的代码编译成ES5或者CommonJS, AMD, UMD等其他形式。然后再通过Browserify 或 Webpack 一类的构建工具来进行构建。&/p&&p&2.使用&a href=&https://link.zhihu.com/?target=http%3A//rollupjs.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Rollup.js&/a&,这其实和上面差不多,只是Rollup还会捎带的利用“tree shaking”技术来优化你的代码。在构建ES6模块时Rollup优于Browserify或Webpack的也正是这一点,它打包出来的文件体积会更小。Rollup也可以把你的代码转换成包括ES6, CommonJS, AMD, UMD, IIFE在内的各种格式。其中IIFE和UMD可以直接在浏览器里运行,AMD, CommonJS, ES6等还需要你通过Browserify, Webpack, RequireJS一类的工具才能在浏览器中使用。&/p&&h2&小心踩坑&/h2&&p&这里有一些坑还需要和大家说明一下。转换语法优雅的ES6代码以便在浏览器里运行并不是一件令人舒爽的事情。&/p&&p&问题在于,什么时候我们才能免去这些多余的工作。&/p&&p&令人感动的答案是:“差不多快了。”&/p&&p&ECMAScript目前包含一个名为&a href=&https://link.zhihu.com/?target=https%3A//github.com/ModuleLoader/es6-module-loader& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ECMAScript 6 module loader API&/a& 的解决方案。简单来讲,这个解决方案允许你动态加载模块并缓存。还是来举例说明:&/p&&p&&strong&myModule.js&/strong&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&export&/span& &span class=&kr&&class&/span& &span class=&nx&&myModule&/span& &span class=&p&&{&/span&
&span class=&nx&&constructor&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&s1&&'Hello, I am a module'&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&nx&&hello&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&s1&&'hello!'&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&nx&&goodbye&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&s1&&'goodbye!'&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&strong&main.js&/strong&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&System&/span&&span class=&p&&.&/span&&span class=&kr&&import&/span&&span class=&p&&(&/span&&span class=&s1&&'myModule'&/span&&span class=&p&&).&/span&&span class=&nx&&then&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&myModule&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&new&/span& &span class=&nx&&myModule&/span&&span class=&p&&.&/span&&span class=&nx&&hello&/span&&span class=&p&&();&/span&
&span class=&p&&});&/span&
&span class=&c1&&// ‘Hello!, I am a module!’&/span&
&/code&&/pre&&/div&&p&同样,你可以在script标签上设置type=module的属性来直接定义模块:&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&script&/span& &span class=&na&&type&/span&&span class=&o&&=&/span&&span class=&s&&&module&&/span&&span class=&p&&&&/span&
&span class=&c1&&// loads the 'myModule' export from 'mymodule.js'&/span&
&span class=&kr&&import&/span& &span class=&p&&{&/span& &span class=&nx&&hello&/span& &span class=&p&&}&/span& &span class=&nx&&from&/span& &span class=&s1&&'mymodule'&/span&&span class=&p&&;&/span&
&span class=&k&&new&/span& &span class=&nx&&Hello&/span&&span class=&p&&();&/span& &span class=&c1&&// 'Hello, I am a module!'&/span&
&span class=&p&&&/&/span&&span class=&nt&&script&/span&&span class=&p&&&&/span&
&/code&&/pre&&/div&&p&更加详细的介绍也可以在Github上查看:&a href=&https://link.zhihu.com/?target=https%3A//github.com/ModuleLoader/es6-module-loader& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&es6-module-loader&/a&&/p&&p&如果你现在就想测试这个解决方案的话,我在这里也安利一下 &a href=&https://link.zhihu.com/?target=https%3A//github.com/systemjs/systemjs& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&SystemJS&/a&. SystemJS支持在浏览器端和Node动态加载之前介绍过所有格式的模块(ES6 modules, AMD, CommonJS等),通过把已加载的模块还存在&module registry&里来避免重复加载。它也同样支持转换ES6的代码至其他格式。&/p&&h2&我们已经有了原生ES6模块,还需要那些乱七八糟的玩意儿么?&/h2&&p&越来越多的人使用ES6模块产生了一些有趣的影响:&/p&&p&&strong&HTTP/2 出现之后,模块化构建工具是不是都该被淘汰了?&/strong&&/p&&p&在HTTP/1中,一次TCP连接只允许一个请求,所以我们需要通过减少载入的文件数来优化性能。而HTTP/2改变了这一切,请求和响应可以并行,一次连接也允许多个请求。&/p&&p&每次请求的消耗也会远远小于HTTP/1,所以载入一堆模块就不再是一个影响性能的问题了。所以许多人认为打包模块完全就是多余的了。这听起来很合理,但我们也需要具体情况具体分析。&/p&&p&其中有一条,模块化构建解决了一些HTTP/2解决不了的问题。例如去除冗余的代码以压缩体积。要是你开发的是一个对性能要求很高的网站,模块化构建从长远上考虑会给你带来更多好处。当然,要是你不那么在意性能问题,以后完全就可以省却这些烦人的步骤了。&/p&&p&总之,我们离所有的网站都采用HTTP/2传输还有相当一段时间。短期内模块化构建还是很有必要的。&/p&&p&要是你对HTTP/2的其他特性也感兴趣,可以查阅这里:&a href=&https://link.zhihu.com/?target=https%3A//http2.github.io/faq/%23what-are-the-key-differences-to-http1x& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&HTTP/2&/a&&/p&&p&&strong&CommonJS , AMD, UMD这类标准会过时么?&/strong&&/p&&p&一旦ES6成为了模块化的标准,我们还需要这些非原生的东西么?&/p&&p&这点还值得商榷。&/p&&p&在JavaScript中采用统一标准,通过import和export来使用模块,省略所有繁杂的多余步骤确实很爽。不过到底要多久ES6才能成为真正的模块化标准呢?&/p&&p&反正不会很快。&/p&&p&并且开发者也有各自的偏好,“唯一的解决方案”永远也不会存在。&/p&&h2&总结&/h2&&p&我希望这两篇文章对于你理解JS模块有所帮助,要是你忘了之前聊过的内容,可以现在点开再看看:&/p&&p&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&JavaScript 模块化入门Ⅰ:理解模块&/a&&br&&/p&&p&有任何问题和建议也欢迎在评论区讨论。&/p&
原文链接:作者:在里我们已经讨论过什么是模块,为什么要使用模块以及多种实现模块化的方式。这次,我们会聊一聊什么是模块打包,为什么要打包模块,模块打包的方式工具,还有它当…
&p&本爬虫系列入门教程假设读者仅有一点点Python基础或者近乎为零的基础。如果是有Python基础的可以跳过一些对于Python基本知识的补充。&br&&/p&&p&本爬虫系列教程会持续完善,有看不懂的地方或者有错误的地方欢迎大家在评论区指出~如果是比较大的问题我会专门写专栏讲~&/p&&p&友情链接:&a href=&https://link.zhihu.com/?target=http%3A//xlzd.me/tag/crawler/1/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&标签 Crawler 下的文章 - xlzd杂谈&/a& 这是一个非常好的爬虫教程~推荐大家去学习~&/p&&p&系列专栏目录:&/p&&p&第一讲:&a href=&https://zhuanlan.zhihu.com/p/?refer=xmucpp& class=&internal&&Python爬虫|Python爬虫入门(一):爬虫基本结构&简单实例&/a&&/p&&p&第二讲:&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&Python爬虫|Python爬虫入门(二):请求&/a&&/p&&p&第三讲:&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&Python爬虫|Python爬虫入门(三):解析&/a&&/p&&p&第四讲:&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&Python爬虫|Python爬虫入门(四):储存&/a&&/p&&p&------------------------萌萌哒的分割线------------------------&/p&&br&&p&爬虫能干什么呢?一句话概括,正常通过浏览器可以获取的数据,爬虫都可以获取。这句话可以说是包罗万象。一是说明了爬虫的本质是一个服务端,实现的功能类似于浏览器;二是说明了爬虫的界限,如果不能正常访问到的数据就不能通过爬虫获取;三是爬虫的最高境界,只要是浏览器能正常访问的都可以用爬虫获取。更多爬虫的神奇用处,请戳:&a href=&https://www.zhihu.com/question/& class=&internal&&利用爬虫技术能做到哪些很酷很有趣很有用的事情? - 互联网 - 知乎&/a&&/p&&p&下面我们讲讲爬虫的基本结构和简单实现。这篇专栏我不准备详细地讲具体怎么写爬虫,只是先用一个非常简单的实现,给大家看看爬虫是个什么样子。详细的内容我们后面一个一个慢慢说。&/p&&p&一、爬虫的基本结构&/p&&p&让我们忽略掉来自各种各样的资料对于爬虫结构的描述,把问题尽可能地描述简单一点。前面说到,爬虫是一个获取正常浏览器可以获取的数据的自动化获取程序。那么,从这个功能出发,我们需要干的事情其实就两件事情:找到我们需要的网页,然后把他们一个一个处理一遍。(这句话很重要,我们在后面的后面讲到海量数据爬取策略的时候还要回到这句话。)那么问题就来了:一,怎么找到我们需要的网页的那个入口?二,怎么处理我们需要处理的网页?&/p&&p&对于单个页面来说,入口的来源有两种,一种是已知地址,比如我们院的网站的教学研究人员:&a href=&https://link.zhihu.com/?target=http%3A//www.wise.xmu.edu.cn/people/faculty& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&教学研究人员 - WISE&/a& ;另外一种是你可以通过前面爬下来的网页中获取入口,比如这个页面上所有老师的个人主页。这一点很重要,后面我们在把小爬虫逐步变大的时候还要反复回到这里。&/p&&p&好的,后面的部分,让我们通过解决第二个问题(处理网页)的过程,顺便看看怎么解决第一个问题(获取更多入口)。我们一起开看看一个单页爬虫是怎么实现的。&/p&&p&二、爬虫实例:简单的单页爬虫&/p&&p&好了,现在我们要处理网页了。可是网页要怎么处理呢?让我们回忆一下我们使用浏览器的过程:你先把一个地址复制进浏览器的框框里面,或者点开一个链接;然后浏览器的进度条跑一下(有可能快也有可能慢),然后我们就在浏览器里面看到了数据。首先,进度条跑的过程完成了一个对网页的请求,然后浏览器把请求下来的数据进行处理,然后就输出出来。这是一个极其简化但是不太准确的对于浏览器工作原理的描述。那么爬虫和浏览器有什么不同呢?一般来说,我们是只需要解析网页,而不需要渲染浏览器环境的;另外,我们需要特定网页的特点数据,因此要用一定的方式把数据组织并储存起来。所以,爬虫的核心模块有三个:&b&请求、解析、储存&/b&。&/p&&p&首先我们给一个入口:&a href=&https://link.zhihu.com/?target=http%3A//www.wise.xmu.edu.cn/people/faculty& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&教学研究人员 - WISE&/a&。这就是我们今天要处理的目标网页。我们今天要实现的目的就是把这个页面上所有老师的姓名和个人主页的链接(也就是可能的下一次爬虫的入口)提取出来。下面开始写爬虫吧~&/p&&p&1. 请求&br&&/p&&p&这里我们使用的package是requests。这是一个第三方模块(具体怎么下载以后再说),对HTTP协议进行了高度封装,非常好用。所谓HTTP协议,简单地说就是一个请求过程。我们先不管这玩意是啥,以后再讨论。这个部分,我们要实现的目的是把网页请求(或者说下载)下来。&/p&&p&首先我们导入requests:&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span&&/span&&span class=&kn&&import&/span& &span class=&nn&&requests&/span&
&/code&&/pre&&/div&&p&下面调用requests的get函数,把网页请求下来:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&r = requests.get('http://www.wise.xmu.edu.cn/people/faculty')
&/code&&/pre&&/div&&p&返回的“r”的是一个包含了整个HTTP协议需要的各种各样的东西的对象。我们先不管都有啥,先把我们需要的网页提取出来:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&html = r.content
&/code&&/pre&&/div&&p&好了,到这一步我们已经获取了网页的源代码。具体源代码是什么样的呢?右键,点击“查看源文件”或者“查看源”就可以看到:&/p&&p&&a href=&view-source:http://www.wise.xmu.edu.cn/people/faculty& class=&&&view-source:http://www.wise.xmu.edu.cn/people/faculty&/a&&br&&/p&&p&2. 解析&/p&&p&当然从这一大坨代码里面找信息太麻烦了。我们可以用浏览器提供的另外一个工具:审查元素。这里我们先不讲怎么使用审查元素,先从源代码里面找。找到的我们需要的信息如下:&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span& &span class=&p&&&&/span&&span class=&nt&&div&/span& &span class=&na&&class&/span&&span class=&o&&=&/span&&span class=&s&&&people_list&&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&table&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&tr&/span& &span class=&na&&class&/span&&span class=&o&&=&/span&&span class=&s&&&people_title&&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&th&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&th&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&th&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&th&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&th&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&th&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&tr&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&tr&/span& &span class=&na&&class&/span&&span class=&o&&=&/span&&span class=&s&&&people_cate&&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&td&/spa

我要回帖

更多关于 微信视频保存在哪里 的文章

 

随机推荐