Giter Site home page Giter Site logo

hello-world-blog's People

Contributors

gnosis23 avatar

hello-world-blog's Issues

入门安卓开发

也许只有一日

搭环境

下 android studio;更改源

allprojects {
    repositories {
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public'}
        google()
        jcenter()
    }
}

JavaScript 各种模块化方式

模块化

ES6 模块

介绍的书太多了。

// file1.js
export function rush() {
  
}

// file2.js
import { rush } from './file1';
rush();

CommonJS 模块规范

由 Node.js 采用的模块化规范。

模块定义

// math.js
exports.add = function(a, b) {
  ...
}

模块导入

var math = require('math');
// use 
math.add(1, 1);

实现原理

待续

AMD

具体参考 AMD 规范

   define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
       exports.verb = function() {
           return beta.verb();
           //Or:
           return require("beta").verb();
       }
   });

UMD

改进版 AMD

参考资料

preact 源码分析

preact 源码分析

VNode

虚拟节点的数据结构很普通,多了一个key属性,children 是个数组;

class VNode {
  nodeName: string | function;
  children: (VNode | string)[];
  key: string | number | undefined;
  attributes: object;
}

diff

从 Mocha 上学到的

一些疑问

使用 Mocha 的过程中有没有以下疑问:

  • 没有定义过 expect,describe 为什么可以访问?
  • 如何测试 ES2015+ 的代码
  • 异步测试是什么原理

问题一

Node 环境下可以通过访问全局变量 global,设置 expect 和 describe 即可
浏览器环境下就访问 window

问题二

mocha 有个 --require 的选项,可以传入转码器的名称。常用的如 babel-register ,可以运行时改变引用的源文件。

// 这里这个路径要注意,require 的时候最好传入测试文件所在项目的 node_module 下的路径
require(babelRegisterPath);

关于 redux 的 中间件

redux 的中间件概念上不大难理解,直到它和异步操作搅合在一起....

模型

洋葱圈吧

例子

不要长篇大论了,直接看一个日志的例子

const logger = store => next => action => {
  console.group(action.type)
  console.info('dispatching', action)
  // 下一个中间件处理
  let result = next(action)
  console.log('next state', store.getState())
  console.groupEnd()
  return result
}

注意中间件的返回值,最后的返回值可以通过 store.dispatch 获得。
有些时候我们可以从中间件返回一个 promise ,就可以在 redux 里做一些异步操作, 比如

// middleware
const gaga = store => next => action => {
  if (action.type !== 'DELAY') return next(action);
  return new Promise((resolve, reject) => {
    next(Object.assign({}, action, {resolve, reject}))
  })
}

// user interface 
store.dispatch({ type: 'DELAY' }).then(result => {
  // do sth
})

有名的中间件

redux-thunk

源代码很短...

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

redux-saga

前端考点归纳

一些 JavaScript 面试题

基础

  • 介绍JS数据类型,基本数据类型和引用数据类型的区别
  • Array是Object类型吗
  • == 和 ===的区别,什么情况下用相等==
  • 介绍纯函数
  • JS的继承方法

Object

  • 介绍 Object.defineProperty 方法,什么时候需要用到
  • for..in 和 Object.keys 的区别
  • 两个对象如何比较
  • 深拷贝和浅拷贝
  • loadsh深拷贝实现原理
  • new 是怎么实现的

原型

  • 什么是的原型
  • prototype 和 __proto__ 区别
  • 介绍原型链
  • 写一个原型链继承的例子
  • 使用原型最大的好处
  • constructor 是什么
  • instanceof 原理

注:prototype 是函数的属性。

execution process

  • 变量作用域链
  • JS怎么实现异步
  • 异步整个执行周期
  • JS为什么要区分微任务和宏任务
  • JS执行过程中分为哪些阶段
  • setInterval需要注意的点
  • setTimeout(1)和setTimeout(2)之间的区别
  • 介绍宏任务和微任务
  • 介绍各种异步方案

Promise

  • Promise有几个状态 (pending, resolved, rejected)
  • 介绍下Promise的用途和性质,优缺点
  • Promise有没有解决异步的问题(promise链是真正强大的地方)
  • Promise 和 async/await 和 callback的区别
  • Promise 和 Async 处理失败的时候有什么区别
  • Promise 构造函数是同步还是异步执行,then 呢 (同步,异步)
  • Promise 和 setTimeout 的区别(微任务和宏任务)
  • promise如何实现 then 处理
  • 介绍下 Promise 内部实现
  • Promise.all 实现原理
  • 如何设计 Promise.all()

注:

async

  • 对 async 、await 的理解,内部原理
  • 使用 async 会注意哪些东西
  • async 里面有多个 await 请求,可以怎么优化

注:

this

  • 介绍this各种情况 (member expression, call, apply, bind, 箭头函数)
  • 介绍箭头函数的this(函数词法环境里没this,要到外层作用域里找)
  • bind、call、apply的区别(call和apply是参数有区别,bind后返回一个特殊的函数对象)
  • 怎么实现this对象的深拷贝

闭包

  • 介绍闭包(函数+环境)
  • 闭包的使用场景(实际上是个函数都是闭包,问题应该特指封装局部变量的函数)
  • 闭包的核心是什么(函数的实例)
  • 使用闭包特权函数的使用场景

mdn closure

ES6

  • ES6中的map和原生的对象有什么区别
  • 取数组的最大值(ES5、ES6)
  • ES5和ES6有什么区别
  • 介绍ES6的功能
  • ES6使用的语法
  • let、const以及var的区别
  • 介绍class和ES5的类以及区别
  • 介绍箭头函数和普通函数的区别(箭头函数的词法环境是不完整的,没有this、super、new.target,而且没有arguments,都是用外层环境里的)

GC

  • 栈和堆的区别
  • 垃圾回收时栈和堆的区别
  • 栈和堆具体怎么存储
  • 堆和栈的区别
  • JS里垃圾回收机制是什么,常用的是哪种,怎么处理的
  • 还有哪些地方会内存泄露

数组

  • [1, 2, 3, 4, 5]变成[1, 2, 3, a, b, 5]
  • some、every、find、filter、map、forEach有什么区别
  • 数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少
  • 如何判断一个变量是不是数组
  • 类数组和数组的区别
  • 手写数组去重函数
  • 手写数组扁平化函数

