浅入浅出前端这些技术

2018-06-07 阅读 875 收藏 0 原链:zhuanlan.zhihu.com
分享到:

前端必备图书《Web应用安全权威指南》 >> >> 

近期要给外包培训,需要准备一些内容,稍微梳理了一下,发现前端技术名词的确多啊,不过很多新概念都是换汤不换药的纸老虎,本文尝试浅入浅出的梳理一下。

从 HTML 说起

老司机可跳过本段直接下一题。

大家学前端应该都是从 HTML 开始的吧,1991年的时候出现了世界上第一个网页,你可以在这里访问它,感受下当年的源代码:

<HEADER>
<TITLE>The World Wide Web project</TITLE>
<NEXTID N="55">
</HEADER>
<BODY>
<H1>World Wide Web</H1>The WorldWideWeb (W3) is a wide-area<A
NAME=0 HREF="WhatIs.html">
hypermedia ...

一开始全都是标签,而且没有规范,于是一帮人组成了复仇者万维网联盟专门来搞网络标准化,也就是今天常说的 W3C。

当时的网页只能做简单信息展示还很 LOW,后来一家叫 SUN 的公司搞了个 Java 小程序(Applet),可以跑在网页中制造很酷炫的效果。另外一家叫网景的公司觉得很牛逼,于是赶紧招人准备出复刻版,由于项目紧,程序员花了两周时间搞出了JavaScript,结果项目上线后效果出乎意外的好!于是 JS 大火。

有了 JS 用户就可以和网页更好的交互了,但是页面还是很 LOW,于是又过了一年,CSS 出现了,专门用来美化页面。至此,前端三巨头诞生了,网页看起来终于有模有样了,于是 WEB 发展进入繁荣时代,出现了一大波新技术比如 Spring/JSP/ASP/AJAX 等等,也出现了一大波浏览器比如 IE、Firefox、Safari、Chrome、Opera…

于是前端出现了一个非常棘手的问题:浏览器兼容问题。同一个DOM操作需要写很多适配代码来兼容不同浏览器,这是一个很枯燥低效的事情。于是jQuery 诞生了:一套代码,多端运行。众前端大喜,纷纷使用至今。

由于技术发展飞快,人们对网页的要求越来越高,这时一家叫 Google 的公司认为浏览器需要更好的体验和性能,JS 需要一款更快速的引擎来迎接现代化 Web 应用。而当时微软的 IE6 占据了大部分市场份额,微软认为 IE6 已经很完美了,于是解散了IE6开发团队。

后来的事大家都知道了,IE 成了前端开发的阴影,而谷歌的 Chrome 搭上 V8 引擎上了天,美滋滋。

V8 的出现让一个叫 Ryan Dahl 的程序员大喜。Ryan一直在研究高性能Web服务,但平时用 C/C++写太痛苦了,JS 的单线程事件驱动+异步 IO+V8引擎刚好满足他的需求,于是他搞出 Node.js。自此 JS 从前端迈向了后端。

然而 Node.js出现的意义远不止向后端迈了一步,它意味着 JS 可以脱离浏览器了,这直接促进了前端工具开发和生态的繁荣,程序员都很懒,为了方便写代码发明了各种新技术,比如懒得写 HTML 便发明了 Jade、Handlebars等模板语言,懒得操作 DOM 便发明了 Angular、React、Vue,懒得写 CSS 便发明了 SASS、LESS 这种预处理语言、懒得写 JS 便发明了 coffeeScript。但这还不够,前端页面变得越来越复杂,为了写出更好的代码,又发明了各种语法检测工具,比如 jshint、eslint,为了让JS代码变得更易维护,还发明了各种各样的模块化方案比如 CMD,AMD,直到 W3C 站出来规范了 ES6 的模块化方案。但是如何把这些模板、预处理语言、JS模块合并、压缩、打包成最终能够线上跑的文件却成了问题,于是程序员们又发明了各种各样的打包工具比如 Grunt、Gulp、Bower、Browserify、Webpack、rollup、parcel…

终于前端迈向了工业化之路,下面开始进入正题。

从Webpack说起

在众多打包工具中,Webpack 是目前最流行的方案,其理念非常简单:

给定入口文件,webpack 会分析所有依赖到的静态资源并加载相应代码进行处理,最终打包成指定的bundle输出到目标文件夹中。

当然其背后的实现是相当复杂,这里只抛几个关键问题:

1 如何根据入口文件打包所有依赖资源?

Webpack 本身只能理解 JavaScript,但是却可以打包 sass、less、png 等非 JS 资源,主要是因为在加载这些资源前都会链式调用 loader 进行预处理,将其转换成 Webpack可以理解的 JS 模块,比如下面这个 loader 配置:

module.exports = {
  entry: {
    app: './src/app.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      }, {
        test: /\.(less|css)$/,
        use: 'style-loader?css-loader!less-loader!autoprefixer-loader'
      }
    ]
};

当检测到 app.js 入口文件时就会调用 babel-loader 进行预处理,如果在 js 文件里面又检测到 require或import 依赖了.less后缀的文件,就会调用 autoprefixer-loader 进行预处理,处理结果传递给 less-loader,css-loader,最后传递到 style-loader处理为 webpack 可识别的 JS 模块。

有了 loader,webpack 理论上可以处理图片、视频、字体等各种文件,将其转化为 JS 模块,递归构造出依赖关系图,并打包成一个或者多个bundle。

2 Webpack 是如何对 JS 进行模块化打包的?

3 为什么Webpack有时很慢?如何优化?

4 插件是如何对依赖资源进行处理的?

