I have the following code.
class Node {
constructor(value, parent, possibleChildren = []) {
this.value = value;
this.parent = parent;
this.children = []
this.setChildren(possibleChildren);
}
setChildren(possibleChildren) {
if (possibleChildren.length === 0) return [];
while (possibleChildren.length > 0) {
const value = possibleChildren.pop();
// keyword *this* messes up the context. Save them function calls for lazy execution
let childNode = () => new Node(value, this, possibleChildren);
this.children.push(childNode);
}
this.children = this.children.map(child => child())
}
getChildrenValues() {
return this.children.map((child) => child.value);
}
}
In the above the this.children variable is set properly. If I save the this.children array directly, without wrapping it in a function, I see incorrect children being set.
Example:
setChildren(possibleChildren) {
if (possibleChildren.length === 0) return [];
while (possibleChildren.length > 0) {
const value = possibleChildren.pop();
// keyword *this* messes up the context. Save them function calls for lazy execution
let childNode = new Node(value, this, possibleChildren);
this.children.push(childNode);
}
}
I know that the context of this is not consitent without the function wrapper. What I do not understand is why. Any ideas?
Calling getChildrenValues on the first example returns ["A", "B", "C"].
Calling getChildrenValues on the second example returns ["C"]
class Node {
constructor(value, parent, possibleChildren = []) {
this.value = value;
this.parent = parent;
this.children = []
this.setChildren(possibleChildren);
}
setChildren(possibleChildren) {
if (possibleChildren.length === 0) return [];
while (possibleChildren.length > 0) {
const value = possibleChildren.pop();
// keyword *this* messes up the context. Save them function calls for lazy execution
const childNode = new Node(value, this, possibleChildren);
this.children.push(childNode);
}
}
getChildrenValues() {
return this.children.map((child) => child.value);
}
}
let root = new Node(null, null, "ABC".split(""));
console.log(root.getChildrenValues())
I know that the context of this is not consistent without the function wrapper. What I do not understand is why?
This has nothing to do with the this keyword. Since you used an arrow function, it does refer to exactly the same object in both your code snippets, there is no difference.
The reason why you get different results from the two snippets is the lazy execution, but not with respect to this, but rather the possibleChildren array. In your first code, the while (possibleChildren.length > 0) runs and empties the possibleChildren array before you do the recursive new Node calls. In the second example, you call new Node during that loop, and you pass on the reference to the same possibleChildren array, which is being emptied by the recursive call and the loop therefore terminates right after the first iteration.
To fix this, just don't recursively pass the possibleChildren:
class Node {
constructor(value, parent, childrenValues = []) {
this.value = value;
this.parent = parent;
this.children = []
this.setChildrenValues(childrenValues);
}
setChildrenValues(childrenValues) {
for (let i=childrenValues.length; i--; ) {
const value = childrenValues[i];
const childNode = new Node(value, this );
// ^ no third argument, no grandchildren
this.children.push(childNode);
}
}
getChildrenValues() {
return this.children.map((child) => child.value);
}
}
let root = new Node(null, null, "ABC".split(""));
console.log(root.getChildrenValues())
Related
I'm working on a pet project, a little front-end library for students. It reads variables/code in a JS file and tests it, outputting some panels. The code itself roughly follows the Jest framework.
My problem is that I'm trying to create a function that watches the execution of other functions, counts them, and lets me access the count.
function watchFunction(funcName){
let originalFunction = window[funcName];
let counter = 0;
// Wrap the function, counts when called
window[funcName] = function(...args){
console.log("watching");
counter++;
return originalFunction(...args);
}
return {
getCount: () => {return counter},
reset: () => {
// Unwrap the function
window[funcName] = originalFunction
}
}
}
This seems to work for methods like Number() or parseInt(), but I don't know how I would go about accessing methods like Math.floor(), or prototype methods like Array.prototype.map().
I've tried passing in the function reference instead of using window["funcNameString"], but that doesn't seem to work.
Does anyone have suggestions or tips for wrapping functions or watching functions like this?
EDIT:
It appears a solution was found!
function watchFunction(obj, fName) {
let counter = 0;
const originalFunction = obj[fName];
obj[fName] = (...args) => {
counter++;
return originalFunction.bind(obj)(...args);
};
return {
removeWatcher: () => (obj[fName] = originalFunction),
resetCount: () => (counter = 0),
getCount: () => counter,
};
}
Example of use:
// Array.prototype.push
const arrayPushWatcher = watchFunction(Array.prototype, "push");
let arr = [];
// 0
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arr.push(1);
// 1
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arr.push(1);
// 2
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arrayPushWatcher.removeWatcher();
arr.push(1);
// 2 (stopped counting)
console.log("Array.prototype.push", arrayPushWatcher.getCount());
How to watch for any function call
Is that what you want? I can also write a block for this function so that it determines whether an object has been passed in or a string. If string -> run this function on window as a property "objectThatStoresFunction".
I've tried playing around with the Function.prototype, but it doesn't really work. So the function turned out a bit more complex.
This code below works both with functions / objects on window Array.prototype.map (Prototype / Class functions)
function watchFunction(objectThatStoresFunction, functionName) {
let counter = 0;
const originalFunction = objectThatStoresFunction[functionName];
objectThatStoresFunction[functionName] = (...args) => {
counter += 1;
return originalFunction(...args);
}
return {
getCount: () => {
return counter
}
}
}
const mathRoundWatcher = watchFunction(Math, 'round');
// 0
console.log(mathRoundWatcher.getCount());
// 1
Math.round(99666.9999999);
console.log(mathRoundWatcher.getCount());
// 2
Math.round(999999999.99);
console.log(mathRoundWatcher.getCount());
function watchFunction(objectThatStoresFunction, functionName, optionalOriginalFunction) {
const self = this;
if (optionalOriginalFunction) {
objectThatStoresFunction = this.window;
functionName = optionalOriginalFunction.name;
}
let counter = 0;
const originalFunction = objectThatStoresFunction[functionName] || optionalOriginalFunction;
objectThatStoresFunction[functionName] = (...args) => {
counter += 1;
return originalFunction.bind(self)(...args);
}
return {
// should it remove the watcher or reset the count?
reset: () => objectThatStoresFunction[functionName] = originalFunction,
getCount: () => {
return counter;
}
}
}
const arrayMapWatcher = watchFunction(Array.prototype, 'map');
// 0
console.log('Array.prototype.map', arrayMapWatcher.getCount());
[-99].map(() => {});
// 1
console.log('Array.prototype.map', arrayMapWatcher.getCount());
const mathRoundWatcher = watchFunction(Math, 'round');
// 0
console.log('Math.round', mathRoundWatcher.getCount());
// 1
Math.round(99666.9999999);
console.log('Math.round', mathRoundWatcher.getCount());
// 2
Math.round(999999999.99);
console.log('Math.round', mathRoundWatcher.getCount());
const alertWatcher = watchFunction(null, null, window.alert);
// 0
console.log('window.alert', alertWatcher.getCount());
// 1
window.alert('1');
console.log('window.alert', alertWatcher.getCount());
// 2
alert('2')
console.log('window.alert', alertWatcher.getCount());
// reset the alertWatcher counter
alertWatcher.reset();
This code above breaks the stacksnippets.com when used with Array.prototype.map for some reason, please see this JsFiddle link:
https://jsfiddle.net/ctbjnawz/3/
Do you mean a method of an instance or object? One way is to create a new function. e.g
function WatchInstanceMethods(instance, functionName){
let originalFunction = window[instance][funcName];
let counter = 0;
window[instance][functionName] = function(...args){
console.log("watching");
counter++;
return originalFunction(...args);
}
return {
getCount: () => {return counter},
reset: () => {
// Unwrap the function
window[funcName] = originalFunction
}
}
}
although adding support for chaining methods will get difficult with more nested methods but you can pass a string for functionName name and split it to have each layer of calling instance for function and repeat the logic above.
I'm trying to replicate jQuery's element manipulation to a certain extent. Now what I have found to be very useful is the .first() selector. I would like to be able to chain functions like this;
getElement(selector).first().hasClass(className);
Now, there are 2 issues with how far I've gotten (Do note that my code example is minimised, so please, no comments about error-handling.)
var getElement = function(selector, parent) {
ret = document.querySelectorAll(selector);
this.element = ret;
this.hasClass = function(className) {
className.replace('.', '');
if(this.multiple())
{
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
this.first = function() {
this.element = this.element[0];
return this;
};
return this;
};
My current problem
If I call my function;
var $test = getElement('.something');
//result: nodelist with .something divs
If I call for the first element within the result;
$test.first();
//Result: First div, perfect!
However, now if I call $test again, it will replace the elements property with the result of first(), meaning I have "lost" my old values. I don't want to lose them, I only want the first() functions for that specific functionality. Then I want $test to return all elements again.
Also, recalling first() will now end up undefined, since there is only 1 element left within this as it has deleted the old elements from within the object.
Another attempt
Now, I've also tried to turn it around a bit by returning the first-child instead of the entire class object;
this.first = function() {
return this.element[0];
};
However, I will
$test.first().hasClass(className);
//Returns ERROR, method hasClass undefined
this is because .hasClass exists on the original this, which doesn't get returned anymore since I'm now returning the element.
I have tried to get something out of jQuery's library, though that just confused me more...
I have googled this subject, but with all the 'chaining methods' solutions I'm finding, all of them seem to be overwriting the original values of the object, which is not what I want to happen. One other solution actually required me to re-initiate the object over and over again, which did not seem very efficient to me... Any help is appreciated. I'm assuming I'm going about this completely the wrong way.
-- If you can help me, please do explain why your solution works. I really feel like if I understand this, my understanding of javascript can expand a lot further. I just need to get past this structural(?) issue.
A method like first() should not modify this, it should create a new object and return that. You only use return this; in methods that modify an element rather than returning information derived from the element.
this.first = function() {
return new getElement(this.element[0]);
};
And note that you have to use new getElement to create an object, not just getElement.
This also requires a change to the constructor, so it can accept either a selector string or an element:
var getElement = function(selector, parent) {
var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
...
}
You should also consider doing this in proper OO fashion, by putting the methods in a prototype, rather than defining them in every object.
var getElement = function(selector, parent) {
var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
this.element = ret;
};
getElement.prototype.hasClass = function(className) {
className.replace('.', '');
if (this.multiple()) {
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
getElement.prototype.first = function() {
return new getElement(this.element[0])
};
this in your outer function refers to the window / global object.
Instead, return the ret variable itself.
In the inner functions (which become the object's methods), this acts the way you expect it to.
Here's an alternative solution, which allows chaining, even after you've called the first method:
var getElement = function(selector, parent) {
var ret = typeof selector == 'string' ? document.querySelectorAll(selector)
: selector;
ret.hasClass = function(className) {
if(!this.classList) {
console.log('Cannot use hasClass function on multiple elements');
return false;
} else {
return this.classList.contains(className);
}
};
ret.first = function() {
return new getElement(this[0]);
};
return ret;
};
console.log(getElement('p').length); //2
console.log(getElement('p').first().innerHTML); //abc
console.log(getElement('p').first().hasClass('test')); //true
console.log(getElement('p').first().hasClass('nope')); //fase
console.log(getElement('p').hasClass('test')); //false (multiple elements)
<p class="test">
abc
</p>
<p>
def
</p>
Here is how I would approach this:
Create a constructor, say Search, tasked to find the elements based on the input. Using a constructor is proper OO Programming and you also have the advantage of defining methods once in the prototype and they can be accessed by all instances.
Ensure that the context (this) is an array-like object, with numeric properties and a length, so that you can easily iterate over every matched element in the traditional way (using for loops, [].forEach etc).
Create a function, say getElement, that will use the constructor and return the result without having to use the new keyword all the time. Since the function returns an instance of our constructor, you can chain the methods you want as you would normally do.
The method first uses the constructor to create a new instance instead of modifying the original, since its role is to return the first element, not delete everything but the first element.
Each time you come up with a new method you want your object to have, you can simply add it to the prototype of the constructor.
Snippet:
;(function () {
function Search (value) {
var elements = [];
/* Check whether the value is a string or an HTML element. */
if (typeof value == "string") {
/* Save the selector to the context and use it to get the elements. */
this.selector = value;
elements = document.querySelectorAll(value);
}
else if (value instanceof Element) elements.push(value);
/* Give a length to the context. */
this.length = elements.length;
/* Iterate over every element and inject it to the context. */
for (var i = 0, l = this.length; i < l; i++) this[i] = elements[i];
}
/* The method that returns the first element in a Search instance. */
Object.defineProperty(Search.prototype, "first", {
value: function () {
return new Search(this[0]);
}
});
/* The global function that uses the Search constructor to fetch the elements. */
window.getElement = (value) => new Search(value);
/* Create a reference to the prototype of the constructor for easy access. */
window.getElement.fn = Search.prototype;
})();
/* Get all elements matching the class, the first one, and the first's plain form. */
console.log(getElement(".cls1"));
console.log(getElement(".cls1").first());
console.log(getElement(".cls1").first()[0]);
/* ----- CSS ----- */
.as-console-wrapper {
max-height: 100%!important;
}
<!----- HTML ----->
<div id = "a1" class = "cls1"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>
Example:
In this example, I'm adding a new method called hasClass to the prototype of the constructor.
/* The method that returns whether the first element has a given class. */
Object.defineProperty(getElement.fn, "hasClass", {
value: function (value) {
return this[0].classList.contains(value);
}
});
/* Check whether the first element has the 'cls2' class. */
console.log(getElement(".cls1").first().hasClass("cls2"));
<!----- HTML ----->
<script src="//pastebin.com/raw/e0TM5aYC"></script>
<div id = "a1" class = "cls1 cls2"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>
I think the easiest would be to return a new class that contains the nodes you have selected. That would be the easiest solution, as you don't really want to mutate any of your previous selectors.
I made a small example, using some ES6 that makes a few things easier to work with, which also has a $ to initiate the selections being made.
You would notice that first of all, any selection that is made, is just calling the native document.querySelectorAll but returns a new Node class. Both first and last methods also return those elements.
Lastly, hasClass should work on all elements in the current nodes selections, so it will iterate the current node, and check all classes in there, this one returns a simple bool, so you cannot continue with the method chaining there.
Any method you wish to chain, should either:
return this object (the current node)
return an element of the this object as a new node so any further manipulations can be done there
const $ = (function(global) {
class Node extends Array {
constructor( ...nodes ) {
super();
nodes.forEach( (node, key) => {
this[key] = node;
});
this.length = nodes.length;
}
first() {
return new Node( this[0] );
}
last() {
return new Node( this[this.length-1] );
}
hasClass( ...classes ) {
const set = classes.reduce( (current, cls) => {
current[cls] = true;
return current;
}, {} );
for (let el of this) {
for (let cls of el.classList) {
if (set[cls]) {
return true;
}
}
}
return false;
}
}
global.$ = function( selector ) {
return new Node( ...document.querySelectorAll( selector ) );
};
return global.$;
}(window));
let selector = $('.foo');
let first = selector.first(); // expect 1
console.log(first[0].innerHTML);
let last = selector.last();
console.log(last[0].innerHTML); // expect 4
console.log( first.hasClass('foo') ); // expect true
console.log( first.hasClass('bar') ); // expect false
console.log( selector.hasClass('foo') ); // expect true
console.log( selector.hasClass('bar') ); // expect true
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo bar">3</div>
<div class="foo">4</div>
You can update getElement so it returns back again when you send it an element.
var getElement = function(selector, parent) {
var ret = null
if (typeof selector === "string") {
ret = document.querySelectorAll(selector);
} else {
ret = selector
}
this.element = ret;
this.hasClass = function(className) {
className.replace('.', '');
if (this.multiple()) {
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
this.first = function() {
this.element = getElement(this.element[0]);
return this;
};
return this;
};
var test = getElement(".foo");
console.log(test.first())
console.log(test.first().hasClass)
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo">3</div>
<div class="foo">4</div>
You can use .querySelectorAll(), spread element and Array.prototype.find(), which returns the first match within an array or undefined
const getElement = (selector = "", {prop = "", value = "", first = false} = {}) => {
const el = [...document.querySelectorAll(selector)];
if (first) return el.find(({[prop]:match}) => match && match === value)
else return el;
};
let first = getElement("span", {prop: "className", value: "abc", first: true});
console.log(first);
let last = getElement("span");
console.log(all);
<span class="abc">123</span>
<span class="abc">456</span>
I have the next object:
var persons= {};
persons["Matt"].push("A");
persons["Matt"].push("B");
persons["Matt"].push("C");
And I want to know if the object contains the element which I try to insert.
E.g:
persons["Matt"].push("A"); /* The element A already exist... And I don't want duplicate elements.*/
Anybody know one way to make it?
EDIT WITH MORE DETAILS:
I have a the next code:
function insertIfNotThere(array, item) {
if (array.indexOf(item) === -1) {
array.push(item);
}
}
function EventManager(target) {
var target = target || window, events = {};
this.observe = function(eventName, cb) {
if (events[eventName]){
insertIfNotThere(events[eventName], cb);
}else{
events[eventName] = []; events[eventName].push(cb);
}
return target;
};
this.fire = function(eventName) {
if (!events[eventName]) return false;
for (var i = 0; i < events[eventName].length; i++) {
events[eventName][i].apply(target, Array.prototype.slice.call(arguments, 1));
}
};
}
I use your method for checking if the element with the content indicated exist. But... It push the element ever... I don't know what's happening...
First things first. When you do
persons= {};
you are creating a global property called persons and assigning an empty object to it. You might want a local variable here. So, change it to
var persons = {};
And then, when you create a new key in the object, by default, the value will be undefined. In your case you need to store an array. So, you have to initialize it like this
persons['Matt'] = [];
and then you can use the Array.prototype.indexOf function to find out if the item being added is already there in the array or not (it returns -1 if the item is not found in the array), like this
if (persons['Matt'].indexOf("A") === -1) {
persons['Matt'].push("A");
}
if (persons['Matt'].indexOf("B") === -1) {
persons['Matt'].push("B");
}
You can create a function to do this
function insertIfNotThere(array, item) {
if (array.indexOf(item) === -1) {
array.push(item);
}
}
var persons = {};
persons['Matt'] = [];
insertIfNotThere(persons['Matt'], 'A');
insertIfNotThere(persons['Matt'], 'B');
// This will be ignored, as `A` is already there in the array
insertIfNotThere(persons['Matt'], 'A');
Use indexOf to check for the existence of A. If it doesn't exist (is -1), add it to the array:
if (persons['Matt'].indexOf('A') === -1) {
persons['Matt'].push('A');
}
I am trying to recursively build an object with a tree of properties based on a MongoDB-ish selector "top.middle.bottom". There are some underscorejs helpers as well:
function setNestedPropertyValue(obj, fields, val) {
if (fields.indexOf(".") === -1) {
// On last property, set the value
obj[fields] = val;
return obj; // Recurse back up
} else {
var oneLevelLess = _.first(fields.split("."));
var remainingLevels = _.rest(fields.split(".")).join(".");
// There are more property levels remaining, set a sub with a recursive call
obj[oneLevelLess] = setNestedPropertyValue( {}, remainingLevels, val);
}
}
setNestedPropertyValue({}, "grandpaprop.papaprop.babyprop", 1);
Desired:
{
grandpaprop: {
papaprop: {
babyprop: 1
}
}
}
Outcome:
undefined
Helps and hints would be appreciated.
Instead of recursion I would choose for an iterative solution:
function setNestedPropertyValue(obj, fields, val)
{
fields = fields.split('.');
var cur = obj,
last = fields.pop();
fields.forEach(function(field) {
cur[field] = {};
cur = cur[field];
});
cur[last] = val;
return obj;
}
setNestedPropertyValue({}, "grandpaprop.papaprop.babyprop", 1);
EDIT
And here is another version thanks to the suggestions by Scott Sauyet:
function setPath(obj, [first, ...rest], val) {
if (rest.length == 0) {
return {...obj, [first]: val}
}
let nestedObj = obj[first] || {};
return {...obj, [first]: setPath(nestedObj, rest, val)};
}
function setNestedPropertyValue(obj, field, val) {
return setPath(obj, field.split('.'), val);
}
// example
let test_obj = {};
test_obj = setNestedPropertyValue(test_obj, "foo.bar.baz", 1);
test_obj = setNestedPropertyValue(test_obj, "foo.bar.baz1", 1);
// will output {"foo":{"bar":{"baz":1,"baz1":1}}}, while in the original version only "baz1" will be set
console.log(JSON.stringify(test_obj));
It's plain javascript
It only appends properties and will not override a top level object
setNestedPropertyValue() does not mutate the passed object (although keep in mind it only returns a shallow copy of the object, so some properties may be shared references between the original object and the new one)
I know this is old, but I needed exactly that kind of function and wasn't happy with the implementation, so here is my version:
function setNestedPropertyValue(obj, field, val) {
if (field.indexOf(".") === -1) {
obj[field] = val;
} else {
let fields = field.split(".");
let topLevelField = fields.shift();
let remainingFields = fields.join(".");
if (obj[topLevelField] == null) {
obj[topLevelField] = {};
}
setNestedPropertyValue(obj[topLevelField], remainingFields, val);
}
}
// example
let test_obj = {};
setNestedPropertyValue(test_obj, "foo.bar.baz", 1);
setNestedPropertyValue(test_obj, "foo.bar.baz1", 1);
// will output {"foo":{"bar":{"baz":1,"baz1":1}}}, while in the original version only "baz1" will be set
console.log(JSON.stringify(test_obj));
It's plain javascript
It only appends properties and will not override a top level object
setNestedPropertyValue() does not return the object so it is clear that it mutates the passed object
As mentioned by Jack in the question, I was not returning my object in the last line in the else statement. By adding this, it is now working:
obj[oneLevelLess] = setNestedPropertyValue( {}, remainingLevels, val);
return obj; // Add this line
}
According to the mustache RFC
A {{name}} tag in a basic template will try to find the name key in
the current context. If there is no name key, nothing will be
rendered.
I therefore expected this:
var template = '{{#anArray}}{{aString}}{{/anArray}}';
var json = {
"aString":"ABC",
"anArray": [1,{"aString":"DEF"}]
};
To give me once rendered:
"DEF"
However mustache.js looks for values in the parent's scope. Which gives me
"ABCDEF"
Do the context actually means including all the parents scopes ?
http://jsfiddle.net/ZG4zd/20/
Short answer: yes.
A bit longer answer. Context.prototype.lookup does a while loop, looking up a token in current context and it's parent contexts, while there is a parent context.
Relevant bit of code:
Context.prototype.lookup = function (name) {
var value = this._cache[name];
if (!value) {
if (name === ".") {
value = this.view;
} else {
var context = this;
//Iterate ancestor contexts
while (context) {
if (name.indexOf(".") > 0) {
var names = name.split("."), i = 0;
value = context.view;
while (value && i < names.length) {
value = value[names[i++]];
}
} else {
value = context.view[name];
}
if (value != null) {
break;
}
context = context.parent;
}
}
this._cache[name] = value;
}
if (typeof value === "function") {
value = value.call(this.view);
}
return value;
};