Passing value to Symbol.iterator - javascript

If I define an iterator for an object and create an instance, I can iterator over those values, as expected:
class Hello {
*[Symbol.iterator]() {
yield 5;
}
*iterator (value) {
yield value;
}
}
var hello = new Hello();
for (let val of hello) {
console.log(val); // 5
}
for (let val of hello.iterator('wow')) {
console.log(val); // 'wow'
}
In the previous example, the [Symbol.iterator] is declared as a generator. I can declare another generator and pass arguments to it, but is there a way to pass arguments to the default iterator?
I've tried the following, but it throw an error:
for (let val of hello('wow')) {
console.log(val); // 5
}
// Uncaught TypeError: hello is not a function
Am I missing something? Or is this just not possible?

When you pass a value to be iterated in a for…of loop, it needs to implement the iterable interface - that is, its Symbol.iterator method will be implicitly called with no arguments and should return an iterator.
You also can pass iterators directly, as all iterators are "iterable", their Symbol.iterator method just returns themselves.
So while there is no way to make a for…of loop call the default iterator with special arguments, you always can call the method yourself and pass the iterator:
var hello = {
*[Symbol.iterator](val) {
yield val;
}
};
for (let val of hello[Symbol.iterator]('works')) {
console.log(val); // 'works'
}

Related

Writing memoize function without ES6 syntax

I am working on a problem and I am not allowed to use ES6 syntax so I can't use the spread/rest operator. How can I write a function that will take any number of arguments without a spread/rest operator?
function memoize(func) {
var cache = {};
return function (...args) {
if (args in cache) {
return cache[args];
} else {
cache[args] = func(...args);
return func(...args);
}
};
};
Firstly you have to use the arguments object. It is an older feature which makes a variable called arguments available in non arrow functions. The value of arguments is all the arguments a function receive. It is an array-like object, not an array. This eliminates the use of rest operator.
Also, it is better to create a key, when you are creating a generic function.
Why? Javascript object keys can only be string. If an object is used as a key it gets auto converted to a string : [object Object]. So basically all your object keys will override each other. Here is a demo:
const obj1 = { a : 1};
const obj2 = { b : 2};
const x = {};
x[obj1] = 2;
console.log(x);
x[obj2] = 2;
console.log(x);
To generate hash key you could use any method. Below is not a bullet proof implementation but I have just joined all the arguments into a . separated array.
Now we have to call your function with these arguments, in the else condition. How? When you want to forward you arguments to another function, and they are in the form of an array, you can use something like apply. It is pre ES6 feature which lets you run a function in a different context, or lets you pass arguments as an array. Here we won't be changing the context, but the second use case is what we will be using.
function memoize(func) {
var cache = {};
return function () {
const key = Object.values(arguments).join('.');
console.log(arguments);
console.log(cache);
if (key in cache) {
return cache[key];
} else {
cache[key] = func.apply(null,arguments);
return cache[key];
}
};
};
const conc = (str,str2,str3) => `${str}_${str2}_${str3}`;
const memoizedSq = memoize(conc);
memoizedSq('hello','hi','hey');
memoizedSq('bye','see you','so long');
memoizedSq('hello','hi','hey');
I have used join to create the string.
Minor optimization I did by changing your code in the else condition is not calling function multiple times. I just saved it in the object and returned the same value.
Note: This could break for cases where if a function takes a string, and the string itself contains ..

Do Generators return both an iterator and an iterable?

From this question I asked before I learned that for...of expects an iterable, which is
an object that implements the ##iterable method, meaning that the object (or one of the objects up its prototype chain) must have a property with a ##iterator key which is available via constant Symbol.iterator
So, an iterable would look something like this:
const iterableObject: {
[Symbol.iterator]: function someIteratorFunction() {
//...
}
}
On the other hand we have generators, which look like this:
function* generatorFoo(){
yield 1;
yield 2;
yield 3;
yield 4;
}
And can be used in for...of constructs like this:
for(const item of generatorFoo())
console.log(item);
So, calling generatorFoo() returns an iterable since for...of has no problem dealing with it. I can also call generatorFoo()[Symbol.iterator]() and receive an iterator, to confirm this.
However, calling generatorFoo would also return an iterator since I can call generatorFoo() I get an object with a method next to get the next value of the iterator, like so:
const iteratorFromGen = generatorFoo();
iteratorFromGen.next().value; //1
iteratorFromGen.next().value; //2
iteratorFromGen.next().value; //3
iteratorFromGen.next().value; //4
Does this mean that calling a generator function returns an object that has access to a next method and a [Symbol.iterator] method?
When calling a generator function, do we get an object that is both an iterator and an iterable? How is this accomplished?
In short, yes, a generator is both an iterable and an iterator.
From MDN:
The Generator object is returned by a generator function and it
conforms to both the iterable protocol and the iterator protocol.
The iterator returned by calling the Symbol.iterator method of the returned generator object is the same as the generator object itself. For example:
function* gen() {
let i = 0;
while (i < 10) yield i++;
}
const iterableIterator = gen();
console.log(iterableIterator === iterableIterator[Symbol.iterator]()); // true
You can even replicate that pattern pretty easily by conforming to the iterator protocol, and having a Symbol.iterator method that returns this. So for example:
class MyIterableIterator {
constructor(arr) {
this.arr = arr;
this.i = 0;
}
[Symbol.iterator]() {
return this;
}
next() {
if (this.i === this.arr.length) return {done: true}
return {value: this.arr[this.i++], done: false}
}
}
const iterableIterator = new MyIterableIterator([1, 2, 3, 4, 5]);
console.log(iterableIterator === iterableIterator[Symbol.iterator]()); // true
// Works fine if you call next manually, or using a for-of loop.
console.log(iterableIterator.next())
for (let item of iterableIterator) {
console.log(item);
}

