The Birth of underscore: Basic Structure Construction

Keywords: Javascript Attribute JQuery Programming Vue

1. introduction

Ununderscore is a mature and reliable third-party open source library. Just as jQuery unifies the differences of DOM operations among different browsers, it allows us to operate DOM simply. Ununderscore provides a set of perfect functional programming interfaces, which makes it easier for us to implement functional programming in JavaScript.

When jQuery loads, it binds itself to the only global variable, $like underscore, which binds itself to the only global variable, _which is why its name is underscore.

Before building underscore, let's look at what is "immediate execution function" (IIFE).

2. Immediate Execution Function (IIFE)

To execute a function immediately, as its name implies, is to execute a defined anonymous function immediately, as follows:

(function(name) {
  console.log(name);
})('suporka');

Its function is to create a new function scope by defining an anonymous function, which is equivalent to creating a "private" namespace. The variables and methods of the namespace will not destroy the pollution of the global namespace.

// Internal variables are not available outside the function, so they will not pollute the function. Internal variables can be used internally.
(function() {
  var name = 'suporka';
})();

console.log(name); // name is undefinded

3. Mounting of Global Variable _

When we use. map([1,2,3], function(item){console.log(item)}) in browsers, is mounted on Window s objects, if we want to use it in node environment?

(function() {
  // root is the mount object, self or global or this or {}
  var root =
    (typeof self == 'object' && self.self === self && self) ||
    (typeof global == 'object' && global.global === global && global) ||
    this ||
    {};

  // _ It should be an object with attribute functions in it.
  var _ = {};
  root._ = _;
  _.VERSION = '1.9.1'; // Give our underscore a version number.
})();

4. Functional Style & Double Implementation of Object-Oriented Style

First, we implement an inverted string method

(function() {
  // root is the mount object, self or global or this or {}
  var root =
    (typeof self == 'object' && self.self === self && self) ||
    (typeof global == 'object' && global.global === global && global) ||
    this ||
    {};

  // _ It should be an object with attribute functions in it.
  var _ = {};

  root._ = _;

  _.VERSION = '1.9.1'; // Give our underscore a version number.

  /**
   * String inversion
   */
  _.reverse = function(string) {
    return string
      .split('')
      .reverse()
      .join('');
  };
})();

_.reverse('suporka'); // akropus

Yes, it will be implemented very soon, but this is a functional way of writing, calling a function to implement, if we want to achieve object-oriented writing? For example ('suporka').reverse()! underscore supports this way of writing. If you look carefully at ('suporka'), you will find that is a function, which is inconsistent with our previous definition of var = {}; then how to achieve it?

Instance prototype

Let's test it first: if is a function, we need to save its parameter obj. new () to generate an instance prototype object.

function _(obj) {
  this._wrapped = obj;
}
_.reverse = function(string) {
  return string
    .split('')
    .reverse()
    .join('');
};
_.reverse('suporka'); // "akropus", functional call is no problem

new _('suporka');

From the figure, we can see that the constructor constructor of the prototype object of the instance refers to the original (obj) function. To invoke its reverse() method, only new ('suporka'). constructor. reverse() has one more level, which does not meet our original expectations. So why don't we add a function like reverse under the _proto_attribute, so that we can call it directly?

let us try it !

function _(obj) {
  this._wrapped = obj;
}
_.reverse = function(string) {
  return string
    .split('')
    .reverse()
    .join('');
};
_.reverse('suporka'); // "akropus", functional call is no problem

_.prototype.reverse = function() {
  return this._wrapped
    .split('')
    .reverse()
    .join('');
};
new _('suporka').reverse(); // "akropus", object-oriented invocation is no problem

5. Modification () function

New ('suporka'). reverse () is a bit cumbersome. Remove new and rewrite function ()

var _ = function(obj) {
  // If the object after the instance is passed in, return it
  if (obj instanceof _) return obj;
  // If it has not been instantiated, new_ (obj)
  if (!(this instanceof _)) return new _(obj);
  this._wrapped = obj;
};

_('suporka').reverse(); // "akropus", object-oriented invocation is no problem

6. Write an iterative function map()

/**
 * Array or object traversal method and return the modified object or array
 * @param iteratee callback
 * @param context The direction of this in callback function
 */
_.map = function(obj, iteratee, context) {
  var length = obj.length,
    results = Array(length);
  for (var index = 0; index < length; index++) {
    results[index] = iteratee.call(context, obj[index], index, obj);
  }

  return results;
};

_.prototype.map = function(iteratee, context) {
  var length = this._wrapped.length,
    results = Array(length);
  for (var index = 0; index < length; index++) {
    results[index] = iteratee.call(
      context,
      this._wrapped[index],
      index,
      this._wrapped
    );
  }

  return results;
};

