面试官:谈谈你对前端模块化的理解

110 阅读4分钟

一、混沌初开时

世上本没有模块,代码写得多了,便成了灾。

那光景,凡写前端者,必在全局撒野。如那未庄的闲汉,赤着膊,将function胡乱抛在window上。你且看这般景象:

// 赵家的函数
function calculate(a, b) {
  return a + b + 100 // 赵老爷偏要多加一百
}

// 钱家的函数
function calculate(str) {
  return str.slice(0, 5) // 钱太爷定要截断五字
}

console.log(calculate(2,3)) // 究竟用哪个?好比九斤老太的秤,总要缺斤短两!

后有人学那孔乙己,用对象裹着代码当茴香豆——这便是简单对象封装:

// 孔家的算术铺子
var math = {
  _pi: 3.14, // 单下划线装体面
  add: (a, b) => a + b + this._pi 
}

math._pi = 100 // 赵举人抬手就改
console.log(math.add(1,2)) // 103.14变作102,好比辫子盘在头顶,到底遮不住秃

那window对象上,各种命名互相践踏,如辫子般纠缠。有识之士见了,总要摇头叹道:"这代码,怕是要吃人的!"

二、改革者的足迹

后来有人学了乖,把代码裹进麻袋里——这便是IIFE的法子。如闰土在瓜田里扎的稻草人,虽防不得真猹,到底是个模样:

var 周家模块 = (function(){
  var 秘银 = "不可示人"
  return {
    露脸的法子: function(){
      console.log(秘银.slice(0,1)) // 只许看个不字
    }
  }
})()

周家模块.露脸的法子() // 输出"不"
周家模块.秘银 // undefined 好比孔乙己的茴香豆,是摸不着的

这法子虽好,终究是单打独斗。各模块如未庄的乡民,隔着土墙叫骂:

"阿Q!你家的jQuery可传过来了么?我这日历组件离了它,便如辫子离了头,是要出人命的!"

"七斤嫂,你且等等,待我先引了Bootstrap的CDN,再引你的样式文件!"

原来这IIFE好比抓药,须得药引子在前,药材在后。若把当归错放在黄芪前头,便要闹出人命。待到项目大时,几十个script标签排着队,宛如送葬的队伍,稍有不慎便全盘崩溃。

三、新青年的觉醒

CommonJS派的长衫客

CommonJS者,如孔乙己穿长衫,最是四平八稳。此派在Node.js地界盛行,讲究的是同步加载:

// 咸亨酒店的账本(math.js)
let 酒钱 = 9
module.exports = {
  赊账: () => 酒钱++,
  结账: () => {
    if(酒钱 > 0) console.log("多乎哉?不多也!")
    return 酒钱
  }
}

// 酒客的主顾(main.js)
const 账本 = require('./math')
账本.赊账()
console.log(账本.结账()) // "多乎哉?不多也!"(实则欠10文)

这派有个怪病:输出的皆是死物。好比复制人偶,原身改了,分身却不知:

// 真身.js
let count = 1
setTimeout(() => count = 2, 1000)
module.exports = count

// 分身.js
const num = require('./真身')
console.log(num) // 1
setTimeout(() => console.log(num), 2000) // 还是1

AMD与CMD的辫子军

AMD者,假洋鬼子做派,最喜前置依赖。RequireJS便是其马前卒:

// 假洋鬼子的新派做法
define(['jquery', 'lodash'], function($, _) { // 先备齐弹药
  let 银元 = 100
  return {
    买自由: () => {
      $('body').append(`还剩${银元--}块大洋`)
      _.debounce(() => alert('革命成功'), 500)
    }
  }
})

CMD却像改良派,讲究就近取物。Sea.js便是典型:

// 改良派的折衷之道
define(function(require, exports, module) {
  // 用时方取
  var 大炮 = require('革命炮')
  exports.起义 = () => 大炮.开火()
})

这两派都学那西洋钟,异步加载不阻塞。区别在于:AMD如西医开刀,先把零件备齐;CMD似中医煎药,文火慢炖时再取药材。

四、将来的天下

ES6模块的真革命

如今ES6模块一统江湖,如狂人日记撕开铁屋:

// 新青年的呐喊(counter.mjs)
export let 觉醒人数 = 0
export const 敲钟 = () => 觉醒人数++

// 吃人的旧社会终要灭亡(main.mjs)
import { 觉醒人数, 敲钟 } from './counter.mjs'
敲钟()
console.log(觉醒人数) // 1(这是活的数字,不是死物!)

// 若在别处也敲钟...
setTimeout(() => console.log(觉醒人数), 1000) // 看那数字自己会跑!

这般的模块,才是真革命:

  • 活的引用:不似CommonJS的纸人纸马,拷贝些死物;ES6的乃是血肉之躯,改一处则处处皆变

  • 铁屋中的曙光:编译时就定下规矩,好比先生批注的狂人日记,错处早被圈出

  • 静默的革新:Tree Shaking如快刀剪辫,未用的代码皆化作尘土

// CommonJS的纸人纸马
const {活字} = require('./印刷术') // 管你用不用,全套搬来
// ES6的精准打击
import {火药} from './四大发明.mjs' // 只取所需,余者灰飞烟灭

结语

面试官放下茶杯,镜片后透出精光:"依你之见,模块化竟是一场革命?"

我整了整长衫下摆的补丁,答道:

"先生看这代码世界,先前是混沌的奴才,今朝要做自己的主人。模块化哪里是写代码?分明是在铁屋中凿窗——为的是教那变量各得其所,教那依赖不再吃人,教那复用如野草蔓生,便是关了沙箱,也要倔强地长!"

那面试官听罢,手中的笔竟微微颤抖起来。窗外隐约传来《新青年》的卖报声,混着编译器的嗡鸣,恰似新时代的晨钟。