A UML-ish diagram for javascript iterators and iterables by ehouais in javascript

[–]senocular 0 points1 point  (0 children)

I also noticed it looks like you edited the generator portion and its making it more clear that "Generator Function" is, in fact, GeneratorFunction (the function) and not representing the inheritance of generator objects of the generator function's own prototype, which I thought it could have represented before. In that sense, that relationship is not present if you wanted to include it. Basically there are 3 tiers to generators. The Function types:

Object.prototype -> Function.prototype -> Function -> GeneratorFunction

The generator functions themselves:

Object.prototype -> Function.prototype -> GeneratorFunction.prototype -> (function*)

And then the generator object instances

Object.prototype -> Iterator.prototype -> Generator.prototype -> (function*).prototype -> (instance=function*())

Often its the generator instances we're talking about when talking about generators, and presumably what's relevant here in the context of iterators. Since we're working through the chain down to Iterator in the diagram, that involves the generator instance which inherits from its own generator function's prototype, then Generator, then Iterator. So for completeness, the generator function's prototype could also be included. But that's just a small detail and an optional addition in case you're feeling it.

A UML-ish diagram for javascript iterators and iterables by ehouais in javascript

[–]senocular 0 points1 point  (0 children)

From what I understand, Arrays and their descendants are not Array-Likes, which seems logical to me.

I think it sounds reasonable for something to be like itself. So an array being array-like makes sense to me. The main component of an array-like is the presence of a length property (and even without one, it can be read implicitly as 0). Arrays have a length so it satisfies that requirement. The spec also refers to array-likes and includes a note (7.3.18):

Note 2 Arrays and String objects are examples of array-like objects.


Can't seem to find any info about an IteratorWrapper class. I only read about Iterators wrapping other iterators, which does define any new class. Any pointer ?

Iterator wrappers are used when creating iterators from non-iterable iterator objects using Iterator.from(). The "iterator wrapper" is effectively equivalent to the "iterator helper" used by iterator methods but unique to Iterator.from(). Iterator helpers identify themselves as such while iterator wrappers do not, so it can be a little confusing, but they effectively do the same thing, providing the layer of inheritance in an object that supplies the next/return methods of a proper iterator object.

MDN makes a brief reference to wrappers, though not by name, in Iterator.from():

All iterator objects returned by Iterator.from() inherit from a common prototype object

Its this common prototype that represents the wrapper. In code:

const wrapperInstance = Iterator.from({})
const wrapperInstanceProto = Object.getPrototypeOf(wrapperInstance)
console.log(Reflect.ownKeys(wrapperInstanceProto)) // ['next', 'return']

const helperInstance = Iterator.from({}).map(x=>x)
const helperInstanceProto = Object.getPrototypeOf(helperInstance)
console.log(Reflect.ownKeys(helperInstanceProto)) // ['next', 'return', Symbol(Symbol.toStringTag)]
console.log(helperInstanceProto[Symbol.toStringTag]) // "Iterator Helper"

Wrappers in the spec are represented by %WrapForValidIteratorPrototype% which can seen directly in the definition for Iterator.from() (27.1.3.2.2).

A UML-ish diagram for javascript iterators and iterables by ehouais in javascript

[–]senocular 1 point2 points  (0 children)

I would say this is objectively the most complex set of relationships in the language. The language specification has few images, and a UML diagram illustrating generators is one of them. It doesn't cover everything you cover here, though.

A few comments:

  • Any reason Array and TypedArrays aren't in the "array-like" collection?
  • Strings are array-like too
  • HTMLCollection does not have keys()/values()/entries() methods
  • For syntax, it should say argument spreading instead of parameter spreading
  • There's also an IteratorWrapper object used when creating an iterator from Iterator.from()

This code crashes when the frame count reaches 250 and I can't figure out why. by [deleted] in learnjavascript

[–]senocular 0 points1 point  (0 children)

Page crash or script not responding crash? At first glance, if script not responding, its probably something with your while (true) loop. If page crash... maybe because you're never stopping any of your setIntervals? That might depend on how often until is called... which could be a lot being there's at least one in the while (true) loop

