Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Keys, Allkeys, extends, pick, clone, etc.

Keys and allkeys

_.keys({one: 1, two: 2, three: 3});
=> ["one", "two", "three"]

// allkeys
function Stooge(name) {
  this.name = name;
}
Stooge.prototype.silly = true;
_.allKeys(new Stooge("Moe"));
=> ["name", "silly"]
Copy the code

How to obtain the key of an object (not including the prototype chain)

Var nativeKeys = object.keys; // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys`. function Keys (obj) {// Not an object, return [] if (! isObject(obj)) return []; If (nativeKeys) return nativeKeys(obj); if (nativeKeys) return nativeKeys(obj); var keys = []; // the for-in loop iterates through all enumerable attributes on the prototype. For (var key in obj) if (has$1(obj, key)) keys.push(key); If (hasEnumBug) collectNonEnumProps(obj, keys); if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }Copy the code

The function collectNonEnumProps is called.

It’s for… There is a problem with browser compatibility in the browser.

  1. Determine compatibility problems in the current environment and list problematic attributes;
  2. Check whether the constructor property of the passed Object has been modified. If not, use the constructor’s prototype Object
  3. Add the constructor object and the keys array without it
  4. Loop through the list of properties with bugs, pull them out one by one, and determine whether their property values are the same as those on the prototype object. If they are different, it indicates that they have been rewritten and added
// Keys in IE < 9 that won't be iterated by `for key in ... 'and thus missed. // IE < 9 不 用 for key in... {toString: {toString: {toString: {toString: {toString: {toString: {toString: {toString: {toString: {toString: Null}. PropertyIsEnumerable ('toString') returns false Var hasEnumBug =! {var hasEnumBug =! {toString: null}.propertyIsEnumerable('toString'); // nonEnumerableProps () {// the constructor attribute is not the same as the other attributes. Var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; // obj is an object that needs to iterate over a key pair. // keys is an array of keys. Can directly change the values of an array of function collectNonEnumProps (obj, keys) {var nonEnumIdx = nonEnumerableProps. Length; var constructor = obj.constructor; / / get the Object's prototype / / if obj constructor is rewriting / / proto variables for the Object. The prototype / / if not be rewritten. / / it is obj constructor. The prototype var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; // Constructor is a special case. // the Constructor attribute needs special handling (is it necessary?) / / see / / https://github.com/hanzichi/underscore-analysis/issues/3 if you have any ` obj constructor ` this key / / and the key is not in the array of keys Var constructor = 'constructor'; if (_.has(obj, prop) && ! _.contains(keys, prop)) keys.push(prop); Keys while (nonEnumIdx--) {prop = nonEnumerableProps[nonEnumIdx]; // prop in obj returns true. Is judgment necessary? // obj[prop] ! If (prop in obj &&obj [prop]! == proto[prop] = if (prop in obj &&obj [prop]! == proto[prop] && ! _.contains(keys, prop)) { keys.push(prop); }}}Copy the code

The implementation of allKeys is roughly the same as this:

// Retrieve all the enumerable property names of an object. function allKeys(obj) { if (! isObject(obj)) return []; var keys = []; for (var key in obj) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }Copy the code

The difference between keys and keys is that… In is a property of itself

Once you have the keys of the object, it is easy to implement the values and Pairs methods

// _.values({one: 1, two: 2, three: 3}); ==> [1, 2, 3] // Retrieve the values of an object's properties. function values(obj) { var _keys = keys(obj); Var length = _keys.length; var values = Array(length); for (var i = 0; i < length; Values [I] = obj[_keys[I]]; } return values; }Copy the code

I looked at pairs as having the same functionality as Object.entrie, but this didn’t use Object.entrie to make a judgment

// _.pairs({one: 1, two: 2, three: 3}); // => [["one", 1], ["two", 2], ["three", 3]]

