javascript
JavaScript中不得不说的断言?
斷言主要應(yīng)用于“調(diào)試”與“測(cè)試”
一、前端中的斷言
仔細(xì)地查找一下JavaScript中的API,實(shí)際上并沒有多少關(guān)于斷言的方法。唯一一個(gè)就是console.assert:
// console.assert(condition, message)const a = '1'console.assert(typeof a === 'number', 'a should be Number')當(dāng)condition為false時(shí),該方法則會(huì)將錯(cuò)誤消息寫入控制臺(tái)。如果為true,則無任何反應(yīng)。
實(shí)際上,很少使用console.assert方法,如果你閱讀過vue或者vuex等開源項(xiàng)目,會(huì)發(fā)現(xiàn)他們都定制了斷言方法:
// Vuex源碼中的工具函數(shù)function assert (condition, msg) {if (!condition) {throw new Error(`[Vuex] ${msg}`)}}二、Node中的斷言
Node中內(nèi)置斷言庫(assert),這里我們可以看一個(gè)簡(jiǎn)單的例子:
try {assert(false, '這個(gè)值應(yīng)該是true')} catch(e) {console.log(e instanceof assert.AssertionError) // trueconst { actual, expected, operator } = econsole.log(`實(shí)際值: ${actual},期望值: ${expected}, 使用的運(yùn)算符:${operator}`)// 實(shí)際值: false,期望值: true, 使用的運(yùn)算符:==}assert模塊提供了不少的方法,例如strictEqual、deepStrictEqual、notDeepStrictEqual等,仔細(xì)觀察這幾個(gè)方法,我們又得來回顧一下JavaScript中的相等比較算法:
- 抽象相等比較算法 (==)
- 嚴(yán)格相等比較算法 (===)
- SameValue (Object.is())
- SameValueZero
幾個(gè)方法的區(qū)別可以查看這可能是你學(xué)習(xí)ES7遺漏的知識(shí)點(diǎn)。
在Node10.2.0文檔中你會(huì)發(fā)現(xiàn)像assert.equal、assert.deepEqual這樣的api已經(jīng)被廢除,也正是避免==的復(fù)雜性帶來的易錯(cuò)性。而保留下來的api基本上多是采用后幾種算法,例如:
- strictEqual使用了嚴(yán)格比較算法
- deepStrictEqual在比較原始值時(shí)采用SameValue算法
三、chai.js
從上面的例子可以發(fā)現(xiàn),JavaScript中內(nèi)置的斷言方法并不是特別的全面,所以這里我們可以選擇一些三方庫來滿足我們的需求。
這里我們可以選擇chai.js,它支持兩種風(fēng)格的斷言(TDD和BDD):
const chai = require('chai')const assert = chai.assertconst should = chai.should()const expect = chai.expectconst foo = 'foo'// TDD風(fēng)格 assertassert.typeOf(foo, 'string')// BDD風(fēng)格 shouldfoo.should.be.a('string')// BDD風(fēng)格 expectexpect(foo).to.be.a('string')大部分人多會(huì)選擇expect斷言庫,的確用起來感覺不錯(cuò)。具體可以查看官方文檔,畢竟確認(rèn)過眼神,才能選擇適合的庫。
四、expect.js源碼分析
expect.js不僅提供了豐富的調(diào)用方法,更重要的就是它提供了類似自然語言的鏈?zhǔn)秸{(diào)用。
鏈?zhǔn)秸{(diào)用
談到鏈?zhǔn)秸{(diào)用,我們一般會(huì)采用在需要鏈?zhǔn)秸{(diào)用的函數(shù)中返回this的方法實(shí)現(xiàn):
class Person {constructor (name, age) {this.name = namethis.age = age}updateName (val) {this.name = valreturn this}updateAge (val) {this.age = valreturn 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中并不僅僅采用這樣的方式實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,首先我們要知道expect實(shí)際上是Assertion的實(shí)例:
function expect (obj) {return new Assertion(obj)}接下來看核心的Assertion構(gòu)造函數(shù):
function Assertion (obj, flag, parent) {this.obj = obj;this.flags = {};// 通過flags記錄鏈?zhǔn)秸{(diào)用用到的那些標(biāo)記符,// 主要用于一些限定條件的判斷,比如not,最終返回結(jié)果時(shí)會(huì)通過查詢flags中的not是否為true,來決定最終返回結(jié)果if (undefined != parent) {this.flags[flag] = true;for (var i in parent.flags) {if (parent.flags.hasOwnProperty(i)) {this.flags[i] = true;}}}// 遞歸注冊(cè)Assertion實(shí)例,所以expect是一個(gè)嵌套對(duì)象var $flags = flag ? flags[flag] : keys(flags), self = this;if ($flags) {for (var i = 0, l = $flags.length; i < l; i ) {// 避免進(jìn)入死循環(huán)if (this.flags[$flags[i]]) {continue}var name = $flags[i], assertion = new Assertion(this.obj, name, this)// 這里要明白修飾符中有一部分也是Assertion原型上的方法,例如 an, be。if ('function' == typeof Assertion.prototype[name]) {// 克隆原型上的方法var old = this[name];this[name] = function () {return old.apply(self, arguments);};// 因?yàn)楫?dāng)前是個(gè)函數(shù)對(duì)象,你要是在后面鏈?zhǔn)秸{(diào)用了Assertion原型上方法是找不到的。// 所以要將Assertion原型鏈上的所有的方法設(shè)置到當(dāng)前的對(duì)象上for (var fn in Assertion.prototype) {if (Assertion.prototype.hasOwnProperty(fn) && fn != name) {this[name][fn] = bind(assertion[fn], assertion);}}} else {this[name] = assertion;}}}}為什么要這樣設(shè)計(jì)?我的理解是:首先expect.js的鏈?zhǔn)秸{(diào)用充分的體現(xiàn)了調(diào)用的邏輯性,而這種嵌套的結(jié)構(gòu)真正的體現(xiàn)了各個(gè)修飾符之間的邏輯性。
所以我們可以這樣書寫:
const student = {name: 'xiaoming',age: 20}expect(student).to.be.a('object')當(dāng)然這并沒有完,對(duì)于每一個(gè)Assertion原型上的方法多會(huì)直接或者間接的調(diào)用assert方法:
Assertion.prototype.assert = function (truth, msg, error, expected) {// 這就是flags屬性的作用之一var msg = this.flags.not ? error : msg, ok = this.flags.not ? !truth : truth, err;if (!ok) {// 拋出錯(cuò)誤err = new Error(msg.call(this));if (arguments.length > 3) {err.actual = this.obj;err.expected = expected;err.showDiff = true;}throw err;}// 為什么這里要再創(chuàng)建一個(gè)Assertion實(shí)例?也正是由于expect實(shí)例是一個(gè)嵌套對(duì)象。this.and = new Assertion(this.obj);};并且每一個(gè)Assertion原型上的方法最終通過返回this來實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。所以我們還可以這樣寫:
expect(student).to.be.a('object').and.to.have.property('name')到此你應(yīng)該已經(jīng)理解了expect.js的鏈?zhǔn)秸{(diào)用的原理,總結(jié)起來就是兩點(diǎn):
- 原型方法還是通過返回this,實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用;
- 通過嵌套結(jié)構(gòu)的實(shí)例對(duì)象增強(qiáng)鏈?zhǔn)秸{(diào)用的邏輯性;
所以我們完全可以這樣寫:
// 強(qiáng)烈不推薦 不然怎么能屬于BDD風(fēng)格呢?expect(student).a('object').property('name')????喜歡本文的小伙伴們,歡迎關(guān)注我的訂閱號(hào)超愛敲代碼,查看更多內(nèi)容.
總結(jié)
以上是生活随笔為你收集整理的JavaScript中不得不说的断言?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [JSConf EU 2018] 大脑控
- 下一篇: 2016 年崛起的 JS 项目