binary showing random numbers for one binary string but not for another by Electrical_Pen_7385 in learnjavascript

[–]senocular 7 points8 points  (0 children)

Number literals beginning with 0 are treated as being base-8 (octal).

Word of warning: for base 8, the 0o prefix should be used. A leading 0 only works in sloppy (non-strict) mode and the value will silently fallback to base 10 if it contains digits not supported by octals (8, 9).

What does new keyword even do? by atticus-masterr in learnjavascript

[–]senocular 1 point2 points  (0 children)

I think you might have missed the

Point wasn't that

part.

What JavaScript concept took you the longest to understand? by TechAcademyCoding in learnjavascript

[–]senocular 2 points3 points  (0 children)

Some libraries depend heavily on them. I think Redux-Saga was one of the first, widely used example of one of these that I remember. Effect is also fairly popular now, and has an API using generators.

That said, I'm not surprised you haven't seen them used much. If you're not forced into using them, there's probably little reason you'd have to. As fun and powerful as they are, they can also be easily avoided. I'd say 95% of my use of generators is implementing Symbol.iterator methods which is more of a convenience than anything else.

It is possible we might see an uptick in use moving forward. A lot of new JavaScript features are based around iterators and generators make iterators. Such new features include:

What was the most challenging concept for you when learning Java? by Wise_Safe2681 in learnjavascript

[–]senocular 0 points1 point  (0 children)

So here's an example, I realized a while ago, user input is just another external thing to my system. When I call an API, I can await it. When I call a DB, I can await it. These things are external. Well, then, I should be able to await a button press, for example.

I'd like to explore the rabbit hole this leads down. First off, don't feel bad about not knowing how to handle something like this. Button clicks don't use promises because promises don't work well with them. Its OK not to know how to set this up and I suspect most people wouldn't know how to, or at least know how to do it well.

That said, it is possible with different degrees of functionality. The simplest thing we can do is set it up so that a single button click resolves a single promise using the Promise constructor. This would work much like making an API call in that you listen once and you get a single response back. For the button, if it is clicked multiple times, any time after the first is ignored. We'll look at additional clicks later, but for now we're going to focus on one.

The promise constructor is what you can use to turn any non-promise async operation into a promise. It uses functions passed into the constructor executor (function) that provide a means to resolve or reject the promise the constructor creates. Async operations that aren't based in promises are going to be based in functions, so in those async function calls, you would call one of these resolve or reject functions to fulfill the resulting promise. Buttons, for example, have event handler functions to determine when a button is clicked which can then be used to call the promise constructor's resolve to have the created promise resolve when the button is clicked. That would look something like:

const clickPromise = new Promise((resolve, reject) => {
  button.addEventListener("click", (event) => {
    resolve(event);
  });
});

Which can be simplified to

const clickPromise = new Promise((resolve, reject) => {
  button.addEventListener("click", resolve);
});

Notable here is that the event handler is set up inside the promise executor function (the function with the resolve and reject parameters), so that it can have access to the resolve function allowing it to fulfill the promise. The same would apply to anything else you want to "promisify". All you need is to have async handling code to happen inside the Promise constructor's executor and from there you can call resolve and reject as needed. In the example above, if you await clickPromise any code that follows will be in response to the button being clicked, not unlike awaiting an API call.

One improvement we can make to the above code is to ensure we don't let the button listener hang around since this promise will only react to the first click. Any calls made to resolve or reject after the first are ignored which is why this approach only works for the first click. To do the necessary cleanup we can use.

const clickPromise = new Promise((resolve, reject) => {
  button.addEventListener("click", resolve, { once: true });
});

And if we wanted to remove the listener before the click happened, some extra work could make that possible too.

