Home > Web Front-end > JS Tutorial > A brief analysis of the overall architecture of Underscore

A brief analysis of the overall architecture of Underscore

高洛峰
Release: 2016-11-05 09:23:51
Original
1130 people have browsed it

Foreword

Finally, the poster’s “Underscore Source Code Interpretation Series” underscore-analysis is coming to an end. If you pay attention to the timeline, you will find that the poster has recently accelerated the speed of interpretation. November is an eventful month. The author has been exhausted mentally and physically due to a lot of things happening recently. He also wants to finish this series as soon as possible, but he just has one thing on his mind.

This article is expected to be the penultimate article in the interpretation series, and the last article is obviously the summary. The full version of the poster's Underscore series interpretation is at https://github.com/hanzichi/u...

Regular call

The articles written before mostly focus on specific methods, specific knowledge details, and there are also readers' comments and suggestions. The poster talks about the overall architecture. This is something that must be talked about, but the poster arranges it at the end, that is, in this article, because the poster feels that there is no big problem in understanding the specific methods without mastering the overall architecture.

Underscore most of the time is called in the form of _.funcName(xx, xx), which is also the calling method in the document.

_.each([1, 2, 3], alert);
Copy after login

The simplest way to implement it, we can regard _ as a simple object:

var _ = {}; 
_.each = function() { 
  // ... 
};
Copy after login

In JavaScript, everything is an object. In fact, the _ variable in the source code is a method:

var _ = function(obj) { 
  if (obj instanceof _) return obj; 
  if (!(this instanceof _)) return new _(obj); 
  this._wrapped = obj; 
};
Copy after login

Why is it a method? ?Let’s go see it next.

OOP

Underscore supports OOP form of calling:

_([1, 2, 3]).each(alert);
Copy after login

This is actually a very classic "no new construction", _ is actually a constructor, and the result of _([1, 2, 3]) is an object instance , this instance has a _wrapped attribute, and the attribute value is [1, 2, 3]. The instance needs to call the each method. If it does not have this method, it should come from the prototype chain, that is to say, there should be this method on _.prototype. So, how is the method mounted?

Method mounting

Now we The following two points have been made clear:

_ 是一个函数(支持无 new 调用的构造函数)
_ 的属性有很多方法,比如 _.each,_.template 等等
Copy after login

Our goal is to allow the constructed instance of _ to also call these methods. If you think about it carefully, it's actually not difficult. We can traverse the attributes on _. If the attribute value type is a function, then hang the function on the prototype chain of _.

The _.mixin method used to accomplish this in the source code:

// Add your own custom functions to the Underscore object. 
// 可向 underscore 函数库扩展自己的方法 
// obj 参数必须是一个对象(JavaScript 中一切皆对象) 
// 且自己的方法定义在 obj 的属性上 
// 如 obj.myFunc = function() {...} 
// 形如 {myFunc: function(){}} 
// 之后便可使用如下: _.myFunc(..) 或者 OOP _(..).myFunc(..) 
_.mixin = function(obj) { 
  // 遍历 obj 的 key,将方法挂载到 Underscore 上 
  // 其实是将方法浅拷贝到 _.prototype 上 
  _.each(_.functions(obj), function(name) { 
    // 直接把方法挂载到 _[name] 上 
    // 调用类似 _.myFunc([1, 2, 3], ..) 
    var func = _[name] = obj[name]; 
 
    // 浅拷贝 
    // 将 name 方法挂载到 _ 对象的原型链上,使之能 OOP 调用 
    _.prototype[name] = function() { 
      // 第一个参数 
      var args = [this._wrapped]; 
 
      // arguments 为 name 方法需要的其他参数 
      push.apply(args, arguments); 
      // 执行 func 方法 
      // 支持链式操作 
      return result(this, func.apply(_, args)); 
    }; 
  }); 
}; 
 