算法

  • 介绍快速排序
  • 算法:前K个最大的元素
  • 介绍排序算法和快排原理

其他

  • 柯里化函数两端的参数具体是什么东西
  • 介绍暂时性死区
  • 防抖和节流的区别
  • Emit事件怎么发,需要引入什么
  • 如何找0-5的随机数,95-99呢
  • var a = {name: "前端开发"}; var b = a; a = null那么b输出什么
  • var a = {b: 1} 存放在哪里
  • var a = {b: {c: 1}}存放在哪里
  • 发布-订阅和观察者模式的区别
  • 事件委托

拖拽学习资源

drag and drop

Html5 拖拽资源:

React 拖拽相关资源:

React dnd 例子:

Library

以下都是各种类库冗长的实现细节。

拖拽实现

react-draggable

在 mouseDown 的时候添加 mouseMove, mouseUp 事件到 document 和 本体上,然后 mouseUp 或者 unMount 的时候移除挂载的事件。
位移是通过 css 的 translate 来实现的。

  • 抽象出 Core 架构,内部封装拖拉和位移等状态;暴露出 onDrag ,onMove 等属性用于位移等功能。
  • 通过 React.cloneElement 将事件和样式直接合并到 children 上
  • 通过 ReactDOM.findDOMNode 来获取 document 和 位置信息
  • 通过 PropTypes 来设定一些默认属性(自定义)

怎么学习 Webpack

谈谈我怎么学习 Webpack的。

这个工具配置项目繁多,看了一遍文档一头雾水。

第一个阶段:学习

找几个模板学习一下,大概知道哪个地方是做什么的。然后试着自己折腾一套配置。

推荐两个模板

第二个阶段:开发

这个阶段可以自己开发一些 loader 和 plugin ;主要是大概了解下原理,可以试着找些小的项目来看。

这里推荐几个loader:

开发资源:

第三个阶段:深入

这个阶段可以去研究下 webpack 的原理了;试着造些轮子和高度定制 webpack。

推荐下几个教程

常见问题

  • 有哪些常见的Loader?他们是解决什么问题的?
  • 有哪些常见的Plugin?他们是解决什么问题的?
  • 是否写过Loader和Plugin?描述一下编写loader或plugin的思路?
  • webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全
  • webpack的热更新是如何做到的?说明其原理?
  • 如何提高webpack的构建速度?
  • 怎么配置单页应用?怎么配置多页应用?
  • npm打包时需要注意哪些?如何利用webpack来更好的构建?
  • 如何在vue项目中实现按需加载?

我的 2019 前端技术雷达

从招聘网站上搜罗的前端技术栈,持续收集中

结论

从 2019 年各种发展趋势来看:除了三大框架外,推荐学习

强力推荐

  • TypeScript 很强势

一般推荐

  • GraphQL 见过实际项目,挺好用
  • Relay

观望

  • RxJS 我们的小破站需要用这种核武器吗...

网站前端技术栈

力扣

  • GraphQL + React + Redux + RxJS + Emotion + TypeScript
  • Test: Jest + enzyme + Sinon
  • CI: Cypress

webpack-dev-middleware 相关

通常使用 webpack 开发的时候会用 webpack-dev-server 配合热部署,然而有些时候已经有了自己的server,就不是很方便。官方提供了一个 webpack-dev-middleware 插件来帮助开发。

下面是使用 webpack-dev-middleware 插件(简称wdm) 的时候碰到的问题。

单页应用刷新

在一个 SPA 应用里刷新网页,如 'localhost:3000/foo/bar',后台会报错 not found error。
原因比较好找,因为后台只有 / 对应的文件,其他的路由都是前端路由。
所以我们需要一种方式将这些路径全部定向到 index.html 上。

默认配置

const express = require('express')
const path = require('path')
const app = express()

// eslint-disable-next-line no-undef
if (!__DEV__) {
  app.use(express.static(path.resolve(process.cwd(), 'public')))
} else {
  const webpack = require('webpack')
  const webpackConfig = require('../webpack.config')
  const compiler = webpack(webpackConfig)
  compiler.apply(new webpack.ProgressPlugin())

  app.use(require('webpack-dev-middleware')(compiler, {
    publicPath: webpackConfig.output.publicPath,
    headers: { 'Access-Control-Allow-Origin': '*' },
    hot: true,
    quiet: true,
    noInfo: true,
    stats: 'minimal',
  }))

  app.use(
    require('webpack-hot-middleware')(compiler, {
      log: false
    })
  )
}

// eslint-disable-next-line no-console
app.listen(3000, () => console.log('Example app listening on port 3000!'))

解决方案1

express的中间件采用一种洋葱圈模型,一环套一环,wdm 也不例外;
最好的方案当然是先访问 wdm 这个中间件。然后如果不匹配,再访问我们自己的中间件,将路由重定向到 /
但是不清楚如何将其余路由重定向到index这步,只能先用一种比较蠢的方法:在 wdm 前面加个中间件,当发现路由不是静态资源的时候,就把请求的url改成/。

+ // stupid spa router middleware
+ app.use(function (req, res, next) {
+  const validSuffix = ['.js', '.css', '.png', '.jpg', '.jpeg'];
+   if (validSuffix.every(x => !req.url.endsWith(x))) {
+     req.url = '/'
+   }
+   next()
+ })

这种方式就是要过滤一大堆后缀,拓展性比较差。

解决方案2

wdm 有个选项叫 writeToDisk 可以将某些文件写到硬盘上,可以将 index.html 写到本地。

else {
  app.use(require('webpack-dev-middleware')(compiler, {
    publicPath: webpackConfig.output.publicPath,
    headers: { 'Access-Control-Allow-Origin': '*' },
    hot: true,
    quiet: true,
    noInfo: true,
    stats: 'minimal',
    // index.html 存到本地,方便下一步返回
+    writeToDisk: filePath => /index.html$/.test(filePath)
  }))

   app.use(
    require('webpack-hot-middleware')(compiler, {
      log: false
    })
  )
}

+ // 如果没有命中wdm,默认返回首页
+ app.use((req, res) => {
+   res.sendFile(path.resolve(process.cwd(), 'public/index.html'))
+ })

// eslint-disable-next-line no-console
app.listen(3000, () => console.log('Example app listening on port 3000!'))

如何开发一个 Node.js CLI 程序 二

