forEach is a handy loop. However, it is a tricky bugger too. Two things can happen:
- this is undefined inside the closure function()
- the return statement doesn't exit the main function
I'd never really considered the nuances of closures until I started using forEach loops in object-oriented JavaScript.
Defining a method in a JavaScript class is done using a prototype. this is required to reference other methods and properties in the class. It's often necessary to loop over an array property, forEach is a handy construct but it requires a closure. this inside a closure refers to the closure itself. Therefore the properties of the class are inaccessible. For instance this code results in an error because this.fileNameStartingWithA is undefined.
const fs = require('fs'); const util = require('util'); function DirUtil(dir) { this.dir = dir || "."; this.fileNames = fs.readdirSync(this.dir); this.fileNameStartingWithA = []; }; DirUtil.prototype.getSubList = function (pattern) { this.fileNames.forEach(function (fileName, i) { if (pattern.test(fileName)) { this.fileNameStartingWithA.push(fileName); } }); return this.fileNameStartingWithA; }; // expose the classes module.exports = { DirUtil: DirUtil }; var d = new DirUtil(); console.log(d.getSubList(/^A/)); this.fileNameStartingWithA.push(fileName); ^ TypeError: Cannot read property 'push' of undefined
To access this inside the closure, you must save a reference to it before it is redefined in the closure. I like to use thing as a temporary variable:
const fs = require('fs'); const util = require('util'); function DirUtil(dir) { this.dir = dir || "."; this.fileNames = fs.readdirSync(this.dir); this.fileNameStartingWithA = []; }; DirUtil.prototype.getSubList = function (pattern) { var thing = this; this.fileNames.forEach(function (fileName, i) { if (pattern.test(fileName)) { thing.fileNameStartingWithA.push(fileName); } }); return thing.fileNameStartingWithA; }; // expose the classes module.exports = { DirUtil: DirUtil }; var d = new DirUtil(); console.log(d.getSubList(/^A/)); [ 'Applications', 'Applications (Parallels)' ]