2020年4月

基于NPM的包或者库,项目中的package.json是对项目的描述,这个json对象中的script标签就是npm运行脚本,vue.js在这里配置了如下的内容

"build": "node build/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer", (**注意这一块是用逗号分割的**)
"build:weex": "npm run build -- weex"

vue当然有很多script命令,但是仅仅只有这几种是build的,build:ssr和build:weex其实和build一样,只不过提供了不同的运行参数

查找build的入口文件,vue是如何做build源码的?

打开对应的build/build.js

let builds = require('./config').getAllBuilds()
// filter builds via command line arg
if (process.argv[2]) {
  const filters = process.argv[2].split(',') (**分割出来的数组是["web-runtime-cjs","web-server-renderer"]** )
 // 通过getAllBuild函数返回的config对象对其打包模式进行了rollup的配置,包括output等设置
 // 而下面这一段代码的b则是返回的config对象,filters这个参数数组使用some来判断builds中output中的file(被resolve函数定义了,这个resolve函数下面会具体讨论)
  builds = builds.filter(b => {
    return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
  })
} else {
  // filter out weex builds by default
  builds = builds.filter(b => {
    return b.output.file.indexOf('weex') === -1
  })
}

这一段代码其实非常简单,作者也在源码中写了注释解释了这段代码的作用

通过命令行arg构建过滤器

其中引入的config 文件调用了getAllBuilds这个方法,在config.js可以看出

 exports.getAllBuilds = () => Object.keys(builds).map(genConfig)

这一句代码是导出config的关键代码,builds是预定义的一些对象

 // runtime-only build (Browser)
  'web-runtime-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.js'),
    format: 'umd',
    env: 'development',
    banner
  },
entry: 入口文件
dest: 输出目标文件
format: 构建的格式 umd为umd格式 cjs遵循commonJS规范 es遵循esmodule规范
env:开发模式/生产模式
banner指的是每个js的页头,比如作者,信息,开源协议等信息
还有其他的一些关于rollup(很像webpack的一些设计)别名诸如此类的配置,这边就不阐述了,因为我也忘记的差不多了,文档都很好查,想知道具体的意思也很容易

resolve函数的定义

这里的resolve函数比较简短,很容易理清

//假定一个config中使用resolve这个函数,它传递的字符串是这样的
'web-runtime-cjs': {
    entry: resolve('web/entry-runtime.js')
  }

const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

首先base则是 “web”这个字符串,
这个base并不是真实的路径,而这个web则指向了aliases的配置

module.exports = {
  web: resolve('src/platforms/web'),
}

这里的web指向的路径就是src/platforms/web
那么resolve函数返return的就是path.resolve,其中第一个参数就是web,第二个参数则是entry-runtime
所以由此得知,通过这样的一个过程找到了build的入口文件然后经过rollup的打包就会在dist目录下生成web/entry-runtime.js

拓展阅读

Runtime Only VS Runtime + Compiler 推荐阅读

在初级和中级前端面试笔试中,函数的防抖和节流要求候选人有能力去精通或者熟悉里面的机制,不仅仅要知道基本版的防抖和节流,还要知道更完美进阶的节流防抖,比如一些lodash中的实现等等,所以这个系列也是从基础版开始,我会和大家一起补充这方面的知识


配套代码地址在线调试和编辑
参考文章

防抖

【背景故事】
某商业楼每天早上上班高峰期的电梯非常堵,通常生活来讲,一个电梯肯定是要进去很多人的直到人满为止,这对于自身和电梯都是一种节约资源的表现,不可能出现每个人一人一个电梯的情况,所以对于电梯和我们,这是一种防抖函数的表现。

电梯每当停到一个楼层,会等我们进去,人没有塞满或者没有人它才会到达指定楼层。
在web端最容易遇到的就是input搜索,用户输入一个字符,会在指定的时间中监听是否还有字符进入,如果没有,那么就执行对应的函数,这样就解决了用户频繁输入字符而前端发起请求给服务器造成压力的问题
// 防抖函数
function debounce(fn, time = 300) {
  let timer = null;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  };
}

基础版的代码梳理,通过这个函数将fn传递,就能实现防抖的效果,当用户输入的第一个字符,会把timer函数赋予一个延时器,这个延时器执行了对应的fn,this是为了绑定执行的this指向,arguments则是这个函数的参数类数组对象,当用户在延时还未触发的时候再次输入字符,那么clearTimeout把上一个timer清除掉了,因为闭包的作用,timer不会被销毁,这样的一个函数就表示了,只要在延时间隔中(此时timer被赋值了),如果谁再调用此函数,会把上一个timer清空掉,重新延时,依次类推...

节流

【背景故事】
在0几年的时候包括可能现在都有,很多家庭知道水管把阀门开到最小,让水一滴一滴的掉下,是不会走水表的(不计费的),所以在我很小的时候就知道这个事情,虽然现在没有了,但是还是觉得很羞愧,节流函数就是起到的作用就是,把原本频繁的调用变得有秩序有间隔;

以监听scroll滚动为例:

// 节流函数
function throttle(fn, time = 300) {
  let canRun = true;
  return function() {
    if (!canRun) return;
    canRun = false;
    setTimeout(() => {
      fn.apply(this, arguments);
      canRun = true;
    }, time);
  };
}

$(window).on(
  "scroll",
  throttle(() => {
    console.log("触发了scroll");
  }, 300)
);

在这个函数中,canRun这个变量变化了3次,判断了一次如果为false就return掉

  1. 初始化时设置为true
  2. 判断canRun是否为false后设置了canRun为false
  3. 最终事件执行完毕之后设置canRun为true,否则就进入不了下一次的节流

其实这个函数从开始到结束的canRun形成了一次循环,这个循环唯一保证的就是只有事件结束才可以开始下一个事件

总结

节流:保证回调能在指定的时间间隔中依次有规律的执行
防抖:保证回调能在指定的时间间隔中等待是否有再次执行回调,如果没有则执行,如果有则重置继续等待;