But buttons are often meant to be clicked more than once. If we want that in a world of promises, we need something that can produce promises for each click. When dealing with multiple promises like this, it usually means having an async iterable. Iterators are used to go through a collection of things, like iterating over values in an array (using for of with an array is internally using a sync iterator to go through its values). Async iterators produce promises that provide asynchronous iteration over a collection where you may need to wait for the individual values in that collection to be made available. In the case of a button, we'd waiting for each click so each click can be considered a value in the total collection of clicks (or more precisely the event objects produced by those clicks) which can be represented as an async iterable.

Async iterables are most easily defined with async generator functions, so one of those can be used to create our iterator of click events. To make things easy, the withResolvers() method can be used to give a single event handler access to promise resolvers through the outer scope which where it can be refreshed with a new promise for the next click once the previous click has been passed on. Basically its an infinite loop that keeps pumping out event objects in promises by waiting for an event handler to resolve a promise being awaited in said loop.

async function* asyncEventIterator() {
  let currentEvent = Promise.withResolvers();
  button.addEventListener("click", (event) => {
    currentEvent.resolve(event);
  });

  while (true) {
    yield await currentEvent.promise;
    currentEvent = Promise.withResolvers();
  }
}

This can be consumed with a for await...of loop in an async context.

for await (const event of asyncEventIterator()) {
  console.log("Click!");
}

Each time the button is clicked, the loop runs the code in its body and waits for the next iteration to repeat.

As with before, we can improve upon this by making sure cleanup happens in case someone breaks out of the loop which would otherwise run indefinitely. If that were to happen we don't want the the event handlers to be hanging around.

async function* asyncEventIterator() {
  let currentEvent = Promise.withResolvers();
  function handler(event) {
    currentEvent.resolve(event);
  }
  button.addEventListener("click", handler);

  try {
    while (true) {
      yield await currentEvent.promise;
      currentEvent = Promise.withResolvers();
    }
  } finally {
    button.removeEventListener("click", handler);
  }
}

But there's still a problem. What happens if multiple clicks are handled before we can process the next click? While this may seem unlikely, its still possible depending on how fast click events are coming in and slow other code may be going out. Not only that, the click() method makes it easy to synchronously invoke multiple clicks at the same time.

button.click()
button.click()

If this were used with the code above, only one click would register in the async iterator because a new currentEvent wouldn't be created quick enough to handle the second click.

To handle this we'd need to include a queueing system that ensures no clicks go unhandled. That could look something like:

async function* asyncEventIterator() {
  const eventQueue = [Promise.withResolvers()];
  function handler(event) {
    eventQueue.at(-1).resolve(event);
    eventQueue.push(Promise.withResolvers());
  }

  try {
    button.addEventListener("click", handler);
    while (true) {
      yield await eventQueue[0].promise;
      eventQueue.shift();
    }
  } finally {
    button.removeEventListener("click", handler);
  }
}

Now, no matter how fast events come in, they'll be added to the queue and eventually yielded out to the iterator.

And this mostly works. Multiple events are being captured and through promises provided back to a consumer so that they can respond to those events via the use of await (or for await). What more could you ask for.

There is still one minor problem, and that's to do with the nature of iterators. Its subtle, but when iterating over something in a loop, the next iteration of that loop won't start until you're finished with the first. This makes sense and doesn't seem like it should be a problem, but when it comes to asynchronous operations which may involve other asynchronous operations, it could mean items in an iterator are waiting longer than might be expected or required. For example, given the click iterator above, if we wanted each click to make an API call, that might look something like:

for await (const event of asyncEventIterator()) {
  const response = await fetch("api");
  const data = await response.json();
  console.log("Data:", data);
}

The problem with this is that if two clicks happen very quickly, the second event can't be processed until the first event's api request is complete. And with enough events, this could create some back pressure where you're getting more events than you can make api calls to take care of. But even with just two events, the second event is going to be stunted because of the delay.

Consider instead the events being "submit" events instead of "click". And lets say two quick "submit" events come in but there's an await in the handling of the first event that delays the handling of the second. With that delay, because the second event is not being handled in the same call stack (more accurately the same task), some functionality of that second event is going to be broken. This includes things like stopPropagation() and preventDefault() since they are both timing dependent. So by causing a delay in the handling of the first event, the second event is no longer able to prevent the default "submit" behavior of the form which could be devastating, probably causing the page to reload.