用 Node.js 开发小工具

通过上一篇 《如何开发一个 Node.js CLI 程序》#28,我们已经知道怎么开发一些简单的命令行程序了。这篇文章主要介绍一些常用的库,来辅助我们开发程序。

常用的

commander

TJ 开发的,用来创建命令行工具的程序,提供了如命令行解析以及自动生成帮助等等功能。
可以看下面的小例子找找感觉~

var program = require('commander');
 
program
  .version('0.1.0')
  .option('-p, --peppers', 'Add peppers')
  .option('-P, --pineapple', 'Add pineapple')
  .option('-b, --bbq-sauce', 'Add bbq sauce')
  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
  .parse(process.argv);
 
console.log('you ordered a pizza with:');
if (program.peppers) console.log('  - peppers');
if (program.pineapple) console.log('  - pineapple');
if (program.bbqSauce) console.log('  - bbq');
console.log('  - %s cheese', program.cheese);

express

很流行的 web server 框架,几行代码就能拉一个服务起来。
学习下路由,就能创造 mock 服务了。

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

yeoman-generator

用来复制粘贴代码块的工具,主要功能有:

  • 交互式输入,用于后续步骤的定义
  • 自动npm安装
  • 各种复制粘贴的办法。而且使用了基于内存的文件系统,只有成功后才会写入。

不过需要先在本地 npm install -g 一下才能使用模板,比较麻烦。

更多小工具

  • chalk: 用来在终端显示彩色文字的
  • chokidar: 监控文件变化,如保存后重新编译等操作
  • [sockjs]: 可用来创建 websocket 服务
  • download-git-repo: 下载并提取 git 仓库,用于下载项目模板。
  • Inquirer.js: 通用的命令行用户界面集合,用于和用户进行交互。
  • ora: 下载过程久的话,可以用于显示下载中的动画效果
  • ansi-escapes: 移动光标、清空内容
  • readline: 命令行输入控制

React 入门

给新手指条道路,顺便整理下知识点。

推荐学习资源

开发环境

  • Create React App: 预配置React所需环境,不用先学 Webpack、Babel、ESLint...

书籍

React 生态

除了 React,还需要搭配 周边库 才能编写完整的应用(也就是俗称的全家桶)

  • Redux: 全局状态管理,里面还包含了许多中间件
  • React-Router: 路由管理
  • fetch / axios: http请求库

JavaScript 中的数据类型

感觉把这本 《You Don't Know JS: Types & Grammar》 看完就好了。

基本类型

  • Null
  • Undefined
  • Boolean
  • Number
  • String
  • Object
  • Symbol

比较神奇的是基本类型里没有函数,函数也是对象,只不过这个对象有 [[call]] 或者 [[constructor]] 属性。

Number

  • 浮点误差
  • NaN, Infinity

运行时对象类型判断

你去看流行的库里面基本上都有对参数对象类型的判断。这个可以参考 lodash 的代码。

typeof

Type Result
Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Symbol (new in ECMAScript 2015) "symbol"
Function object (implements [[Call]] in ECMA-262 terms) "function"
Any other object "object"

类型转化

抽象值操作

标准中定义的一些“抽象操作”(即“内部使用的操作”)。

ToString

其他的基本类型转换为字符串没什么特别的地方。

对普通对象来说,除了自行定义,否则 Object.prototype.toString() 返回内部属性 [[class]] 的值,如 [object Object]

如果对象有自己的 toString 方法,字符串化时就会调用该方法并使用其返回值。

数组的 toString 方法:调用元素的 toString 然后拼在一起,中间加逗号。

ToNumber

对象会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型,则再遵循以上规则将其强制转换为数字。

为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先检查该值是否有 valueOf 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString 的返回值并进行强制类型转换。

var a = {
    valueOf: function() {
        return "42";
    }
};
var b = {
    toString: function() {
        return "42";
    }
};

var c = [4, 2];
c.toString = function() {
    return this.join(""); // "42"
};

Number( a ); // 42
Number( b ); // 42
Number( c ); // 42
Number( "" ); // 0
Number( [] ); // 0
Number( ["abc"] ); // NaN

ToBoolean

...

显式转换

字符串与数字

使用 String(...)Number(...) 进行转换,注意不用 new ,不会进行装箱。

var a = 42;
var b = String( a );
var c = "3.14";
var d = Number( c );

b; // "42"
d; // 3.14

除了上面的方法,还有其他转化方式

var a = 42;
var b = a.toString();  // 这个算隐式转换
var c = "3.14";
var d = +c; // 这里的+是一元操作符
  • 还能将日期转换为数字。

解析字符串

parseFloat 与 Number 的区别在于前者接受后面有非字符串的情况

var a = "42";
var b = "42px";
Number(a);   // 42
parseInt(a); // 42
Number(b);   // NaN
parseInt(b); // 42

还有 JavaScript 中的数字有十进制、二进制、八进制和十六进制;还有科学计数法。
parseInt 不支持下面所有的数字解析,推荐 Number。

30
0b111
0o13
0xFF
1e3
-1e-2

隐式转换

字符串和数字之间的隐式转换

+ 号:

根据 ES5 规范 11.6.1 节,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话, + 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作在调用 [[DefaultValue]],以数字作为上下文。

JS 转化编码

今天碰到一个需要将 JS 字符串转换为 GBK 的 Base64 形式的需求,特别记录下。

UTF-8 至 GBK

JS 内部的字符串是存储为 UTF-16 形式的 (根据实现有所区别),所以要转化为 GBK 没有直接支持,只能通过 iconv-lite 这样的包处理。

import iconv from 'iconv-lite';
 
// Convert from js string to an encoded buffer.
buf = iconv.encode("Sample input string", 'gbk'); 

处理后是一个 Uint8Array 对象,每个里面存一个字节

GBK 至 Base64

要将上面的字节数组转化为 base64 编码,可以求助于 btoa 方法,这个方法能把字符串形转换为 base64 形式编码

const text = String.fromCharCode.apply(null, buf);
const base64 = btoa(text);

JavaScript ASTs

通过 Babel.js 来修改 AST

有时候需要从 JS 源代码中提取一些信息,或者将源代码进行一定程度上的变换。这时就可以通过某种 parser 来将源代码转化为 AST(抽象语法树)形式,然后进行后续操作。

Alt text

例子

