Git Product home page Git Product logo

__'s People

Contributors

noneven avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

yoghurtxu

__'s Issues

学习如何学习?

学习如何学习技术

实用型or理解深入型

前提: 兴趣+导师

  • 理解深入型:

    • 1、系统性的规划(规划学习进度)
    • 2、书籍/官方文档(en>cn)
    • 3、调试验证自己的想法(验证理解和想法是学习的必要部分)
    • 4、搜索引擎 问答社区(google/github/stackoverflow)
    • 5、不追求记住 而追求理解(学习是一个积累理解的过程,不是记忆的过程)
    • 6、以此为中心 扩散 相关知识
    • 7、回顾
  • 实用型

    • 1、scan API
    • 2、blog 总结
    • 3、笔记备忘
    • 4、实际demo

客户端离线存储方式汇总

客户端存储

客户端存储的种类

  • LocalStorage
  • SessionStorage
  • Cookie
  • Indexed DB
  • Web SQL
  • Application Cache
  • Service Worker
  • File System
  • Global Store
  • Cache Storage

Mozilla localForage

致力于简化web应用中离线数据存储过程的新型JavaScript库

介绍:

浏览器支持情况

存储类型 Safari Mobile Android Webview Chrome for Android W3C标准 size Async expires HTTP fomat 访问限制
LocalStorage iOS 3.2 Android 2.1 all Recommendation 5M No No No ArrayLike 当前域(不能跨域/可以跨tab)[可以通过postMessage实现跨域读取]
SessionStorage iOS 3.2 Android 2.1 all Recommendation 5M No session No ArrayLike 当前域(不能跨域/不可跨tab)
Cookie all all all Recommendation 4k No Yes Yes string 设置访问域(可跨子域)
Indexed DB iOS 8 Android 4.4 all Candidate Recommendation 5M Yes No No JSON 同localStorage
Web SQL iOS 9 Android 4.4 all Dropped 需向浏览器申请 Yes No No JSON 可跨域读写[危险]
Application Cache iOS 9 Android 4.4 all Recommendation 5M Yes No Yes File ?(应该有同源限制[JS动态更新])
Service Worker No Android 5.1 51 Working Draft Yes No Yes File
Cache Storage ? ? 40 Working Draft Yes No Yes URL/Response对
File System iOS 9 (Part) Android 4.4 (Part) 51 Working Draft 需向浏览器申请 Yes No No File

转载请注明出处

SpriteFlow:雪碧图工作流及Sprite插件

  • 工作中关于雪碧图的处理流程大致如下:
    spriteflow

一般的大厂UI都会把小图icon单独切出来,PSD上会有字体颜色以及间距的标注,就像这样: 所以上面的流程图UI直接给出了icon assets不用前端切icon图


uipic


uifile


uiicon

但是如果项目比较紧,PM会要UI尽快出图,所以UI就会少了标注和icon assets步骤(还有甚者UI不分组,不分组,不分组)。前端需要在PSD上把所有的icon和颜色字体以及位置都抓出来合成雪碧图,然后在我们的组件里面用到了的图片在雪碧图里面找出来,把图片的高度宽度以及位置量出来,写入css里面。展示到页面。

这个过程对我(PSD不熟,懒癌患者)来说简直了,所以用工具(程序员的工具(命令行))简化这个过程:sprite.plugin。

以上是个人理解,有误请指出
转载请注明出处

AST 抽象语法树学习

webpack 源码系列之 bundler 实现

webpack bundler 实现

前言

Q: 为什么我们要做这件事?
A: 太多小伙伴对 webpack 的认识都在 webpack.config.js 上,对 webpack 的使用也都是黑盒的,对其打包、loader、plugin等实现原理知之甚少

Q: webpack 现在如此庞大,应该如何着手?
A: 我们可以从 webpack 的第一个提交版本着手研究,它主要实现了 bundler和 loader,代码量很小,并且可读性很高 webpack 的第一个 commit

bundler 主要功能

  • 将多个符合 CommonJS 规范的模块打包成一个 JS 文件,使其可以运行在浏览器中。
  • 显然,浏览器没法直接执行 CommonJS 规范的模块,怎么办呢?我们可以将 module, module.exports, require 等函数在运行模块前定义好,各个模块分别调用执行逻辑
  • 一个打包后的 bundle.js 如下
/******/(function(modules) {
/******/	var installedModules = {};
/******/	function require(moduleId) {
/******/		if(installedModules[moduleId])
/******/			return installedModules[moduleId].exports;
/******/		var module = installedModules[moduleId] = {
/******/			exports: {}
/******/		};
/******/		modules[moduleId](module, module.exports, require);
/******/		return module.exports;
/******/	}
/******/	return require(0);
/******/})
/******/({
/******/0: function(module, exports, require) {

var a = require(/* ./a.js */1);
var b = require(/* ./b.js */2);
var luna = require(/* @alipay/luna-core */3);
a();
b();


/******/},
/******/
/******/1: function(module, exports, require) {

// module a

module.exports = function () {
    console.log('a')
};

/******/},
/******/
/******/2: function(module, exports, require) {

// module b

module.exports = function () {
    console.log('b')
};

/******/},
/******/
/******/3: function(module, exports, require) {

module.exports = {
    // ...
};

/******/},
/******/
/******/})

bundler 实现思路

分析 bundle.js,我们能够发现:

  • 1、不管有多少个模块,头部那一块都是一样的,它实现了 commonJS 的 module、module.exports、require 等函数,所以可以写成一个模板,也就是 webpack 里面的 templateSingle.js

  • 2、需要分析出各个模块间的依赖关系。也就是说,bundler 需要知道 example 依赖于 a、b 和 模块 luna。

  • 3、luna 模块位于 node_modules 文件夹当中,但是我们调用的时候却可以直接 require('@alipay/luna-core'),所以 bundler 肯定是存在某种自动查找的功能。

  • 4、在生成的 bundle.js 中,每个模块的唯一标识是模块的 ID,所以在拼接bundle.js 的时候,需要将每个模块的名字替换成模块的 ID

    // 转换前
    var a = require('./a.js');
    var b = require('./b.js');
    var luna = require('@alipay/luna-core');
    
    // 转换后
    var a = require(/* ./a.js */1);
    var b = require(/* ./b.js */2);
    var luna = require(/* @alipay/luna-core */3);
    

下面我们逐一分析一下上面的 4 各部分

1、头部模板

  • templateSingle

Q: 为什么叫 templateSingle?
A: 因为 webpack 在打包其他比如代码切割等时,头部模板会不一样,这儿为了区分,就叫 templateSingle 了,算是将所有的模块都打包到一个 JS 文件里面

// templateSingle
/******/(function(modules) {
/******/	var installedModules = {};
/******/	function require(moduleId) {
/******/		if(installedModules[moduleId])
/******/			return installedModules[moduleId].exports;
/******/		var module = installedModules[moduleId] = {
/******/			exports: {}
/******/		};
/******/		modules[moduleId](module, module.exports, require);
/******/		return module.exports;
/******/	}
/******/	return require(0);
/******/})

2、分析模块依赖

CommonJS 不同于 AMD,不会在模板定义时将所有依赖的声明。CommonJS 最显著的特征就是用到的时候再 require,所以我们得在整个文件的范围内查找依赖了哪些模块。

Q: 怎么在整个文件里面查找出依赖?
A: 正则匹配?

Q: 如果 require 是写在注释里面了怎么办?性能如何?
A: 正则行不通,我们可以采用 babel 等语言编译器的原理,将 JS 代码解析转换成 抽象语法树(AST),再对 AST 进行遍历,找到所有的 require 依赖。