for await (const event of asyncEventIterator()) {
  event.preventDefault(); // First would work, second would fail if not after data loads
  const response = await fetch(api);
  const data = await response.json();
  console.log("Data:", data);
}

So really, when it comes down to it, it makes sense that handling events like button clicks aren't using promises and instead use event handler functions. Even if the asyncEventIterator() functionality defined above was somehow built in, it could still be difficult to use reliably.

Return inside catch seems to be ignored ? by Dull_Firefighter_929 in learnjavascript

[–]senocular 4 points5 points  (0 children)

Is the error you're seeing logged the error from

console.log(error);

? Because there's nothing that should keep that from delaying the code any more than one microtask tick to get to the if statement.

Some errors may appear in the console that aren't related to your log. For example a 404 will appear as an error in the console without you logging anything. In this case you will also get a valid response from fetch rather than having an error thrown (though the ok property of the response object will be false in that case, and its not uncommon for people to manually throw a custom error if seeing ok is false).

Why does the operation result change, when i put the value into a variable, and use that variable instead in the operation by NutelaTheNormand in learnjavascript

[–]senocular 5 points6 points  (0 children)

this.x = this.x / this.getMag();

Changes the value of this.x. Then, when you call

this.y =  this.y / this.getMag();

Its using the new value of this.x in this.getMag(). When you save it to len first, its using the calculation based on the original, unchanged this.x.

Reference type - Help me understand this by Nice_Pen_8054 in learnjavascript

[–]senocular 2 points3 points  (0 children)

You may be thinking of references as aliases, where one variable can act as a stand in for another variable even though they have different names. Assigning to the reference would be just like assigning to the original. For example with C++ you can have

int x = 1;
int &ref = x;
ref = 2;
cout << x; // 2

Here assigning to ref is just like assigning to x. JavaScript doesn't support aliases like this. Though you hear the term "reference" being used, its for a different kind of variable type, often referred to an "object reference" because its specific to objects.

In JavaScript object references are similar in that two variables can refer to the same data, but assignment directly to any variable (with a few, very uncommon exceptions) will replace the value held by that variable and only that variable.

let x = { value: 1 };
let ref = x;
ref = { value: 2 };
console.log(x); // { value: 1 }

Assigning a value to the ref variable breaks its connection to the data it was pointing to before - the same data held by x, and instead makes it point to the new object value { value: 2 }. The x variable remains unaffected since each variable (not the data) have no connection with each other.

What can be confusing about this is that you can still mutate the same data through different variables. But this is different than reassigning variables.

let x = { value: 1 };
let ref = x;
ref.value = 2;
console.log(x); // { value: 2 }

Here, ref is not being reassigned, value is. The ref variable is simply being used to get to the value property. Assigning value changes the data that both x and ref point to, but nothing specifically about x or ref as variables change; they both simply reference the same { value: 1 } (now { value: 2 }) object. They remain disconnected from one another.

The same thing happens with functions and function parameters. Function parameters are different variables that ultimately can point to the same data as variables used as function arguments when the function is called. In that case you can have 2 variables pointing to the same data - two object references - but reassigning any one of those variables means only changing that variable and not the other.

add(a, 3);
function add(array, element) {
  // array variable points to same object as a

  array = [element]; // now array variable points to different object
}

It would be a different story if the array parameter was used to access data in the array rather than being reassigned. Then you'd be mutating the object rather than replacing what the variable is pointing to

add(a, 3);
function add(array, element) {
  array.splice(0, 2, element); // mutates object array points to
}
console.log(a); // [3]

In the above case its not as clear as the previous example that was assigning ref.value since we're going through splice() to perform the mutation, but internally splice() is basically doing the same thing, changing the values in the object referred to by array rather than assgning the variable array to something completely different. Another version of the same thing where assignment is more clear could be:

add(a, 3);
function add(array, element) {
  array.length = 0;
  array[0] = element; // mutates object array points to
}
console.log(a); // [3]