下面给出一个 babel.js 的用例,将下面代码转换为:

function max(a, b){
  if(a > b){
    return a;
  }else{
    return b;
  }
}

//  =>
function max(a, b) {
  return a > b ? a : b;
}

parse

将源代码转化为 AST 的过程称为 parse,使用代码如下

const babylon = require('babylon');

const sampleCode = `
function max(a, b){
  if(a > b){
    return a;
  }else{
    return b;
  }
}`;

// code => ast
const ast = babylon.parse(sampleCode);

对于 ast 具体的样子,除了打印以外,还可以借助如 astexplorer 这样的工具。

transform

将源代码变换的过程称为 transform 。

使用 babel-traverse 来遍历和修改 AST ,babel-traverse 使用了 visitor设计模式 来遍历 AST。

因为 AST 是一棵树,深度优先遍历的时候会有一个进入和回来的过程,所以 visitor 里面有 enterexit。 举个最简单的 visitor 的例子:

const MyVisitor = {
  Identifier: {
    enter(path) {
      console.log("Entered!");
    },
    exit(path) {
      console.log("Exited!");
    }
  }
};

这个例子表示碰到遍历的时候,需要采取对应的处理。

不一定每次都要写 enter、exit,可以用如下形式,表示 enter 。

const MyVisitor = {
  Identifier(path) {
    console.log("Entered!");
  }
};

这里的 path 包含了一些结点和其他信息

{
  "parent": {...},
  "node": {...},
  "hub": {...},
  "contexts": [],
  "data": {},
  "shouldSkip": false,
  "shouldStop": false,
  "removed": false,
  "state": null,
  "opts": null,
  "skipKeys": null,
  "parentPath": null,
  "context": null,
  "container": null,
  "listKey": null,
  "inList": false,
  "parentKey": null,
  "key": null,
  "scope": null,
  "type": null,
  "typeAnnotation": null
}

下面我们来看看代码(请结合 astexplorer)

const traverse = require("babel-traverse").default;
const t = require("babel-types");

const myVisitor = {
  IfStatement(path) {
    let { consequent, alternate, test } = path.node;

    if(consequent && alternate){
      var consequentExp = consequent.body[0],
          alternateExp = alternate.body[0];

      if(t.isReturnStatement(consequentExp) 
        && t.isReturnStatement(alternateExp)){

        var node = t.returnStatement(
          t.conditionalExpression(
            test,
            consequentExp.argument,
            alternateExp.argument
          )
        ); 

        path.replaceWith(node);
      }
    }
  }
};

traverse(ast, myVisitor);

babel-types 提供了许多简便的函数来创建和判断结点。

generate

最后从 AST 转换回源代码

var generate = require("babel-generator").default;

console.log(generate(ast, {
  retainLines: false,
}).code);

更短的方式

var babel = require("babel-core");
var code = "...";

var result = babel.transform(code, {
  plugins: [{
    visitor: myVisitor
  }]
});
console.log(result.code);

参考

React Router

搭个开发框架熟悉下React,想配个路由可没那么轻松。

测试

你知道下面问题的答案吗?

  • react-router 怎么实现路由切换 ?
  • react-router 中的 <Link/><a/> 的区别 ?
  • blabla

初级阶段

最最最普通的路由配置,手工编码路由地址。

// index.js
ReactDOM.render(
  <BrowserRouter>
    <AppRouter />
  </BrowserRouter>,
  document.getElementById('root')
);

// router.js
const AppRouter = () => (
  <Switch>
      <Route path="/" exact component={Foo} />
      <Route path="/foo" component={Foo} />
      <Route path="/bar" component={Bar} />
  </Switch>
);

export default AppRouter;

动态加载代码

使用 react-loadable 包装返回类

export default Loadable({
  loader: () => import('./Foo'),
  loading: <div>loading...</div>
});

少造轮子

多用些基础库,少造轮子

warning
Similar to Facebook's (FB) invariant but only logs a warning if the condition is not met. This can be used to log issues in development environments in critical paths.

invariant
如名字所示,在关键地方提供个条件,来让

invariant(condition, '抛出异常如果条件错误');

debug
可以配合环境变量 DEBUG 来开启部分输出

var debug = require('debug')('http')
debug('booting %o', name);

ServiceWorker 挖个坑

没事找个课程学习下 The Benefits of Offline First

还挺受益的

Register

注册 ServiceWorker,注意默认路径能管的范围是 /path/

if (navigator.serviceWorker) {
  navigator.serviceWorker.register('/path/sw.js').then(...).catch(...)
}

Lifecyle

当修改 ServiceWorker 后,F5 后新更新的不会生效,必须等老的结束后才行。
可通过 DevTools 观察 Waiting 状态
一种刷新方法是转到其他页面,再转回来;另一种是按 Shift + 刷新 ;

Hijacking Requests

最暴力的劫持请求,参考学习下 Response 对象。

self.addEventListener('fetch', function(event) {
  event.respondWith(new Response('<div class="a-winner-is-me">666</div>', {
    headers: {
      'Content-Type': 'text/html;charset=utf-8'
    }
  }));
});

FetchEvent.respondWith 同时也接受 fetch 对象

Cache API

主要 API

  • cache.put(request, response)
  • cache.addAll([ '/foo', '/bar' ])
  • cache.match(request)
  • caches.match(request)

缓存时机 install

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('wittr-static-v1').then(function (cache) {
      return cache.addAll([
        '/',
        'js/main.js',
        'css/main.css',
      ]);
    })
  );
});

使用时机 fetch

self.addEventListener('fetch', function(event) {
  // respond with an entry from the cache if there is one.
  // If there isn't, fetch from the network.
  event.respondWith(
    caches.open('wittr-static-v1').then(function (cache) {
      return cache.match(event.request.url);
    }).then(response => {
      return response ? response : fetch(event.request);
    })
  );
});

Update Static

清理旧缓存

  • caches.delete(name)
  • caches.keys()

在获取不到新的SW时,旧的SW能够使用缓存;而当新的SW能运行时,干掉旧的缓存。
这个时机为 activate

玩玩 Elm

在 Redux 和 dvajs 的文档里都看到了 elm 语言的介绍,也许我可以在周末玩玩。

计划

  • 周五晚上:刷下 An Introduction to Elm

介绍了下怎么处理 side effects
qq

资源

Enzyme 入门教程