// Convert an object into a list of `[key, value]` pairs.
// The opposite of `_.object` with one argument.
function pairs(obj) {
  var _keys = keys(obj);
  var length = _keys.length;
  var pairs = Array(length);
  for (var i = 0; i < length; i++) {
    pairs[i] = [_keys[i], obj[_keys[i]]];
  }
  return pairs;
}
Copy the code

_.extend & _.extendOwn & _.defaults

CreateAssigner (); createAssigner (); createAssigner ();

/** * extend: simply overwrites all properties in the source object onto the Destination object and returns the destination object. Copying is sequential, so subsequent object properties overwrite previous object properties (if there are duplicates). * extendOwn: Similar to extend, but only copies its own attributes to override the target object. * Defaults: Use defaults objects to fill undefined properties in an object. */ / An internal function for creating assigner functions. Function createAssigner(keysFunc, // function(target, [source]){} iterates objects in [source] one by one, Target return function (obj) {var length = arguments.length; if (defaults) obj = Object(obj); // var defaults = createAssigner(allKeys, true); if (length < 2 || obj == null) return obj; For (var index = 1; var index = 1; index < length; Var source = arguments[index], // Return source keys(_. ExtendOwn) or allKeys keys = keysFunc(source), l = keys.length; For (var I = 0; i < l; i++) { var key = keys[i]; // The attributes of the target object are filled with undefiend attributes instead of those in defaults (extend and extendOwn). If the target object has a value, no processing is done (not overwriting the target object's attribute value) if (! defaults || obj[key] === void 0) obj[key] = source[key]; } } return obj; }; }Copy the code

Once the above function is implemented, the rest is easy, and now all three methods are implemented.

// Extend a given object with all the properties in passed-in object(s).
var extend = createAssigner(allKeys);
  
// Assigns a given object with all the own properties in the passed-in
// object(s).
// (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
var extendOwn = createAssigner(keys);

// Fill in a given object with default properties.
var defaults = createAssigner(allKeys, true);
Copy the code

isEqual

The next big thing to do is to see how underscore, arguably the most complex function of all, implements isEqual to determine whether two elements are equal

So let’s just say, if we were to implement a function like this, what would we do? What would the steps be?

Js is divided into basic data type and reference type, basic data type value equal is the same, but there are several special cases in basic data type, 0 and -0 are equal, but they are different; NaN is not equal to itself; Null undefined equals itself; Then determine the two parameter types, different types must be different, if the reference type to continue the depth comparison. For details, see the code comment:

Function equal (a, b) {return eq(a, b); // Perform a deep comparison to check if two objects are equal. }Copy the code
// Internal recursive comparison function for `_.isEqual`. function eq(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal). /** ** = * 1. ==> The basic type value is the same (1===1), and the reference type reference address is the same (a=b={} a===b) * 2. */ if (a === b) return a! If (a === b) return a! If (a === b) return a! == 0 || 1 / a === 1 / b; /** the following is a! == b, reference types may not be equal, */ // 'null' or 'undefined' only equal to itself (strict comparison) ` undefined `, it shows that the two are not identical, because ` null ` or ` undefined ` equal to their own the if (a = = null | | b = = null) return false. // NaN is equivalent, but non-reflexive. // NaN is equivalent, but non-reflexive. == a) return b ! == b; // Exhaust primitive checks var type = typeof a; // a is a basic type, b is not an object type, and the two basic types are not equal and are not special cases. == 'function' && type ! == 'object' && typeof b ! = 'object') return false; Return deepEq(a, b, aStack, bStack); }Copy the code

So how do you tell if two reference types are the same?

  1. The two arguments passed in are not_An instance of the function is reassign, compare the two values
// Internal recursive comparison function for `_.isEqual`. function deepEq(a, b, aStack, BStack) {// Unwrap any wrapped objects. // If (a instanceof _$1) a = A. _wrapped; if (b instanceof _$1) b = b._wrapped; }Copy the code
  1. throughObject.prototype.toString.callCheck whether the two types are the same or differentfalse(They must be different depending on the type)
Var className = tostring.call (a); // Compare '[[Class]]' names. // Compare '[[Class]]' names. if (className ! == toString.call(b)) return false;Copy the code
  1. They are of the same type
  2. They areRegExp or String“, compare the two strings to see if they are equal
`return '' + a === '' + b; `Copy the code
  1. When it is Number, the wrapper type of Number is converted to the base type+a, first judge NaN, then judge0and0Finally, it’s normal
  2. isDate or Boolean“, convert them to the number type for judgmentreturn +a === +b; DateType changes to timestamp, compare timestamp;BooleanThe type changes to 0 and 1 for comparison
Switch (className) {// These types are compared by value. Case '[object RegExp]': // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return '' + a === '' + b; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. // Object(NaN) is equivalent to NaN. if (+a ! == +a) return +b ! == +b; // An `egal` comparison is performed for other numeric values. return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a === +b; case '[object Symbol]': return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); case '[object ArrayBuffer]': case tagDataView: // Coerce to typed array so we can fall through. return deepEq(toBufferView(a), toBufferView(b), aStack, bStack); }Copy the code

So we’re just left with Array and Object, so we’re going to do a recursive thing here, so let’s do one by one.

// Internal recursive comparison function for `_.isEqual`. function deepEq(a, b, aStack, bStack) { var areArrays = className === '[object Array]'; // if (! areArrays && isTypedArray$1(a)) { var byteLength = getByteLength(a); if (byteLength ! == getByteLength(b)) return false; if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true; areArrays = true; } // Not array if (! AreArrays) {// If a is not object or B is not object return false if (Typeof a! = 'object' || typeof b ! = 'object') return false; // Objects with different constructors are not equivalent, But 'Object' or 'Array' s // from different frames are. bCtor = b.constructor; if (aCtor ! == bCtor && ! (isFunction$1(aCtor) && aCtor instanceof aCtor && isFunction$1(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) { return false; }} // Assume equality for cyclic structures. The algorithm for detecting cyclic structures is best-seller from ES Section 15.12.3, abstract operation 'JO'. // Assume that loop structures are equal. The algorithm for detecting cyclic structures is adapted from section 15.12.3 of ES 5.1, // Initializing stack of traversed objects. // It's done here since we only need them for objects and Arrays Comparison. // Initializes the stack of objects to traverse, which is done here because we only need them to compare objects and arrays. / / the first call to eq () function, no incoming aStack and bStack parameters after the recursive call will be introduced to these two parameters aStack = aStack | | []; bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures.  if (aStack[length] === a) return bStack[length] === b; } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); // Recursively compare objects and arrays. if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. length = a.length; If (length!) {if (length!); == b.length) return false; // While (length--) {if (!) {while (length--) {if (! eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. Var _keys = keys(a), key; length = _keys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. If (keys(b).length! == length) return false; while (length--) { // Deep compare each member key = _keys[length]; if (! (has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false; }} // Remove the first traversed object from the stack of traversed objects Astack.pop (); astack.pop (); bStack.pop(); return true; }Copy the code

So that’s the end of the isEqual function, and the idea is pretty clear, and it’s a little bit easier to understand, and you can take a look at it.

create

_. Create (prototype, props) Creates a new object with the given prototype, optionally adding props as an attribute of own. Basically, the same as Object.create, but without all the property descriptors.

// An internal function for creating a new object that inherits from another. function baseCreate(prototype) { if (! isObject(prototype)) return {}; if (nativeCreate) return nativeCreate(prototype); var Ctor = ctor(); Ctor.prototype = prototype; var result = new Ctor; Ctor.prototype = null; return result; } // Creates an object that inherits from the given prototype object. // If additional properties are provided then they  will be added to the // created object. function create(prototype, props) { var result = baseCreate(prototype); if (props) extendOwn(result, props); return result; }Copy the code

Objects related methods are basically introduced, the other more simple and direct, you can go directly to see, here is not much to say, next ready to look at the Array extension method related code