const Player = (name, mark) => {
let markedSpots = [];
const markSpot = (spot) => {
markedSpots.push(spot);
};
const clearSpots = () => {
markedSpots = [];
};
return {name, mark, markedSpots, markSpot, clearSpots};
};
let player1 = Player('Player 1', 'X');
let player2 = Player('Player 2', 'O');
const gameLogic = (() => {
let currentPlayer = player1;
const reset = () => {
player1.clearSpots();
player2.clearSpots();
currentPlayer = player1;
};
return {startGame, reset};
})();
Above I have part of the code from a tictactoe game that I am trying to make. In the reset function I need to clear the array in the both player objects and set the current player back to player1. Through debugging, I know that calling the clearSpots() function does clear the array in the player objects. But there is a problem with reseting the currentPlayer. I believe this might be a closure problem, but I have no idea how to identify the problem or how to solve it. CAn anyone explain to me?
I suggest using a class instead. You could use the class syntax:
create a constructor with the player initialization logic saving the variables in this as properties
add methods to the class: here markSpot and clearSpots
instantiate new objects with new: let players1 = new Player(...)
Here is a code example:
class Player {
constructor(name, mark) {
this.markedSpots = [];
this.mark = mark;
this.name = name;
return this;
}
markSpot(spot) {
this.markedSpots.push(spot);
};
clearSpots() {
this.markedSpots = [];
};
};
let player1 = new Player('Player 1', 'X');
let player2 = new Player('Player 2', 'O');
const gameLogic = (() => {
let currentPlayer = player1;
const reset = () => {
player1.clearSpots();
player2.clearSpots();
currentPlayer = player1;
};
return {reset}; // removed startGame for demo purposes
})();
player1.markSpot(1);
console.log('before:', player1)
gameLogic.reset();
console.log('after:', player1)
Notice how you can access the object's property when inside the class with this: this.markedSpots, this.mark, this.name etc...
The issue is that when you call markedSpots = [];, you're changing the markedSpots reference.
This means that the returned markedSpots from earlier is not being changed, and instead only the markedSpots variable inside of the function's value is. When you try to access the returned markedSpots outside of the Player function, it is still referencing the original value, not the new empty one.
What you can do is replace markedSpots = []; with markedSpots.splice(0, markedSpots.length);, which will empty the array without changing the reference.
const Player = (name, mark) => {
let markedSpots = [];
const markSpot = (spot) => {
markedSpots.push(spot);
};
const clearSpots = () => {
markedSpots.splice(0, markedSpots.length); // <= Change is here.
};
return {name, mark, markedSpots, markSpot, clearSpots};
};
let player1 = Player('Player 1', 'X');
let player2 = Player('Player 2', 'O');
const gameLogic = (() => {
let currentPlayer = player1;
const reset = () => {
player1.clearSpots();
player2.clearSpots();
currentPlayer = player1;
};
return {reset};
})();
// Testing (should output two empty arrays):
player1.markSpot(1);
player1.markSpot(2);
player2.markSpot(3);
gameLogic.reset();
console.log(player1.markedSpots)
console.log(player2.markedSpots)
Related
I am coding the game Battleship and have the following code for a Ship factory function:
const Ship = (len) => {
const length = len;
let _hitNumber = 0;
const hit = () => {
_hitNumber = _hitNumber + 1;
return _hitNumber
};
return {
length,
get hitNumber() {
return _hitNumber;
},
hit,
};
};
const ship = Ship(3);
ship.hit();
console.log(ship.hitNumber);
It works as expected. If I call hit() and then examine .hitNumber it has increased. I then made a Fleet factory as shown below. It returns an array of Ships:
const Ship = (len) => {
const length = len;
let _hitNumber = 0;
const hit = () => {
_hitNumber = _hitNumber + 1;
return _hitNumber
};
return {
length,
get hitNumber() {
return _hitNumber;
},
hit,
};
};
const Fleet = () => {
return [{
name: "Carrier",
...Ship(5),
},
{
name: "Battleship",
...Ship(4),
},
]
}
const testFleet = Fleet();
testFleet[0].hit();
console.log(testFleet[0].hitNumber)
But now calling hit doesn't work:
It's as if .hitNumber is not a getter anymore or testFleet[0].hit() doesn't refer to testFleet[0]'s _hitnumber.
I'd like help understanding what is going on and whether it is the array brackets or the spread operator that is responsible. I suspect that the pitfalls outlined in this article are relevant, but my situation is different enough (and my understanding is shallow enough) that I can't quite make the connection.
In your Ship() function, you return an object with a getter, then you later use the spread operator to copy all properties of the ship to a new object.
Behind the scenes, the getters are syntactic sugar for calls to Object.defineProperty(). Object.defineProperty() can set special settings on properties, such as calling a function when a property is access (this is a getter).
However, the spread operator simply copies properties from one object to another, and does not copy the special settings set by Object.defineProperty(). So, when the spread operator tries to copy the hitNumber property, it calls the getter once and copies the result, not the function itself.
To fix this you need to implement your own replacement for the spread operator that can copy accessors (getters and setters). Luckily, the issue is common enough that MDN provides a function in the documentation for Object.assign(). Here is an example with your code:
// from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#copying_accessors
function completeAssign(target, ...sources) {
sources.forEach((source) => {
const descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});
Object.getOwnPropertySymbols(source).forEach((sym) => {
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
}
const Ship = (len) => {
const length = len;
let _hitNumber = 0;
const hit = () => {
_hitNumber = _hitNumber + 1;
return _hitNumber
};
return {
length,
get hitNumber() {
return _hitNumber;
},
hit,
};
};
const Fleet = () => {
const arr = [{
name: "Carrier",
len: 5,
},
{
name: "Battleship",
len: 5,
},
];
arr.forEach(obj => {
completeAssign(obj, Ship(obj.len));
});
return arr;
}
const fleet = Fleet();
fleet[0].hit();
console.log(fleet[0].hitNumber);
Here I'm trying store the name of the id and the number of times it has been clicked.
It's stores as an object but when I tried JSON.Stringify() it returns a empty array like this
'[]'
if (localStorage.getItem('cart') == null) {
var cartArr = {};
} else {
cartArr = JSON.parse(localStorage.getItem('cart'));
}
const cartClass = document.querySelectorAll(".cart");
cartClass.forEach((ele) => {
ele.addEventListener('click', (e) => {
let cartId = (e.target.getAttribute('id'));
if (cartArr[cartId] == undefined) {
cartArr[cartId] = 1;
} else {
cartArr[cartId] += 1;
}
localStorage.setItem('cart', JSON.stringify(cartArr));
console.log(localStorage.getItem('cart'));
});
});
Your code should work. You never stored an array in that code. Perhaps you have another code that stored cart as an array?
I would delegate and give the system a tiny bit of time to react
const cartStr = localStorage.getItem('cart')
cartArr = cartStr ? JSON.parse(cartStr) : {}
const cartContainer = document.getElementById("cartContainer");
cartContainer.addEventListener('click', (e) => {
const tgt = e.target.closest(".cart");
if (tgt) {
let cartId = tgt.id;
cartArr[cartId] = cartArr[cartId] ? cartArr[cartId] : 0;
cartArr[cartId]++;
localStorage.setItem('cart', JSON.stringify(cartArr));
setTimeout(function() {
console.log(localStorage.getItem('cart'))
}, 10); // give the system time to react
}
});
I have checked your code, it works correctly. Your problem can be solved by cleaning your localStorage. It seems that at some point you have stored an empty array into your local storage. What happens is, JSON.stringify stores the contents of array, but ignores any custom properties you set on an array object:
// This will log "[]"
const arr = [];
arr.prop = "val"
console.log(JSON.stringify(arr));
// This will log {"prop": "val"}
const obj = {};
obj.prop = "val"
console.log(JSON.stringify(obj));
I'm trying to create the following thing, I couldn't find any success, let me know if there's a way :)
I have 2 variables called text1 and text2, each one of them should represent a key inside an object and the value of the key will be the data variables.
In the beginning, the object will start as empty, and I need to create a function that will create that object inside the object as many times as need, it can be a case where the text will have more than 3 keys, for e.g. items.courses.lessons.assets.title etc.. so the function must be dynamic.
notice that the function must check if the key already exists so it will not overwrite it if there is another text with the same starting keys (see example below)
const data1 = 'hello';
const text1 = 'lessons.first.title';
const data2 = 'hello there';
const text2 = 'lessons.first.description';
// how the end result should look like
const result = {
lessons: {
first: {
title: 'hello',
description: 'hello there',
},
},
};
Thanks! 😁😁
Split the path up, remove the last part. Loop over an object with the remaining path. When you get to the end, set the property wit the value.
function setProp(obj, path, value) {
var parts = path.split(".");
var last = parts.pop();
var current = parts.reduce(function (acc, part) {
acc[part] = acc[part] || {};
return acc[part];
}, obj);
current[last] = value;
}
const data1 = 'hello';
const text1 = 'lessons.first.title';
const data2 = 'hello there';
const text2 = 'lessons.first.description';
var myObj = {};
setProp(myObj, text1, data1 );
setProp(myObj, text2, data2);
console.log(myObj);
Here's how I did it :)
const data1 = 'hello';
const text1 = 'lessons.first.title';
const data2 = 'hello there';
const text2 = 'lessons.first.description';
const res = {};
const addProp = (keys, value) => {
let temp = res;
const len = keys.split('.').length
keys.split('.').forEach((key,index)=>{
if(!temp[key]) {
temp[key] = {};
}
if(index === len - 1){
temp[key] = value;
}
temp = temp[key];
});
}
addProp(text1, data1);
addProp(text2, data2);
console.log(res);
Here is my attempt
function constructObj(obj, path, value) {
const pathArray = path.split('.');
let currentNode = obj;
for (let i = 0; i < pathArray.length; i++) {
const path = pathArray[i];
if (i === pathArray.length - 1) { // if on last element assign the value
currentNode[path] = value;
}
if (currentNode[path] === undefined) { // check if key exists
const newObj = {};
currentNode[path] = newObj;
currentNode = newObj;
} else {
currentNode = currentNode[path];
}
}
}
const result = {};
const data1 = 'hello';
const text1 = 'lessons.first.title';
constructObj(result, text1, data1);
const data2 = 'hello there';
const text2 = 'lessons.first.description';
constructObj(result, text2, data2);
I have a string like this:
let user = "req.user.role"
is there any way to convert this as nested objects for using in another value like this?
let converted_string = req.user.role
I know I can split the user with user.split(".")
my imagination :
let user = "req.user.role".split(".")
let converted_string = user[0].user[1].user[2]
I found the nearest answer related to my question : Create nested object from query string in Javascript
Try this
let user = "req.user.role";
let userObj = user.split('.').reduceRight((obj, next) => ({
[next]: obj
}), {});
console.log(userObj);
Or this, for old browsers
var user = "req.user.role";
var userArray = user.split('.'), userObj = {}, temp = userObj;
for (var i = 0; i < userArray.length; i++) {
temp = temp[userArray[i]] = {};
}
console.log(userObj);
The function getvalue() will return the nested property of a given global variable:
var user="req.user.role";
var req={user:{role:"admin"}};
function getvalue(str){
return str.split('.').reduce((r,c,i)=>i?r[c]:window[c], '');
}
console.log(getvalue(user));
I'll take my shot at this:
let user = "req.user.role"
const trav = (str, o) => {
const m = str.split('.')
let res = undefined
let i = 0
while (i < m.length) {
res = (res || o)[m[i]]
if (!res) break
i++
}
return res
}
const val = trav(user, {
req: {
user: {
role: "admin"
}
}
})
console.log(val)
this function will traversed the passed in object for the entire length of the provided string.split "." list returning either a value or undefined.
You can do it like this:
let userSplitted = "req.user.role".split('.');
let obj, o = obj = {};
userSplitted.forEach(key=>{o=o[key]={}});
I have a challenge to create a simple Notes manager in JS, I've written a function that takes one string, gives it and id and pushes it to an array of notes.
let nextId = 0;
const getId = () => nextId++;
let notes = [{id: getId(), value: 'Note'}];
const addNote = (input) => {
notes.push({id:getId(), value: input});
console.log('Note added');
I now struggle with a function that will take multiple strings as parameters
('own', 'snail', 'platypus')
create an object for each element with id/value(string) and push it to the main array.
The result should look like:
[{ id: 1, value: 'owl'},
{ id: 2, value: 'snail'}]
So far I have this, it assigns ID correctly, but the loop fails
const batchAddNotes = (values) => {
let obj = {};
for (i = 0; i < values.length; i++) {
obj.id = (getId());
obj.value = (values[i]);}
return obj;};
To have your variables in a certain scope, I'd pack it all in a class (or a function). As you're using arrow functions, the class should be ok. To add multiple nodes the way you've shown; using var-args, you can create a method that expects those with (...input)
class Notes {
constructor() {
this.nextId = 0;
this.nodes = [{
id: this.getId(),
value: 'Note'
}];
}
addNote(input) {
this.nodes.push({
id: this.getId(),
value: input
})
}
getId() {
return this.nextId++;
}
addNotes(...input) {
input.forEach(e => this.addNote(e));
}
}
const notes = new Notes();
notes.addNotes('own', 'snail', 'platypus');
console.log(notes.nodes);
Use the functions arguments object. It's an array of all the arguments that are being passed to a function. Then you can loop over them and run your functionality on them each time.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
You could use the arguments passed in your function as var args
const addNote = _ => {
for(var i = 0; i < arguments.length; i++){
notes.push({id:getId(), value: arguments[i]});
console.log('Note added');
}
}
use rest params :
const myFn = (...values) => {
let tmpArr = [];
for(let i = 0 ; i < values.length ; i++){
tmpArr.push({
id : i + 1,
value : values[i]
});
}
return tmpArr;
}
const result = myFn('own', 'snail', 'platypus');
console.log(result);
This is how it look like when using Rest Params and reusing your first function. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
You can add so many notes as you want (addMultipleNotes can receive indefinite number of arguments )
let nextId = 0;
const getId = () => nextId++;
let notes = [{id: getId(), value: 'Note'}];
const addSingleNote = (input) => {
notes.push({id:getId(), value: input});
console.log('Note added');
};
const addMultipleNotes = (...args) => {
for(let i = 0; i < args.length; i++){
addSingleNote(args[i]);
}
};
addMultipleNotes('one', 'two', 'three');
console.log(notes);
First of all, note how I've used an IIFE and a closure to create an id generator.
In the other hand, rest parameters, Array#map and parameter spread are your friends:
const incrId = (() => {
let id = 0
return () => ++id
})()
const valuesToNotes = (...values) => values.map(value => ({
id: incrId(),
value
}))
const notes = []
// Parameter spread (i.e. '...') gives each
// array item in the output of valuesToNotes
// as if you would use Function#apply
notes.push(...valuesToNotes('a', 'b', 'c'))
console.log(notes)
Yet another more functional approach which doesn't mutate the input notes and produces a new one with existing notes plus the ones transformed from values:
const concat = xs => ys => xs.concat(ys)
const map = f => xs => xs.map(f)
const pipe = xs => x => xs.reduce((r, f) => f(r), x)
const incrId = (() => {
let id = 0
return () => ++id
})()
const valueToNote = value => ({
id: incrId(),
value
})
const notes = []
const appendNotes = pipe([map(valueToNote), concat(notes)])
const moreNotes = appendNotes(['a', 'b', 'c'])
console.log(moreNotes)