Enzyme 入门教程

Enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output.

Enzyme's API is meant to be intuitive and flexible by mimicking jQuery's API for DOM manipulation and traversal.

安装

Enzyme 本身安装

npm i --save-dev enzyme enzyme-adapter-react-16

另外还需要一个 Test runner, 这里以 Jest 为例:

首先安装依赖:

npm i --save-dev jest babel-jest babel-preset-env babel-preset-react
# 需要配置 .babelrc

然后需要配置 adapter。在 package.json 里加入

{
  "jest": {
    "setupTestFrameworkScriptFile": "<rootDir>src/setupTests.js"
  }
}

然后在 setupTests.js 文件里输入

// setup file
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

样例

测试 state

假设要测试一个非常傻瓜的组件,组件的功能是每次按下一个按钮,计数器加一。

// One.js
import React from 'react';

export default class One extends React.Component {
  state = {
    count: 0
  };

  add = () => {
    this.setState({count: this.state.count + 1});
  };

  render() {
    return (
      <button onClick={this.add}>Click me</button>
    );
  }
}

那么相应的测试代码如下

import React from 'react';
import One from './One';
import {shallow} from 'enzyme';

describe('test One', () => {
  it('should add 1', () => {
    // shallow: 只会创建第一层的组件
    const wrapper = shallow(<One />);

    // like jQuery selector
    wrapper.find('button').at(0).simulate('click');

    expect(wrapper.state('count')).toEqual(1);
  });
});

测试 props

通过 .prop() 不仅能读取属性,还能调用函数

import React from 'react';

export default class Two extends React.Component {
  state = {
    text: ''
  };

  change = e => {
    this.setState({ text: e.target.value || '' })
  };

  render() {
    return (
      <input onChange={this.change} />
    );
  }
}

那么我们怎么测试呢 ?

import React from 'react';
import Two from './Two';
import {shallow} from 'enzyme';

describe('test Two', () => {
  it('should update text', () => {
    // shallow: 只会创建第一层的组件
    const wrapper = shallow(<Two />);

    // like jQuery selector
    wrapper.find('input').at(0).prop('onChange')({target: {value: 'hello'}});

    expect(wrapper.state('text')).toEqual('hello');
  });
});

使用 Chrome 开发者工具定位前端内存泄漏

本文介绍如何使用 Chrome 的开发者工具(F12)来定位内存泄漏的问题。读本文前请确保有基础的 HTML、JavaScript 和垃圾回收的知识。

开发者工具简介

如下图所示,开发者工具里的 【Memory】选项卡里,有三个选项:
1)用来分析某个瞬间的内存
2)用来分析某段时间轴上的内存分配

一般用第二个来分析内存泄漏。使用方法非常简单,点击开始,然后果断时间按结束

QQ截图20190402205535

前端常见内存泄漏案例

全局对象

比如说误用了this,而存储到了 window 上

   function hello() {
      this.obj = [];
      for (var i = 0; i < 1000000; i++) {
        this.obj[i] = i;
      }
    }
    hello();

点击录制-结束,然后选择突出的那条线,定位结果。
QQ截图20190402212628

闭包引用

函数内引用了一个对象,而且这个函数一直在被使用

    (function() {
      var counter = new Array(4008123);
      var i = 0
      setInterval(() => {
        i += 1
        counter[i] = i;
      }, 1000);
    })()

方法同行,然后在多个地方都能观察到结果,甚至能定位到具体那行代码.
QQ截图20190402213658

DOM 引用和监听事件

JS代码里引用了 DOM 对象,然后删除 document 里的 DOM 后没有及时清除 JS 里的引用造成的内存泄漏。

    var doms = []
    for (var i = 0; i < 10000; i++) {
      var p = document.createElement('p')
      p.textContent = 'hello ' + i;
      doms.push(p)
      document.body.appendChild(p)
      document.body.removeChild(p)
    }

图片4

还有就是 DOM 对象上的监听函数使用不当。比如有一万个 <button /> ,然后每个上面都有不同的click事件,这留给大家作为课堂作业

webpack 热模块替换

探索下热模块更新(以下简称 HMR )原理,主要偏重开发者所能控制的部分,原理部分尽量减小。

入门资源

什么是 HMR

想想看以前每次改变代码以后,需要手动刷新页面查看效果,非常低效。后来出现的 live reload 改变了一点,但是每次刷新原来页面的状态就全部丢失了。
HMR 是一种在运行时动态改变模块代码的技术,可以理解为模块范围内的 live reload,这样的好处时可以保留页面的状态。

怎么利用 HMR

那怎么写热更新部分的代码呢?

参照下一节

其他思路

每次都要这么写也太折腾人了,而且不能指望所有用户都对 HMR 有了解。

dva.js 就用了一个 babel 插件 来自动替我们处理这些事情。(很有局限性)

浅谈 JavaScript 中的 this

浅谈 JavaScript 中的 this

本文不是来批判 this 的,只是用来帮助理解 this 的。多数内容都是从 《你不知道的JavaScript》 中学到的。结论请直接跳到最后一章。

入门

JavaScript 中的 this 是动态作用域(dynamic scope)的,而不是词法作用域(lexical scope)的。它们两个分别是什么意思呢?举个例子:

var a = 1; // 位置一
function foo() {
  console.log(a);
}

function bar() {
  var a = 2; // 位置二
  foo();
}

上面代码中的 foo 函数里有个变量 a ,当我们在查找 a 变量值的时候,会从这个函数定义的地方附近寻找,这里就是位置一, 这就是我们常说的词法作用域。而像 bar 函数里,调用 foo 函数,很明显结果还是输出 1。但如果此时是动态作用域,那么此时就会输出 2,也就是说从函数调用的地方附近寻找,这里就是位置二

所以正因为 this 是动态作用域的,所以它绑定的对象是在调用或运行时决定的。

影响 this 绑定的规则

那么在调用的时候由什么决定 this 的值呢 ? 参考下 ES5 的定义:

11.1.1 The this Keyword # Ⓣ Ⓡ Ⓖ
  The this keyword evaluates to the value of the ThisBinding of the current execution context.

10.4.1.1 Initial Global Execution Context # Ⓣ Ⓐ
  The following steps are performed to initialize a global execution context for ECMAScript code C:
  Set the VariableEnvironment to the Global Environment.
  Set the LexicalEnvironment to the Global Environment.
  Set the ThisBinding to the global object.