loader 只能对资源进行预处理,如果需要更强大的能力就需要引入插件了。Webpack 的插件是一个具有 apply 属性的 JS 对象,apply 属性会被 Webpack compiler 调用,而这个 compiler 在整个编译的生命周期都是可以访问的。比如最常用的HMR插件:

module.exports = {
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
};

有了 compiler 的访问权,就可以监听整个模块系统的 runtime,检测文件依赖变化并且实时更新,实现 HMR。

可以说 loader 和插件机制给 Webpack 提供了无限可能,几乎可以满足所有前端开发场景了。但是程序员不仅懒,还精益求精。比如 ES6规范出来后又杀出个 rollup,获得了tree shaking 的新技能,可以干掉 JS 中的 dead-code 以精简代码。众前端大喜,纷纷试用。但本质上,tree shaking 是依赖了ES6中 static module structure 的设计,在编译阶段就可以确定依赖关系而非等到运行时。所以说…好像这个功能 webpack 用 loader 和插件也能做?的确, Webpack 后来机智地加上了。

然而程序员的作风就是你这个东西不好用,我要重新造一个。所以不仅杀出了 rollup,还中出了一个号称极速0配置的打包工具 Parcel,众前端大喜,纷纷试用。

而当我开始试用时发现这个0配置原来是就是个最简单的 index.html 引入 index.js的 demo??哼,我就是要用 less,ts,vue,你给我0配置试试?所以说0配置是不存在滴,只有绝对可配置才能满足乱七八糟的前端场景,懒的话搞个 yeoman generator 就好啦。不过这个极速倒是真的(惊喜),多核编译+文件缓存,让重启构建后也能快速编译,虽然这些 feature 用 Webpack 也能做,不过二者在理念上还是有所差异。Webpack基于JS,强调可配置,而 Parcel对文件无感知,不同资源会被并行处理,并且强调的是插件,你把东西都在插件里面弄好,然后我直接用就好了,尽量别让我做配置程序员。然前端场景太多,现有插件可能无法满足你项目需求,这时你就得亲自动手写插件了,比如我写的这个 markdown to html的插件 parcel-plugin-md

新轮子的出现总是伴随着解决新的问题,有着一定的使用场景, 比如在写小 DEMO 或者某些轻量级场景/模块库下用 Parcel 还是很高效的。Webpack 的灵活让其经久不衰,但使用场景终究还是打包构建,在工业化之路上还有发布、部署等一系列流程,再加上程序员只要稍微用的不爽就很可能抄家伙的特点,日后必然还会有新的轮子出现的,众前端大喜。

从 AngularJS 说起

AngularJS 的出现可以说是前端的一个里程碑,其双向绑定和 MVC 模式(后面改成 MVW 了)影响深远。尤其是将前端从 DOM 操作中解放出来,可以把重点放在数据和业务上(当时用的时候是在15年做毕设,还是1.x 版本,现在没怎么关注了)。Angular 是个庞然大物,相比之下 Vue.js 要轻巧的多,很适合当下无线端项目,自己工作后也一直在用。外包同学在这方便疑惑较多,重点讲下。

Vue 数据和视图绑定的原理?

这个问题已经成常用面试题了,而且根据面试者的回答深度可以大致了解其CS段位。 本文既然是浅入浅出,就先实现一个最简单粗暴的Vue版本。

这个最简单的场景就是:实现一个代表元素内部文案的 v-text 指令,当内部数据变化时 DOM 元素上的内容会自动变化:

<div id="app">
  <h1 v-text="title"></h1>
  <p>What's Time now: <span v-text="datetime"></span></p>
</div>

调用代码跟完整版的 Vue 实例化方式类似:

window.app = new Vue({
  el: 'app', // 为简单处理这里的 el 直接传 id
  data: {
    title: '阉割版响应式数据方案',
    datetime: '2018-03-01: 00: 00: 00'
  }
})

执行之后的效果是这样的:

当你在控制台实时改变 app.data 的 title 和 datatime 属性值的时候,页面上的数据会自动更新。

下面就是实现方案(二十行代码粗暴版):

function Vue({ data = {}, el = '' } = {}) {
  this.data = {}
  const compiler = ($node) => {
    if (!$node) return
    watcher($node, 'v-text')
    for (let $child of $node.children) {
      compiler($child)
    }
  }
  const watcher = ($node, directive) => {
    const key = $node.getAttribute(directive)
    key && ($node.innerText = data[key]) && Object.defineProperty(this.data, key, {
      configurable: true,
      enumerable: true,
      get: () => data[key],
      set: val => data[key] = $node.innerText = val
    })
  }
  compiler(document.getElementById(el))
}

递归读取指令,通过 Object.defineProperty的 setter/getter 方法将内部数据和 DOM 更新关联起来,实现响应式变化。但是这里只处理了最简单的字符串响应,而且只实现了一个指令,没有考虑字符串插值,没有构造 Virtual DOM,没有依赖收集…真要实现一个完善的响应式系统,可以深挖的东西很多,还好这里只是浅入浅出。

Vue 开发中注意的点?

只要理解了 Vue 的响应式系统和组件设计,就可以避免走很多弯路,比如:

  • 为什么我改了数据页面没更新?
  • 为什么输入框没反应?
  • 为什么要给 style 加 scope?

这些问题网上都有解答,Vue 的资料应该是非常好找了,平时多看下原理多总结,遇到问题按照 分析错误提示 -> google -> github issue -> 源码定位 的流程去解决一般没啥问题。

嗯,感觉还有好多东西,先去看个电影吧。

// TODO

回到顶部