you-dont-need / You-Dont-Need-Lodash-Underscore

List of JavaScript methods which you can use natively + ESLint Plugin
MIT License
18.83k stars 814 forks source link

Guide: Creating Valid Substitutions for Lodash Functions #385

Open Uzlopak opened 1 year ago

Uzlopak commented 1 year ago

Creating Valid Substitutions for Lodash Functions

This document provides a comprehensive guide on creating valid substitutions for Lodash functions, using the _.isBoolean function as an exemplar.

Retrieving the Original Lodash Functionality

Lodash can be complex, with each function residing in its own file. To simplify the process of determining the original functionality, you can utilize per-method packages provided by Lodash. Follow these steps:

  1. Visit the npm page of the function you want to substitute. In our example, we visit the npm page of the lodash.isboolean package.
  2. Click on the code tab to access the source code of the package, which is bundled in a single file named index.js.

Here is the content of the index.js for reference:

/**
 * lodash 3.0.3 (Custom Build) <https://lodash.com/>
 * Build: `lodash modularize exports="npm" -o ./`
 * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
 * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
 * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 * Available under MIT license <https://lodash.com/license>
 */

/** `Object#toString` result references. */
var boolTag = '[object Boolean]';

/** Used for built-in method references. */
var objectProto = Object.prototype;

/**
 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
 * of values.
 */
var objectToString = objectProto.toString;

/**
 * Checks if `value` is classified as a boolean primitive or object.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
 * @example
 *
 * _.isBoolean(false);
 * // => true
 *
 * _.isBoolean(null);
 * // => false
 */
function isBoolean(value) {
  return value === true || value === false ||
    (isObjectLike(value) && objectToString.call(value) == boolTag);
}

/**
 * Checks if `value` is object-like. A value is object-like if it's not `null`
 * and has a `typeof` result of "object".
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
 * @example
 *
 * _.isObjectLike({});
 * // => true
 *
 * _.isObjectLike([1, 2, 3]);
 * // => true
 *
 * _.isObjectLike(_.noop);
 * // => false
 *
 * _.isObjectLike(null);
 * // => false
 */
function isObjectLike(value) {
  return !!value && typeof value == 'object';
}

module.exports = isBoolean;

Creating the Substitution

Template

Begin with the following template for creating the substitution:

'use strict';

const assert = require('assert');
const _ = require('lodash');

function isBoolean() {
  // TODO
}

// Assertions
// TODO

Assertions or tests are essential to ensure the validity of your substitution. You can use the examples provided in the comments of the original function. It is also recommended to add additional tests while implementing the substitution to ensure the function behaves as expected.

In this example, we use Lodash as the reference implementation and do not test it against the expected result.

From the comments in the original function, extract the following assertions:

assert.strictEqual(_.isBoolean(false), isBoolean(false));
assert.strictEqual(_.isBoolean(null), _.isBoolean(null));

Additionally, include the not documented true case:

assert.strictEqual(_.isBoolean(true), isBoolean(true));

Now, let's start with the implementation.

Implementation

A clear understanding of the original function is crucial:

// isBoolean from lodash
function isBoolean(value) {
  return value === true || value === false ||
    (isObjectLike(value) && objectToString.call(value) == boolTag);
}

Lodash's isBoolean explicitly compares against true and false. While the typeof operator might seem sufficient, benchmarking shows that explicitly comparing with true and false is faster.

Lodash's isBoolean uses isObjectLike in combination with objectToString. This check is for "instantiated" Boolean objects.

Keep in mind that using objects of primitive types is not recommended. However, for the sake of creating a valid substitution, it's necessary to implement this check. You can add a comment to the substitution, indicating that the check can be removed if the user is sure that no Boolean objects are used.

isObjectLike essentially performs a null check and a typeof check. objectToString is a reference to Object.prototype.toString. The variable boolTag is simply a string reference to the toString result of a Boolean object. You can remove this variable and use the string directly.

The final implementation is as follows:

// isBoolean substitute
function isBoolean(value) {
  return (
    value === true ||
    value === false ||
    // not necessary if no Boolean objects are used
    (
      value && // null check
      typeof value === 'object' && // check if it is an object
      Object.prototype.toString.call(value) === '[object Boolean]' // check if it is a Boolean object
    )
  )
}

For the Boolean object branch, include assertions/tests:

// Truthy cases
assert.strictEqual(_.isBoolean(Boolean()), isBoolean(Boolean()));
assert.strictEqual(_.isBoolean(Boolean(true)), isBoolean(Boolean(true)));
assert.strictEqual(_.isBoolean(Boolean(false)), isBoolean(Boolean(false)));

// Instantiated Boolean objects
assert.strictEqual(_.isBoolean(new Boolean(true)), isBoolean(new Boolean(true)));
assert.strictEqual(_.isBoolean(new Boolean(false)), isBoolean(new Boolean(false)));

// Falsy cases
assert.strictEqual(_.isBoolean(new String(true)), isBoolean(new String(true)));
assert.strictEqual(_.isBoolean(new String(false)), isBoolean(new String(false)));

Final Result

Here's the final implementation with assertions:

'use strict';

const assert = require('assert');
const _ = require('lodash');

function isBoolean(value) {
  return (
    value === true ||
    value === false ||
    // Not necessary if no Boolean objects are used
    (
      value && // Null check
      typeof value === 'object' && // Check if it is an object
      Object.prototype.toString.call(value) === '[object Boolean]' // Check if it is a Boolean object
    )
  )
}

// Assertions
assert.strictEqual(_.isBoolean(false), isBoolean(false));
assert.strictEqual(_.isBoolean(null), _.isBoolean(null));
assert.strictEqual(_.isBoolean(true), isBoolean(true));

// Truthy cases
assert.strictEqual(_.isBoolean(Boolean()), isBoolean(Boolean()));
assert.strictEqual(_.isBoolean(Boolean(true)), isBoolean(Boolean(true)));
assert.strictEqual(_.isBoolean(Boolean(false)), is

Boolean(Boolean(false)));

// Instantiated Boolean objects
assert.strictEqual(_.isBoolean(new Boolean(true)), isBoolean(new Boolean(true)));
assert.strictEqual(_.isBoolean(new Boolean(false)), isBoolean(new Boolean(false)));

// Falsy cases
assert.strictEqual(_.isBoolean(new String(true)), isBoolean(new String(true)));
assert.strictEqual(_.isBoolean(new String(false)), isBoolean(new String(false));

Running this code in Node will demonstrate that all assertions pass.

Publishing the Substitution

You can now propose your substitution to the You-Dont-Need-Lodash-Underscore project. You can open an issue to discuss the substitution or create a pull request directly. Follow the patterns outlined in the Readme.md and add your substitution to the appropriate section, listed alphabetically. Use the Lodash website to identify the correct section.

Transform the assertions into Mocha tests in the test/unit/all.js file.

To determine browser support, refer to resources such as the caniuse website or the Mozilla Developer Network.

Uzlopak commented 1 year ago

@stevemao

Do you think this is something we want to pin or store as a markdown file in the repo?