GithubHelp home page GithubHelp logo

blog's Introduction

blog's People

Contributors

abinnq avatar

Stargazers

 avatar

Watchers

James Cloos avatar  avatar

blog's Issues

Promise/A+ 实现

一. Promise 与 Promise/A+

Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一。
Promise/A+ 是 Promise 最小的一个规范。包括
- Promise 状态
- then 方法
- Promise 解析过层
只有一个then 方法, 没有catchraceall等方法
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 接收两个参数resolvereject, 同时这两个参数也是函数。
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;
  1. 异步执行, then方法多次调用
    举个栗子:
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);
    })
  }
}
  1. 错误捕获
    举个栗子:
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);
  }
}
  1. then方法链式调用
    举个栗子:
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

分析一下:

  1. promise 是异步执行函数。 故先打印start, 使用setTimeout 保证执行顺序。
  2. Promise 实例调用then 方法后, 返回了一个新的Promise实例,
  3. 该Promise 执行成功或者失败的结果, 传递给下一个promise实例的then方法 onResolvedonRejected 回调的参数。
  4. Promise 实例链式调用 then 时, 当任何一个then执行出错时, 链时调用的下一个then时会执行错误的回调,
  5. 返回值未定义即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);
}
  1. 值的穿透
    举个栗子:
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方法的两个参数都是可选参数onResolvedonRejected,
故, 判断回调函数是否为函数, 就把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};
  
  // ...
}
  1. 循环引用
    举个栗子:
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('循环引用'));
  }
}
// ...
  1. resolvePromise 函数实现
    resolvePromise 涉及到的Promise/A+ 规范
    • 将每个 Promise 实例调用then后返回的新 Promise 实例称为 promise2,将 then 回调返回的值称为x
    • promise2 不可以和 x 为同一个对象, 自己等待自己, 循环引用。
    • 如果x是一个对象或者函数且不是null,就去取 x 的 then 方法,如果 x 是对象,防止x 是通过 Object.defineProperty 添加 then 属性,并添加 get 和 set 监听,如果在监听中抛出异常,需要被捕获到,x.then 是一个函数,就当作 x 是一个 Promise 实例,直接执行xthen 方法,执行成功就让 promise2 成功,执行失败就让promise2 失败,如果 x.then 不是函数,则说明 x 为普通值,直接调用promise2resolve 方法将 x 传入,不满足条件说明该返回值就是一个普通值,直接执行 promise2resolve 并将 x 作为参数传入;
    • 如果每次执行xthen 方法,回调中传入的参数还是一个 Promise 实例,循环往复,需要递归 resolvePromise 进行解析
    • 在递归的过程中存在内、外层同时调用了 resolvereject 的情况,应该声明一个标识变量 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
}

四. 扩展方法

  1. catch方法
    举个栗子:
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);
}
  1. resolve 方法
    举个栗子:
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);
  })
}
  1. reject 方法
    举个栗子:
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)
  })
}
  1. race 方法
    举个栗子:
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)
    }) 
  })
}

五. 全部代码

查看promise/A+显示全部代码

六. Reference

你不知道的JavaScript-上卷

你不知道的JavaScript-上卷

目录

第一部分 作用域和闭包

第一章 作用域是什么

作用域: 能够存储变量当中的值, 并且能在之后对这个值进行访问或修改

1.1 编译原理

通常将JavaScript称为'动态'语言,或者'解释执行'执行语言, 但其实是 编译语言

  1. 分词/词法分析:
    将字符组成的字符串,分解成编程语言有意义的代码块。 这些代码块叫做词法单元 (token)
  2. 解析/语法分析:
    将词法单元流(数组), 转换成一个由元素逐级嵌套所组成的程序语法结构的树。 这个树被称为抽象语法树(AST)
  3. 代码生成
    将AST转换成可执行代码的过程, 被称为代码的生成。

何时编译
与其他语言不同, JavaScript的编译过程不是发生在构建之前,
JavaScript: 编译发生在代码执行前的几微秒,甚至更短。

过程演示

var a = 2;
// 1.词法分析: 分解成词法单元 `var`、`a`、`=`、`2`
// 2.语法分析: 创建顶级节点 VariableDeclaration, 
//   子节点 Identifier 值为a, 以及一个Literal的子节点 value为2
// 3.代码生成: 用来创建一个叫做a的变量(包括分配内存), 并将一个值存在a中

1.2 理解作用域

  • 引擎: 从头到尾负责JavaScript的编译及执行过程
  • 编译器: 词法分析、语法分析及代码生成
  • 作用域: 收集维护所有声明的表示符(变量), 确定当前执行代码对这些标识符的访问权限