Q: 如果模块 a 依赖 b,b 又依赖 c,然后 c 又依赖 d 这又怎么办?
A: 当解析 a 模块时,如果模块 a 中又 require 了其他模块,那么将继续解析依赖的模块。也就是说,总体上遵循深度优先遍历

  • 解析依赖具体实现:(详见 parse.js
/**
 * @file 解析模块依赖
 * @author chunk.cj
 */

const esprima = require('esprima');

/**
 * 解析模块包含的依赖
 * @param {string} source 模块内容字符串
 * @returns {object} module 解析模块得出的依赖关系
 */
module.exports = source => {
  const ast = esprima.parse(source, {
    range: true
  });
  const module = {};
  walkStatements(module, ast.body);
  module.source = source;
  return module;
};

/**
 * 遍历块中的语句
 * @param {object} module 模块对象
 * @param {object} statements AST语法树
 */
function walkStatements(module, statements) {
  statements.forEach(statement => walkStatement(module, statement));
}

/**
 * 分析每一条语句
 * @param {object} module 模块对象
 * @param {object} statement AST语法树
 */
function walkStatement(module, statement) {
  switch (statement.type) {
  case 'VariableDeclaration':
    if (statement.declarations) {
      walkVariableDeclarators(module, statement.declarations);
    }
    break;
  }
}

/**
 * 处理定义变量的语句
 * @param {object} module 模块对象
 * @param {object} declarators
 */
function walkVariableDeclarators(module, declarators) {
  declarators.forEach(declarator => {
    switch (declarator.type) {
    case 'VariableDeclarator':
      if (declarator.init) {
        walkExpression(module, declarator.init);
      }
      break;
    }
  });
}

/**
 * 处理表达式
 * @param {object} module  模块对象
 * @param {object} expression 表达式
 */
function walkExpression(module, expression) {
  switch (expression.type) {
  case 'CallExpression':
    // 处理普通的require
    if (expression.callee && expression.callee.name === 'require' && expression.callee.type === 'Identifier' && expression.arguments && expression.arguments.length === 1) {
      // TODO 此处还需处理require的计算参数
      module.requires = module.requires || [];
      const param = Array.from(expression.arguments)[0];
      module.requires.push({
        name: param.value,
        nameRange: param.range
      })
    }
    break;
  }
}

3、深度优先遍历构建依赖树:(详见 buildDeep.js

const fs = require('fs');
const path = require('path');
const parse = require('./parse');
const resolve = require('./resolve');

module.exports = async(mainModule, options) => {

  let depTree = {
    // 递增模块 id
    nextModuleId: 0,
    // 用于存储各个模块对象
    modules: {},
    // 用于映射模块名到模块 id 之间的关系
    mapModuleNameToId: {},
  };

  depTree = await parseModule(depTree, mainModule, options.context, options);
  return depTree;
};

const parseModule = async(depTree, moduleName, context, options) => {
  // 查找模块
  const absoluteFileName = resolve(moduleName, context, options.resolve);
  // 用模块的绝对路径作为模块的键值,保证唯一性
  module = depTree.modules[absoluteFileName] = {
    id: depTree.nextModuleId++,
    filename: absoluteFileName,
    name: moduleName
  };

  if (!absoluteFileName) {
    throw `找不到文件${absoluteFileName}`;
  }
  const source = fs.readFileSync(absoluteFileName).toString();
  const parsedModule = parse(source);

  module.requires = parsedModule.requires || [];
  module.source = parsedModule.source;

  // 写入映射关系
  depTree.mapModuleNameToId[moduleName] = depTree.nextModuleId - 1;

  // 如果此模块有依赖的模块,采取深度遍历的原则,遍历解析其依赖的模块
  const requireModules = parsedModule.requires;
  if (requireModules && requireModules.length > 0) {
    for (let require of requireModules) {
      depTree = await parseModule(depTree, require.name, path.dirname(absoluteFileName), options);
    }
  }
  return depTree;
}

4、模块寻址:(详见 resolve.js

简单的寻址方法

  • 如果给出的是绝对路径/相对路径,只查找一次。找到?返回绝对路径。找不到?返回 false。
  • 如果给出的是模块的名字,先在入口 js(example.js)文件所在目录下寻找同名JS文件(可省略扩展名)。找到?返回绝对路径。找不到?走第3步。
  • 在入口js(example.js)同级的 node_modules 文件夹(如果存在的话)查找。找到?返回绝对路径。找不到?返回 false。

这儿可以再考虑实现逐层往上查找 node_modules,可以参考 nodejs 默认的模块查找算法

/**
 * 查找模块所在绝对路径
 * @author chunk.cj
 */

const fs = require('fs');
const path = require('path');

// 判断给出的文件是否存在
const isFile = path => {
  try {
    const stats = fs.statSync(path);
    return stats && stats.isFile()
  } catch(e) {
    return false;
  }
};
const isDir = path => {
  try {
    const stats = fs.statSync(path);
    return stats && stats.isDirectory()
  } catch(e) {
    return false;
  }
};

/**
 * 根据模块的标志查找到模块的绝对路径
 * @param {string} moduleIdentifier 模块的标志,可能是模块名/相对路径/绝对路径
 * @param {string} context 上下文,入口 js 所在目录
 * @returns {string} 返回模块绝对路径
 */
module.exports = (moduleIdentifier, context, options) => {
  // 模块是绝对路径,只查找一次
  if (path.isAbsolute(moduleIdentifier)) {
    if (!path.extname(moduleIdentifier)) {
      moduleIdentifier += '.js';
    }
    if (isFile(moduleIdentifier)) {
      return moduleIdentifier;
    };
  } else if (moduleIdentifier.startsWith('./') || moduleIdentifier.startsWith('../')) {
    if (!path.extname(moduleIdentifier)) {
      moduleIdentifier += '.js';
    }
    moduleIdentifier = path.resolve(context, moduleIdentifier);
    if (isFile(moduleIdentifier)) {
      return moduleIdentifier;
    };
  } else {
    // 如果上述的方式都找不到,那么尝试在当前目录的 node_modules 里面找
    
    // 1、直接是node_modules文件夹下的文件
    if (isFile(path.resolve(context, './node_modules', moduleIdentifier))) {
      return moduleIdentifier;
    };
    if (isFile(path.resolve(context, './node_modules', `${moduleIdentifier}.js`))) {
      return `${moduleIdentifier}.js`;
    }

    // 2、node_modules 文件夹下的文件夹
    if (isDir(path.resolve(context, './node_modules', moduleIdentifier))) {
      var pkg = fs.readFileSync(path.resolve(context, './node_modules', moduleIdentifier, 'package.json'));
      var pkgJSON = JSON.parse(pkg);
      var main = path.resolve(context, './node_modules', moduleIdentifier, pkgJSON.main);
      if (isFile(main)) {
        return main;
      }
    } else {
      // 逐层往根目录查找
      const dirList = context.split('/');
      dirList.shift();
      while (dirList.length > 0) {
        dirList.pop();
        const dir = `/${dirList.join('/')}`;
        const moduleDir = path.resolve(dir, './node_modules', moduleIdentifier);

        if (isFile(moduleDir)) {
          return moduleDir;
        };
        if (isFile(`${moduleDir}.js`)) {
          return `${moduleDir}.js`;
        }
        if (isDir(moduleDir)) {
          // 解析 package.json 的 main 字段获取入口
          const pkg = fs.readFileSync(path.resolve(moduleDir, 'package.json'), 'utf-8');
          const pkgJSON = JSON.parse(pkg);
          const main = path.resolve(moduleDir, pkgJSON.main);
          if (isFile(main)) {
            return main;
          }
        }
      }
    }

    return moduleIdentifier;
  }
};

拼接 bundle:(详见 webpack.js

生成的 deepTree 如下:

{
  "nextModuleId": 5,
  "modules": {
    "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/entry.js": {
      "id": 0,
      "filename": "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/entry.js",
      "name": "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/entry.js",
      "requires": [
        {
          "name": "./a.js",
          "nameRange": [
            16,
            24
          ]
        },
        {
          "name": "./b.js",
          "nameRange": [
            43,
            51
          ]
        }
      ],
      "source": "var a = require('./a.js');\nvar b = require('./b.js');\n\na();\nb();"
    },
    "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/a.js": {
      "id": 1,
      "filename": "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/a.js",
      "name": "./a.js",
      "requires": [
        {
          "name": "./c.js",
          "nameRange": [
            16,
            24
          ]
        }
      ],
      "source": "var c = require('./c.js');\n\nmodule.exports = function() {\n  console.log('a');\n};"
    },
    "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/c.js": {
      "id": 4,
      "filename": "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/c.js",
      "name": "./c.js",
      "requires": [],
      "source": "module.exports = function() {\n  console.log('c');\n};"
    },
    "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/b.js": {
      "id": 3,
      "filename": "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/b.js",
      "name": "./b.js",
      "requires": [
        {
          "name": "./c.js",
          "nameRange": [
            16,
            24
          ]
        }
      ],
      "source": "var c = require('./c.js');\n\nmodule.exports = function() {\n  console.log('b');\n  c();\n};"
    }
  },
  "mapModuleNameToId": {
    "/Users/chunk/Alipay/github/webpack-bundler/example/bundle/entry.js": 0,
    "./a.js": 1,
    "./c.js": 4,
    "./b.js": 3
  }
}

循环遍历 modules,拼接 module 模块的 source,拼接结构如下:

/******/(function(modules) {
/******/  var installedModules = {};
/******/  function require(moduleId) {
/******/    if(installedModules[moduleId])
/******/      return installedModules[moduleId].exports;
/******/    var module = installedModules[moduleId] = {
/******/      exports: {}
/******/    };
/******/    modules[moduleId](module, module.exports, require);
/******/    return module.exports;
/******/  }
/******/  return require(0);
/******/})

// 上面是 templateSingle

({

// 入口
0: function(module, exports, require) {
  // 模块 source
},

// 模块
1: function(module, exports, require) {
  // 模块 source
},

// ...

// 结尾
});

具体实现如下:

const buffer = [];
const modules = deepTree.modules;
for(let moduleName in modules) {
  const module = modules[moduleName];
    
  buffer.push("/******/");
  buffer.push(module.id);
  buffer.push(": function(module, exports, require) {\n\n");

  buffer.push(writeSource(module, deepTree));
  buffer.push("\n\n/******/},\n/******/\n");
}

return buffer.join("");

我们发现模块 source 里面的模块名还需要替换成模块 id。具体实现如下:

module.exports = function(module, deepTree) {
  const source = module.source;
  if (!module.requires || !module.requires.length) {
    return source.split('\n').map(line => `  ${line}\n`).join('');
  }

  const replaces = [];
  module.requires.forEach(requireItem => {
    if(requireItem.nameRange && requireItem.name) {
      const prefix = `/* ${requireItem.name} */`;
      replaces.push({
        from: requireItem.nameRange[0],
        to: requireItem.nameRange[1],
        value: prefix + deepTree.mapModuleNameToId[requireItem.name]
      });
    }
  });

  const result = [source];
  //  模块替换算法: https://github.com/coderwin/__/issues/20
  replaces.sort((a, b) => b.from - a.from).forEach(replace => {
    const remSource = result.shift();
    result.unshift(
      remSource.substr(0, replace.from),
      replace.value,
      remSource.substr(replace.to)
    );
  });

  // 给每行加上两个空格
  return result.join('').split('\n').map(line => `  ${line}\n`).join('');
};

打包工具的 bundle 主流程

  • 1、入口文件路径生成、配置文件解析
  • 2、构建扁平依赖树
    • 初始化依赖树 deepTree
    • 读取文件 => 跑一边所有的 loader
    • 生成 AST
    • 根据 AST 解析模块依赖
    • 深度优先遍历将依赖的依赖
    • 将依赖扁平化到 deepTree,包含自增 id 和模块绝对路径
  • 3、plugin hook在整个主流程的各个操作

小团队的Github工作流

小团队的Github工作流

Flow流程图

zzflow

Flow解释:

  • 整体项目长期存在master和dev两个分支,master分支主要用来发布上线使用,dev主要是用来开发使用,同时隔离线上。
  • 在每个人开发的时候,有两种可能
    • 1、开发新需求(时间较长):这时从dev分支clone并创建一个基于功能的分支(futrue分支),如果几个人同时开发一个功能都在这个分支上开发,当开发工作完成后,提交本地仓库并git push到futrue分支(自测阶段),最后将futrue merge到dev分支提测。当测试没有问题的时候讲dev分支merge到master分支完成上线
    • 2、紧急bug修复(时间短):这时直接从master分支clone并创建一个基于bug的分支(bug_xxx分支),修复完bug后git push到bug_xxx分支提测(这里结合jenkins自动构建提测到不同于dev分支提测的服务器),测试没有问题后直接把bug_xxx分支merge到master分支完成上线。同时merge大dev分支完成master和dev的bug修复同步
  • PS: 这里不考虑在master下有多个不同的功能同时开发(多个future共存)[如果真出现这种情况,把这些功能合并开发]

转载请注明出处

Tapable API 文档及使用教程

Tapable API 文档及使用教程

Tapable(0.2.x)

使用

import Tapable from 'tapable';
//var Tapable = require('tapable');

Tapable 是一个用于事件发布订阅执行的插件架构。
在使用时你仅仅需要像这样继承它:

function MyClass() {
  Tapable.call(this);
}
MyClass.prototype = Object.create(Tapable.prototype);

// 或者这样
function MyClass2() {
  Tapable.call(this);
}
Tapable.mixin(MyClass2.prototype);

// 或者这样
class MyClass3 extends Tapable {
  constructor() {
    // TODO
  }
}

API

静态方法(Static functions)

  • mixin
void mixin(obj: Object)

复制 Tapable 的原型方法到目标对象

实例公共方法(Public functions)

  • apply

插件加载函数

void apply(plugin1: Plugin, plugin2: Plugin...)

通过 arguments 获得所有传入的插件对象,并调用插件对象的 apply 方法,注册插件(所以,一个合法的插件应该包含入口方法 apply)

  • plugin

事件绑定函数,类似于 EventEmitter 的 on 方法

void plugin(names: string|string[], handler: Function)

names: 需要监听的事件名称,可以传入事件名称集合(同时绑定多个事件),也可以传入单个事件名称
handler: 事件的处理函数

例子1:

var Tapable = require('tapable');

function Compiler(plugins) {
  Tapable.call(this);
  // 注册插件
  this.apply(...plugins);
  // 编译器在某个异步操作完成后触发事件
  setTimeout(() => {
    // 触发事件
    this.applyPlugins('compiled', 'test');
  }, 4000);
}
Compiler.prototype = Object.create(Tapable.prototype);

function CustomPlugin() {}
CustomPlugin.prototype.apply = function (compiler) {
  // 注册插件时会调用插件的 apply 方法,会执行绑定插件
  compiler.plugin('compiled', (arg) => {
    console.log('arg:', arg); // arg: test
  });
};

new Compiler([new CustomPlugin()]);

实例受保护方法(Protected functions)

  • applyPlugins

类似 EventEmitter 的 emit 方法,使用实例见例子1

void applyPlugins(name: string, args: any...)

触发事件 name,传入参数 args,并行调用所有注册在事件 name 上的处理函数。

  • applyPluginsWaterfall
any applyPluginsWaterfall(name: string, init: any, args: any...)

触发事件 name,串行的调用注册在事件 name 上的处理函数(先入先出),最先执行的处理函数传入 init 和 args,后续的处理函数传入前一个处理函数的返回值和 args,函数最终返回最后一个处理函数的返回结果
例子2:【后面的例子可以由这个例子修改观察结果】

var Tapable = require('tapable');

function Compiler(plugins) {
  Tapable.call(this);
  // 注册插件
  this.apply(...plugins);
  // 编译器在某个异步操作完成后触发事件
  setTimeout(() => {
    // 触发事件
    const afterPluginValue = this.applyPluginsWaterfall('compiled', 'InitValue');
    console.log('after all plugins value:', afterPluginValue);
  }, 4000);
}
Compiler.prototype = Object.create(Tapable.prototype);

function CustomPlugin() {}
CustomPlugin.prototype.apply = function (compiler) {
  // 注册插件时会调用插件的 apply 方法,会执行绑定插件
  compiler.plugin('compiled', (arg) => {
    console.log('in plugin value:', arg);
    // todo
    console.log('out plugin value:', 'PluginValue');
    return 'PluginValue';
  });
};
function CustomPlugin1() {}
CustomPlugin1.prototype.apply = function (compiler) {
  // 注册插件时会调用插件的 apply 方法,会执行绑定插件
  compiler.plugin('compiled', (arg) => {
    console.log('in plugin1 value::', arg);
    // todo
    console.log('out plugin1 value:', 'PluginValue1');
    return 'Plugin1Value';
  });
};

new Compiler([new CustomPlugin(), new CustomPlugin1()]);

// in plugin value: InitValue
// out plugin value: PluginValue
// in plugin1 value:: PluginValue
// out plugin1 value: PluginValue1
// after all plugins value: Plugin1Value
  • applyPluginsAsyncWaterfall
applyPluginsAsyncWaterfall(
  name: string,
  init: any,
  callback: (err: Error, result: any) -> void
)

触发事件 name,串行的调用注册在 name 上的处理函数(先入先出),第一个处理函数传入参数 init,后续的函数依赖于前一个函数执行回调的时候传入的参数nextValue,倘若某一个处理函数报错,则执行传入的 callback(err),后续的处理函数将不被执行,否则最后一个处理函数调用 callback(value)
注意:插件注册此类事件,处理函数需要调用 callback(err, nextValue),这样才能保证监听链的正确执行

var myPlugin = function() {}
myPlugin.prototype.apply(tapable) {
  tapable.plugin('name', function(arg1, ..., argn, callback)) {
    // todo
    // ...
    // 调用 callback
    callback(null, value);
  }
}
  • applyPluginsAsync = applyPluginsAsyncSeries
void applyPluginsAsync(
  name: string,
  args: any...,
  callback: (err?: Error) -> void
)

触发事件 name,串行的调用注册在事件 name 上的处理函数(先入先出),倘若某一个处理函数报错,则执行传入的 callback(err),后续的处理函数将不被执行,否则最后一个处理函数调用 callback。
注意:插件注册此类事件,处理函数需要调用callback,这样才能保证监听链的正确执行

var myPlugin = function() {}
myPlugin.prototype.apply(tapable) {
  tapable.plugin('name', function(arg1, ..., argn, callback)) {
    // todo
    // ...
    // 调用callback
    callback();
  }
}
  • applyPluginsBailResult
any applyPluginsBailResult(name: string, args: any...)

触发事件 name,串行的调用注册在事件 name 上的处理函数(先入先出),传入参数 args,如果其中一个处理函数返回值 !== undefined,直接返回这个返回值,后续的处理函数将不被执行

  • applyPluginsParallel
applyPluginsParallel(
  name: string,
  args: any...,
  callback: (err?: Error) -> void
)

触发事件 name,传入参数 args,并行的调用所有注册在事件 name 上的处理函数,倘若任一处理函数执行报错,则执行 callback('err'),否则当所有的处理函数都执行完的时候调用 callback()

同样,插件注册此类事件的时候,回调函数要执行 callback

  • applyPluginsParallelBailResult
applyPluginsParallelBailResult(
  name: string,
  args: any...,
  callback: (err: Error, result: any) -> void
)

触发事件 name,串行的执行注册在事件 name 上的处理函数(先入先出),每个处理函数必须调用 callback(err, result),倘若任一处理函数在调用callback(err, result) 的时候,err !== undefined || result !== undefined,则 callback 将真正被执行,后续的处理函数则不会再被执行。

call和apply的性能对比

以前看jQuery源码的时候有看到在源码的注释中有些过call的性能会比apply好,在lodash的源码中也同样的发现有call比apply性能更好的注释,这里我在jsperf上写了几个test case,验证了一下确实call比apply的性能更好。

  • 0、lodash源码apply方法重写
  • 1、无指向无参数对比:
  • 2、有指向无参数对比:
  • 3、无参数有指向:
  • 4、有参数有指向对比:

总结: 在我们平时的开发中其实不必关注call和apply的性能问题,但是可以尽可能的去用call,特别是es6的reset解构的支持,call基本可以代替apply,可以看出lodash源码里面并没有直接用Function.prototype.apply,而是在参数较少(1-3)个时采用call的方式调用(因为lodash里面没有超过4个参数的方法,PS如果一个函数的设计超过4个入参,那么这个函数就要考虑重构了)

转载请注明出处

企业前端项目的持续集成

在说jenkins之前,先介绍一下前端开源项目的持续集成方案:

比较认可的是(Mocha+Chai)+Travis CI+Coveralls+SauceLabs。

  • 1、Mocha+Chai作单元测试,
  • 2、Travis CI作项目持续集成,
  • 3、Coveralls作测试代码覆盖率,
  • 4、SauceLabs作跨浏览器集成测试。

也就是大家在github上看到的大部分项目的持续集成方案(从各种徽章看)。但是在企业的前端项目中一般不采用Travis CI作为持续集成,而是采用Jenkins,几个重要的原因如下:

  • 1、Travis CI和github强绑定,但是国内还有很多公司采用的SVN作为版本管理工具
  • 2、Travis CI不提供独立部署的CI server,而Jenkins可以独立部署
  • 3、Travis CI不可定制,不像Jenkins一样提供各种插件扩展
  • 4、价格没有优势
    所以大家都不约而同的采用了Jenkins作为企业应用的持续集成工具

再说说一般团队的workflow:

  • 1、gulp flow/webpack flow/fis flow + plug-ins开发
    • 集成自动化流程
      • Less / Sass -> CSS 编译
      • CSS**** Autoprefixer 前缀自动补全
      • 自动生成图片 CSS 属性,width & height 等
      • CSS cssnano 压缩
      • CSS Sprite 雪碧图合成
      • Retina @2x & @3x 自动生成适配
      • Imagemin 图片压缩
      • px -> rem 兼容适配方案
      • 智能 WebP 解决方案
      • 去缓存文件 Reversion (MD5) 解决方案
      • ES6/7的babel编译
      • JS 合并压缩按依赖加载
  • 2、eslint作为代码检查工具
  • 3、mocha+chai单元测试(不过很少有团队把这个作为项目的必须流程)
  • 4、gitlab管理项目代码
  • 5、gitlab hook+Jenkins自动部署dev/test/prod

对于1-4步都在前端开发环境中完成,很多团队都集成到一个命令行工具完成(比如tmt-workflow),也有用Electron做成桌面可视化工具(比如weFlow),第五步需要进行(权限管理),开发自测时在dev服务器上,提测QA时部署到测试服务器,上线部署到线上服务器。
可以看到Jenkins在持续集成的过程中作用和意义重大

转载请注明出处

webpack 替换模块算法

let source = `require('a');console.log(123);require('b');`
// source => AST => replaces
let replaces = [{
  from: 8,
  to: 11,
  name: 'a',
  value: 1
}, {
  from: 38,
  to: 41,
  name: 'b',
  value: 2
}];
// 期望输出
'require(/* a */1);console.log(123);require(/* b */2);'

Eslint规则Rules详解

{
  ///////////////
  // 可能的错误 //
  ////////////////

  // 禁止条件表达式中出现赋值操作符
  "no-cond-assign": 2,
  // 禁用 console
  "no-console": 0,
  // 禁止在条件中使用常量表达式
  // if (false) {
  // doSomethingUnfinished();
  // } //cuowu
  "no-constant-condition": 2,
  // 禁止在正则表达式中使用控制字符 :new RegExp("\x1f")
  "no-control-regex": 2,
  // 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,
  // always-multiline:多行模式必须带逗号,单行模式不能带逗号
  "comma-dangle": [1, "always-multiline"],
  // 禁用 debugger
  "no-debugger": 2,
  // 禁止 function 定义中出现重名参数
  "no-dupe-args": 2,
  // 禁止对象字面量中出现重复的 key
  "no-dupe-keys": 2,
  // 禁止重复的 case 标签
  "no-duplicate-case": 2,
  // 禁止空语句块
  "no-empty": 2,
  // 禁止在正则表达式中使用空字符集 (/^abc[]/)
  "no-empty-character-class": 2,
  // 禁止对 catch 子句的参数重新赋值
  "no-ex-assign": 2,
  // 禁止不必要的布尔转换
  "no-extra-boolean-cast": 2,
  // 禁止不必要的括号 //(a * b) + c;//报错
  "no-extra-parens": 0,
  // 禁止不必要的分号
  "no-extra-semi": 2,
  // 禁止对 function 声明重新赋值
  "no-func-assign": 2,
  // 禁止在嵌套的块中出现 function 或 var 声明
  "no-inner-declarations": [2, "functions"],
  // 禁止 RegExp 构造函数中无效的正则表达式字符串
  "no-invalid-regexp": 2,
  // 禁止在字符串和注释之外不规则的空白
  "no-irregular-whitespace": 2,
  // 禁止在 in 表达式中出现否定的左操作数
  "no-negated-in-lhs": 2,
  // 禁止把全局对象 (Math 和 JSON) 作为函数调用 错误:var math = Math();
  "no-obj-calls": 2,
  // 禁止直接使用 Object.prototypes 的内置属性
  "no-prototype-builtins": 0,
  // 禁止正则表达式字面量中出现多个空格
  "no-regex-spaces": 2,
  // 禁用稀疏数组
  "no-sparse-arrays": 2,
  // 禁止出现令人困惑的多行表达式
  "no-unexpected-multiline": 2,
  // 禁止在return、throw、continue 和 break语句之后出现不可达代码
  /*
   function foo() {
   return true;
   console.log("done");
   }//错误
   */
  "no-unreachable": 2,
  // 要求使用 isNaN() 检查 NaN
  "use-isnan": 2,
  // 强制使用有效的 JSDoc 注释
  "valid-jsdoc": 1,
  // 强制 typeof 表达式与有效的字符串进行比较
  // typeof foo === "undefimed" 错误
  "valid-typeof": 2,

  //////////////
  // 最佳实践 //
  //////////////

  // 定义对象的set存取器属性时,强制定义get
  "accessor-pairs": 2,
  // 强制数组方法的回调函数中有 return 语句
  "array-callback-return": 0,
  // 强制把变量的使用限制在其定义的作用域范围内
  "block-scoped-var": 0,
  // 限制圈复杂度,也就是类似if else能连续接多少个
  "complexity": [2, 9],
  // 要求 return 语句要么总是指定返回的值,要么不指定
  "consistent-return": 0,
  // 强制所有控制语句使用一致的括号风格
  "curly": [2, "all"],
  // switch 语句强制 default 分支,也可添加 // no default 注释取消此次警告
  "default-case": 2,
  // 强制object.key 中 . 的位置,参数:
  // property,'.'号应与属性在同一行
  // object, '.' 号应与对象名在同一行
  "dot-location": [2, "property"],
  // 强制使用.号取属性
  // 参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性
  // false 使用保留字做属性名时, 只能使用[]方式取属性 e.g [2, {"allowKeywords": false}]
  // allowPattern: 当属性名匹配提供的正则表达式时,允许使用[]方式取值,否则只能用.号取值 e.g [2, {"allowPattern": "^[a-z]+(_[a-z]+)+$"}]
  "dot-notation": [2, {"allowKeywords": false}],
  // 使用 === 替代 == allow-null允许null和undefined==
  "eqeqeq": [2, "allow-null"],
  // 要求 for-in 循环中有一个 if 语句
  "guard-for-in": 2,
  // 禁用 alert、confirm 和 prompt
  "no-alert": 0,
  // 禁用 arguments.caller 或 arguments.callee
  "no-caller": 2,
  // 不允许在 case 子句中使用词法声明
  "no-case-declarations": 2,
  // 禁止除法操作符显式的出现在正则表达式开始的位置
  "no-div-regex": 2,
  // 禁止 if 语句中有 return 之后有 else
  "no-else-return": 0,
  // 禁止出现空函数.如果一个函数包含了一条注释,它将不会被认为有问题。
  "no-empty-function": 2,
  // 禁止使用空解构模式no-empty-pattern
  "no-empty-pattern": 2,
  // 禁止在没有类型检查操作符的情况下与 null 进行比较
  "no-eq-null": 1,
  // 禁用 eval()
  "no-eval": 2,
  // 禁止扩展原生类型
  "no-extend-native": 2,
  // 禁止不必要的 .bind() 调用
  "no-extra-bind": 2,
  // 禁用不必要的标签
  "no-extra-label:": 0,
  // 禁止 case 语句落空
  "no-fallthrough": 2,
  // 禁止数字字面量中使用前导和末尾小数点
  "no-floating-decimal": 2,
  // 禁止使用短符号进行类型转换(!!fOO)
  "no-implicit-coercion": 0,
  // 禁止在全局范围内使用 var 和命名的 function 声明
  "no-implicit-globals": 1,
  // 禁止使用类似 eval() 的方法
  "no-implied-eval": 2,
  // 禁止 this 关键字出现在类和类对象之外
  "no-invalid-this": 0,
  // 禁用 __iterator__ 属性
  "no-iterator": 2,
  // 禁用标签语句
  "no-labels": 2,
  // 禁用不必要的嵌套块
  "no-lone-blocks": 2,
  // 禁止在循环中出现 function 声明和表达式
  "no-loop-func": 1,
  // 禁用魔术数字(3.14什么的用常量代替)
  "no-magic-numbers":[1, {"ignore": [0, -1, 1] }],
  // 禁止使用多个空格
  "no-multi-spaces": 2,
  // 禁止使用多行字符串,在 JavaScript 中,可以在新行之前使用斜线创建多行字符串
  "no-multi-str": 2,
  // 禁止对原生对象赋值
  "no-native-reassign": 2,
  // 禁止在非赋值或条件语句中使用 new 操作符
  "no-new": 2,
  // 禁止对 Function 对象使用 new 操作符
  "no-new-func": 0,
  // 禁止对 String,Number 和 Boolean 使用 new 操作符
  "no-new-wrappers": 2,
  // 禁用八进制字面量
  "no-octal": 2,
  // 禁止在字符串中使用八进制转义序列
  "no-octal-escape": 2,
  // 不允许对 function 的参数进行重新赋值
  "no-param-reassign": 0,
  // 禁用 __proto__ 属性
  "no-proto": 2,
  // 禁止使用 var 多次声明同一变量
  "no-redeclare": 2,
  // 禁用指定的通过 require 加载的模块
  "no-return-assign": 0,
  // 禁止使用 javascript: url
  "no-script-url": 0,
  // 禁止自我赋值
  "no-self-assign": 2,
  // 禁止自身比较
  "no-self-compare": 2,
  // 禁用逗号操作符
  "no-sequences": 2,
  // 禁止抛出非异常字面量
  "no-throw-literal": 2,
  // 禁用一成不变的循环条件
  "no-unmodified-loop-condition": 2,
  // 禁止出现未使用过的表达式
  "no-unused-expressions": 0,
  // 禁用未使用过的标签
  "no-unused-labels": 2,
  // 禁止不必要的 .call() 和 .apply()
  "no-useless-call": 2,
  // 禁止不必要的字符串字面量或模板字面量的连接
  "no-useless-concat": 2,
  // 禁用不必要的转义字符
  "no-useless-escape": 0,
  // 禁用 void 操作符
  "no-void": 0,
  // 禁止在注释中使用特定的警告术语
  "no-warning-comments": 0,
  // 禁用 with 语句
  "no-with": 2,
  // 强制在parseInt()使用基数参数
  "radix": 2,
  // 要求所有的 var 声明出现在它们所在的作用域顶部
  "vars-on-top": 0,
  // 要求 IIFE 使用括号括起来
  "wrap-iife": [2, "any"],
  // 要求或禁止 “Yoda” 条件
  "yoda": [2, "never"],
  // 要求或禁止使用严格模式指令
  "strict": 0,

  //////////////
  // 变量声明 //
  //////////////

  // 要求或禁止 var 声明中的初始化(初值)
  "init-declarations": 0,
  // 不允许 catch 子句的参数与外层作用域中的变量同名
  "no-catch-shadow": 0,
  // 禁止删除变量
  "no-delete-var": 2,
  // 不允许标签与变量同名
  "no-label-var": 2,
  // 禁用特定的全局变量
  "no-restricted-globals": 0,
  // 禁止 var 声明 与外层作用域的变量同名
  "no-shadow": 0,
  // 禁止覆盖受限制的标识符
  "no-shadow-restricted-names": 2,
  // 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
  "no-undef": 2,
  // 禁止将变量初始化为 undefined
  "no-undef-init": 2,
  // 禁止将 undefined 作为标识符
  "no-undefined": 0,
  // 禁止出现未使用过的变量
  "no-unused-vars": [2, {"vars": "all", "args": "none"}],
  // 不允许在变量定义之前使用它们
  "no-use-before-define": 0,

  //////////////////////////
  // Node.js and CommonJS //
  //////////////////////////

  // require return statements after callbacks
  "callback-return": 0,
  // 要求 require() 出现在顶层模块作用域中
  "global-require": 1,
  // 要求回调函数中有容错处理
  "handle-callback-err": [2, "^(err|error)$"],
  // 禁止混合常规 var 声明和 require 调用
  "no-mixed-requires": 0,
  // 禁止调用 require 时使用 new 操作符
  "no-new-require": 2,
  // 禁止对 __dirname 和 __filename进行字符串连接
  "no-path-concat": 0,
  // 禁用 process.env
  "no-process-env": 0,
  // 禁用 process.exit()
  "no-process-exit": 0,
  // 禁用同步方法
  "no-sync": 0,

  //////////////
  // 风格指南 //
  //////////////

  // 指定数组的元素之间要以空格隔开(, 后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
  "array-bracket-spacing": [2, "never"],
  // 禁止或强制在单行代码块中使用空格(禁用)
  "block-spacing":[1, "never"],
  //强制使用一致的缩进 第二个参数为 "tab" 时,会使用tab,
  // if while function 后面的{必须与if在同一行,java风格。
  "brace-style": [2, "1tbs", {"allowSingleLine": true}],
  // 双峰驼命名格式
  "camelcase": 2,
  // 控制逗号前后的空格
  "comma-spacing": [2, {"before": false, "after": true}],
  // 控制逗号在行尾出现还是在行首出现 (默认行尾)
  // http://eslint.org/docs/rules/comma-style
  "comma-style": [2, "last"],
  //"SwitchCase" (默认:0) 强制 switch 语句中的 case 子句的缩进水平
  // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
  "computed-property-spacing": [2, "never"],
  // 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了
  // e.g [0,"that"] 指定只能 var that = this. that不能指向其他任何值,this也不能赋值给that以外的其他值
  "consistent-this": [1, "that"],
  // 强制使用命名的 function 表达式
  "func-names": 0,
  // 文件末尾强制换行
  "eol-last": 2,
  "indent": [2, 4, {"SwitchCase": 1}],
  // 强制在对象字面量的属性中键和值之间使用一致的间距
  "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
  // 强制使用一致的换行风格
  "linebreak-style": [1, "unix"],
  // 要求在注释周围有空行 ( 要求在块级注释之前有一空行)
  "lines-around-comment": [1, {"beforeBlockComment": true}],
  // 强制一致地使用函数声明或函数表达式,方法定义风格,参数:
  // declaration: 强制使用方法声明的方式,function f(){} e.g [2, "declaration"]
  // expression:强制使用方法表达式的方式,var f = function() {} e.g [2, "expression"]
  // allowArrowFunctions: declaration风格中允许箭头函数。 e.g [2, "declaration", { "allowArrowFunctions": true }]
  "func-style": 0,
  // 强制回调函数最大嵌套深度 5层
  "max-nested-callbacks": [1, 5],
  // 禁止使用指定的标识符
  "id-blacklist": 0,
  // 强制标识符的最新和最大长度
  "id-length": 0,
  // 要求标识符匹配一个指定的正则表达式
  "id-match": 0,
  // 强制在 JSX 属性中一致地使用双引号或单引号
  "jsx-quotes": 0,
  // 强制在关键字前后使用一致的空格 (前后腰需要)
  "keyword-spacing": 2,
  // 强制一行的最大长度
  "max-len":[1, 200],
  // 强制最大行数
  "max-lines": 0,
  // 强制 function 定义中最多允许的参数数量
  "max-params":[1, 7],
  // 强制 function 块最多允许的的语句数量
  "max-statements":[1, 200],
  // 强制每一行中所允许的最大语句数量
  "max-statements-per-line": 0,
  // 要求构造函数首字母大写 (要求调用 new 操作符时有首字母大小的函数,允许调用首字母大写的函数时没有 new 操作符。)
  "new-cap": [2, {"newIsCap": true, "capIsNew": false}],
  // 要求调用无参构造函数时有圆括号
  "new-parens": 2,
  // 要求或禁止 var 声明语句后有一行空行
  "newline-after-var": 0,
  // 禁止使用 Array 构造函数
  "no-array-constructor": 2,
  // 禁用按位运算符
  "no-bitwise": 0,
  // 要求 return 语句之前有一空行
  "newline-before-return": 0,
  // 要求方法链中每个调用都有一个换行符
  "newline-per-chained-call": 1,
  // 禁用 continue 语句
  "no-continue": 0,
  // 禁止在代码行后使用内联注释
  "no-inline-comments": 0,
  // 禁止 if 作为唯一的语句出现在 else 语句中
  "no-lonely-if": 0,
  // 禁止混合使用不同的操作符
  "no-mixed-operators": 0,
  // 不允许空格和 tab 混合缩进
  "no-mixed-spaces-and-tabs": 2,
  // 不允许多个空行
  "no-multiple-empty-lines": [2, {"max": 2}],
  // 不允许否定的表达式
  "no-negated-condition": 0,
  // 不允许使用嵌套的三元表达式
  "no-nested-ternary": 0,
  // 禁止使用 Object 的构造函数
  "no-new-object": 2,
  // 禁止使用一元操作符 ++ 和 --
  "no-plusplus": 0,
  // 禁止使用特定的语法
  "no-restricted-syntax": 0,
  // 禁止 function 标识符和括号之间出现空格
  "no-spaced-func": 2,
  // 不允许使用三元操作符
  "no-ternary": 0,
  // 禁用行尾空格
  "no-trailing-spaces": 2,
  // 禁止标识符中有悬空下划线_bar
  "no-underscore-dangle": 0,
  // 禁止可以在有更简单的可替代的表达式时使用三元操作符
  "no-unneeded-ternary": 2,
  // 禁止属性前有空白
  "no-whitespace-before-property": 0,
  // 强制花括号内换行符的一致性
  "object-curly-newline": 0,
  // 强制在花括号中使用一致的空格
  "object-curly-spacing": 0,
  // 强制将对象的属性放在不同的行上
  "object-property-newline": 0,
  // 强制函数中的变量要么一起声明要么分开声明
  "one-var": [2, {"initialized": "never"}],
  // 要求或禁止在 var 声明周围换行
  "one-var-declaration-per-line": 0,
  // 要求或禁止在可能的情况下要求使用简化的赋值操作符
  "operator-assignment": 0,
  // 强制操作符使用一致的换行符
  "operator-linebreak": [2, "after", {"overrides": {"?":"before", ":": "before"}}],
  // 要求或禁止块内填充
  "padded-blocks": 0,
  // 要求对象字面量属性名称用引号括起来
  "quote-props": 0,
  // 强制使用一致的反勾号、双引号或单引号
  "quotes": [2, "single", "avoid-escape"],
  // 要求使用 JSDoc 注释
  "require-jsdoc": 1,
  // 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,)
  "semi": [2, "always"],
  // 强制分号之前和之后使用一致的空格
  "semi-spacing": 0,
  // 要求同一个声明块中的变量按顺序排列
  "sort-vars": 0,
  // 强制在块之前使用一致的空格
  "space-before-blocks": [2, "always"],
  // 强制在 function的左括号之前使用一致的空格
  "space-before-function-paren": [2, "always"],
  // 强制在圆括号内使用一致的空格
  "space-in-parens": [2, "never"],
  // 要求操作符周围有空格
  "space-infix-ops": 2,
  // 强制在一元操作符前后使用一致的空格
  "space-unary-ops": [2, {"words": true, "nonwords": false}],
  // 强制在注释中 // 或 /* 使用一致的空格
  "spaced-comment": [2, "always", {"markers": ["global", "globals", "eslint", "eslint-disable", "*package","!"] }],
  // 要求或禁止 Unicode BOM
  "unicode-bom": 0,
  // 要求正则表达式被括号括起来
  "wrap-regex": 0,
  
  //////////////
  // ES6.相关 //
  //////////////

  // 要求箭头函数体使用大括号
  "arrow-body-style": 2,
  // 要求箭头函数的参数使用圆括号
  "arrow-parens": 2,
  "arrow-spacing":[2, {"before": true, "after": true}],
  // 强制在子类构造函数中用super()调用父类构造函数,TypeScrip的编译器也会提示
  "constructor-super": 0,
  // 强制 generator 函数中 * 号周围使用一致的空格
  "generator-star-spacing": [2, {"before": true, "after": true}],
  // 禁止修改类声明的变量
  "no-class-assign": 2,
  // 不允许箭头功能,在那里他们可以混淆的比较
  "no-confusing-arrow": 0,
  // 禁止修改 const 声明的变量
  "no-const-assign": 2,
  // 禁止类成员中出现重复的名称
  "no-dupe-class-members": 2,
  // 不允许复制模块的进口
  "no-duplicate-imports": 0,
  // 禁止 Symbol 的构造函数
  "no-new-symbol": 2,
  // 允许指定模块加载时的进口
  "no-restricted-imports": 0,
  // 禁止在构造函数中,在调用 super() 之前使用 this 或 super
  "no-this-before-super": 2,
  // 禁止不必要的计算性能键对象的文字
  "no-useless-computed-key": 0,
  // 要求使用 let 或 const 而不是 var
  "no-var": 0,
  // 要求或禁止对象字面量中方法和属性使用简写语法
  "object-shorthand": 0,
  // 要求使用箭头函数作为回调
  "prefer-arrow-callback": 0,
  // 要求使用 const 声明那些声明后不再被修改的变量
  "prefer-const": 0,
  // 要求在合适的地方使用 Reflect 方法
  "prefer-reflect": 0,
  // 要求使用扩展运算符而非 .apply()
  "prefer-spread": 0,
  // 要求使用模板字面量而非字符串连接
  "prefer-template": 0,
  // Suggest using the rest parameters instead of arguments
  "prefer-rest-params": 0,
  // 要求generator 函数内有 yield
  "require-yield": 0,
  // enforce spacing between rest and spread operators and their expressions
  "rest-spread-spacing": 0,
  // 强制模块内的 import 排序
  "sort-imports": 0,
  // 要求或禁止模板字符串中的嵌入表达式周围空格的使用
  "template-curly-spacing": 1,
  // 强制在 yield* 表达式中 * 周围使用空格
  "yield-star-spacing": 2
}

异步流程控制器pjs的渐进式实现

异步流程控制器实现

//最终效果:
pjs.when(["A","B"],(val)=>{
	console.log(val);
});
pjs.when(["A","C"],(val)=>{
	console.log(val);
});
setTimeout(()=>{
	pjs.trigger("A", "dataA");
},1000)
setTimeout(()=>{
	pjs.trigger("B", "dataB");
},2000)
setTimeout(()=>{
	pjs.trigger("C", "dataC");
},3000)

step1 简版实现

思路:调用when的时候将异步任务(A B)用数组存起来,并将异步回调用callback存储;然后当A/B异步任务完成时调用trigger方法把异步任务数组里面的A/B删除,并且检测异步任务队列是否为空,为空时调用callback。

// 最简版
var pjs = {};
var _tasks = []; var _callback;
pjs.when = function(tasks, callback){
	_tasks = tasks;
	_callback = callback;
};
pjs.trigger = function(task){
	_tasks.splice(task.indexOf(_tasks), 1);
	if(_tasks.length == 0){
		_callback();
	}
};

step2 trigger时支持传值

思路:在step1的基础上 每次trigger的时候将trigger的值存入一个map里面,最后执行回调的时候把这个map作为参数传入

// 支持传值
var pjs = {};
var _tasks = []; var _callback;var valMap = {};
pjs.when = function(tasks, callback){
	_tasks = tasks;
	_callback = callback;
};
pjs.trigger = function(task, val){
	_tasks.splice(task.indexOf(_tasks), 1);
	valMap[task] = val;
	if(_tasks.length == 0){
		_callback(valMap);
	}
};

step3 支持多个异步队列(多个when)

思路:每次调用when的时候用一个异步队列map存储多个when,每次trigger的时候在多个异步队列里面循环,删除每个异步队列的异步任务。相当于增加一个维度。

// 支持多个when
var pjs = {};
var uid = 0;
pjs.when = function(tasks, callback){
	tasksMap[++uid] = {
		tasks: tasks,
		valMap: {},
		callback: callback
	};
};
pjs.trigger = function(task, val){
	for(var k in tasksMap){
		var uidtask = tasksMap[k].tasks;
		if(uidtask.indexOf(task)==-1) continue;

		var uidvalmap = tasksMap[k].valMap;
		var uidcallback = tasksMap[k].callback;
		uidvalmap[task] = val;
		uidtask.splice(uidtask.indexOf(task), 1);
		if(uidtask.length==0){
			uidcallback(uidvalmap);
		}
	}
}

step4 优化

思路:step3里面存在一个问题,就是每次trigger都会去循环所有的异步队列,即使该异步队列里面没有trigger的任务,所以在这里需要做一个改进,在when的时候我们以异步任务为key存储它关联的异步队列(uid),在trigger的时候只需要去循环它关联的异步队列uid,优化循环

var pjs = {};
var tasksMap = {};var evtMap = {};
var uid = 0;

pjs.when = function(tasks, callback){
	tasksMap[++uid] = {
		tasks: tasks,
		valMap: {},
		callback: callback
	};
	tasks.map(function(evt, index){
		var evtuid = evtMap[evt];
		evtuid?evtuid.push(uid):evtuid = [uid];
	});
};
pjs.trigger = function(task, val){
	for(var i=0;i<evtMap[task].length;i++){
		var uid = evtMap[task][i]
		var uidtask = tasksMap[uid].tasks;
		if(uidtask.indexOf(task)==-1) continue;

		var uidvalmap = tasksMap[uid].valMap;
		var uidcallback = tasksMap[uid].callback;
		uidvalmap[task] = val;
		uidtask.splice(uidtask.indexOf(task), 1);
		if(uidtask.length==0){
			uidcallback(uidvalmap);
		}
	}
}

为什么我们要做eslint代码规范?

我一直认为编码规范没有什么用处。我坚信这些规范都是官僚制度下产生的浪费大家的编程时间、影响人们开发效率的东西。我是大错特错了。—— 为什么谷歌要执行严格的代码编写规范

有太多的前端工程师都认为:

  • eslit就是在浪费时间!(当然eslint是代码规范的一个小范畴)
代码写下来eslint一下看到全是红的,还以一行一行去整理规范,有时候不熟悉eslint规范的时候根本不知道
这个error到底是为什么,又要去查eslint规范,这样下来eslint的时间都占了写代码的时间的一大部分了,
并且现在的互联网排期紧,压榨程序员的编码时间,(这个需求这个项目上线可能效果不好...各种理由)。

这理由真的很充分,但是当我们都习惯了eslint的规范的时候,我们写完代码就eslint一下发现真的规范了
  • 我就是规范
我的技术很好,我可以写出清晰的、易于理解的代码,我觉得我写的代码就是规范。
为什么我要浪费时间遵守这些愚蠢的规范?程序员界有一个通用的说法: 
“程序员往往都是2B,我们可能不会觉得自己的代码写得多好,但是一般会觉得别人的代码写得很烂!”
这就造成了一个误区: 所有人在看别人写的代码(后期维护的时候)都会觉得这个代码是谁写的哇! 怎么这么乱
真想把他重构一下

这样就费神费力没心情
  • 规范不适合我
每个人对同一个事物的看法是不一样的,这个规范我觉得xxxx不好,规范都不能完全的满足所有人的意愿,
但是规范是满足大多数人的意愿,并且谷歌Facebook等大公司的规范都是经过了千锤百炼,
证实这样写能更方便维护,更方便代码审查

代码规范的重要性

这已经是老生常谈的话题了
讲讲我们经常会遇到的一个问题: tab几个空格/分号是不是要加
最近一个比较大的项目,前端6人协作开发,最开始我们各自按着各自的编码方式编码,前期没发现什么问题,后来项目代码增加的时候,我们采用的是这种work flow,大家都向future分支上面提代码合代码,我们采用的编辑器不一样,tab有2空格的有4空格的,在合代码的时候发现全是不同的,一不小心就把代码合掉了,一不小心分号掉了,一不小心大括号少了

  • 规范的代码可以促进团队合作
  • 规范的代码可以减少bug处理时间
  • 规范的代码可以降低维护成本
  • 规范的代码有助于代码审查
  • 养成代码规范的习惯,有助于程序员自身的成长

PS: 制定一个符合自己公司情况的开发规范是很简单的,重要的是我们能够认识到规范的重要性,并坚持规范的开发习惯。

提升前后端联调开发效率

1、写在前面:

前后端分工协作是一个老生常谈的大话题,很多公司都在尝试用工程化的方式去提升前后端之间交流的效率,降低沟通成本,并且也开发了大量的工具。

2、传统前后端开发流程

前端切完图,模拟后端接口数据,处理好接口信息,接着就是把静态资源交给后端组装(套模板),这是一般的前后端开发流程。这种流程存在很多的缺陷next。

3、 存在的问题

  • 后端同学对文件进行拆分拼接的时候(公共头部/尾部抽离等),由于对前端知识不熟悉,可能会搞出一些BUG,到最后又需要前端同学帮助分析原因,而前端同学又不是特别了解后端使用的模板,造成尴尬的局面。
  • 如果前端没有使用统一的文件夹结构,并且静态资源(如图片,css,js等)没有剥离出来放到 CDN,而是使用相对路径去引用,当后端同学需要对静态资源作相关配置时,又得修改各个link,script标签的src属性,容易出错。
  • 接口问题,后端数据没有准备好,前端需要自己模拟一套,成本高,如果后期接口有改变,前端又要修改已经给后端处理好了的模板,接口的变动使得前端和后端有需要重新沟通。

4、解决方案

为了解决传统开发模式的一些问题,很早前Facebook就提出了SPA(single page application)解决方案,前后端职责相当清晰,后端给前端接口数据,前端全部用 ajax 异步请求(SPA也有一些弊端,这里就不详细讨论),再由前端渲染页面。

上面👆提到的SPA的关键在数据,数据交给谁去处理?在我们的团队中数据的约定首先由前端根据UI+UE出一套接口文档并同步到一个前后端都能访问的接口平台:接口平台支持JSON导入导出方便编写文档

然后由后端和前端做一次接口评审(拉上PM),后端指出前端给出的接口数据有哪些不恰当的地方,最终产出一个前后端都认可的接口文档(V1.0),后面前后端就按照这个V1.0各自进行开发,前端可以通过json-server等开启模拟接口开发页面和交互,当后端接口完成时,前端把模拟接口去掉直接走后端的接口数据进行联调测试。当后端/前端需要变动接口的时候,在接口文档里面修改接口生成V1.1周知后端即可。
同时这样也可以让测试童鞋在项目提测前就介入部分接口测试,让整个项目并行。

转载请注明出处

浅谈java和javascript中的多态

什么是多态?

  • 初略理解:

    多态指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(消息就是指函数调用)[比如继承:父类的实例和子类的实例调用同一个方法,如果子类重写了父类的方法,那么父类和子类就可以采用不同的行为处理]

  • 进一步理解:

    所谓多态就是指不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态[比如函数重载:根据函数调用的参数类型和参数数量采用不同的行为方式,而不用修改代码],这就是多态性。

Java和JS中的多态

  • 在java(强类型语言)中 多态表现在一下三个方面:
    1、继承(子类重写父类方法[父类虚函数])
    2、方法重载(根据参数的差异触发不同的行为[参数个数、参数类型])
    3、父类声明子类(类型检查[绕过类型检查])
  • 在javascript(弱类型语言)中 多态表现在一下三个个方面:
    1、继承(子类重写父类方法[严格来说JS没有类,没有虚函数])
    2、方法重载(根据参数的差异触发不同的行为[参数个数,JS的弱类型不检查参数类型])
    3、context重指(bind/call/apply改变函数内部this指向,使得同一段程序有不同的行为)

现实中的多态举例

1、键盘上面统一个按键在不同的软件上有不同的行为

以上是个人理解,有误请指出
转载请注明出处

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.