noneven / __ Goto Github PK
View Code? Open in Web Editor NEW想到什么写什么
想到什么写什么
客户端存储+Diff+Version 占位
前提: 兴趣+导师
理解深入型:
实用型
致力于简化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 | ? |
转载请注明出处
一般的大厂UI都会把小图icon单独切出来,PSD上会有字体颜色以及间距的标注,就像这样: 所以上面的流程图UI直接给出了icon assets不用前端切icon图
但是如果项目比较紧,PM会要UI尽快出图,所以UI就会少了标注和icon assets步骤(还有甚者UI不分组,不分组,不分组)。前端需要在PSD上把所有的icon和颜色字体以及位置都抓出来合成雪碧图,然后在我们的组件里面用到了的图片在雪碧图里面找出来,把图片的高度宽度以及位置量出来,写入css里面。展示到页面。
这个过程对我(PSD不熟,懒癌患者)来说简直了,所以用工具(程序员的工具(命令行))简化这个过程:sprite.plugin。
以上是个人理解,有误请指出
转载请注明出处
占位
Q: 为什么我们要做这件事?
A: 太多小伙伴对 webpack 的认识都在 webpack.config.js
上,对 webpack 的使用也都是黑盒的,对其打包、loader、plugin等实现原理知之甚少
Q: webpack 现在如此庞大,应该如何着手?
A: 我们可以从 webpack 的第一个提交版本着手研究,它主要实现了 bundler和 loader,代码量很小,并且可读性很高 webpack 的第一个 commit
/******/(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 = {
// ...
};
/******/},
/******/
/******/})
分析 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 各部分
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);
/******/})
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 了其他模块,那么将继续解析依赖的模块。也就是说,总体上遵循深度优先遍历
/**
* @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;
}
}
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;
}
简单的寻址方法
这儿可以再考虑实现逐层往上查找 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;
}
};
生成的 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('');
};
NW占位
Electro占位
Hex占位
React-Native-Macos占位
react-desktop UI库占位
转载请注明出处
Flow解释:
转载请注明出处
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
}
}
void mixin(obj: Object)
复制 Tapable 的原型方法到目标对象
插件加载函数
void apply(plugin1: Plugin, plugin2: Plugin...)
通过 arguments 获得所有传入的插件对象,并调用插件对象的 apply 方法,注册插件(所以,一个合法的插件应该包含入口方法 apply)
事件绑定函数,类似于 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()]);
类似 EventEmitter 的 emit 方法,使用实例见例子1
void applyPlugins(name: string, args: any...)
触发事件 name,传入参数 args,并行调用所有注册在事件 name 上的处理函数。
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(
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);
}
}
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();
}
}
any applyPluginsBailResult(name: string, args: any...)
触发事件 name,串行的调用注册在事件 name 上的处理函数(先入先出),传入参数 args,如果其中一个处理函数返回值 !== undefined,直接返回这个返回值,后续的处理函数将不被执行
applyPluginsParallel(
name: string,
args: any...,
callback: (err?: Error) -> void
)
触发事件 name,传入参数 args,并行的调用所有注册在事件 name 上的处理函数,倘若任一处理函数执行报错,则执行 callback('err'),否则当所有的处理函数都执行完的时候调用 callback()
同样,插件注册此类事件的时候,回调函数要执行 callback
applyPluginsParallelBailResult(
name: string,
args: any...,
callback: (err: Error, result: any) -> void
)
触发事件 name,串行的执行注册在事件 name 上的处理函数(先入先出),每个处理函数必须调用 callback(err, result),倘若任一处理函数在调用callback(err, result) 的时候,err !== undefined || result !== undefined,则 callback 将真正被执行,后续的处理函数则不会再被执行。
先实现后封装,先满足需求后性能优化,文档约束/代码约束
以前看jQuery源码的时候有看到在源码的注释中有些过call的性能会比apply好,在lodash的源码中也同样的发现有call比apply性能更好的注释,这里我在jsperf上写了几个test case,验证了一下确实call比apply的性能更好。
总结: 在我们平时的开发中其实不必关注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。
也就是大家在github上看到的大部分项目的持续集成方案(从各种徽章看)。但是在企业的前端项目中一般不采用Travis CI作为持续集成,而是采用Jenkins,几个重要的原因如下:
再说说一般团队的workflow:
对于1-4步都在前端开发环境中完成,很多团队都集成到一个命令行工具完成(比如tmt-workflow),也有用Electron做成桌面可视化工具(比如weFlow),第五步需要进行(权限管理),开发自测时在dev服务器上,提测QA时部署到测试服务器,上线部署到线上服务器。
可以看到Jenkins在持续集成的过程中作用和意义重大
转载请注明出处
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);'
{
///////////////
// 可能的错误 //
////////////////
// 禁止条件表达式中出现赋值操作符
"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
}
coding其实是precoding正确性的验证
//最终效果:
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)
思路:调用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();
}
};
思路:在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);
}
};
思路:每次调用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);
}
}
}
思路: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一下看到全是红的,还以一行一行去整理规范,有时候不熟悉eslint规范的时候根本不知道
这个error到底是为什么,又要去查eslint规范,这样下来eslint的时间都占了写代码的时间的一大部分了,
并且现在的互联网排期紧,压榨程序员的编码时间,(这个需求这个项目上线可能效果不好...各种理由)。
这理由真的很充分,但是当我们都习惯了eslint的规范的时候,我们写完代码就eslint一下发现真的规范了
我的技术很好,我可以写出清晰的、易于理解的代码,我觉得我写的代码就是规范。
为什么我要浪费时间遵守这些愚蠢的规范?程序员界有一个通用的说法:
“程序员往往都是2B,我们可能不会觉得自己的代码写得多好,但是一般会觉得别人的代码写得很烂!”
这就造成了一个误区: 所有人在看别人写的代码(后期维护的时候)都会觉得这个代码是谁写的哇! 怎么这么乱
真想把他重构一下
这样就费神费力没心情
每个人对同一个事物的看法是不一样的,这个规范我觉得xxxx不好,规范都不能完全的满足所有人的意愿,
但是规范是满足大多数人的意愿,并且谷歌Facebook等大公司的规范都是经过了千锤百炼,
证实这样写能更方便维护,更方便代码审查
这已经是老生常谈的话题了
讲讲我们经常会遇到的一个问题: tab几个空格/分号是不是要加
最近一个比较大的项目,前端6人协作开发,最开始我们各自按着各自的编码方式编码,前期没发现什么问题,后来项目代码增加的时候,我们采用的是这种work flow,大家都向future分支上面提代码合代码,我们采用的编辑器不一样,tab有2空格的有4空格的,在合代码的时候发现全是不同的,一不小心就把代码合掉了,一不小心分号掉了,一不小心大括号少了
PS: 制定一个符合自己公司情况的开发规范是很简单的,重要的是我们能够认识到规范的重要性,并坚持规范的开发习惯。
前后端分工协作是一个老生常谈的大话题,很多公司都在尝试用工程化的方式去提升前后端之间交流的效率,降低沟通成本,并且也开发了大量的工具。
前端切完图,模拟后端接口数据,处理好接口信息,接着就是把静态资源交给后端组装(套模板),这是一般的前后端开发流程。这种流程存在很多的缺陷next。
为了解决传统开发模式的一些问题,很早前Facebook就提出了SPA(single page application)解决方案,前后端职责相当清晰,后端给前端接口数据,前端全部用 ajax 异步请求(SPA也有一些弊端,这里就不详细讨论),再由前端渲染页面。
上面👆提到的SPA的关键在数据,数据交给谁去处理?在我们的团队中数据的约定首先由前端根据UI+UE出一套接口文档并同步到一个前后端都能访问的接口平台:接口平台支持JSON导入导出方便编写文档
然后由后端和前端做一次接口评审(拉上PM),后端指出前端给出的接口数据有哪些不恰当的地方,最终产出一个前后端都认可的接口文档(V1.0),后面前后端就按照这个V1.0各自进行开发,前端可以通过json-server等开启模拟接口开发页面和交互,当后端接口完成时,前端把模拟接口去掉直接走后端的接口数据进行联调测试。当后端/前端需要变动接口的时候,在接口文档里面修改接口生成V1.1周知后端即可。
同时这样也可以让测试童鞋在项目提测前就介入部分接口测试,让整个项目并行。
转载请注明出处
初略理解:
多态指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(消息就是指函数调用)[比如继承:父类的实例和子类的实例调用同一个方法,如果子类重写了父类的方法,那么父类和子类就可以采用不同的行为处理]
进一步理解:
所谓多态就是指不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态[比如函数重载:根据函数调用的参数类型和参数数量采用不同的行为方式,而不用修改代码],这就是多态性。
1、继承(子类重写父类方法[父类虚函数])
2、方法重载(根据参数的差异触发不同的行为[参数个数、参数类型])
3、父类声明子类(类型检查[绕过类型检查])
1、继承(子类重写父类方法[严格来说JS没有类,没有虚函数])
2、方法重载(根据参数的差异触发不同的行为[参数个数,JS的弱类型不检查参数类型])
3、context重指(bind/call/apply改变函数内部this指向,使得同一段程序有不同的行为)
1、键盘上面统一个按键在不同的软件上有不同的行为
以上是个人理解,有误请指出
转载请注明出处
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.