abinnq / blog Goto Github PK
View Code? Open in Web Editor NEW📝blog
📝blog
Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一。
Promise/A+ 是 Promise 最小的一个规范。包括
- Promise 状态
- then 方法
- Promise 解析过层
只有一个then
方法, 没有catch
、race
、all
等方法
ECMAscript 6 Promise 是符合Promise/A+ 标准之一。
举个栗子:
let p = new Promise(function(resolve, reject) {
resolve('200');
})
p.then(data => {
console.log(data);
}, err => {
console.log('err', err)
})
// 200
分析一下:
1. new Promise 返回一个promise对象, 接受一个executor
执行器函数, 立即调用函数。
2. executor
接收两个参数resolve
、reject
, 同时这两个参数也是函数。
3. Promise 实例具有状态, 默认pending
(等待), 执行器调用resolve后,实例状态变为resolved
(成功)。 执行器调用reject,实例状态变为rejected
(失败)。
4. Promise 实例状态一经改变, 将不能再修改。
5. promise 实例 都有 then
方法。then 方法中有两个参数。onResolved
成功回调函数, onRejected
失败回调函数
6. 执行器executor
调用resolve
后, then中onResolved
将会执行, 当执行器executor
调用reject
后, then方法第二个参数onRejected
将会执行。
实现一下:
// promise 三个状态
var PENDING = 'pending';
var RESOLVED = 'resolved';
var REJECTED = 'rejected';
function PromiseA (executor) {
// 保存一下this, 防止this出现指向不明
var _this = this;
// 初始化 promise 的值
_this.data = undefined;
// 初始化 promise 的状态
_this.status = PENDING;
function resolve(value) {
// 在pending时修改对应状态, 和 promise 值
if(_this.status === PENDING) {
_this.status = RESOLVED;
_this.data = value;
}
}
function reject(reason) {
// 在pending时修改对应状态, 和 promise 值
if(_this.status === PENDING) {
_this.status = REJECTED;
_this.data = reason;
}
}
executor(resolve, reject);
}
PromiseA.prototype.then = function(onResolved, onRejected) {
var _this = this;
// 状态是成功状态, 立即执行成功回调, 并传入其值
if(_this.status === RESOLVED) {
onResolved(_this.data);
}
// 状态是失败状态, 立即执行失败回调, 并传入其值
if(_this.status === REJECTED) {
onRejected(_this.data);
}
}
module.exports = PromiseA;
let p = new PromiseTest(function(resolve, reject) {
setTimeout(() => {
resolve('200');
}, 1000)
})
p.then(data => {
console.log(1, data)
}, err => {
console.log('err', err)
})
p.then(data => {
console.log(2, data)
}, err => {
console.log('err', err)
})
// 1, '200'
// 2, '200'
分析一下:
结果将会在一秒中之后打印, 即then
方法的失败和成功回调是在promise 的异步执行完之后才触发的,
所以 在调用then
方法的时候 promise 的状态并不是成功或者失败,
先将成功或者失败的回调函数保存起来,等异步执行完成后再执行对应的成功或者失败回调函数。
then 方法可以调用多次, 所以保存时需要使用数组
实现一下:
function PromiseA (executor) {
// ...
// 保存成功和失败的回调函数
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
// 调用成功函数
function resolve(value) {
// 在pending时修改对应状态, 和 promise 值
if(_this.status === PENDING) {
_this.status = RESOLVED;
_this.data = value;
_this.resolvedCallbacks.forEach(function(fn) {
fn();
});
}
}
// 调用失败函数
function reject(reason) {
// 在pending时修改对应状态, 和 promise 值
if(_this.status === PENDING) {
_this.status = REJECTED;
_this.data = reason;
_this.rejectedCallbacks.forEach(function(fn) {
fn();
});
}
}
}
PromiseA.prototype.then = function(onResolved, onRejected) {
// ...
// 状态是等待, 将回调函数保存起来
if(_this.status === PENDING) {
_this.resolvedCallbacks.push(function() {
onResolved(_this.data);
})
_this.rejectedCallbacks.push(function() {
onRejected(_this.data);
})
}
}
let p = new PromiseA((resolve, reject) => {throw new Error('error')});
p.then(data => {
console.log(data);
}, err => {
console.log('err', err)
})
// err Error: error
分析一下:
Promise 出错时会直接改变到失败状态, 并将失败原因传递过去。
直接对执行函数executor
进行异常处理, 出错就进入reject
函数。
实现一下:
function PromiseA (executor) {
// ...
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
let p = new Promise(function(resolve, reject) {
resolve('200');
})
p.then(data => {
console.log(1, data)
throw Error('oooo')
}, err => {
console.log('1err', err)
}).then(data => {
console.log(2, data);
}, err => {
console.log('2err', err)
}).then(data => {
console.log(3, data)
}, err => {
console.log('3err', err)
})
console.log('start');
// start
// 1 '200'
// 2err Error: oooo
// 3 undefined
分析一下:
start
, 使用setTimeout
保证执行顺序。then
方法后, 返回了一个新的Promise实例,then
方法 onResolved
或onRejected
回调的参数。then
时, 当任何一个then
执行出错时, 链时调用的下一个then
时会执行错误的回调,undefined
, 再次调用会执行成功的回调, 即上面的 3 undefined
。实现一下:
function PromiseA (executor) {
// ...
function resolve(value) {
// 在pending时修改对应状态, 和 promise 值
setTimeout(function() {
if(_this.status === PENDING) {
_this.status = RESOLVED;
_this.data = value;
_this.resolvedCallbacks.forEach(function(fn) {
fn();
});
}
})
}
function reject(reason) {
// 在pending时修改对应状态, 和 promise 值
setTimeout(function() {
if(_this.status === PENDING) {
_this.status = REJECTED;
_this.data = reason;
_this.rejectedCallbacks.forEach(function(fn) {
fn();
});
}
})
}
}
PromiseA.prototype.then = function(onResolved, onRejected) {
var _this = this;
var promise2;
promise2 = new PromiseA(function(resolve, reject) {
// 异步执行, 保证调用顺序
setTimeout(function() {
// 状态是成功状态, 立即执行成功回调, 并传入其值
if(_this.status === RESOLVED) {
// then方法执行 异常处理, 错误进入执行reject
try {
var x = onResolved(_this.data);
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason)
}
}
// 状态是失败状态, 立即执行失败回调, 并传入其值
if(_this.status === REJECTED) {
var x = onRejected(_this.data);
resolvePromise(promise2, x, resolve, reject);
}
// 状态是等待, 将回调函数保存起来
if(_this.status === PENDING) {
_this.resolvedCallbacks.push(function() {
var x = onResolved(_this.data);
resolvePromise(promise2, x, resolve, reject);
})
_this.rejectedCallbacks.push(function() {
var x = onRejected(_this.data);
resolvePromise(promise2, x, resolve, reject);
})
}
})
})
return promise2;
}
function resolvePromise(promise2, x, resolve, reject) {
resolve(x);
}
let p = new Promise(function(resolve, reject) {
resolve('200');
})
p.then()
.then(null, err => {
console.log('1err', err)
})
.then(data => {
console.log(data)
}, err => {
console.log('2err', err)
})
// '200'
分析一下:
当上一个then
没有传递回调参数, 或者参数为null
时, 需要将值传递给下一个then
方法
then
方法的两个参数都是可选参数onResolved
和 onRejected
,
故, 判断回调函数是否为函数, 就把then
的参数留空并且让值穿透到后面。
实现一下:
PromiseA.prototype.then = function(onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value};
onRejected = typeof onRejected === 'function' ? onREjected : function(reason) {throw reason};
// ...
}
let p = new Promise(function(resolve, reject) {
resolve('200');
})
var p2 = p.then(() => {
return p2;
})
p2.then(() => {
console.log(1)
}, err => {
console.log('err1', err)
})
// err1 TypeError: Chaining cycle detected for promise
分析一下:
上述代码, 让 p 的then
方法回调自己, 就会产生循环回调,
故, then
方法中的回调函数不能是自己本身
实现一下:
function resolvePromise(promise2, x, resolve, reject) {
if(promise2 === x) {
return reject(new TypeError('循环引用'));
}
}
// ...
then
后返回的新 Promise 实例称为 promise2,将 then
回调返回的值称为x
x
为同一个对象, 自己等待自己, 循环引用。x
是一个对象或者函数且不是null
,就去取 x
的 then 方法,如果 x
是对象,防止x
是通过 Object.defineProperty
添加 then
属性,并添加 get 和 set 监听,如果在监听中抛出异常,需要被捕获到,x.then
是一个函数,就当作 x
是一个 Promise 实例,直接执行x
的 then
方法,执行成功就让 promise2
成功,执行失败就让promise2
失败,如果 x.then
不是函数,则说明 x
为普通值,直接调用promise2
的 resolve
方法将 x
传入,不满足条件说明该返回值就是一个普通值,直接执行 promise2
的 resolve
并将 x 作为参数传入;x
的 then
方法,回调中传入的参数还是一个 Promise 实例,循环往复,需要递归 resolvePromise
进行解析resolve
和 reject
的情况,应该声明一个标识变量 called
做判断来避免这种情况实现一下:
function resolvePromise(promise2, x, resolve, reject) {
var then;
// 为了避免多次调用
var called = false;
if(promise2 === x) {
return reject(new TypeError('循环回调'));
}
// x 如果是普通值(非 object 或 function), 直接resolve
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
// 每个promise 都会有then方法, 使用_then保存, 防止出错, 使用try catch
try {
then = x.then;
if(typeof then === 'function') {
// 确定 this 指向x
then.call(x, function(y) {
if(called) return;
called = true;
return resolvePromise(promise2, y, resolve, reject);
}, function(e) {
if(called) return;
called = true;
return reject(e);
})
} else {
resolve(x);
}
} catch (err) {
if(called) return;
called = true;
reject(err);
}
} else {
resolve(x);
}
}
使用 这个包promises-aplus-tests 走下单元测试
按其说明,需要提供一个这样的类静态函数:
PromiseA.deferred = PromiseA.defer = function() {
var dfd = {}
dfd.promise = new PromiseA(function(resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
let p = new Promise(function(resolve, reject) {
resolve('200');
})
p.then(data => {
throw new Error('eeee');
}, err => {
console.log('err', err)
}).catch(err => {
console.log(err)
})
// Error eeee
实现一下:
PromiseA.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
let p = Promise.resolve('200');
p.then(data => {
console.log(data)
}, err => {
console.log('err', err)
})
// 200
实现一下:
Promise.prototype.resolve = function(value) {
return new Promise(function(resolve, reject) {
resolve(value);
})
}
let p = Promise.reject('eeee');
p.then(data => {
console.log(data)
}, err => {
console.log('err', err)
})
// err eeee
实现一下:
Promise.reject = function(reason) {
return new Promise(function(resolve, reject) {
reject(reason)
})
}
let p1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(1)
}, 1000)
})
let p2 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(2)
}, 2000)
})
let p3 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(3)
}, 3000)
})
let p = Promise.race([p1, p2, p3])
p.then(data => {
console.log(data)
}, err => {
console.log('err', err)
})
// 1
实现一下:
Promise.race = function(promises) {
return new Promise(function(resolve, reject) {
promises.forEach(function(promise) {
promise.then(resolve, reject)
})
})
}
作用域: 能够存储变量当中的值, 并且能在之后对这个值进行访问或修改
通常将JavaScript称为'动态'语言,或者'解释执行'执行语言, 但其实是 编译语言
何时编译
与其他语言不同, JavaScript的编译过程不是发生在构建之前,
JavaScript: 编译发生在代码执行前的几微秒,甚至更短。
过程演示
var a = 2;
// 1.词法分析: 分解成词法单元 `var`、`a`、`=`、`2`
// 2.语法分析: 创建顶级节点 VariableDeclaration,
// 子节点 Identifier 值为a, 以及一个Literal的子节点 value为2
// 3.代码生成: 用来创建一个叫做a的变量(包括分配内存), 并将一个值存在a中
变量的赋值
引擎查找变量规则
a = 2
console.log(a)
作用域链
引擎在当前的执行作用域查找变量, 如果找不到就向上一级查找,
当查找到最外层的全局作用域, 无论找到与否都会停止查找;
RHS: 查询整个作用域链都找不到其值, 引擎将会抛出ReferenceError的异常错误
LHS: 查询整个作用域链都找不到,将在全局作用域创建一个该名称变量(非严格模式下), 严格模式下仍然会报错
ReferenceError: 和作用域相关的异常错误
TypeError: 作用域成功了, 但是对结果的操作非法
=
操作符, 和调用函数传入的参数, 都会导致关联作用域的赋值操作
JavaScript引擎执行代码前,会先对代码进行编译, var a = 2;
被分解成两个步骤
var a
在其作用域中声明新变量, 在代码执行前a = 2
进行查询(LHS查询)变量a, 并为其赋值2小测试
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
// LHS查询 三处: c =.. 、 a = 2(隐式变量分配)、b=a
// RHS查询 四处: foo(2)、 = a 、 a +、 +b
词法阶段:
词法作用域是由书写代码函时数声明的位置来决定,
作用域查找会在找到第一个匹配的标识符时停止(遮蔽效应)
欺骗词法
引擎无法在编译时 对作用域查找进行优化, 故两个都会导致代码变慢
词法作用域:
意味着作用域是由写代码时,函数声明的位置决定的
编译的词法分析阶段, 基本能够知道全部标识符在哪里及如何声明, 从而能预测执行过程中如何进行查找
隐藏内部实现
规避冲突:
隐藏作用域中的变量和函数所带来的另一个好处, 避免同名标识符之间的冲突
规避冲突方法:
Jquery $
函数作用域
匿名函数: 回调参数
setTimeout(function() {
console.log('I waited');
}, 0);
// 1. 匿名函数在栈追踪不会显示出有意义的函数名, 使得调试困难
// 2. 没有函数名,当函数需要引用自身时, 需要使用`arguments.callee`
// 3. 匿名函数省略了代码的可读性/可理解性很重要的函数名
函数声明和函数表达式
立即执行函数表达式
立即执行函数表达式 IIFE: (function IIFE(){console.log(1)})()
使用()将函数包裹起来, 后面的()代表调用
块作用域
定义: 变量和函数不仅属于所处的作用域, 也可以属于某个代码块{...}
包括变量和函数在内的所有声明都会在代码被执行前首先被处理。
提升
所有的声明(变量和函数)都会被移动到各自所在作用域的最顶端,这个过程叫提升
闭包
模块
现代模块机制
调用了函数定义的包装函数, 并将返回值作为该模块的API
ES6模块
将文件作为独立的模块来处理, 每个模块都可以导入其他模块或者特定的API成员, 同时也可以导出自己的的API成员。
TODO...
TODO...
简单一句话,代码是写给人看的,机器顺便执行一下
Bad:
const yyyymmdstr = moment().format('YYYY/MM/DD');
Good:
const currentDate = moment().format('YYYY/MM/DD');
Bad:
getUserInfo();
getClientData();
getCustomerRecord();
Good:
getUser();
每个常量应该被命名
Bad:
// 8640000 是什么含义?
setTimeout(blastOff, 8640000);
Good:
const MILLISEXONDS_IN_A_DAY = 8640000;
setTimeout(blastOff, MILLISEXONDS_IN_A_DAY);
Bad:
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]);
Good:
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
Bad:
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l)=> {
doStuff();
doSomeOtherStuff();
// ...
// ...
// 不观看上下文无法理解 l 代表什么
dispatch(l);
});
Good:
const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((location)=> {
doStuff();
doSomeOtherStuff();
// ...
// ..
dispatch(location);
});
如果定义了car 没必要在car下再定义carColor 直接定义color即可
Bad:
const Car = {
carMake: 'Honda',
carModel: 'Accord',
carColor: 'Blue',
};
function paintCar(color){
Car.carColor = color;
}
Good:
const Car = {
make: 'Honda',
model: 'Accord',
color: 'Blue',
};
function paintCar(color){
Car.color = color;
}
Bad:
function createMicrobrewery(name) {
const breweryNmae = name || 'Hipster Brew Co.';
// ...
}
Good:
function createMIcrobrewery(name = 'Hipster Brew Co.') {
// ...
}
如果参数超过两个,使用 解构语法,不用考虑参数顺序
Bad:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
Good:
function createMenu({title, body, buttonText, cancellable}) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true,
});
这是迄今为止软件工程中最重要的规则。
当函数执行多个操作时,它们更难以编写,测试和推理。
Bad:
function emailClients(clients) {
clients.forEach((client) => {
const clientRecord = database.lookup(client);
if(clientRecord.isActive()) {
email(client);
}
});
}
Good:
function emailClients(clients) {
clients
.filter(isActiveClient)
.forEach(email)
}
function isActiveClient(client) {
const ClientRecord = database.lookup(client);
return clientRecord.isActive();
}
Bad:
function addToDate(date, month) {
// ...
}
const date = new Date();
// 无法从名称得知参数作用
addToDate(data, 1);
Good:
function addMonthToDate(date, month) {
// ...
}
const date = new Date();
addMonthToDate(1, date)
函数嵌套过多,应当拆出,否则将难以编写、测试和阅读。
Bad:
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
// ...
});
});
const ast = [];
tokens.forEach((token) => {
// lex...
});
ast.forEach((node) => {
// parse...
});
}
Good:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const syntaxTree = parse(tokens);
syntaxTree.forEach((node) => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
tokens.push( /* ... */ );
});
});
return tokens;
}
function parse(tokens) {
const syntaxTree = [];
tokens.forEach((token) => {
syntaxTree.push( /* ... */ );
});
return syntaxTree;
}
重复代码将不利于维护、阅读和测试,
使用模块、函数、类处理重复代码中的差异,应该遵循Classes部分中规定的SOLID原则
Bad:
function showDeveloperList(developers) {
developers.forEach((developer) => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach((manager) => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}
Good:
function showEmployeeList(employees) {
employess.forEach((employee) => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience,
};
switch (employee.type) {
case 'manager':
data.portfolio = employee.getMBAProjects();
break;
case 'developer':
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}
Bad:
const menuConfig = {
title: null,
body: 'Bar',
buttonText: null,
cancellable: true
};
function createMenu(config) {
config.title = config.title || 'Foo';
config.body = config.body || 'Bar';
config.buttonText = config.buttonText || 'Baz';
config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);
Good:
const menuConfig = {
title: 'Order',
buttonText: 'Send',
cancellable: true
};
function createMenu(config) {
config = Object.assign({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}, config);
}
createMenu(menuConfig);
通过flag的true 或者 false, 来判断执行逻辑,违反了一个函数干一件事的原则
Bad:
function createFile(name, temp) {
if(temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}
Good:
function cretaeFile(name) {
fs.create(name);
}
function createTempFile(name) {
fs.create(`/temp/${name}`);
}
函数接收一个值,返回一个新值,除此之外都被称为副作用。 比如修改全局变量进行异步操作
当函数确实需要副作用时, 需要在唯一的地方处理。
副作用常见陷阱: 1.没有任何结构的对象之间共享状态 2.随意修改可变数据类型 3.没有在统一的地方处理副作用
Bad:
let name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
name = name.split('');
}
splitIntoFirstAndLastName();
console.log(name);
Good:
function splitIntoFirstAndLastName(name) {
return name.split('');
}
const name = 'Ryan McDermott';
const newName = splitIntoFirstAndLastName(name);
console.log(name, newName);
基本类型通过赋值传递, 对象和数组通过引用类型传递,浅拷贝
Bad:
const addItemToCart = (cart, item) => {
cart.push({item, date: new Date()});
};
Good:
const addItemToCart = (cart, item) => {
return [...cart, {item, date: new Date()}];
};
永远不要污染全局变量
Bad:
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};
Good:
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};
}
函数式变编程可以让代码的逻辑更清晰更优雅,方便测试。
Bad:
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
let totalOutput = 0;
for (let i = 0; i < programmerOutput.length: i++) {
totalOutput += programmerOutput[i].linesOfCode;
}
Good:
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
const totalOutput = programmerOutput
.reduce((totalLines, output) => totalLines + output.lineOfCode, 0)
Bad:
if(fsm.state === 'fetching' && isEmpty(listNode)) {
// ...
}
Good:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if(shouldShowSpinner(fsmInStance, listNodeInstance)) {
// ...
}
Bad:
function isDOMNodeNotPresent(node) {
// ...
}
if(!isDOMNodeNotPresent(node)) {
// ...
}
Good:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}
Q: 不使用if 改如何做?
A: 绝大多数场景可以使用多态来代替。
Q: 为什么要多态? 而非条件语句?
A: 让代码简单易读,如果出现了条件判断说明函数并非只做了一件事, 违反了函数单一原则。
Bad:
class Airplane {
getCruisingAltitude() {
switch (this.type) {
case '777':
return this.getMaxAltitude() - this.getPassengerCount();
case 'Air Force One':
return this.getMaxAltitude();
case 'Cessna':
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}
Good
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
javascript是无类型的,意味着传参是任意类型的,这很可怕,
很多时候需要类型检查,这就需要在设计api式就要考虑进去类型的
Bad:
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(this.currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location('texas'));
}
}
Good:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location('texas'));
}
如果需要静态类型检查, 比如字符串 整数等 推荐使用TypeScript
Bad:
function combine(val1, val2) {
if(typeof val1 === 'number' && typeof val2 === 'numer'
|| typeof val1 === 'string' && typeof val2 === 'string') {
return val1 + val2
}
throw new Error('Must be of type String or Number');
}
Good
function combine(val1, val2) {
return val1 + val2;
}
现代浏览器已经在底层做了很多优化,过去的很多优化方案都是无效的,会浪费时间和沉于代码
现代浏览器优化内容
Bad:
// 在旧浏览器中每次使用未缓存的list.length, 每次迭代都需要计算 造成不必要的计算
// 现代浏览器已经做了优化
for (let i = 0; len = list.length; i < len; i++) {
// ...
}
Good:
for(let i = 0; i < list.length; i++ ) {
// ...
}
很多时候代码已经没用了, 担心以后会用到,舍不得删除
如果你忘了无用代码会一直都在,
放心删除,代码库历史版本可以找的到
Bad:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
Good:
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');
在操作数据时打日志,方便跟踪。在set 时很容易对数据进行校验
Bad:
function makeBankAccount() {
// ...
return {
balance: 0
};
}
const account = makeBankAccount();
account.balance = 100;
Good:
function makeBankAccount() {
let balance = 0;
function getBalance() {
return balance;
}
function setBalance(amount) {
balance = amount;
}
return {
getBalance,
setBalance,
};
}
const account = makeBankAccount();
account.setBalance(100);
使用闭包来创建私有变量
Bad:
const Employee = function(name) {
this.name = name;
};
Employee.prototype.getName = function getName() {
return this.name;
};
const employee = new Employee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined
Good:
function makeEmployee(name) {
return {
getName() {
return name;
}
};
}
const employee = new Employee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
在 ES2015/ES6 之前,没有类的语法,只能用构造函数的方式模拟类,可读性非常差。
Bad:
const Animal = function(age) {
if(!(this instanceof Animal)) {
throw new Error('Instantiate Animal with `new`');
}
this.age = age;
};
Animal.prototype.move = function move() {};
const Mammal = function(age, furColor) {
if(!this instanceof Mammal)) {
throw new Error('Instantiate Mammal with `new`');
}
Animal.call(this, age);
this.furColor = furColor;
};
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};
const Human = function(age, furColor, languageSpoken) {
if (!(this instanceof Human)) {
throw new Error('Instantiate Human with `new`');
}
Mammal.call(this, age, furColor);
this.languageSpoken = languageSpoken;
};
Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};
Good:
class Animal {
constructor(age) {
this.age = age;
}
move() { /* ... */};
}
class Mammal extends Animal {
constructor(age, furColor) {
super(age);
this.furColor = furColor;
}
liveBirth() {/* ... */};
}
class Human extends Mammal {
constructor(age, furColor, languageSpoken) {
super(age, furColor);
this.languageSpoken = languageSpoken;
}
speak() { /* ... */ }
}
在JQuery、Lodash中常见。在类的方法最后返回this。
Bad:
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
}
setModel(model) {
this.model = model;
}
setColor(color) {
this.color = color;
}
save() {
console.log(this.make, this.model, this.color);
}
}
const car = new Car('Ford','F-150','red');
car.setColor('pink');
car.save();
Good:
class Car {
constructor(make, model, color) {
this.make = make;
this.model = model;
this.color = color;
}
setMake(make) {
this.make = make;
return this;
}
setModel(model) {
this.model = model;
return this;
}
setColor(color) {
this.color = color;
return this;
}
save() {
console.log(this.make, this.model, this.color);
return this;
}
}
const car = new Car('Ford','F-150','red');
car.setColor('pink');
car.save();
很多时候继承被滥用,导致可读性极差
继承表达的是属于关系 并非包含关系
Bad:
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
// ...
}
class EmployeeTaxData extends Employee {
constructor(san, salary) {
super();
this.san = san;
this.salary = salary;
}
// ...
}
Good:
class EmployeeTaxData {
constructor(ssn, salary) {
this.ssn = ssn;
this.salary = salary;
}
// ...
}
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
setTaxData(ssn, salary) {
this.taxData = new EmployeeTaxData(ssn, salary);
}
// ...
}
SOLID 是几个单词首字母组合而来,分别表示
单一功能原则、开闭原则、里氏替换原则、接口隔离原则以及依赖反转原则。
class UserSettings {
constructor(user) {
this.user = user;
}
changSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...
}
}
Good:
class UserAuth {
constructor(user) {
this.user = user;
}
verifyCreadentials() {
// ...
}
}
class UserSettings {
constructor(user) {
this.user = user;
this.auth = new UserAuth(user);
}
changeSettings(settings) {
if (this.auth.verifyCredentials()) {
// ...
}
}
}
“开”指的就是类、模块、函数都应该具有可扩展性,
“闭”指的是它们不应该被修改。也就是说你可以新增功能但不能去修改源码。
Bad:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = 'ajaxAdapter';
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = 'nodeAdapter';
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
if (this.adapter.name === 'ajaxAdapter') {
return makeAjaxCall(url).then((response) => {
// transform response and return
});
} else if (this.adapter.name === 'nodeAdapter') {
return makeHttpCall(url).then((response) => {
// transform response and return
});
}
}
}
function makeAjaxCall(url) {
// request and return promise
}
function makeHttpCall(url) {
// request and return promise
}
Good:
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = 'ajaxAdapter';
}
request(url) {
// request and return promise
}
}
class NodeAdapter extends Adapter {
constructor() {
super();
this.name = 'nodeAdapter';
}
request(url) {
// request and return promise
}
}
class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}
fetch(url) {
return this.adapter.request(url).then((response) => {
// transform response and return
});
}
}
子类不要去重写父类的方法
Bad:
class Rectangle {
constructor() {
this.width = 0;
this.height = 0;
}
setColor(color) {
// ...
}
render(area) {
// ...
}
setWidth(width) {
this.width = width;
}
setHeight(height) {
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width;
}
setHeight(height) {
this.width = height;
this.height = height;
}
}
function renderLargeRectangles(rectangles) {
rectangles.forEach((rectangle) => {
rectangle.setWidth(4);
rectangle.setHeight(5);
const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
rectangle.render(area);
});
}
const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);
Good:
class Shape {
setColor(color) {
// ...
}
render(area) {
// ...
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Shape {
constructor(length) {
super();
this.length = length;
}
getArea() {
return this.length * this.length;
}
}
function renderLargeShapes(shapes) {
shapes.forEach((shape) => {
const area = shape.getArea();
shape.render(area);
});
}
const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);
接口最小化, 方便接口解藕
Bad:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.animationModule.setup();
}
traverse() {
// ...
}
}
const $ = new DOMTraverser({
rootNode: document.getElementsByTagName('body'),
animationModule() {} // Most of the time, we won't need to animate when traversing.
// ...
});
Good:
class DOMTraverser {
constructor(settings) {
this.settings = settings;
this.options = settings.options;
this.setup();
}
setup() {
this.rootNode = this.settings.rootNode;
this.setupOptions();
}
setupOptions() {
if (this.options.animationModule) {
// ...
}
}
traverse() {
// ...
}
}
const $ = new DOMTraverser({
rootNode: document.getElementsByTagName('body'),
options: {
animationModule() {}
}
});
高层次模块不能依赖低层次模块,它们依赖于抽象接口。
抽象接口不能依赖具体实现,具体实现依赖抽象接口。
总结下来就两个字,解耦。
Bad:
class InventoryRequester {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
class InventoryTracker {
constructor(items) {
this.items = items;
this.requester = new InventoryRequester();
}
requestItems() {
this.items.forEach((item) => {
this.requester.requestItem(item);
});
}
}
const inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();
Good:
class InventoryTracker {
constructor(items, requester) {
this.items = items;
this.requester = requester;
}
requestItems() {
this.items.forEach((item) => {
this.requester.requestItem(item);
});
}
}
class InventoryRequesterV1 {
constructor() {
this.REQ_METHODS = ['HTTP'];
}
requestItem(item) {
// ...
}
}
class InventoryRequesterV2 {
constructor() {
this.REQ_METHODS = ['WS'];
}
requestItem(item) {
// ...
}
}
const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
inventoryTracker.requestItems();
Bad:
import assert from 'assert';
describe('MakeMomentJSGreatAgain', () => {
it('handles date boundaries', () => {
let date;
date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});
Good:
import assert from 'assert';
describe('MakeMomentJSGreatAgain', () => {
it('handles 30-day months', () => {
const date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
});
it('handles leap year', () => {
const date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
});
it('handles non-leap year', () => {
const date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});
Bad:
import { get } from 'request';
import { writeFile } from 'fs';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => {
if (requestErr) {
console.error(requestErr);
} else {
writeFile('article.html', response.body, (writeErr) => {
if (writeErr) {
console.error(writeErr);
} else {
console.log('File written');
}
});
}
});
Good:
import { get } from 'request';
import { writeFile } from 'fs';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) => {
return writeFile('article.html', response);
})
.then(() => {
console.log('File writeFile');
})
.catch((error) => {
console.log(error);
});
Bad:
import { get } from 'request';
import { writeFile } from 'fs';
get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
.then((response) => {
return writeFile('article.html', response);
})
.then(() => {
console.log('File writeFile');
})
.catch((error) => {
console.log(error);
});
Good:
import { get } from 'request';
import { writeFile } from 'fs';
async function getCleanCodeArticle() {
try {
const response = await get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
await writeFile('article.html', response);
console.log('File written');
} catch(err) {
console.log(err);
}
}
不要忽略抛出异常
Bad:
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
Good:
try {
functionThatMightThrow();
} catch (error) {
// 这一种选择,比起 console.log 更直观
console.error(error);
// 也可以在界面上提醒用户
notifyUserOfError(error);
// 也可以把异常传回服务器
reportErrorToService(error);
// 其他的自定义方法
}
Bad:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
console.log(error);
});
Good:
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
// 这一种选择,比起 console.log 更直观
console.error(error);
// 也可以在界面上提醒用户
notifyUserOfError(error);
// 也可以把异常传回服务器
reportErrorToService(error);
// 其他的自定义方法
});
Good:
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;
const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}
Good:
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}
方便只上而下的阅读习惯
Bad:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getManagerReview() {
const manager = this.lookupManager();
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();
Good:
class PerformanceReview {
constructor(employee) {
this.employee = employee;
}
perfReview() {
this.getPeerReviews();
this.getManagerReview();
this.getSelfReview();
}
getPeerReviews() {
const peers = this.lookupPeers();
// ...
}
lookupPeers() {
return db.lookup(this.employee, 'peers');
}
getManagerReview() {
const manager = this.lookupManager();
}
lookupManager() {
return db.lookup(this.employee, 'manager');
}
getSelfReview() {
// ...
}
}
const review = new PerformanceReview(employee);
review.perfReview();
评论是解释,不是要求。好的代码主要是文档本身。
Bad
function hashIt(data) {
// The hash
let hash = 0;
// Length of string
const length = data.length;
// Loop through every character in data
for (let i = 0; i < length; i++) {
// Get character code.
const char = data.charCodeAt(i);
// Make the hash
hash = ((hash << 5) - hash) + char;
// Convert to 32-bit integer
hash &= hash;
}
}
Good:
function hashIt(data) {
let hash = 0;
const length = data.length;
for (let i = 0; i < length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
// Convert to 32-bit integer
hash &= hash;
}
}
版本控制可以找到旧代码,放心删除注释代码
Bad:
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
Good:
doStuff();
git log 可以帮助你干
Bad:
/**
* 2016-12-20: Removed monads, didn't understand them (RM)
* 2016-10-01: Improved using special monads (JP)
* 2016-02-03: Removed type-checking (LI)
* 2015-03-14: Added combine with type-checking (JR)
*/
function combine(a, b) {
return a + b;
}
Good:
function combine(a, b) {
return a + b;
}
Bad:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
menu: 'foo',
nav: 'bar'
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
// ...
};
Good:
$scope.model = {
menu: 'foo',
nav: 'bar'
};
const actions = function() {
// ...
};
跨域: 浏览器不能执行其他网站的脚本, 由浏览器的同源策略导致
同源策略 : 域名、协议、端口均相同
同源限制了以下:
三个标签允许跨资源
<img src="xxx">
<link href="xxx">
<script src="xxx">
Jsonp (json with padding)
动态创建script标签
script 标签没有同源策略限制, 可以跨域, 兼容性好
缺点
CORS: "跨域资源共享"(Cross-origin resource sharing)
*
表示任意域名兼容性
需要浏览器和服务器同时支持, 目前所有的浏览器都支持, IE10及以上
适用主域相同, 不同子域之间的跨域
设置相同的主域, 使同源检测通过
// a.abin.com
// b.abin.com
document.domain = 'abin.com';
完全不同域的跨域
html5引入的API, 用于多窗口间的跨页面通信
otherWindow.postMessage(message, targetOrigin, [transfer])
接受数据: 监听页面message
事件的发生
兼容性
需要IE11及以上
安全性
采用双向安全机制, 通过event.origin
来判断是否来自正确可靠的发送方
window.name: name值在不同的页面(甚至不同域名),
加载后依旧存在, size 在2MB
通过iframe的src属性由外域转向本域,
跨域数据即由iframe的window.name从外域转到本域。
通过hash值的传递,来实现 a -> c
a、b : 3000
c: 4000
利用b 的传递
WebSocket: HTML5的持久化协议, 实现了浏览器与服务器的双工通信, 也是跨越的一种解决方案。
WebSocket 是一种双向通信协议, 在建立连接后,WebSocket的 server 与 client 都能主动向双方发送或接收消息。
WebSocket 在建立时需要借助http协议, 建立好之后就和HTTP无关了
nginx反向代理: 需要搭建一个中转nginx服务器, 用于转发请求
实现思路: 通过nginx配置一个代理服务器:跳板机(域名与domain1相同, 端口不同),
反向代理访问domain2的接口, 并且可以修改 cookie 中domain信息,方便当前域cookie写入, 实现跨域登录
node中间件代理
同源策略是浏览器要遵循的策略, 而服务器向服务器请求无需遵循同源策略;
代理服务器需要做:
浏览器向代理服务器发送请求, 也要遵循同源策略。
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.