变量的赋值

  1. 编译器在作用域中声明变量(已声明则忽略)
  2. 运行时引擎在作用域中查找变量
  3. 找到变量进行赋值

引擎查找变量规则

  • LHS: 找到变量的容器本身, 为其赋值。a = 2
  • RHS: 找到源值。console.log(a)

1.3 作用域嵌套

作用域链
引擎在当前的执行作用域查找变量, 如果找不到就向上一级查找,
当查找到最外层的全局作用域, 无论找到与否都会停止查找;

1.4 异常

  • RHS: 查询整个作用域链都找不到其值, 引擎将会抛出ReferenceError的异常错误

  • LHS: 查询整个作用域链都找不到,将在全局作用域创建一个该名称变量(非严格模式下), 严格模式下仍然会报错

  • ReferenceError: 和作用域相关的异常错误

  • TypeError: 作用域成功了, 但是对结果的操作非法

1.5 小结

  • 作用域: 是一套规则, 用于确定何处如何查找变量(标识符)
  • LHS: 查找的变量是对其赋值, 赋值操作;
  • RHS: 查找的变量是为了获取值;

= 操作符, 和调用函数传入的参数, 都会导致关联作用域的赋值操作

JavaScript引擎执行代码前,会先对代码进行编译, var a = 2; 被分解成两个步骤

  1. var a 在其作用域中声明新变量, 在代码执行前
  2. 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

第二章 词法作用域

词法阶段:
词法作用域是由书写代码函时数声明的位置来决定,
作用域查找会在找到第一个匹配的标识符时停止(遮蔽效应)

欺骗词法

  • eval: 对一个或者多个代码字符串进行演算, 并修改已存在的作用域(运行时)
  • with: 对一个对象的引用当作作用域, 其属性当作标识符。从而创建一个新的词法作用域(运行时)

引擎无法在编译时 对作用域查找进行优化, 故两个都会导致代码变慢

词法作用域:
意味着作用域是由写代码时,函数声明的位置决定的
编译的词法分析阶段, 基本能够知道全部标识符在哪里及如何声明, 从而能预测执行过程中如何进行查找

第三章 函数作用域和块作用域

隐藏内部实现

  • 把变量和函数包裹在一个函数的作用域中, 用这个函数来隐藏他们
  • 最小授权或最小暴露原则

规避冲突:
隐藏作用域中的变量和函数所带来的另一个好处, 避免同名标识符之间的冲突

规避冲突方法:

  • 全局命名空间: 多数库常用暴露一个对象Jquery $
  • 模块管理

函数作用域

匿名函数: 回调参数

setTimeout(function() {
  console.log('I waited');
}, 0);
// 1. 匿名函数在栈追踪不会显示出有意义的函数名, 使得调试困难
// 2. 没有函数名,当函数需要引用自身时, 需要使用`arguments.callee`
// 3. 匿名函数省略了代码的可读性/可理解性很重要的函数名

函数声明和函数表达式

  • 函数声明: function 是声明中的第一个词,就是函数声明。(不可以省略函数名)
  • 函数表达式: function 不是函数声明中的第一个词, 就是函数表达式
    区别是他们的名称标识符将会绑定在何处

立即执行函数表达式
立即执行函数表达式 IIFE: (function IIFE(){console.log(1)})()使用()将函数包裹起来, 后面的()代表调用

块作用域
定义: 变量和函数不仅属于所处的作用域, 也可以属于某个代码块{...}

  • with 关键字
  • try/catch 中的catch会创建一个块作用域, 仅在catch中有效
  • let
  • const 其值是固定的常量

第四章 提升

包括变量和函数在内的所有声明都会在代码被执行前首先被处理。

提升

  • 引擎会在解释JavaScript代码前进行编译, 编译阶段找到所有的声明, 并用合适的作用域将其关联起来
  • var a = 2; 被解析成两个阶段: var a(编译阶段), a = 2;(执行阶段), 这个过程叫做提升;
  • 普通块内部的函数声明通常会提升到所在作用域的最顶部
  • 函数首先被提升, 接下来才是变量
  • 声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会被提升

所有的声明(变量和函数)都会被移动到各自所在作用域的最顶端,这个过程叫提升

第五章 作用域闭包

闭包

  • 函数是在当前词法作用域外执行
  • 函数不在定义词法中执行, 但是依然保持对该词法作用域的引用,叫做闭包
  • 闭包阻止了引擎的垃圾回收机制, 及释放不再引用的内存

模块

  1. 必须有外部的封闭函数, 该函数至少被调用一次(每次调用都会被创建一个新的模块实例)
  2. 封闭函数内部必须返回至少一个内部函数(形成闭包)