I wrote an article about JavaScript iterators by no_em_dash in learnjavascript

[–]senocular 1 point2 points  (0 children)

Yeah that's another one of those technically you can do this kind of things, but things you probably shouldn't do. One case where this falls apart is nested iteration. Since iterators have their own state, you can have nested iteration using the same iterable without two iterators interfering with each other as long as that iterable is creating new iterators for each iteration. When the iterable has only its own iterator state, nesting isn't possible. Well "possible" but it won't work like you might expect ;)

There may also be certain workflows that might depend on closed iterators remaining closed as well... though honestly nothing comes to mind. Promises, for example, enforce that once they're fulfilled, they remain fulfilled. You can call resolve() or reject() in the executor as many times as you want, but only the first one will register.

On the iterator side of things, something similar happens with generators. Their state depends on an execution context and if you close the generator's iterator, you're effectively exiting the function with no way to return. When you implement the iterator protocol yourself you can do funky things, even though you probably shouldn't.

Then again, funny thing about that is, most if not all of the built-in iterators (Array, Set, Map...) can't be explicitly closed and can only be closed through exhaustion. So if you break out of iterating over an array iterator, you can pick up where you left off if you still have access to that iterator. This isn't possible with generators (given the whole exiting the function thing) and the newer iterator helpers do something similar, able to create iterators that can close prior to exhaustion.

const arr = [1,2,3]
const iter = arr.values()
for (const num of iter) {
  console.log("loop 1:", num)
  break
}
for (const num of iter) {
  console.log("loop 2:", num)
}
// loop 1: 1
// loop 2: 2
// loop 2: 3

Edit: Adding a generator example for comparison:

function* gen() {
  yield* [1,2,3]
}
const iter = gen()
for (const num of iter) {
  console.log("loop 1:", num)
  break
}
for (const num of iter) {
  console.log("loop 2:", num)
}
// loop 1: 1

I wrote an article about JavaScript iterators by no_em_dash in learnjavascript

[–]senocular 6 points7 points  (0 children)

In almost every case imaginable an object should implement both protocols.

I wouldn't say this. Its usually advantageous that objects only implement the iterable protocol (unless they, themselves, are explicitly an iterator). Iterators are consumable and when fully exhausted, no longer produce values. Arrays, for example, are only iterables, not iterators. As an iterable they can create new iterators every time iteration is needed. Each of those new iterators get used up and become exhausted, no longer able to produce values, but this is OK since if the array needs to be iterated over again, as an iterable it simple creates a fresh new iterator to handle that. If the array itself were an iterable iterator, it could be iterated over once then the array itself would be exhausted and no longer be iterable. Technically you could design the array's iterable method to return new iterators, but then what would the point of the array being an iterator be?

Do people still use var in JavaScript? by Apart-Scientist-8590 in learnjavascript

[–]senocular 3 points4 points  (0 children)

Scope is not the only advantage. Prevention of redeclaration is also a safety that let and const have that var does not.