10.4.3 Entering Function Code # Ⓣ 
The following steps are performed when control enters the execution context for function code contained in function object F, a caller provided thisArg, and a caller provided argumentsList:
  If the function code is strict code, set the ThisBinding to thisArg.
  Else if thisArg is null or undefined, set the ThisBinding to the global object.
  Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).
  Else set the ThisBinding to thisArg.
  ...

11.2.3 Function Calls # Ⓣ 
  The production CallExpression : MemberExpression Arguments is evaluated as follows:
  Let ref be the result of evaluating MemberExpression.
  Let func be GetValue(ref).
  Let argList be the result of evaluating Arguments, producing an internal list of argument values (see 11.2.4).
  If Type(func) is not Object, throw a TypeError exception.
  If IsCallable(func) is false, throw a TypeError exception.
  If Type(ref) is Reference, then
  If IsPropertyReference(ref) is true, then
    Let thisValue be GetBase(ref).
  Else, the base of ref is an Environment Record
    Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
  Else, Type(ref) is not Reference.
    Let thisValue be undefined.

13.2.2 [[Construct]] # Ⓣ 
When the [[Construct]] internal method for a Function object F is called with a possibly empty list of arguments, the following steps are taken:
  Let obj be a newly created native ECMAScript object.
  Set all the internal methods of obj as specified in 8.12.
  Set the [[Class]] internal property of obj to "Object".
  Set the [[Extensible]] internal property of obj to true.
  Let proto be the value of calling the [[Get]] internal property of F with argument "prototype".
  If Type(proto) is Object, set the [[Prototype]] internal property of obj to proto.
  If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.
  Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
  If Type(result) is Object then return result.
  Return obj.

我知道你们也不会看的,直接看如下几种分类:

  • 默认情况
  • Function.prototype.apply 与 Function.prototype.call
  • new 调用
  • obj.method() 调用

具体如何绑定,参考结论。

结论

如果要判定一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面四条规则来判断 this 的绑定对象。

  1. 由 new 调用?绑定到新创建的对象。
  2. 有 call 或者 apply(或者 bind)调用?绑定到指定的对象。
  3. 由对象方法调用?绑定到被调用的对象上
  4. 默认情况下绑定到全局对象上。严格模式下绑定到 undefined

快速搭建JS开发环境

快速搭建个JS开发环境,包括 ESLint、webpack等等

ESLint

使用 airbnb 的

npx install-peerdeps --dev eslint-config-airbnb-base

然后添加.eslintrc到目录下:

{
  "extends": "airbnb-base",
  "rules": {
    "prefer-arrow-callback": 0,
    "func-names": 0
  }
}

React 进阶

进阶

接上篇 #54 ,入门好以后写一些普通页面没什么问题了。但是要写出可复用性高、易于维护的组件需要多多练习,除了看代码、写代码外没有什么别的捷径。

我会按难易度从小到大列出一些源代码:

  • react router:代码非常好懂,测试很友好。关键字:history,legacy context。
  • formik:表单组件,代码比较易读。关键字:TypeScript,Render Props,Context API。
  • swr:学习hooks写法
  • react redux:4.x版本还比较易懂,5.x后过于硬核,全是各种优化。

自己去玩玩看看

候选

访问 HTML 元素属性

访问HTML元素属性的方法

介绍下几种方式的区别

attributes 属性

一般 Element 都有这个属性,他是一个 NamedNodeMap 类型,结构类似于

var dom = document.getElementById('111');

console.log(dom.attributes);
// NamedNodeMap {0: id, 1: ok, id: id, ok: ok, length: 2}

// 通过以下方式遍历它
for (var i = 0; i < dom.attributes.length; i++) {
  console.log(dom.attributes[i].name + dom.attributes[i].value);
}

通过 setAttribute 属性来设置的值,会出现在 attributes 中

var shit = document.getElementById('shit');
shit.setAttribute('value', '10');
shit.setAttribute('WIDTH', 100);
shit.setAttribute('NAME-WANG', false);
console.log(shit.attributes);
// NamedNodeMap {0: id, 1: value, 2: width, 3: name-wang, id: id, value: value, width: width, name-wang: name-wang, length: 4}

getAttribute and setAttribute

权威指南上说这两个方法用于设置非标准的属性。(当然标准的也能访问,那么相较而言全用它不就好了么...)例如:

var image = document.images[0];
var width = parseInt(image.getAttribute('WIDTH'));
image.setAttribute('class', 'thumbnail');

和第三种方式来访问属性的区别:

  • 值为当作字符串
  • 属性名不区分大小写,最后会被转化成小写。

DOM 对象属性

HTMLElement 和其子类型定义了一些属性,他们对应于元素的标准 HTML 属性。
qq 20181110211448

直接修改值会出现在标签里,比如:

  var shit = document.getElementById('shit');
  shit.max = 20;
  shit.readOnly = true;
  shit._str = {hello: 2000} // 值可以是对象

qq 20181110211649

这种方式和其他的区别就是:

  • 某些关键字要换一下:比如 class => className, for => htmlFor
  • 区分大小写

JavaScript 中的对象

Built-in Objects

  • Intrinsic Objects
  • Native Objects

Instrinsic Objects

标准所定义的,在执行以前创建的对象实例。

  • isFinite, isNaN
  • encodeUri/decodeUri/encodeUriComponent/decodeUriComponent
  • String, Boolean, Object, Symbol, Number
  • Array, Date, Set, WeakSet, Map, WeakMap, Promise, RegExp, Function...
  • parseInt, parseFloat
  • Error, EvalError...
  • Uint8Array, Uint16Array...
  • JSON/Math/Reflect/Proxy

Native Objects

使用内置的构造函数(实现了[[constructor]]方法)创建的对象。常见的构造函数如:

  • Array
  • Function
  • RegExp
  • Date
  • Map, WeakMap, Set, WeakMap
  • Promise

比较好玩的是 Function ,咱们可以用 new Function 来创建函数对象 。

Reference

  • Winter 的 《重学前端》

使用 Chrome 开发者工具优化回流

从问题出发

在面试的时候有没有碰到过如下这类问题:

  • 居中为什么要使用transform(为什么不使用marginLeft/Top)
  • opacity: 0 与 visibility: 'hidden' 的区别

