1

So I am learning Javascript and testing this in the console. Can anybody come up with a good explanation why something like this does not work?

var students = ['foo', 'bar', 'baz'];

function forEach2(arr1) {
    for (i = 0; i < arr1.length; i++) {
        console.log(arr1[i]);
    }
}
students.forEach2(students)

I get : Uncaught TypeError: students.forEach2 is not a function

For me it would seem logical to work but it doesn't why?

  • 3
    forEach2 is not a function of students. students is an array containing 3 string values. just use `forEach2` without `students.` before it. – George Mar 31 '18 at 02:13
  • you've defined a function `forEach2` ... it has nothing to do with `students`, why would it? you could add `students.forEach2 = function() { return forEach2(this); };` - if you really want this to work this way – Jaromanda X Mar 31 '18 at 02:13
  • *Good, clear explanation*. OK. `var students` declares an array, and arrays don't have a function named `foreach2` that you can call. – Ken White Mar 31 '18 at 02:20
  • You need to study up on "scope". – Tripp Kinetics Mar 31 '18 at 02:30
  • what is it you like to be explained? Is it adding behavior to an object using [prototype](https://stackoverflow.com/a/16063711/1641941) or functions that take functions as arguments, maybe [this](https://www.youtube.com/watch?v=FYXpOjwYzcs) can help – HMR Mar 31 '18 at 04:43
  • Seeing some of the answers makes me realize what i needed was not a clear explanation because that would be very simple but a paradigm shift and have no idea how to ask for that. But luckily it started to happen. Now i see what is wrong and it makes sense –  Mar 31 '18 at 12:44

4 Answers4

0

forEach2 is not a function of students. students is an array containing 3 string values. just use forEach2 without students. before it.

var students = ['foo', 'bar', 'baz'];

function forEach2(arr1) {
    for (i = 0; i < arr1.length; i++) {
        console.log(`arr1[${i}]:`, arr1[i]);
    }
}

console.log("students:", students);

console.log("students has .forEach2 function ?", typeof students.forEach2 == "function");

console.log("forEach2 is a function?", typeof forEach2 == "function");

console.log("forEach2(arr1)...");

forEach2(students);

console.log("students.forEach(student)...");
//forEach already has a native implementation
students.forEach((student)=> {
  return console.log("student:", student);
});
George
  • 2,330
  • 3
  • 15
  • 36
  • i know about forEach, but i want to understand how things work under the hood. For me you code feels more like a confirmation rather than an explanation. –  Mar 31 '18 at 02:27
  • See the Array reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array which shows why it has .forEach but why is it unclear why your forEach2 should not be attached to your array? – George Mar 31 '18 at 02:31
0

For your example to work, simply add the following statement after definition of forEach2:

// Here you're redefining the built-in forEach function with your // custom function students.forEach = forEach2;

// Now this must work students.forEach(students);

This is an explanation why is that:

An array is a particular implementation of the Object. Object is an aggregate of the property:value pairs, such as

{'some_property': '1', 'some_function': function(), 'forEach': function() {<function logic>}, ... }

Your error 'Uncaught TypeError: students.forEach2 is not a function' tells, that there is no property forEach2 (which supposed to be a function) in between the Object's properties. So, there are two methods to correct this situation: - add a property (method function) to the object, or alter the existing property with similar functionality (not necessarily though).

[1] The first method assumes you add a new property function using Array.prototype.forEach2 = function() {...}.

[2] The second - you may redefine or alter any property in the Object by just assigning a new value to that property: Object.forEach = forEach2

Thevs
  • 3,189
  • 2
  • 20
  • 32
0

JavaScript is an object-oriented language with prototypal inheritance.

Object-oriented means you have objects with members (called properties in JavaScript) which hold values. In JavaScript, functions are “first-class citizens”, meaning you can assign them to variables just like you would other values.

When you write write a function such as ‘function x(y) { return y +1; }’, what’s really happening is 1) you are declaring a variable named “x”, and 2) you are creating a function “value” which is then assigned to that variable. If you have access to that variable (it’s within scope), you can invoke the function like ‘x(5)’. This evaluates to a new value which you could assign to another variable, and so on.

