0

I'm trying to recreate the _.each() func from underscore.js, however cannot get the 'context' variable correct. I understand what it's meant to do, but seem to going in circles trying to implement it. I've marked the area of the code below with comments. Any tips appreciated, thanks!

_.each = function (collection, iteratee, context) {
  //-----context--------
  if (context) {
    if (Array.isArray(collection)) {
      for (let i = 0; i < collection.length; i++) {
        iteratee.call(context, collection[i], collection);
      }
    } else {
    for (const key in collection) {
      iteratee.call(context, collection[key], collection);
    }
  }
    return collection;
  }
  //-----end of context----
  //check if array or object
  if (Array.isArray(collection)) {
    for (let i = 0; i < collection.length; i++) {
      iteratee(collection[i], i, collection);
    }
  } else {
    for (const key in collection) {
      iteratee(collection[key], key, collection);
    }
  }
  return collection;
};
boscode
  • 79
  • 6
  • 1
    FYI: there is [annotated source for Underscore](https://underscorejs.org/docs/underscore-esm.html#section-161) so you can look at the code with extra comments. – VLAZ Nov 24 '20 at 13:26
  • 1
    Stabler annotated source link: https://underscorejs.org/docs/modules/each.html – Julian Nov 25 '20 at 02:13

2 Answers2

0

You do not need if (context) { at all. Fundamentally, you need to make the same operation regardless of whether or not a context is provided, so you can just leave the code outside this if and always execute .call(context, /* other arguments */. Function#call will always execute the function and the first parameter will be the this value of that invocation. If the value undefined is provided, then that's not an error - in non-strict mode the value of this will be assigned to the global object but in strict mode it How does the "this" keyword work?

Thus, if a context is not provided, then you usually don't have to do anything special.

const _ = {};

_.each = function (collection, iteratee, context) {
  //check if array or object
  if (Array.isArray(collection)) {
    for (let i = 0; i < collection.length; i++) {
      iteratee.call(context, collection[i], collection);;
    }
  } else {
    for (const key in collection) {
      iteratee.call(context, collection[key], collection);
    }
  }
  return collection;
};


const arr = [1, 2, 3];
const obj = {
  foo: "hello",
  bar: "world"
};

function print(value) {
  console.log(value);
}

console.log("--- basic foreach ---");

_.each(arr, print);
_.each(obj, print);

console.log("--- foreach with context ---");

function contextPrint(value) {
  console.log(this[value]);
}

_.each(arr, contextPrint, { 1: "one", 2: "two", 3: "three" });
_.each(obj, contextPrint, { hello: "this", world: "works" });

console.log("--- no context provided ---");

_.each(arr, contextPrint);
_.each(obj, contextPrint);

Compare with Underscore and the behaviour is the same:

const arr = [1, 2, 3];
const obj = {
  foo: "hello",
  bar: "world"
};

function print(value) {
  console.log(value);
}

console.log("--- basic foreach ---");

_.each(arr, print);
_.each(obj, print);

console.log("--- foreach with context ---");

function contextPrint(value) {
  console.log(this[value]);
}

_.each(arr, contextPrint, { 1: "one", 2: "two", 3: "three" });
_.each(obj, contextPrint, { hello: "this", world: "works" });

console.log("--- no context provided ---");

_.each(arr, contextPrint);
_.each(obj, contextPrint);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.11.0/underscore-min.js" integrity="sha512-wBiNJt1JXeA/ra9F8K2jyO4Bnxr0dRPsy7JaMqSlxqTjUGHe1Z+Fm5HMjCWqkIYvp/oCbdJEivZ5pLvAtK0csQ==" crossorigin="anonymous"></script>
VLAZ
  • 26,331
  • 9
  • 49
  • 67
  • Thanks for the answer. However, if there is no context, the first arg of the iteratee function needs to be collection[i] like in my original code, right? Otherwise with no context input, there will be nothing to there. I have tried with your recommendations and it works for context but tests fail for arrays and objects without context input. – boscode Nov 24 '20 at 13:50
  • @SMSO what exactly is failing? I've showed you that your implementation behaves like Underscore's. If there are more specific differences I can't think of them off the top of my head. Perhaps strict mode behave differently but I'm not sure. EDIT: although I'm not sure if you need to support all the iteratee shortcuts like `_.each("propName", someObj)`. If so, you are missing some stuff but it's not to do with the context variable. – VLAZ Nov 24 '20 at 13:54
  • Your answer is probably a bit clearer for novices if you expand a bit on how `Function.prototype.call` works. – Julian Nov 25 '20 at 02:16
  • @Julian since OP used `.call` already, I assumed I didn't need to explain it but you're right. It might be of use to somebody in the future. I've added a brief outline. – VLAZ Nov 25 '20 at 08:53
-1
_.each(
 [1, 2, 3],
 function(value){
   // get context by this
   console.log(this.times * value);
 },
 { times: 10 } // iteratee context,
)
欧阳斌
  • 2,231
  • 1
  • 8
  • 8
  • 1
    The question was how to implement `_.each`, not how to find the value of the `context` variable inside the `iteratee`. – Julian Nov 25 '20 at 02:19