其实这几个问题跟浏览器的渲染有关

回流

回流/重排 (Reflow): 在网络浏览器中执行的一个流程,用于重新计算文档中各元素的位置和几何形状,以便重新呈现该文档的部分内容或全部内容

很多种用户操作和可能的 DHTML 更改都可触发重排。例如:调整浏览器窗口的大小、使用涉及计算出的样式的 JavaScript 方法、在 DOM 中添加或移除元素,以及更改某个元素的类,等等。另外值得注意的是,一些操作可能会导致重排用时比您想象的要长

Chrome 开发者工具

F12 > Performance > record > stop

实战

首先我们用 marginLeft 来让元素居中

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    #parent { width:400px;height:400px;border:1px solid greenyellow; }
    #gaga { width: 200px; height: 200px; background: coral; }
  </style>
</head>
<body>
  <div id="parent">
    <div id="gaga">Gaga</div>
  </div>
  <script>
    setTimeout(() => {
      var gaga = document.getElementById('gaga');
      gaga.style.marginLeft = '100px';
    }, 1500);
  </script>
</body>
</html>

打开调式工具,截图如下:
图片1

可以发现其中几个紫色和绿色的过程: Recalculate Style, Layout, Update Layer Tree, Paint, Composite Layer。其中 Layout 就是回流的计算。

然后我们把 marginLeft 替换成 transform

<style>
    #gaga { transform: translateX(0); ... }
</style>
<script>
    gaga.style.transform = 'translateX(100px)';
</script>

就会发现 Layout 过程没了
图片5

平时需要注意什么

  1. 不要循环设置 DOM 属性,批量更新
  2. 离线更新
  3. 用 translate 替代 top/left 等等

参考

谈谈 Redux saga

这个库好难懂,各种各样的名词。

参考资料

我发现别人几篇文章写的特别好...

预备知识

名称解释

  • Saga:源自 saga pattern , 在这里可以用 generator function 替换。
  • effect:An Effect is simply an object that contains some information to be interpreted by the middleware. You can view Effects like instructions to the middleware to perform some operation

generator

首先要懂什么是 generator function。写作 function *,调用会返回一个 iterator
iterator 有一个 next 方法,调用会返回 {value, done} 这样的东西。

function* generator(i) {
  yield i;
  yield i + 10;
}

var gen = generator(10) // 调用返回个 iterator
gen.next().value // 调用 iterator.next,返回第一个yield右边的10
gen.next().value // 再次 iterator.next,返回第二个yield右边的 i + 10

对于 generator 的理解

yield 后面跟的东西怎么处理,完全取决于环境。

  • 你可以什么也不干一路 iter.next(value) 下去,其实测试的时候会这么干
  • 也可以向 co 里那样将 promise / generator 处理完以后再继续下去,做出 async / await 那种效果
  • 也可以向 redux-saga 那样纯粹是一个 js Object ,让中间件里面去处理。

主要接口

saga 的 effect 可以分为两类:watcher 与 worker。

  1. watcher 负责监听 dispatch 出来的 action ,然后分发给 worker。
  2. worker 负责执行任务

注意每个 effect 都要在前面加个 yield:因为 effect 只是一个纯粹的 JavaScript 对象,它需要被中间件解释才有意义。

watcher 相关

take:监听某个动作

function *saga() {
  // take 监听某个动作
  // 后面接的方法可以是 generator func 或者 返回 promise 的函数
  yield take('FETCH_USER', fetchUser);
}

takeEvery:重复监听某个动作,就相当于

// 因为返回 fork 所以 yield takeEvery 不会阻塞
const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {
  while (true) {
    const action = yield take(patternOrChannel)
    yield fork(saga, ...args.concat(action))
  }
})

worker 相关

call:调用某个方法

function *saga() {
  // 方法可以是 generator func 或者 返回 promise 的函数
  // 会阻塞
  const response = yield call(fetchUser);
}

fork:和 call 的区别是前者为非阻塞的。

// 不会阻塞,返回值为task,用于取消任务
const task = yield fork(fetchApi);
yield take('CANCEL_FETCH');
yield cancel(task);

put:发出一个 action

yield put({ type: 'hello' })

select:获取 state

const state = yield select(state => state.p1)

浅谈原理

d3.js 入门

学习 d3.js 其实也是对数据可视化的入门,这个库里一些设计理念可以学习下。

比例尺

不要手动硬编码图的大小,而是通过比例尺来换算。
除了线性比例尺,还有指数、对数、量子比例尺等。

布局

可以理解为将数值转换为图形位置的函数。常用的布局有弧度布局、力图布局、树形布局,可以做一个桑吉图布局练习以下。

假设我们要渲染一个柱状图,输入是一组数组, [{name: 1, value: 100}, {name: 2, value: 200}, ...],
然后我们通过布局函数得到一组渲染用的数据。

export default function () {
  let values = [];

  function histo() {
    const result = [];
    let sumX = 0;
    values.forEach((value) => {
      result.push({
        x: sumX,
        y: value,
      });
      sumX += 50;
    });
    return result;
  }

  histo.values = function (_) {
    if (arguments.length) {
      values = _;
      return histo;
    }
    return values;
  };

  return histo;
}

使用方法

import histo from './layout.js';

var dataset = [{name: 1, value: 100}, {name: 2, value: 200}, ...];
var layout = histo().values(dataset);
var out = layout.histo();

不要把布局计算和渲染图的逻辑搅合在一起。

模块化

d3从第四版开始后就把代码模块化了。目前市面上的书籍和网上的教程有很多都是v3的代码,迁移过来需要一定的修改。具体改动参考文档 changelog

下面介绍下常用的几个模块

d3-array

提供了常用的如 d3.min, d3.max, d3.sum, d3.range 等数组操作

d3-path

提供了一些用于画图的工厂方法/

d3-selection

select , selectAll 等操作

d3-scale

各种比例尺

d3-shape

提供了各种layout:line, area, arc, pie, stack...

好例子

在理解了一些基本理念后可以直接看例子学习。

如何开发一个 Node.js CLI 程序

我指的 cli 程序定义为:能通过 npm install -g 安装,然后就像 shell 命令一样执行特定任务的程序。

好文章

流程

1 创建入口文件,第一行 shebang 不能省略

#!/usr/bin/env node
const [,, ...args] = process.argv;
console.log(`hello world gaga ${args}`);