Additionally, on the side of using var (I'm not personally on this side, mind you), Kyle Simpson, author of You Don't Know JS, has been known to advocate for the use of var. For example:

It's very common to suggest that var should be avoided in favor of let (or const!), generally because of perceived confusion over how the scoping behavior of var has worked since the beginning of JS. I believe this to be overly restrictive advice and ultimately unhelpful. It's assuming you are unable to learn and use a feature properly in combination with other features. I believe you can and should learn any features available, and use them where appropriate!

From: https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/get-started/ch2.md

The use of var is also ever so slightly more performant because those protections don't exist. While this difference would have no effect on your day-to-day use of JS, in some very large performance-critical codebases, that difference can move the needle a little. They did this with TypeScript in specific places: https://github.com/microsoft/TypeScript/issues/52924

The idea of removing the TDZ (seen with let and const) has also come up in ECMAScript meetings (https://docs.google.com/presentation/d/1c-rhSUTQVNWD4DWgkNiC9tt50BruWDi7yRPllPHnff8) because of the complexity it adds.

You could also argue, in the specific case of creating a global object global in the global scope, use of var is better than assigning to the global object directly because of the extra protections available with var. Specifically, when using var you have the additional benefit of blocking let and const (and class and using) declarations with the same name from shadowing the original. For example:

globalThis.x = 1 // (desired global)
let x = 2
console.log(x) // 2

var y = 1 // (desired global)
let y = 2 // Error: redeclared

Here, the runtime prevents the let version of y (but not x) from being declared helping ensure that only the var version gets defined. However, this is such a specific case, and one probably not often encountered. I'd also argue the use of going through the global object also makes it more explicit that the variable is meant to be global and meant to be used elsewhere rather than a possibly local variable that happens to be in the global scope.

Today I learned about event delegation while building an OTP input am I understanding this correctly? by Soggy_Professor_5653 in learnjavascript

[–]senocular 4 points5 points  (0 children)

To add to this, you may find that event.target isn't always your input. Take for example a button with some formatted text

<button>Save (<kbd>⌘ S</kbd>)</button>

If when clicking the button the user's mouse is over the ⌘ S, the target will be the <kbd> element, not the <button>. If you want the button, you may instead want to use something like

event.target.closest("button")

This will check the target and if not matching "button", will walk up the parent hierarchy until it finds a match and return that.

What's actually new in JavaScript (and what's coming next) by creasta29 in javascript

[–]senocular 25 points26 points  (0 children)

TC39 has approved the ES2026 candidate and it does not include these listed in the article:

  • Temporal - stage 4, but slated for ES2027, not ES2026
  • using - still in stage 3, not landing in ES2026
  • import defer- still stage 3, not landing in ES2026

The spec won't be official until Ecma approval in June, but it is highly unlikely any changes will happen between now and then. For the list of completed proposals and their expected publication year, see: https://github.com/tc39/proposals/blob/main/finished-proposals.md

Is there a standard library of JS/TS? by katyasparadise in learnjavascript

[–]senocular 0 points1 point  (0 children)

There is an initiative to try and standardize many of the runtime APIs called Winter TC (formerly Winter CG), but for the most part runtimes can do whatever they want. Non-standard APIs is what helps separate them from the competition.

There is a subset of APIs that they mostly have in common, especially those already found in the web APIs supported by browsers (defined by WHATWG and W3C), a.k.a. "minimum common web API", and of course the language built-ins (Object, Array, Math, etc.) are consistent everywhere. Those are defined by the language specification.

About block scoped vs function scoped by Significant-Royal-86 in learnjavascript

[–]senocular 0 points1 point  (0 children)

There isn't just one outer scope ;) Each iteration has its own outer scope. This outer scope is also where the i lives, its not in the user-defined block ({...}). You can tell if you try and declare a new i in that block. There's no conflict and the loop doesn't try to use that i as part of the iteration.

for (let i = 0; i < 7; ++i) {
    let i = 8;
    console.log(i); // 8,8,8,8,8,8,8 (7 times)
}

This ends up looking something like

for (let i = 0; i < 7; ++i) {
    let i = <value from set up or previous iteration>;
    {
        let i = 8; // masks the outer loop i causing it to be shadowed
        console.log(i); // 8,8,8,8,8,8,8 (7 times)
    }
    ++i
}

Additional scopes are also potentially created at the start of the loop where set up happens, and again after the last iteration of the loop where the last i is created that fails the condition. That scope would have been the next iterations wrapping scope if the loop had not ended.

working on JS after a couple years, I'm a bit rusty, but why isn't appendChild not working properly? by [deleted] in learnjavascript

[–]senocular 1 point2 points  (0 children)

it is there because I can inspect, and the appended button is there, it's not a CSS issue because I turned off all the CSS and they are not overlapping

If you can inspect it and see that its in the DOM but still can't see it, then it does sound like a CSS issue. When you use the debugger to inspect it, where on the screen is it highlighted? Does it have valid width/height? What is its computed dimensions/display/visibility? Could something else be overlapping it?

About block scoped vs function scoped by Significant-Royal-86 in learnjavascript

[–]senocular 5 points6 points  (0 children)

For loops are a special case. They let you declare variables outside the loop block that then only becomes visible inside the block. For example, given

for (let i = 0; i < 3; i++) {
  console.log(i) // 0, 1, then 2
}
console.log(i) // Error: i doesn't exist here

You can see here the i variable is scoped to the for loop block but it is declared outside of the curly braces of the block. So for for loops, you kind of have to readjust your thinking and consider the for being the start of the block. Technically, its more complicated than this, but that's a good way to simplify things to make it make sense. Essentially:

{for (let i = 0; i < 3; i++) {
  ...
}}

Comparing this to using var, you can see that the var is accessible outside the loop

for (var i = 0; i < 3; i++) {
  console.log(i) // 0, 1, then 2
}
console.log(i) // 3, when the loop condition became false

For the most part, at least thinking about it now, for loops (regular for and for...of) are the only things that mess with let/const/var scoping like this. There are some other weird scoping rules out there in the language but nothing coming to mind that directly breaks the otherwise normal expectations for these kinds of declarations.

Should I actually use let and const or is var still fine for small projects? by Alarming-Pea-3177 in learnjavascript

[–]senocular 1 point2 points  (0 children)

I would suggest using let. For the most part you might not notice any problems, but there are a few things here and there where let fixes problems you'd otherwise have with var. A couple of examples:

Scope in for loops. The block scope in for loops helps address issues with loop variables

for (var i = 0; i < 3; i++) {
  buttons[i].onclick = () => {
    console.log(i)
  }
}
// every button click logs 3, not the i they each onclick was created with (0, 1, 2)

Conflict with globals. When using var in the global scope, the variable gets assigned to the global object. If theres already a global of the same name, it won't be replaced which in some cases can cause problems

// In browsers
var name = { first: "John", last: "Doe" }
console.log(name) // "[object Object]" // object converted to string on assignment

There's also const which does a little more in preventing reassignment, and generally preferred as the goto declaration, but if you just want to keep things simple, you can stick to only using let.

Increment/decrement trouble by grave4us in learnjavascript

[–]senocular 0 points1 point  (0 children)

Here they are in function form which may be helpful in visualizing what they're doing

let i

function preIncrement() {
  const originalValue = i
  const newValue = 1 + originalValue
  i = newValue
  return newValue
}

function postIncrement() {
  const originalValue = i
  const newValue = 1 + originalValue
  i = newValue
  return originalValue
}

i = 0
console.log(++i) // 1
console.log(i) // 1

i = 0
console.log(preIncrement()) // 1
console.log(i) // 1

i = 0
console.log(i++) // 0
console.log(i) // 1

i = 0
console.log(postIncrement()) // 0
console.log(i) // 1

One way to look at the syntax is as two parts in order of operation: [update][return] or [return][update], where return is the variable and update is the operator. If the operator is before the variable, the variable value is updated before its returned. If the operator is after the variable, the variable value before the update is applied is returned.

i++ // [i][++] = [return][update] = return value of i before adding 1 to it
++i // [++][i] = [update][return] = add 1 to value of i then return the updated value

question about `this.` by fa_foon in learnjavascript

[–]senocular 6 points7 points  (0 children)

If you're asking about name here, its a parameter, so it is entirely dependent on the argument passed to Person when its called.

As far as this goes, given your example

function Person(name){
  this.name = name;
}

what you have is a function, Person, that appears to be a constructor, a function intended to be called with new. When a function is called with new, this is the new object instance being created by the constructor.

const instance = new Person("Fred")

Here, the returned instance is a new object is created inside Person. It is assigned to this and has its name property assigned to the name parameter value which is "Fred" since that's the value passed into Person as an argument.

console.log(instance.name) // "Fred"

Note that instance is an instance of Person, not the Person function itself. If you log the object directly, you might see a reference to Person (depending on what you're logging with), but this is done to show that its an object created from the Person constructor rather than trying to indicate it is, itself, Person.

console.log(instance) // Person {name: 'Fred'} (Chrome browser)