// Add all of the Underscore functions to the wrapper object. 
// 将前面定义的 underscore 方法添加给包装过的对象 
// 即添加到 _.prototype 中 
// 使 underscore 支持面向对象形式的调用 
_.mixin(_);
Copy after login

The _.mixin method can add your own defined methods to the Underscore library:

_.mixin({ 
  capitalize: function(string) { 
    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); 
  } 
}); 
_("fabio").capitalize(); 
=> "Fabio"
Copy after login

At the same time, Underscore also adds some Array native methods:

// Add all mutator Array functions to the wrapper. 
// 将 Array 原型链上有的方法都添加到 underscore 中 
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 
  var method = ArrayProto[name]; 
  _.prototype[name] = function() { 
    var obj = this._wrapped; 
    method.apply(obj, arguments); 
 
    if ((name === 'shift' || name === 'splice') && obj.length === 0) 
      delete obj[0]; 
 
    // 支持链式操作 
    return result(this, obj); 
  }; 
}); 
 
// Add all accessor Array functions to the wrapper. 
// 添加 concat、join、slice 等数组原生方法给 Underscore 
_.each(['concat', 'join', 'slice'], function(name) { 
  var method = ArrayProto[name]; 
  _.prototype[name] = function() { 
    return result(this, method.apply(this._wrapped, arguments)); 
  }; 
});
Copy after login

Chained calls

Underscore also supports chained calls:

// 非 OOP 链式调用 
_.chain([1, 2, 3]) 
  .map(function(a) {return a * 2;}) 
  .reverse() 
  .value(); // [6, 4, 2] 
 
// OOP 链式调用 
_([1, 2, 3]) 
  .chain() 
  .map(function(a){return a * 2;}) 
  .first() 
  .value(); // 2
Copy after login

At first glance, it seems that there are two forms of chained calls, OOP and non-OOP. In fact, it is just one, _.chain([1, 2, 3]) The result of _([1, 2, 3]).chain() is the same. How to implement it? Let’s take a closer look at the chain method.

_.chain = function(obj) {

_.chain = function(obj) { 
  // 无论是否 OOP 调用,都会转为 OOP 形式 
  // 并且给新的构造对象添加了一个 _chain 属性 
  var instance = _(obj); 
 
  // 标记是否使用链式操作 
  instance._chain = true; 
 
  // 返回 OOP 对象 
  // 可以看到该 instance 对象除了多了个 _chain 属性 
  // 其他的和直接 _(obj) 的结果一样 
  return instance; 
};
Copy after login

Let’s look at the result of _.chain([1, 2, 3]) and substitute the parameters into the function. In fact, it means constructing the parameters without new and then returning the instance. , except that the instance has an additional _chain attribute, and the others are exactly the same as direct _([1, 2, 3]). Let's look at _([1, 2, 3]).chain(), _([1, 2, 3]) returns a constructed instance. This instance has a chain method. Call the method, add the _chain attribute to the instance, and return the instance. object. Therefore, the effects of the two are consistent, and the results are converted to OOP.

Having said so much, it seems that we haven’t gotten to the point yet, how is it “chained”? Let’s take the following code as an example:

_([1, 2, 3]) 
  .chain() 
  .map(function(a){return a * 2;}) 
  .first() 
  .value(); // 2
Copy after login

When the map method is called, there may actually be a return value. Let’s take a look at the _.mixin source code:

// 执行 func 方法 
// 支持链式操作 
return result(this, func.apply(_, args));
Copy after login

result is an important internal helper function (Helper function):

// Helper function to continue chaining intermediate results. 
// 一个帮助方法(Helper function) 
var result = function(instance, obj) { 
// 如果需要链式操作,则对 obj 运行 chain 方法,使得可以继续后续的链式操作 
// 如果不需要,直接返回 obj 
return instance._chain ? _(obj).chain() : obj; 
};
Copy after login

If a chain operation is required (the instance will have the _chain attribute), call the chain function on the operation result , so that chain calls can continue.



Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template