I'd like to do something like this:
const vegetableColors = {corn: 'yellow', peas: 'green'};
const {*} = vegetableColors;
console.log(corn);// yellow
console.log(peas);// green
I can't seem to find or figure out how to do this but I really thought I had seen it done somewhere before! :P
NOTE: I'm using Babel with stage set to 0;
CONTEXT: I'm trying to be drier in JSX and not reference this.state or this.props everywhere. And also not have to keep adding properties to destructure if the data changes.
I think you're looking for the with statement, it does exactly what you are asking for:
const vegetableColors = {corn: 'yellow', peas: 'green'};
with (vegetableColors) {
console.log(corn);// yellow
console.log(peas);// green
}
However, it is deprecated (in strict mode, which includes ES6 modules), for good reason.
destructure all properties into the current scope
You cannot in ES61. And that's a good thing. Be explicit about the variables you're introducing:
const {corn, peas} = vegetableColors;
Alternatively, you can extend the global object with Object.assign(global, vegetableColors) to put them in the global scope, but really, that's worse than a with statement.
1: … and while I don't know whether there is a draft to allow such things in ES7, I can tell you that any proposal will be nuked by the TC :-)
I wouldn't recommend it, but you can use eval() to accomplish something similar:
vegetableColors = {corn: 'yellow', peas: 'green'};
function test() {
for ( let i=0; i < Object.keys(vegetableColors).length; i++ ) {
let k = Object.keys(vegetableColors)[i];
eval(`var ${k} = vegetableColors['${k}']`);
}
console.log(corn); // yellow
}
test();
console.log(corn); // undefined (out of scope)
I think you're looking for:
const {corn, peas} = vegetableColors;
Live on Babel's REPL
If Pointy's right that you're asking how to do this without knowing the names corn and peas, you can't with destructuring assignment.
You can at global scope only, using a loop, but I'm sure you don't want to do this at global scope. Still, just in case:
// I'm sure you don't really want this, just being thorough
Object.keys(vegetableColors).forEach((key) => {
Object.defineProperty(this, key, {
value: vegetableColors[key]
});
});
(Throw enumerable: true on there if you want these pseudo-constants to be enumerable.)
That works at global scope because this refers to the global object.
I came upon a situation where the object was user-created and the code that uses the object was also user-created. Because the with statement is deprecated, I made my own, using eval to destructure the entire object and call the function that uses the destructured object. Below is a working example.
const vegetableColors = { corn: 'yellow', peas: 'green' };
function with2(obj, func) {
eval(`
var { ${Object.keys(obj).join(",")} } = obj;
(${func.toString()})()
`);
}
/*
with(vegetableColors) {
console.log(corn);
console.log(peas);
}
*/
with2(vegetableColors, function() {
console.log(corn);
console.log(peas);
})
Let me show you my solution to the problem. I don't agree with those who think that destructuring object properties into local scope without specifying their names is bad idea. For me, this feature, if implemented, would be helpful. This would make our code shorter, and improve code maintenance by making it easy to change property names without changing the processing code. After all, there is the extract() function in PHP that does the same thing. Are PHP developers wrong?
My solution is not ideal as it uses eval but it is one liner and it works. Perhaps in the future we will have a solution from JavaScript developers.
function extract(o)
{
var result = [];
for(var key in o)
if(o.hasOwnProperty(key)) {
var item = 'var ' + key + '=' + JSON.stringify(o[key]);
result.push(item);
}
return result.join(';');
}
var vegetableColors = { corn: 'yellow', peas: { unripe: 'green', ripe: 'yellow' } };
eval(extract(vegetableColors));
console.log(corn); // yellow
console.log(peas); // {unripe: "green", ripe: "yellow"}
Related
I want to loop through an array and give their name.
I tried to use template literals, but it doesn't work.
const colors = ['yellow', 'green', 'brown', 'blue', 'pink','black']
for (let color of colors){
const `${color}Button` = document.querySelector(`#${color}`);
}
the results I want should be something like this
yellowButton = document.querySelector(#yellow);
greenButton = document.querySelector(#green);
.
.
.
.
blackButton = document.querySelector(#black);
Could you guys please revise my code?
You can attach variables onto the window object, making it accessible as a global variable. However this is a bad practice, since it pollutes namespace, causing unnecessary bugs and much headache in the future. A much better approach to this is to use native javascript objects, as this is the exact use case it was made for.
With your example:
const colors = ['yellow', 'green', 'brown', 'blue', 'pink', 'black']
const buttons = colors.reduce((accum, color) => {
accum[`${color}Button`] = document.querySelector(`#${color}`);
return accum;
}, {});
console.log(buttons)
// to access certain element:
const elem = buttons.yellowButton
console.log(elem)
<button id='yellow'></button>
<button id='green'></button>
<button id='brown'></button>
<button id='blue'></button>
<button id='pink'></button>
<button id='black'></button>
In the browser, you can attach variables to the window interface and reference them as if they were defined in the global scope.
for (const color of colors) {
window[`${color}Button`] = document.querySelector(`#${color}`)
}
console.log(redButton.textContent)
However, as others have mentioned, it may not be the best approach.
How would I call my method to my other objects?
Been having lots of trouble with everything I've tried.
I'm not that confident with this stuff, just looking on how to tell if the object is safe to drive or not.
//Create a constructor function called `Track`. It will accept two parameters - the name of the track and the maximum capacity of the track.
let track = function(name, capacity){
this.trackName=name
this.personnel=0;
this.cars=[];
this.cap=capacity;
}
//We'll need a value for the average weight of a person but this value will be the same for all tracks.
//Add a property `personWeight` on the `Track` prototype so that all instances share the same value.
track.prototype.personWeight = 200
//Create three methods on the prototype that will calculate track weight, person weight, and if its safe to drive
function personWeight(){
personnelWeight = this.personWeight * this.personnel
return personnelWeight
}
function trackWeight(){
let carsTotal = function myFunc(total, num) {
return total - num;
}
let weightTotal = (this.personnel * this.personWeight) + (this.carsTotal)
return weightTotal
}
function safeToDrive(){
if(this.trackWeight<this.capacity){
return true
}
}
//Create two track objects
let trackOne = new track ("Daytona", 25000);
trackOne.cars = [1800, 2400, 2700, 3200, 3600, 3800, 4200]
trackOne.personnel = 10
let trackTwo = new track ("Indiana",15000);
trackTwo.cars = [2000, 2300, 2800, 3000, 3500, 3700, 4000]
trackTwo.personnel = 8
//Call the `safeToDrive` method for truck objects.
With the code as it is now, you would use safeToDrive.call(trackOne). However, this is not the straight-forward way you would do it normally.
I guess what you really want is assigning these methods to the prototype:
track.prototype.safeToDrive = function () {
if(this.trackWeight<this.capacity){
return true
}
}
Then you'd call them using trackOne.safeToDrive().
The same goes for personWeight and trackWeight.
A few other observations:
Your check for this.capacity won't work because the property is actually called cap and not capacity according to what you set in your constructor.
safeToDrive currently returns true or nothing, i.e. undefined, and not true or false as you would expect.
You could fix that by either adding an else with return false or simply using return this.trackWeight < this.capacity instead of the whole if condition.
Oh, also, your personnelWeight variable is accidentally made global. Add a let before it. To avoid this in the first place, add 'use strict' at the top of your file to get warned about this issue next time.
I'm not sure what you are doing with carsTotal there though, I guess that should be a member function as well (otherwise you couldn't even call it using this.carsTotal as you do now). Plus, your indention is wrong there. (Put your file through a beautifier to see what I mean.)
Do you mean truck instead of track maybe...?
New to Js, sorry if this is an obvious one.
I have some strings in my code that correspond to the names of variables. I'd like to put them into a function and have the function be able to make changes to the variables that have the same names as the strings.
The best example is where this 'string' is passed through from a data tag in html, but I have some other situations where this issue appears. Open to changing my entire approach too is the premise of my question is backwards.
<html>
<div data-location="deck" onClick="moveCards(this.data-location);">
</html>
var deck = ["card"];
function moveCards(location){
location.shift();};
Thanks!
A script should not depend on the names of standalone variables; this can break certain engine optimizations and minification. Also, inline handlers are nearly universally considered to be pretty poor practice - consider adding an event listener properly using Javascript instead. This will also allow you to completely avoid the issue with dynamic variable names. For example:
const deck = ["card", "card", "card"];
document.querySelector('div[data-location="deck"]').addEventListener('click', () => {
deck.shift();
console.log('deck now has:', deck.length + ' elements');
});
<div data-location="deck">click</div>
I think this can technically be done using eval, but it is good practice to think more clearly about how you design this so that you only access objects you directly declare. One example of better design might be:
container = {
obj1: //whatever this object is
...
objn:
}
function applyMethodToObject(object_passed){
container[object_passed].my_method();
}
I'm not sure I 100% follow what you're trying to do, but rather than trying to dynamically resolve variable names you might consider using keys in an object to do the lookup:
const locations = {
deck: ['card']
}
function moveCards (location) {
// if 'deck' is passed to this function, this is
// the equivalent of locations['deck'].shift();
locations[location].shift();
};
Here's a working demo:
const locations = {
deck: ['card 1', 'card 2', 'card 3', 'card 4']
};
function move (el) {
const location = el.dataset.location;
const item = locations[location];
item.shift();
updateDisplay(item);
}
// update the display so we can see the list
function updateDisplay(item) { document.getElementById('display').innerHTML = item.join(', ');
}
// initial list
updateDisplay(locations['deck']);
#display {
font-family: monospace;
padding: 1em;
background: #eee;
margin: 2em 0;
}
<div data-location='deck' onclick="move(this)">click to shift deck</div>
<div id="display">afda</div>
When you assign a value to an object in javascript you can access with dot or array notation. IE
foo = {};
foo.bar = "bar";
console.log(foo.bar);
console.log(foo["bar"]);
Additionally, global variables are added to the window object, meaning deck is available at window["deck"] or window[location] in your case. That means your moveCards function could do:
function moveCards(location) {
// perform sanity checks since you could change data-location="foo"
// which would then call window.foo.shift()
if (window[location]) {
window[location].shift();
}
}
That said, this probably isn't a great approach, though it's hard to say without a lot more context.
my attempts to build a module wrapping a collection fail
i have something like:
// topic: chess gaming
// file: src/core/Refs.js
const COLS = 'abcdefgh'.split('')
const ROWS = '12345678'.split('')
const ALL = new Map()
class Ref {
constructor (col, row) {
this.col = col
this.row = row
this.key = String(col + row)
}
// translate to ref to obtain another
// or null, if off-board
//
// ref.translate(0, 0) === ref (ok ?)
//
translate (dcol, drow) {
// compute something for dcol and drow
// ...
return ALL.get( COLS[colIdx] + ROWS[rowIdx] )
}
}
// the code which seems to not work propertly
for(let c of COLS) {
for(let r of ROWS) {
ALL.set(String(c + r), new Ref(c, r))
}
}
export default {
COLS, ROWS, ALL,
has (ref) {
return ALL.has(ref)
},
get (ref) {
return ALL.get(ref)
},
// trying to grant/warrant "immutability"
// for the "ALL" collection inside the module
keys() {
return [...ALL.keys()]
}
}
After I complie the module with Buble with correct flags (dangerousForOf, ..) and objectAssign
Then I test with karma + jasmine, the first test :
it ('should be 64-length array', function () {
expect (Refs.keys().length).toEqual(64)
})
will faim with 'expects 1 to equal 64', and a log of Refs.ALL seems to show an empty Map
althougth Refs.COLS and Refs.ROWS are properly initialized.
What's happening and how to fix it ?
EDIT:
#Bergi: of course, exposing Refs.ALL breaks the immutability, it was rather for debugging purposes
I'm not exactly sure how to capture test bundle output, but looking at gulp+rollup developpement bundle, the method keys() line :
return [...ALL.keys()]
was replaced by:
return [].concat(ALL.keys())
which produces an 1-element array containing a MapIterator, this breaking tests. putting something like :
return Array.from( ALL.keys() )
will fix the problem, but risks not to work properky in legacy browsers !
I have pushed the code on my repo : https://github.com/hefeust/colorchess-v2
Hoping this could help to fix the bug : how to convert spread (...) operator in source code to have a bundle with the correct Object.assign polyfill ?
Bublé does not support iterators, which is why it transpiles array literals with spread syntax to concatenations. Use Array.from instead (which is more descriptive anyway).
return Array.from( ALL.keys() ) will fix the problem, but risks not to work properly in legacy browsers!
That should be of no concern - you are using Map objects and their keys() method that returns an iterator, which won't work in legacy browsers either. If you plan to support them, you have to use a polyfill anyway - and you just would get a polyfill for Array.from as well.
Code:
initialize: function() {
this.todos = [
{id: 100, text: 'Rich'},
{id: 200, text: 'Dave'}
];
},
activeTodos: function() {
this.todos = this.todos.length(function() {
return this.todos;
});
this.emitChange();
}
<p>Todo's Remaining: {this.activeTodos} </p>
activeItems: function(){
this.context.executeAction(activeToDosAction);
},
Explanation:
I am trying to print out the size of the array to the browser window (this can be seen in the <p> tags within the code). So far nothing is displaying and I cant figure out why. activeTodos should be calling the length of todos.
i can post more code if people require it. i am using reactjs hence the { } brackets within HTML
There are a couple of weird things there. If you only need to display the length of this.todos you can do something like this:
<p>Todo's Remaining: {this.todos.length} </p>
I think the problem here is that you pass a function to length. That is not required.
Your code should work if you change it to:
activeTodos: function() {
return this.todos.length;
}
I'm not sure what your function emitChange should be doing, so I omitted it for now in the code example. If you need it to be called, put it before the return as that is the last thing that will run in your function.
First, to access the length of an array you just have to do
myArray = [1, 2, 3];
myArray.length; //3
Moreover you have 2 fonctions : initialize and activeTodos where you use this.todos = ...
Without further explanations I assume these variables created with the keyword 'this' inside 2 different functions are out of score.
Verify that your this.todos refers to the same thing.
You do not need active.Todos() as .length is a built in function by itself.
Just do
Todo's Remaining: {this.todos.length}
Cheers.