Ok, so now we have a problem. If functions are values, and values take up space (memory), then what happens when you need a bunch of objects with the same function? That’s where prototypal inheritance comes in. When we try to access a value on an object, via either the member access operator ‘.’, like ‘myObj.someValue’, or via an indexing operator ‘[]’ like ‘myObj[“someValue”] (both of which are equivalent in JavaScript, for the most part), the following occurs:

  1. The runtime checks to see if a ‘myObj’ variable exists in the current scope. If it doesn’t? Exception!
  2. The runtime looks at the object referenced by the variable and checks to see if it has a property with the key “someValue”.
  3. If the object has that property, the member access expression (‘myObj.someValue’) evaluates to that property’s value, and we’re done.
  4. If the object does not have that property, we start doing prototypal inheritance stuff. In JavaScript, all this means is that when we try to access a property that doesn’t exist, the runtime says “hey, what if this objects *prototype has a property with that key?” If it does, we use the prototype’s property’s value. If it doesn’t, we return ‘undefined’.

Notice that because prototypes are just objects, and therefore can themselves have a prototype, step 4 is recursive until we run out of prototypes on which to attempt member access.

Ok, so at this point you may be thinking “what’s a prototype and what does it have to do with my question?” It’s just an object. If any object has a property with the key “prototype”, then that property’s value IS a prototype. Specifically, it’s that object’s prototype. And so this is where your problem arises.

There is no property on your object with the key “forEach2”. Why? Because you didn’t put it there, and you didn’t put it on the object’s prototype (or any of the prototypes “up stream”.

The ‘forEach’ function of an Array exists as a property on the Array’s prototype: ‘Array.prototype.forEach = function (...) {...}’. Your function does not, and therefore you cannot use member access on an array to get that value (the function), and that’s why your code is borked.

Fortunately for you, a variable ‘forEach2’ exists in your current scope, and you can just use it without needing to do any member access! You just write ‘forEach2(students);’ and that’s all.

But what if you want to access that function anywhere you have an array? You have two options: put it on every instance of your arrays, or put it on Array’s prototype. ‘Array.prototype.forEach2 = forEach2;’ however, if you do this you will need to change your function a bit. Right now it expects the array as its first argument (‘arr1’), but its redundant to write ‘students.forEach2(students)’ because when a function is invoked immediately following member access, the function will be provided with a special variable ‘this’ which will have the value of the object for which you are accessing its member. So, in this case, you would omit the ‘arr1’ argument and instead just use the the special ‘this’ variable which is magically in scope within your function.

Array.prototype.forEach2 = function ()
{
    for (var i = 0; i < this.length; i++)
    {
        console.log(this[i]);
    }
}

I hope this clarifies some things for you, and I hope it raises a bunch more questions.

P.S: Adding things to prototypes is both powerful and considered harmful unless you know what you’re doing and have a good reason to do it (like writing polyfills)... so do it at your own peril and use responsibly.

cwharris
  • 17,835
  • 4
  • 44
  • 64
  • Your explanation helped me a lot except the first line and i think will help a lot of people especially in the beginning. We need better ways to explain simple things that sometimes more advanced developers take for granted. I voted your answer, Thank you so much.
    PS(friendly feedback): Don't let two words "ruin" a great explanation :)
    –  Mar 31 '18 at 12:24
  • Ah, those words must mean different things to us. I use “oh boy” to express excitement. I was looking forward to trying to give a well rounded answer while everyone else was explaining in circles. :) – cwharris Mar 31 '18 at 14:36
  • Ah, i see.. this is how i perceive it and i think most people would. Regarding the votes stackoverflow does not consider votes from users under 15 points of reputation, anyway for me it was the explanation that made the most sense and i hope that matters more –  Mar 31 '18 at 18:51
  • 1
    What i loved about you approach is that you explained, look when you do this, this is what happens, i wish i would find more beginner resources that take this approach. ps: if you know any i would love to take a look(anything related to js that falls into that explanatory style would be much appreciated) –  Mar 31 '18 at 18:59
-1

Because the function forEach2 is not available either within the Array.prototype or is not a direct property of that array, so, you have two alternatives to use that custom forEach:

Use a decorator to add that function to a specific array

var students = ['foo', 'bar', 'baz'];
function forEach2() {
    for (i = 0; i < this.length; i++) {
        console.log(this[i]);
    }
}

function decorate(arr) {
  arr['forEach2'] = forEach2;
  return arr;
}

decorate(students).forEach2()

Add that function to the Array.prototype

var students = ['foo', 'bar', 'baz'];

Array.prototype.forEach2 = function() {
  for (i = 0; i < this.length; i++) {
        console.log(this[i]);
    }
}

students.forEach2();

Both alternatives use the context this to get the current array.

Ele
  • 33,468
  • 7
  • 37
  • 75
  • Yeah, now it make more sense, i feel silly for asking that, but standing on the other side things did not look that obvious. Thank you –  Mar 31 '18 at 12:53