_([1, 2, 3]).map(
  function(item) {
    console.log(item + this.value);
  },
  { value: 1 }
); // 2,3,4
_.map(
  [1, 2, 3],
  function(item) {
    console.log(item + this.value);
  },
  { value: 1 }
); // 2,3,4

Well, that's good. Perfect. Here you will find a problem. Every time I add a new method, I have to write this similar function on prototype at the same time. You will find that only obj is replaced by this._wrapped. Is there any way to make it automatically generated? The answer must be yes!

7. Automated prototyping

Before that, we need to implement a traversal method each(), as follows:

// Maximum value
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
// Determine whether it is an array
var isArrayLike = function(collection) {
  var length = collection.length;
  return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

/**
 * Array or object traversal method
 */
_.each = function(obj, callback) {
  var length,
    i = 0;

  if (isArrayLike(obj)) {
    // array
    length = obj.length;
    for (; i < length; i++) {
      //   There is an implicit call back. call (obj [i], obj [i], i);
      if (callback.call(obj[i], obj[i], i) === false) {
        break;
      }
    }
  } else {
    // object
    for (i in obj) {
      if (callback.call(obj[i], obj[i], i) === false) {
        break;
      }
    }
  }

  return obj;
};

Use each() to traverse all method functions mounted on and create corresponding method functions for prototype. So, before we do that, we need to know which method names are mounted on to write functions() to implement it.

/**
 * Judging whether it is a function
 */
_.isFunction = function(obj) {
  return typeof obj == 'function' || false;
};

/**
 * Get all the attribute function names of ____________
 */
_.functions = function(obj) {
  var names = [];
  for (var key in obj) {
    if (_.isFunction(obj[key])) names.push(key);
  }
  return names.sort();
};

Implement it with each():

var ArrayProto = Array.prototype;
var push = ArrayProto.push;
_.each(_.functions(_), function(name) {
  var func = _[name];
  _.prototype[name] = function() {
    var args = [this._wrapped];
    // args = [this._wrapped, arguments[0], arguments[1]...], which is equivalent to replacing obj with this._wrapped
    push.apply(args, arguments);
    return func.apply(_, args);
  };
});

7. Current final code

(function() {
  // root is the mount object, self or global or this or {}
  var root =
    (typeof self == 'object' && self.self === self && self) ||
    (typeof global == 'object' && global.global === global && global) ||
    this ||
    {};

  var _ = function(obj) {
    // If the object after the instance is passed in, return it
    if (obj instanceof _) return obj;
    // If it has not been instantiated, new_ (obj)
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  // Maximum value
  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
  var ArrayProto = Array.prototype;
  var push = ArrayProto.push;
  // Determine whether it is an array
  var isArrayLike = function(collection) {
    var length = collection.length;
    return (
      typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX
    );
  };

  root._ = _;

  _.VERSION = '1.9.1'; // Give our underscore a version number.

  /**
   * String inversion
   */
  _.reverse = function(string) {
    return string
      .split('')
      .reverse()
      .join('');
  };
  /**
   * Determine whether it is a function
   */
  _.isFunction = function(obj) {
    return typeof obj == 'function' || false;
  };

  /**
   * Get all the attribute function names of ____________.
   */
  _.functions = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };
  /**
   * Array or object traversal method and return the modified object or array
   * @param iteratee callback
   * @param context The direction of this in callback function
   */
  _.map = function(obj, iteratee, context) {
    var length = obj.length,
      results = Array(length);
    for (var index = 0; index < length; index++) {
      results[index] = iteratee.call(context, obj[index], index, obj);
    }

    return results;
  };

  /**
   * Array or object traversal method
   */
  _.each = function(obj, callback) {
    var length,
      i = 0;

    if (isArrayLike(obj)) {
      // array
      length = obj.length;
      for (; i < length; i++) {
        //   There is an implicit call back. call (obj [i], obj [i], i);
        if (callback.call(obj[i], obj[i], i) === false) {
          break;
        }
      }
    } else {
      // object
      for (i in obj) {
        if (callback.call(obj[i], obj[i], i) === false) {
          break;
        }
      }
    }

    return obj;
  };

  _.each(_.functions(_), function(name) {
    var func = _[name];
    _.prototype[name] = function() {
      var args = [this._wrapped];
      // args = [this._wrapped, arguments[0], arguments[1]...], equivalent to replacing obj with this._wrapped
      push.apply(args, arguments);
      return func.apply(_, args);
    };
  });
})();

To be continued, wait for the next chapter

Advance_front_end

Front-end daily-question

Web Pack 4 builds Vue applications (create Vue)

Posted by salmanshafiq on Sat, 27 Apr 2019 09:30:36 -0700