I am calling an object method in two ways in my code:
this.reveal.updateVisuals(i, 'show');
or
this.reveal.updateVisuals(i, 'hide');
and I am passing the hide and show condition as a string, to be later evaluated and used as a method. Please note the condition: if (effect === 'show/hide').
updateVisuals: function (time, effect) {
// Check if parameter exists and property can be read
if (this.breakpointsMap && typeof this.breakpointsMap[checkTime] !== "undefined") {
if (effect === 'show') {
// display the items that were fast forwarded
var k = this.breakpointsMap[checkTime].length;
while (k--) {
try {
this.breakpointsMap[checkTime][k].show();
} catch (err) {}
}
} else if (effect === 'hide') {
// display the items that were fast forwarded
var k = this.breakpointsMap[checkTime].length;
while (k--) {
try {
this.breakpointsMap[checkTime][k].hide();
} catch (err) {}
}
}
}
}
However the code seems duplicated and I was wondering if there is a way to pass hide or show as a method to the method and apply it on the array, when needed. I tried something like this:
this.reveal.updateVisuals(i).show
There are a lot of ways you can use to simplify this, here are a couple:
updateVisuals: function (time, effect) {
if (this.breakpointsMap && typeof this.breakpointsMap[checkTime] !== "undefined") {
this.breakpointsMap[checkTime].forEach(e => e[effect]());
}
}
Or returning the array:
updateVisuals: function (time, effect) {
if (this.breakpointsMap && typeof this.breakpointsMap[checkTime] !== "undefined") {
return this.breakpointsMap[checkTime];
}else{
return [];
}
}
this.reveal.updateVisuals(i).forEach(e => e.show());
You can access a method property by it's (string) name using [bracket] notation.
updateVisuals: function (time, effect) {
// Check if parameter exists and property can be read
if (this.breakpointsMap && typeof this.breakpointsMap[checkTime] !== "undefined") {
var k = this.breakpointsMap[checkTime].length;
while (k--) {
try {
this.breakpointsMap[checkTime][k][effect]();
} catch (err) {}
}
}
}
if you are using Es6, you can do:
function updateVisuals (time, effect) {
// Check if parameter exists and property can be read
if (this.breakpointsMap && typeof this.breakpointsMap[checkTime] !== "undefined") {
let execFn= (arrindex) => breakpointsMap[checkTime][arrindex].show();
if (effect === 'hide')
{
execFn = (arrindex) => breakpointsMap[checkTime][arrindex].hide();
}
// display the items that were fast forwarded
var k = this.breakpointsMap[checkTime].length;
while (k--) {
try {
execFn(k);
} catch (err) {}
}
}
}
I assume that var checkTime is global or in closure. If you are using version lower tan es6 you can use execFn= function (arrindex) {...}, a then bind this argument when calling method after.
Related
im studying JavaScript and im trying to solve the problem in this test exercise:
FreeCodeCamp Record Collection
I can't understand why it doesnt work. The object details and the problem description are in the link above.
function updateRecords(object, id, prop, value) {
if (value === '') {
delete object[id][prop];
} else if (prop === 'tracks') {
if (object[id][prop].hasOwnProperty('tracks') == false) {
object[id][prop] = [value];
} else if (value !== '') {
object[id][prop].push(value);
}
} else if (prop !== 'tracks' && value !== '') {
object[id][prop] = value;
}
return object;
}
This is the error i get:
// running tests
After updateRecords(collection, 5439, "tracks", "Take a Chance on Me"), tracks should have Take a Chance on Me as the last element.
After updateRecords(collection, 2468, "tracks", "Free"), tracks should have 1999 as the first element.
// tests completed
Thank you for your support.
Let's take a look at this line:
if (object[id][prop].hasOwnProperty('tracks') == false) {
If we replace the variables with their values, we get:
if (object[5439]['tracks'].hasOwnProperty('tracks') == false) {
^ ^
... which is always going to fail. Here is a simplified version:
function updateRecords(object, id, prop, value) {
if (value === '') {
delete object[id][prop];
} else if (prop === 'tracks') {
if (!object[id].hasOwnProperty('tracks')) {
object[id][prop] = [];
}
object[id][prop].push(value);
} else {
object[id][prop] = value;
}
return object;
}
For example, in iOS Swift, I can do something like this:
if (self.user?.company?.pic?.phoneNumber != null) { doSomething() }
Without the need to:
if (self.user != null && self.user!.company != null && self.user!.company!.pic != null && self.user!.company!.pic!.phoneNumber != null) { doSomething() }
In ReactNative (or Javascript), I found out that if an object is undefined, I can't check for the existence of the variable inside of it, so I have to check first whether the object is undefined or not, only then I can safely check whether the variable inside of it undefined or not.
if (typeof this.state.user !== "undefined" && typeof this.state.user.company !== "undefined" && typeof this.state.user.company.pic !== "undefined" && typeof this.state.user.company.pic.phoneNumber !== undefined) { this.doSomething() }
How can I turn this into just:
if (typeof this.state.user.company.pic.phoneNumber !== "undefined") { this.doSomething() }
or something similar?
Thanks.
Currently, optional chaining is a stage 3 draft, and so, you may be able to do it in the future.
EDIT:
Optional chaining will now be part of ES2020, and so you'll be able to do the following:
if (self.user?.company?.pic?.phoneNumber !== undefined) {
doSomething(); // phoneNumber exists
}
With that being said, it still has very limited browser support.
So, for the time being, you could instead create a function which recursively finds each object from a list of properties like so:
const optional_chain = (obj, [key, ...props]) =>
obj !== undefined && key ? optional_chain(obj[key], props) : obj;
const user = {
company: {
pic: {
phoneNumber: 1
}
}
}
console.log(optional_chain(user, ['company', 'pic', 'phoneNumber'])); // 1
console.log(optional_chain(user, ['company', 'pic', 'phoneNumber', 'x'])); // undefined
console.log(optional_chain(user, ['company', 'picture', 'phoneNumber'])); // undefined
console.log(optional_chain(user, ['x', 'picture', 'phoneNumber'])); // undefined
In your case, the usage would be as so:
if (optional_chain(self.user, ['company', 'pic', 'phoneNumber']) !== undefined) {
doSomething();
}
If you can’t use optional chaining which is still a proposal but available via babel plugin you could use a recursive utility function to test for the presence of each path segment:
const pluck = (item, path) => {
const [, part, rest] = /^([^.]+)\.*(.*)/.exec(path) || [];
if (!part) {
return null;
}
const o = (item || {})[part];
if (o == null) {
return null;
}
return rest.length ? pluck(o, rest) : o;
};
if (pluck(this.state, ‘user.company.pic.phoneNumber’)) {
doSomething();
}
So I'm trying to edit this rss feed with these 2 functions because of the media:content property which I have had no luck accessing directly. the functions I have below work for creating a new value called mediaContent which I can then easily access. The issue is in the rss feed not all objects will have media:content and I want to add a default value for the objects that don't have that property so I have consistency in my objects. Otherwise I end up with undefined on on some of mediaContent in my new object. I wanted to start just added a default value in when media:content is not present in the object but these ||'s are not working as I would have expected. How can I get my else if to punch in a default value if media:content does not exist? I'm probably missing something easy.
function getMediaContent(value) {
for (var i in value) {
if (i === "media:content") {
console.log("MC::", i)
return value[i].$;
} else if (i !== "title" || i !== "link" || i !== "pubDate" || i !== "isoDate" || i !== "guid" || i !== "contentSnippet" || i !== "content") {
debugger;
return "no media content"
}
}
}
function getNewsLinks() {
return newsItems.map(value => ({
value,
mediaContent: getMediaContent(value)
}))
}
SOLUTION (based on accepted answer)
function getMediaContent(value) {
return "media:content" in value ? value["media:content"].$ : "no media content";
}
works perfectly. Thanks!
Since you're just looking to see if a property exists on an object, you can use the in operator:
function getMediaContent(value) {
return "media:content" in value ? value["media:content"].$ : "no media content";
}
That checks if the property exists, and if so, gets the value of its $ property. Otherwise, returns the default value.
I needed something similar and optionally it would work for multi-layered JSON objects. Here is the function I use:
function getFromJSON(obj, ...args) {
for (const arg of args) {
if (!Array.isArray(arg)) {
if (arg in obj) {
obj = obj[arg]
} else {
return `${arg} not found in JSON`;
}
} else {
for (const argOpt of arg) {
if (argOpt in obj) {
obj = obj[argOpt]
break;
}
}
}
}
return obj
}
In addition, you can pass multiple keys in an array if you want to get the value of whichever exists.
Given a publish-subscribe pattern using ES6 as follows (extracted from https://davidwalsh.name/pubsub-javascript):
class PubSub {
constructor() {
this.handlers = [];
}
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
{
if (this.getHandler(event, handler) == null) {
this.handlers.push({event: event, handler: handler.bind(context), key: Guid()});
}
}
}
unsubscribe(event, handler) {
let filteredHandler = this.getHandler(event, handler);
if (filteredHandler != null) {
let idx = this.handlers.indexOf(filteredHandler);
if (idx > -1) {
this.handlers.splice(idx, 1);
}
}
}
publish(event, args) {
this.handlers.forEach(topic => {
if (topic.event === event) {
topic.handler(args)
}
})
}
getHandler(event, handler) {
if (this.handlers == null || this.handlers.length < 1) {
return null;
}
let filtered = null;
this.handlers.forEach(topic => {
if (topic.event === event && topic.handler === handler) {
filtered = topic;
}
});
return filtered;
}
getNumOfSubsribers() {
if (this.handlers != null && this.handlers.length > 0) {
return this.handlers.length;
}
return 0;
}
}
The subscribe and publish methods work. However, the getHandler and unsubscribe method do not work as expected (getHandler seems returning null). I have tried to search around but could not get a satisfactory solution to this problem (not sure how a function bound to a given context can be filtered out from an array).
What have I done wrong in the code? Kindly advise me on getHandler and also unsubscribe part of the code.
Appreciate some kind help.
That code is odd in a couple of ways.
The reason getHandler doesn't work is that the handler property of the object pushed on handlers is not the function that was passed in; it's the result of calling bind on that function. Formatted properly, this is subscribe:
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
{
if (this.getHandler(event, handler) == null) {
this.handlers.push({
event: event,
handler: handler.bind(context), // ** NOTE **
key: Guid()
});
}
}
}
That value will never be equal to the original, by definition.
Instead, it should include the original handler as well so it can check for it later. Let's also get rid of the pointless standalone block:
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
if (this.getHandler(event, handler) == null) {
this.handlers.push({
event: event,
handler: handler.bind(context),
originalHandler: handler, // ***
key: Guid()
});
}
}
Now, getHandler can look for matches with originalHandler. While we're there, let's stop looping when we find the handler rather than keeping going, and use the semantically-appropriate Array#find:
getHandler(event, handler) {
if (this.handlers == null || this.handlers.length < 1) {
return null;
}
let filtered = this.handlers.find(topic => topic.event === event && topic.originalHandler === handler);
return filtered;
}
There are other issues with the code (like binding the handler to itself if no context is provided), but a full code review is out of scope; the above is why getHandler doesn't work and thus why unsubscribe doesn't work. With that fix, unsubscribe should also work (though it seems odd to search twice).
I get that everything in Javascript is an object, but how is possible to declare one variable in one scope; I mean merely as a variable and suddenly start using it as an object assigning some properties "INSIDE" other functions. How does scoping for this case work?
https://github.com/ariya/esprima/blob/master/esprima.js
In this code, the extra variable is only declared without being given any properties:
var Token,
extra;
And suddenly it starts being used by object as follows:
function addComment(type, value, start, end, loc) {
var comment, attacher;
assert(typeof start === 'number', 'Comment must have valid position');
// Because the way the actual token is scanned, often the comments
// (if any) are skipped twice during the lexical analysis.
// Thus, we need to skip adding a comment if the comment array already
// handled it.
if (state.lastCommentStart >= start) {
return;
}
state.lastCommentStart = start;
comment = {
type: type,
value: value
};
if (extra.range) {
comment.range = [start, end];
}
if (extra.loc) {
comment.loc = loc;
}
extra.comments.push(comment);
if (extra.attachComment) {
attacher = {
comment: comment,
leading: null,
trailing: null,
range: [start, end]
};
extra.pendingComments.push(attacher);
}
}
The closest example that I can get for this extra to be initiated as object is from the following function:
function tokenize(code, options) {
var toString,
token,
tokens;
toString = String;
if (typeof code !== 'string' && !(code instanceof String)) {
code = toString(code);
}
delegate = SyntaxTreeDelegate;
source = code;
index = 0;
lineNumber = (source.length > 0) ? 1 : 0;
lineStart = 0;
length = source.length;
lookahead = null;
state = {
allowIn: true,
labelSet: {},
inFunctionBody: false,
inIteration: false,
inSwitch: false,
lastCommentStart: -1
};
extra = {};
// Options matching.
options = options || {};
// Of course we collect tokens here.
options.tokens = true;
extra.tokens = [];
extra.tokenize = true;
// The following two fields are necessary to compute the Regex tokens.
extra.openParenToken = -1;
extra.openCurlyToken = -1;
extra.range = (typeof options.range === 'boolean') && options.range;
extra.loc = (typeof options.loc === 'boolean') && options.loc;
if (typeof options.comment === 'boolean' && options.comment) {
extra.comments = [];
}
if (typeof options.tolerant === 'boolean' && options.tolerant) {
extra.errors = [];
}
if (length > 0) {
if (typeof source[0] === 'undefined') {
// Try first to convert to a string. This is good as fast path
// for old IE which understands string indexing for string
// literals only and not for string object.
if (code instanceof String) {
source = code.valueOf();
}
}
}
try {
peek();
if (lookahead.type === Token.EOF) {
return extra.tokens;
}
token = lex();
while (lookahead.type !== Token.EOF) {
try {
token = lex();
} catch (lexError) {
token = lookahead;
if (extra.errors) {
extra.errors.push(lexError);
// We have to break on the first error
// to avoid infinite loops.
break;
} else {
throw lexError;
}
}
}
filterTokenLocation();
tokens = extra.tokens;
if (typeof extra.comments !== 'undefined') {
tokens.comments = extra.comments;
}
if (typeof extra.errors !== 'undefined') {
tokens.errors = extra.errors;
}
} catch (e) {
throw e;
} finally {
extra = {};
}
return tokens;
}
But still it is only inside this function, not in the same scope as
var Token,
extra;
How is it possible just to declare a variable and only instantiate the properties inside a function? How is it shared between different scopes? Once given the properties in one function, is it shared to other scopes such as another scope in another function? So confusing.
Line #3658 sets it to an empty object:
extra = {};
Scoping has to do with the matching of identifiers (i.e. names) to the what they refer to. For an object created in one scope to be accessed from another, you merely need to have a name in each refer to the same thing (for example, by returning an object created in a function to its caller and having the caller assign it to a variable whose name is in its scope).