Утверждения, которые должны быть произнесены в JavaScript?

Node.js внешний интерфейс алгоритм JavaScript
Утверждения, которые должны быть произнесены в JavaScript?

Утверждения в основном используются для «отладки» и «тестирования».

Во-первых, утверждение в начале

При внимательном рассмотрении API в JavaScript действительно не так много методов для утверждений. Единственный — console.assert:

  // console.assert(condition, message)
  const a = '1'
  console.assert(typeof a === 'number', 'a should be Number')

Когда условие ложно, этот метод выведет сообщение об ошибке на консоль. Если правда, ничего не происходит.

На самом деле метод console.assert редко используется.Если вы почитаете проекты с открытым исходным кодом, такие как vue или vuex, вы обнаружите, что все они имеют собственные методы утверждения:

  // Vuex源码中的工具函数
  function assert (condition, msg) {
    if (!condition) {
      throw new Error(`[Vuex] ${msg}`)
    }
  }

2. Утверждения в узле

В Node есть встроенная библиотека утверждений (assert), здесь мы можем увидеть простой пример:

  try {
    assert(false, '这个值应该是true')
  } catch(e) {
    console.log(e instanceof assert.AssertionError) // true
    const { actual, expected, operator } = e
    console.log(`实际值: ${actual},期望值: ${expected}, 使用的运算符:${operator}`)
    // 实际值: false,期望值: true, 使用的运算符:==
  }

Модуль assert предоставляет множество методов, таких как strictEqual, deepStrictEqual, notDeepStrictEqual и т. д. Внимательно изучив эти методы, мы должны рассмотреть алгоритм сравнения равенства в JavaScript:

  • Алгоритм сравнения абстрактного равенства (==)
  • Алгоритм сравнения строгого равенства (===)
  • SameValue (Object.is())
  • SameValueZero

Разницу между несколькими методами можно посмотретьВозможно, это то знание, которое вы упустили при изучении ES7..

В документации Node10.2.0 вы обнаружите, что API, как Assert.equal и Assert.deepequal, были устарели, что именно следует избегать прогрессии, привлеченной к сложности ==. Остальные API в основном используют последние алгоритмы, такие как:

  • strictEqual использует алгоритм строгого сравнения
  • deepStrictEqual использует алгоритм SameValue при сравнении примитивных значений.

3. чай.js

Как видно из приведенных выше примеров, встроенные методы утверждений в JavaScript не являются особенно исчерпывающими, поэтому здесь мы можем выбрать некоторые сторонние библиотеки для удовлетворения наших потребностей.

Здесь мы можем выбрать chai.js, который поддерживает два стиля утверждений (TDD и BDD):

  const chai = require('chai')
  const assert = chai.assert
  const should = chai.should()
  const expect = chai.expect

  const foo = 'foo'

  // TDD风格 assert
  assert.typeOf(foo, 'string')

  // BDD风格 should
  foo.should.be.a('string')

  // BDD风格 expect
  expect(foo).to.be.a('string')

Большинство людей выберет ожидаемую библиотеку утверждений, и ее удобно использовать. В частности, вы можете просмотретьофициальная документация, ведь после подтверждения глаз можно выбрать подходящую библиотеку.

Четыре, анализ исходного кода expect.js

expect.js не только предоставляет богатые методы вызова, но, что более важно, он обеспечивает связанные вызовы, подобные естественному языку.

цепной вызов

Когда дело доходит до связанных вызовов, мы обычно реализуем метод, который возвращает this, в функцию, которую необходимо объединить в цепочку:

  class Person {
    constructor (name, age) {
      this.name = name
      this.age = age
    }
    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 на самом деле является экземпляром Assertion:

  function expect (obj) {
    return new Assertion(obj)
  }

Затем взгляните на основной конструктор Assertion:

  function Assertion (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;
        }
      }
    }

    // 递归注册Assertion实例,所以expect是一个嵌套对象
    var $flags = flag ? flags[flag] : keys(flags)
      , self = this;
    if ($flags) {
      for (var i = 0, l = $flags.length; i < l; i++) {
        // 避免进入死循环
        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);
          };

          // 因为当前是个函数对象,你要是在后面链式调用了Assertion原型上方法是找不到的。
          // 所以要将Assertion原型链上的所有的方法设置到当前的对象上
          for (var fn in Assertion.prototype) {
            if (Assertion.prototype.hasOwnProperty(fn) && fn != name) {
              this[name][fn] = bind(assertion[fn], assertion);
            }
          }
        } else {
          this[name] = assertion;
        }
      }
    }
  }

Почему он разработан таким образом? Я так понимаю: во-первых, цепочка вызовов expect.js полностью отражает логику вызова, и эта вложенная структура действительно отражает логику между модификаторами.

Итак, мы можем написать это так:

  const student = {
    name: 'xiaoming',
    age: 20
  }

  expect(student).to.be.a('object')

Конечно, это не конец, для каждого метода прототипа Assertion прямо или косвенно будет вызываться метод 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) {
      // 抛出错误
      err = new Error(msg.call(this));
      if (arguments.length > 3) {
        err.actual = this.obj;
        err.expected = expected;
        err.showDiff = true;
      }
      throw err;
    }

    // 为什么这里要再创建一个Assertion实例?也正是由于expect实例是一个嵌套对象。
    this.and = new Assertion(this.obj);
  };

И каждый метод прототипа Assertion, наконец, реализует связанные вызовы, возвращая this. Так что мы также можем написать:

  expect(student).to.be.a('object').and.to.have.property('name')

К этому моменту вы должны были понять принцип цепного вызова expect.js, который можно суммировать в двух пунктах:

  • Метод-прототип по-прежнему реализует цепочку вызовов, возвращая this;
  • Улучшить логику связанных вызовов через объекты-экземпляры вложенных структур;

Итак, мы можем написать это так:

  // 强烈不推荐 不然怎么能属于BDD风格呢?
  expect(student).a('object').property('name')

Друзья, которым понравилась эта статья, прошу обратить внимание на мой номер подпискилюблю набирать код, узнать больше.