Can you dynamically build a method call chain?

I ran across some code I'm trying to refactor where they were writing JavaScript as a string and putting it inside HTML script tags, then writing it to the DOM. Very ugly and not maintainable. But one of the things this allowed them to do was build a function call by appending to a string.
var methods = '';
for (key in obj) {
methods += 'func1("'+key+'", "'+obj[key]+'").';
}
var scriptString = '<script>func2().' + methods + 'func3();</script>'
The result could be:
'<script>func2().func1("key1", "value1").func1("key2", "value2").func3();</script>'
So, since I really disapprove of writing JavaScript inside an HTML string inside of JavaScript ... Does anyone know how to accomplish the same result with pure JavaScript? Is there a way to continuously append methods to a function call by iterating over an object?
Array.reduce() should do most of what you need.
The tricky part is the .func1() method call chain, where you depend on an object's key/value pairs. If you're not used to working with the Array.reduce() method, I would suggest reading through the MDN documentation, but it basically loops through an array, performing a transformation on the previous result until it reaches the end, where it returns the final result. This can be used to our advantage since method chaining is just a method call on the previous method call's return value. But, since that's an array method, we need to get the Object's entries into an array first...and that's where Object.entries() comes in.
Note that much of my syntax here involves new features, which may or may not be supported by your target browsers. Be sure to use a transpiler and polyfills to handle going backwards, if needed.
See below for an example:
const wrapperFunc = (obj) => {
// Start with func2()
const func2Result = func2()
// Chain to func1() for each entry in obj (the tricky part)
const func1Result = Object.entries(obj).reduce(
// Call func1 method on previous result to get the next result
(prevResult, [ key, val ]) => prevResult.func1(key, val),
// Initial "prevResult" value used above
func2Result
)
// Chain to func3()
return func1Result.func3()
}
// Inject object to be used for func1() calls
wrapperFunc({
key1: 'value1',
key2: 'value2'
})
Also, here's a second, more complex example with some implemented methods. Unlike the example above, this one actually runs.
class MyObject {
constructor() {
this.innerString = ''
}
// Chainable method (returns this)
func1(key, val) {
this.innerString += `${key}, ${val} `
return this
}
func3() {
return this.innerString.trim()
}
}
const func2 = function () {
return new MyObject()
}
const wrapperFunc = (obj) => {
// Start with func2()
const func2Result = func2()
// Chain to func1() for each entry in obj (the tricky part)
const func1Result = Object.entries(obj).reduce(
// Call func1 method on previous result to get the next result
(prevResult, [ key, val ]) => prevResult.func1(key, val),
// Initial "prevResult" value used above
func2Result
)
// Chain to func3()
return func1Result.func3()
}
// Inject object to be used for func1() calls
console.log(wrapperFunc({
key1: 'value1',
key2: 'value2'
}))
You can create a function that calls the func1 method repeatedly for all key/value pairs in the object.
var script = function(obj) {
var value = func2();
for (var key in obj) {
value = value.func1(key, obj[key]);
}
value.func3();
};

Iterator implementation in JS