现代模块机制
调用了函数定义的包装函数, 并将返回值作为该模块的API

ES6模块
将文件作为独立的模块来处理, 每个模块都可以导入其他模块或者特定的API成员, 同时也可以导出自己的的API成员。

第二部分 this和对象原型

JavaScript代码简介之道

JavaScript 代码简介之道

简单一句话,代码是写给人看的,机器顺便执行一下

Table of Contents

  1. 变量
  2. 函数
  3. 对象和数据结构
  4. SOLID
  5. 测试
  6. 异步
  7. 代码风格
  8. 注释
  9. ref

变量

使用有意义且常用的单词命名

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.') {
  // ...
}

Function 函数

参数越少越好

如果参数超过两个,使用 解构语法,不用考虑参数顺序

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参数

通过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');

对象和数据结构

使用gte、和set操控数据

在操作数据时打日志,方便跟踪。在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

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);
  });
});

异步

选择Promise 放弃回调

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);
  });

优先使用Async/Await 而非Promises

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);
  // 其他的自定义方法
}

Promises 中也要抛异常

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() {
  // ...
};

Reference

实现跨域的几种方式

实现跨域的几种方式

目录

什么是跨域

跨域: 浏览器不能执行其他网站的脚本, 由浏览器的同源策略导致
同源策略 : 域名、协议、端口均相同

同源限制了以下

  • Cookie、LocalStorage、IndexedDB 等存储性质内容
  • DOM节点
  • Ajax 请求

三个标签允许跨资源

  • <img src="xxx">
  • <link href="xxx">
  • <script src="xxx">

实现跨域的几种方式

1.jsonp

Jsonp (json with padding)
动态创建script标签
script 标签没有同源策略限制, 可以跨域, 兼容性好

缺点

  1. 只能get请求
  2. xss 攻击 跨站脚本攻击(Cross Site Scripting)

2.cors

CORS: "跨域资源共享"(Cross-origin resource sharing)

  1. Access-Control-Allow-Origin
    设置那个源可以访问, * 表示任意域名
  2. Access-Control-Allow-Credentials
    布尔值, 表示是否允许接收Cookie
  3. Access-Control-Expose-Headers
    允许返回的头
    CORS XMLHttpRequest 的对象 getResponseHeader() 仅能接受:
    cache-Control、ContentLanguage、Content-Type、Expires、Last-Modified、Pragma
  4. Access-Control-Allow-Headers
    允许携带的头
  5. Access-Control-Allow-Methods
    允许那些方法
  6. Access-Control-Max-Age
    预检测 存活时间毫秒

兼容性
需要浏览器和服务器同时支持, 目前所有的浏览器都支持, IE10及以上

3.document.domain

适用主域相同, 不同子域之间的跨域
设置相同的主域, 使同源检测通过

// a.abin.com
// b.abin.com
document.domain = 'abin.com';

4.postMessage

完全不同域的跨域
html5引入的API, 用于多窗口间的跨页面通信

  otherWindow.postMessage(message, targetOrigin, [transfer])
  • message: 传输数据
  • targetOrigin: 返回的窗口对象
  • transfer: 对象所有权传递给消息接受方

接受数据: 监听页面message事件的发生

兼容性
需要IE11及以上

安全性
采用双向安全机制, 通过event.origin 来判断是否来自正确可靠的发送方

5.windowName

window.name: name值在不同的页面(甚至不同域名),
加载后依旧存在, size 在2MB
通过iframe的src属性由外域转向本域,
跨域数据即由iframe的window.name从外域转到本域。

6.location.hash

通过hash值的传递,来实现 a -> c
a、b : 3000
c: 4000
利用b 的传递

7.websocket

WebSocket: HTML5的持久化协议, 实现了浏览器与服务器的双工通信, 也是跨越的一种解决方案。
WebSocket 是一种双向通信协议, 在建立连接后,WebSocket的 server 与 client 都能主动向双方发送或接收消息。
WebSocket 在建立时需要借助http协议, 建立好之后就和HTTP无关了

8.nginx

nginx反向代理: 需要搭建一个中转nginx服务器, 用于转发请求
实现思路: 通过nginx配置一个代理服务器:跳板机(域名与domain1相同, 端口不同),
反向代理访问domain2的接口, 并且可以修改 cookie 中domain信息,方便当前域cookie写入, 实现跨域登录

9.node

node中间件代理
同源策略是浏览器要遵循的策略, 而服务器向服务器请求无需遵循同源策略;
代理服务器需要做:

  • 接收客户端请求。
  • 将请求转发给服务器。
  • 拿到服务器 响应 数据。
  • 将 响应转发给客户端。

浏览器向代理服务器发送请求, 也要遵循同源策略。

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.