2 在 package.json 里面添加

"bin": "./cli.js" 

3 在本地调试的时候运行 npm link,会在某个文件里创建一个软连接。 (结束后记得 npm unlink)

其他库

  • commander
  • chalk
  • inquirer

初探 G2

最近尝试把桑基图迁移到 G2 ,主要有以下这些好处:

  • 用现成的基础设施,如 tooltip 、事件处理等
  • 可以更好的与 React 集成(Bizcharts)

大概了解了下绘图需要涉及哪些接口:

  • 首先是数据处理,DataSet.View()Transform ,用来计算 node 和 edge 的位置信息; G2自带了一个 diagram.sankey 的 Transform 算法,如果不合适可以自己手写
  • 然后就是 chart.view() 接口,一张图表可以由多个 view 拼接起来; 比如桑基图就是由 NodeView 和 EdgeView 组合而成; 所以如果要在 Node 上再显示一些文字的话,只要继续添加 View 即可。

Babel.js 如何转化 class

Babel.js 如何转化 class

Basic Class

源代码来自 Chrome Samples

class Polygon {
  constructor(height, width) {
    this.name = 'Polygon';
    this.height = height;
    this.width = width;
  }

  sayName() {
    ChromeSamples.log('Hi, I am a ', this.name + '.');
  }

  sayHistory() {
    ChromeSamples.log('"Polygon" is derived from the Greek polus (many) ' +
      'and gonia (angle).');
  }

  static triple(n) {
    n = n || 1;
   	return n * 3;
  }	
}

上面的代码会被转译成:

var Polygon = (function() {
  function Polygon(height, width) {
    _classCallCheck(this, Polygon);

    this.name = "Polygon";
    this.height = height;
    this.width = width;
  }

  _createClass(
    Polygon,
    [{
      key: "sayName",
      value: function sayName() {
        ChromeSamples.log("Hi, I am a ", this.name + ".");
      }
    }, {
      key: "sayHistory",
      value: function sayHistory() {
        ChromeSamples.log(
          '"Polygon" is derived from the Greek polus (many) ' +
            "and gonia (angle)."
        );
      }
    }],
    [{
      key: "triple",
      value: function triple(n) {
        n = n || 1;
        return n * 3;
      }
    }]
  );

  return Polygon;
})();

阅读《重构》

感觉代码写的太“碎”,到处都是散落的 function ...
这周决定重读下《重构》,做一些笔记。

IndexedDB 有何用

这玩意就存储量大点咯?

入门教程

object stores

类似于 Table 。

var dbPromise = idb.open('test-db3', 1, function(upgradeDb) {
  if (!upgradeDb.objectStoreNames.contains('people')) {
    // define primary key
    upgradeDb.createObjectStore('people', {keyPath: 'email'});
  }
  if (!upgradeDb.objectStoreNames.contains('notes')) {
    // define primary key, auto increment
    upgradeDb.createObjectStore('notes', {autoIncrement: true});
  }
  if (!upgradeDb.objectStoreNames.contains('logs')) {
    // define primary key id, auto increment
    upgradeDb.createObjectStore('logs', {keyPath: 'id', autoIncrement: true});
  }
});

Indexes

索引......

// objectStore.createIndex('indexName', 'property', options)
var dbPromise = idb.open('test-db4', 1, function(upgradeDb) {
  if (!upgradeDb.objectStoreNames.contains('people')) {
    var peopleOS = upgradeDb.createObjectStore('people', {keyPath: 'email'});
    peopleOS.createIndex('gender', 'gender', {unique: false});
    peopleOS.createIndex('ssn', 'ssn', {unique: true});
  }
  if (!upgradeDb.objectStoreNames.contains('notes')) {
    var notesOS = upgradeDb.createObjectStore('notes', {autoIncrement: true});
    notesOS.createIndex('title', 'title', {unique: false});
  }
  if (!upgradeDb.objectStoreNames.contains('logs')) {
    var logsOS = upgradeDb.createObjectStore('logs', {keyPath: 'id',
      autoIncrement: true});
  }
});

翻翻 roadhog 源码

谈不上源码解读,仅仅说说学到的东西

1 Mock 接口

roadhog 依赖于 webpack-dev-server,然后 webpack-dev-server 底层使用了 express

1.1 添加路由规则

解析配置文件,逐条添加路由规则。

// .roadhogrc.mock.js
export default {
  // Support type as Object and Array
  'GET /api/users': { users: [1,2] },

  // Method like GET or POST can be omitted
  '/api/users/1': { id: 1 },

  // Support for custom functions, the API is the same as express@4
  'POST /api/users/create': (req, res) => { res.end('OK'); },
};

如上面这个文件,路由就会被转化为如下代码(略作简化):

  const config = getConfig();
  const mockRules = [];

  Object.keys(config).forEach(key => {
    const keyParsed = parseKey(key);

    mockRules.push({
      path: keyParsed.path,
      method: keyParsed.method,
      target: config[key],
    });
  });

  mockRules.forEach(mock => {
    app[mock.method](
      mock.path,
      createMockHandler(mock.method, mock.path, mock.target)
    )
  });

其他的也大致同理。

require.cache

这里有个细节, require 同一个文件会有缓存。可以操作 require.cache 来解决

function getConfig() {
  if (existsSync(configFile)) {
    // disable cache
    Object.keys(require.cache).forEach(file => {
      if (file === configFile) {
        debug(`delete cache ${file}`);
        delete require.cache[file];
      }
    });
    return require(configFile);
  } else {
    return {};
  }
}

1.2 重新刷新路由

文件监控,一旦发生变化就重新加载路由,这里涉及到动态修改 express 的路由。
可以用 app._router.stack 来操作路由对象

  const watcher = chokidar.watch(configFile, {
    ignored: /node_modules/,
    persistent: true
  });
  watcher.on('change', path => {
    console.log(chalk.green('CHANGED'), path);
    watcher.close();

    app._router.stack.splice(lastIndex + 1, mockRules.length + 2);

    applyMock(app);
  });

2 babel/register

让 Node 代码支持 ES6 语法。

const files = [
  'app.js',
  'mock.js',
  'route.config.js'
];

require('@babel/register')({
  only: [new RegExp(files.join('|'))],
  ignore: [/node_modules/]
});
require('./app.js');

另外 .babelrc 需要配好

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.