We can make a regular (POJSO) JS Object iterable, like so:
const tempObj = {a: 1, b: 2, c: 3};
tempObj[Symbol.iterator] = function () {
const self = this;
const keys = Object.keys(self);
return {
next() {
const k = keys.shift();
return {
done: !k,
value: [k, self[k]]
}
}
}
};
now we can use for..of loop:
for (let [k,v] of tempObj) {
console.log(k,v);
}
and we get:
a 1
b 2
c 3
my question is - is there another method we need to implement besides next()? if not, why did the iterator spec choose to return an object instead of just returning a function? why isn't the spec simply:
tempObj[Symbol.iterator] = function () {
return function next {
return {
done: Object.keys(this).length === 0,
value: Object.keys(this).shift()
}
}
};
my only guess is that by returning an object, it leaves room for updates/changes.
The Iterator interface also supports two more optional methods: return and throw. From the ES6 specification, section 25.1.1.2 (Table 54):
return
A function that returns an IteratorResult object. The returned
object must conform to the IteratorResult interface. Invoking this
method notifies the Iterator object that the caller does not intend to
make any more next method calls to the Iterator. The returned
IteratorResult object will typically have a done property whose value
is true, and a value property with the value passed as the argument of
the return method. However, this requirement is not enforced.
throw
A function that returns an IteratorResult object. The returned object
must conform to the IteratorResult interface. Invoking this method
notifies the Iterator object that the caller has detected an error
condition. The argument may be used to identify the error condition
and typically will be an exception object. A typical response is to
throw the value passed as the argument. If the method does not throw,
the returned IteratorResult object will typically have a done property
whose value is true.
The ES6 spec also says:
Typically callers of these methods should check for their existence before invoking them.
So you're definitely not required to implement them; the burden of checking for their existence is on the caller.
Is there another method we need to implement besides next()?
No, none that we need to implement, but we can implement throw and return for the full iterator interface. Generator objects do that, for example.
Why did the iterator spec choose to return an object instead of just returning a function?
Because an iterator is (usually) stateful, and from an OOP viewpoint it should be an object with a method not a (pure) function. This also allows prototypical inheritance for iterator instances.
You could yield a generator with Object.entries as value.
let tempObj = { a: 1, b: 2, c: 3 };
tempObj[Symbol.iterator] = function* () {
yield* Object.entries(this);
};
for (let [k, v] of tempObj) {
console.log(k, v);
}

How do I use the logical NOT (!) operator when invoking functions in JS?

I'm re-creating functions from the underscore library but I'm running into a roadblock while trying to implement the _.reject() function. For the purposes of this question, I'll include the code I've written for three functions: _.each(), _.filter(), and _.reject().
_.each = function(collection, iterator) {
if (Array.isArray(collection)) {
for (var i = 0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
} else {
for (var i in collection) {
iterator(collection[i], i, collection);
}
}
};
_.filter = function(collection, test) {
var results = [];
_.each(collection, function(i) {
if (test(i)) {
results.push(i);
}
})
return results;
};
And here's the code for the function that I'm getting a problem with, the _.reject() method, along with the isEven() function that I'm passing in as the test argument.
_.reject = function(collection, test) {
return _.filter(collection, !test);
};
var isEven = function(x) {
if (x % 2 === 0) return true;
return false;
};
According to MDN's page on Expressions and Operators, the Logical NOT (!) operator Returns false if its single operand can be converted to true; otherwise, returns true.
But when I run the following code _.reject([1,2,3], isEven) I get an error saying that test is not a function. Why am I unable to use the ! operator while invoking a function (e.g., _.filter([1,2,3], !isEven))?
When you refer to a function, rather than calling it, you're referring to the function's object reference:
function foo() {
alert("Hi there");
}
var f = foo; // <== Getting the function's reference, not calling it
f(); // <== Now we call it
So !isEven would be negating the function reference. Since isEven is a non-null reference, it's truthy; and so !isEven is false. Not what you want. :-)
Your reject could be written with a function that calls test and inverts its return value:
_.reject = function(collection, test) {
return _.filter(collection, function(e) { return !test(e); });
};
Or if you want to go the functional programming approach, you can write a function that, when called, will return a new function that negates the return value:
function not(f) {
return function() {
return !f.apply(this, arguments);
};
}
Then anywhere you want to invert a callback's meaning, you'd just use invert:
_.reject = function(collection, test) {
return _.filter(collection, not(test));
};
Why am I unable to use the ! operator while invoking a function (e.g., _.filter([1,2,3], !isEven))?
Note that you are actually not invoking isEven, you are merely referencing it. As you said, ! "Returns false if its single operand can be converted to true; otherwise, returns true."
isEven is a reference to a function, i.e. an object. Objects convert to true, hence !test results in false:
_.filter([1,2,3], false))
Now you are passing a Boolean instead of a function to _.filter, hence the error message "test is not a function".
Instead, you have to pass a function that negates the result of the test function:
_.filter([1,2,3], function() {
return !test.apply(this, arguments);
});
You cannot negate a function. It's a meaningless thing to do (unless you really want the value false without typing it). Instead you want to negate the function's return value. I suspect what you want is something like so
_.reject = function(collection, test) {
return _.filter(collection, function(e) { return !test(e); });
}

Categories