JavaScript中不得不说的断⾔?
断⾔主要应⽤于“调试”与“测试”
⼀、前端中的断⾔
仔细地查找⼀下JavaScript中的API,实际上并没有多少关于断⾔的⽅法。唯⼀⼀个就是console.asrt:
// console.asrt(condition, message)
const a = '1'
console.asrt(typeof a === 'number', 'a should be Number')
复制代码
当condition为fal时,该⽅法则会将错误消息写⼊控制台。如果为true,则⽆任何反应。
实际上,很少使⽤console.asrt⽅法,如果你阅读过vue或者vuex等开源项⽬,会发现他们都定制了断⾔⽅法:
// Vuex源码中的⼯具函数
function asrt (condition, msg) {
if (!condition) {
throw new Error(`[Vuex] ${msg}`)
}
}
复制代码
⼆、Node中的断⾔
Node中内置断⾔库(asrt),这⾥我们可以看⼀个简单的例⼦:
鲁班七号最强出装try {
asrt(fal, '这个值应该是true')
} catch(e) {
console.log(e instanceof asrt.AsrtionError) // true
const { actual, expected, operator } = e
console.log(`实际值: ${actual},期望值: ${expected}, 使⽤的运算符:${operator}`)
// 实际值: fal,期望值: true, 使⽤的运算符:==
}
复制代码
asrt模块提供了不少的⽅法,例如strictEqual、deepStrictEqual、notDeepStrictEqual等,仔细观察这⼏个⽅法,我们⼜得来回顾⼀下JavaScript中的相等⽐较算法:
抽象相等⽐较算法 (==)
严格相等⽐较算法 (===)
SameValue (Object.is())
SameValueZero
⼏个⽅法的区别可以查看。
在Node10.2.0⽂档中你会发现像asrt.equal、asrt.deepEqual这样的api已经被废除,也正是避免==的复杂性带来的易错性。⽽保留下来的api基本上多是采⽤后⼏种算法,例如:
strictEqual使⽤了严格⽐较算法
deepStrictEqual在⽐较原始值时采⽤SameValue算法
三、chai.js
从上⾯的例⼦可以发现,JavaScript中内置的断⾔⽅法并不是特别的全⾯,所以这⾥我们可以选择⼀些三⽅库来满⾜我们的需求。这⾥我们可以选择chai.js,它⽀持两种风格的断⾔(TDD和BDD):
const chai = require('chai')
const asrt = chai.asrt
const should = chai.should()
const expect = pect
const foo = 'foo'
// TDD风格 asrt
// BDD风格 should
foo.should.be.a('string')嘴唇厚的男人
// BDD风格 expect
expect(foo).to.be.a('string')
复制代码
⼤部分⼈多会选择expect断⾔库,的确⽤起来感觉不错。具体可以查看,毕竟确认过眼神,才能选择适合的库。
四、expect.js源码分析
expect.js不仅提供了丰富的调⽤⽅法,更重要的就是它提供了类似⾃然语⾔的链式调⽤。
链式调⽤
谈到链式调⽤,我们⼀般会采⽤在需要链式调⽤的函数中返回this的⽅法实现:
class Person {
constructor (name, age) {
this.name = name
this.age = age
}07j501
updateName (val) {
this.name = val
return this
}
updateAge (val) {
this.age = val
return this
}
sayHi () {
console.log(`my name is ${this.name}, ${this.age} years old`)
}
}
const p = new Person({ name: 'xiaoyun', age: 10 })
p.updateAge(12).updateName('xiao ming').sayHi()
复制代码
然⽽在expect.js中并不仅仅采⽤这样的⽅式实现链式调⽤,⾸先我们要知道expect实际上是Asrtion的实例:
function expect (obj) {
return new Asrtion(obj)
}
复制代码
接下来看核⼼的Asrtion构造函数:
function Asrtion (obj, flag, parent) {
this.obj = obj;
this.flags = {};
// 通过flags记录链式调⽤⽤到的那些标记符,
// 主要⽤于⼀些限定条件的判断,⽐如not,最终返回结果时会通过查询flags中的not是否为true,来决定最终返回结果
if (undefined != parent) {
this.flags[flag] = true;
for (var i in parent.flags) {
if (parent.flags.hasOwnProperty(i)) {
this.flags[i] = true;
}
}
}
// 递归注册Asrtion实例,所以expect是⼀个嵌套对象
var $flags = flag ? flags[flag] : keys(flags)
, lf = this;
if ($flags) {
for (var i = 0, l = $flags.length; i < l; i++) {
结婚三周年// 避免进⼊死循环
if (this.flags[$flags[i]]) {
continue
}
var name = $flags[i]
, asrtion = new Asrtion(this.obj, name, this)
// 这⾥要明⽩修饰符中有⼀部分也是Asrtion原型上的⽅法,例如 an, be。处女男射手女
if ('function' == typeof Asrtion.prototype[name]) {
/
/ 克隆原型上的⽅法
var old = this[name];
this[name] = function () {
return old.apply(lf, arguments);
};
// 因为当前是个函数对象,你要是在后⾯链式调⽤了Asrtion原型上⽅法是找不到的。
// 所以要将Asrtion原型链上的所有的⽅法设置到当前的对象上
for (var fn in Asrtion.prototype) {
if (Asrtion.prototype.hasOwnProperty(fn) && fn != name) {
this[name][fn] = bind(asrtion[fn], asrtion);
}
}
} el {
this[name] = asrtion;
}
}
}
武藤兰不知火舞
}
复制代码
为什么要这样设计?我的理解是:⾸先expect.js的链式调⽤充分的体现了调⽤的逻辑性,⽽这种嵌套的结构真正的体现了各个修饰符之间的逻辑性。
所以我们可以这样书写:
const student = {
name: 'xiaoming',
age: 20
}
expect(student).to.be.a('object')
复制代码
当然这并没有完,对于每⼀个Asrtion原型上的⽅法多会直接或者间接的调⽤asrt⽅法:
Asrtion.prototype.asrt = function (truth, msg, error, expected) {
// 这就是flags属性的作⽤之⼀
var msg = ? error : msg
, ok = ? !truth : truth
, err;
if (!ok) {
// 抛出错误敬而远之的意思
err = new Error(msg.call(this));
if (arguments.length > 3) {
err.actual = this.obj;
err.showDiff = true;
}
throw err;
}
// 为什么这⾥要再创建⼀个Asrtion实例?也正是由于expect实例是⼀个嵌套对象。
this.and = new Asrtion(this.obj);
};
复制代码
并且每⼀个Asrtion原型上的⽅法最终通过返回this来实现链式调⽤。所以我们还可以这样写:
expect(student).to.be.a('object').have.property('name')
罗卜糕复制代码
到此你应该已经理解了expect.js的链式调⽤的原理,总结起来就是两点:
原型⽅法还是通过返回this,实现链式调⽤;
通过嵌套结构的实例对象增强链式调⽤的逻辑性;
所以我们完全可以这样写:
// 强烈不推荐不然怎么能属于BDD风格呢?
expect(student).a('object').property('name')
复制代码
喜欢本⽂的⼩伙伴们,欢迎关注我的订阅号超爱敲代码,查看更多内容.