Underscore, a commonly used javascript tool library for development, provides a rich set of functional programming capabilities that extend over 100 method functions on custom _ objects rather than the original javascript native objects. In this series, you’ll build your own underscore framework from the uderScore source code perspective
First, frame design
1.1 Self-executing functions
Modern JS library framework design, are generally in the form of self-executing functions, self-executing functions generally have two forms
(function(){// form 1}())Copy the code
(function(){// form 2})()Copy the code
As we know, the form of function declaration will be mounted to the Window object as a method, and the form of function expression will be mounted to the Window object as a property, which will cause variable pollution. The advantage of self-executing functions is that it can prevent variable pollution, and the function will be destroyed immediately after execution.
1.2 Usage Style
Underscore has two stylistic forms that can be used, one an object-oriented type and the other a function type.
// example _.map([1, 2, 3],function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });
Copy the code
Therefore, both object and function scenarios need to be considered when defining the underscore class. When called as a function, you need to treat _ as a constructor and return its instantiation. The following code
(function(root){
var _ = function (obj) {
if(! (this instanceof _)) {return new _(obj)
}
}
root._ = _
}(this))
Copy the code
1.3 Operating Environment
Nowadays, front-end development emphasizes modularity. For node server, we have commonJS specification, and for client, we have AMD and CMD specification, corresponding module loaders are RequireJS and SeaJS. Currently the prevailing javascript module specifications are focused on CommonJS and AMD, so the purpose of defining the underscore library is to make it applicable to various specifications. The framework is defined to detect the use of the environment and to comply with various specifications.
- Module. Exports = {} Server: the commonJS specification tests whether or not module. Exports = {} server: the commonJS specification tests whether or not module.
- Client: AMD specification, detect define. AMD exists, meet the pass
define('**', [], function(){ return '***' })
Exposed to the module
(function (root) {
var _ = function (obj) {
if(! (this instanceof _)) {returnNew _(obj)}} // The commonJS specification checks whether module.exports existsif((typeof module ! = ='undefined' && module.exports)) {
module.exports = {
_: _
}
} else{ root._ = _; // amd specification, check if define. Amd existsif (typeof define == 'function' && define.amd) {
define('underscore'[],function () {
return _;
});
}
}(this))
Copy the code
1.3.1 Server
// commonjs
const _ = require('./underscore.js')
console.log(_)
Copy the code
1.3.2 Client Usage
// AMD
require(['underscore'].function (underscore) {
console.log(underscore)
})
Copy the code
1.4 Method Definition
Calls to underscore can be made either through _.unique() or _().unique(), both of which have the same effect but require that two sets of methods be defined at framework design time, one for static methods that define _ objects and the other for methods that extend the prototype chain of _ objects.
_.uniqe = function() {}
_.prototype.unique = function() {}
Copy the code
To avoid redundant code, you can make a copy of a defined static method as a method on the prototype chain
(function(root) {... _. Mixins =function() {// copy static method to prototype} _.mixins() // execute method}(this))Copy the code
The mixins method implementation requires that all static methods on the underscore object be traversed, so the underscore method _. Each needs to be implemented first
1.41 _. Each
_. Each (list, iteratee, [context]) Alias: forEach iteratee by iteratee. If the context argument is passed, iteratee is bound to the context object. Each call to Iteratee passes three arguments: Element, index, list. If list is a JavaScript object, iteratee takes (value, key, list). Returns a list to facilitate chain calls.
The first parameter of each can support three types of array, class array and object according to the document. Array class array and object are handled differently in traversal. The former callback handles values and subscripts, while the latter handles values and attributes.
// Check array, class array method (function(root) {
···
_.each = function(list, callback, context) {// The existence of the context changes this in the callback to var I = 0; var key;if(isArrayLikeLike(list)) {// Like likelikefor (var i = 0; i < list.length; i++) {
context ? callback.call(context, list[i], i, list) : callback(list[i], i, list)
}
} else{/ / objectfor (key in list) {
context ? callback.call(context, list[key], key) : callback(list[key], key)
}
}
}
var isArrayLike = function(collection) {// Return the length attribute of the collection parameter var length = collection.length; // MAX_ARRAY_INDEX = math.pow (2, 53) -1return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
}
}(this))
Copy the code
1.4.2 _. Mixins
Mixin methods are designed to extend more methods on the underscore prototype object, which can be used to extend user-defined methods, for example
_.mixin({
capitalize: function(string) {
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
});
_("fabio").capitalize(); = >"Fabio"
Copy the code
It can also be used to copy static methods internally to methods in the prototype chain.
(function(root){··· · var push = array.prototypefunction (obj) {
if(! (this instanceof _)) {returnNew _(obj)} this.wrap = obj // Store the arguments passed by the instance object} _function (obj) {
_.each(obj, function (value, key) {
_.prototype[key] = function () {
var args = [this.wrap]
push.apply(args, arguments)
return value.apply(this, args)
}
})
}
_.mixins(_)
}(this))
Copy the code
Static methods need to pass the target source as the method argument (_. Unique), while the instance method’s target source is stored in the constructor object’s properties. That is _(target source).unique(callback function), so you need to combine the attributes and callback functions when defining instance methods. The Array. The prorotype. Push. Apply ([this. Wrap], the arguments), then he passed as a parameter to a static method and returns the result of a decision making process.
A method to convert an array of classes to an array
Array. The prototype. Slice. Call (Array)
var a = []; Array. The prototype. Push. Apply (class a, Array); console.log(a);
var a = []; Array. The prototype. Concat. Apply (class a, Array); console.log(a);
- ES6 method
Array.from(class Array)
- ES6 extension operator
Var args = [... class array]
1.5 Chain call
1.5.1 _. Chain ()
Returns an encapsulated object. Calling a method on a wrapped object returns the wrapped object itself, until the value method is called.
Calls to methods in underscore return processed values and therefore cannot support chained calls to methods. If chain calls are required, the chain() method is used, which returns the instance object of the underscore after each call to the method until the value method is called.
(functionThe (root){··· // chain method returns an _ instance marked with whether the _. Chain = of the instance is allowedfunction(obj) {
var instance = _(obj);
instance.chain = true;
returnVar chainResult = var chainResult = var chainResult = var chainResult = var chainResult =function (instance, obj) {
return instance.chain ? _(obj).chain() : obj
}
_.mixins = function (obj) {
_.each(obj, function (value, key) {
_.prototype[key] = function () {
var args = [this.wrap]
push.apply(args, arguments)
returnChainResult (this, value.apply(this, args)) // Modify the return value of the instance method. The return value is wrapped with chainResult, and the return value is changed according to the judgment of chainResult.Copy the code
1.5.2 the value ()
Because chained calls cause the method of underscore to return its instance object, value() is used when the call behavior needs to end. The value() method returns the result of the call.
(function(root) {... _. Value =function(instance) {
return instance.wrap
}
}(this))
Copy the code
- Create your own Underscore series (1) – Frame Design
- Create your own Underscore series (2) – Data type diagnostics
- Create your own Underscore Series (3) – Iterators (1)
- Create your Own Underscore Series (4) – Iterators (2)
- Make your own underscore series (5) – partial functions and function currization
- Create your own Underscore series (6) – Shuffle algorithms