Skip to content

Commit 6ee50f3

Browse files
bittobyJosh-Cena
andauthored
fix: Fix misleading lexical claim in setInterval docs to match the actual example (mdn#43465)
* fix: Fix misleading lexical claim in setInterval docs to match the actual example * feat: Add lexical example with contrast and fix incorrect comment in setInterval docs * Rewrite --------- Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
1 parent 3d7c7d4 commit 6ee50f3

File tree

2 files changed

+117
-60
lines changed

2 files changed

+117
-60
lines changed

files/en-us/web/api/window/setinterval/index.md

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -113,37 +113,73 @@ While this pattern does not guarantee execution on a fixed interval, it does gua
113113

114114
### Functions are called with the global `this`
115115

116-
Methods or functions passed to `setInterval()` do not run in the same execution context as `setInterval()`, and hence do not have the same [`this`](/en-US/docs/Web/JavaScript/Reference/Operators/this) as the function that called `setInterval()`.
117-
Instead the called function has a `this` keyword set to the `window` (or `global`) object.
116+
The functions passed to `setInterval()` is run with normal function call semantics for determining the reference of [`this`](/en-US/docs/Web/JavaScript/Reference/Operators/this).
118117
This problem is explained in detail in the [JavaScript reference](/en-US/docs/Web/JavaScript/Reference/Operators/this#callbacks).
119118

120-
The following example demonstrates how this can cause unexpected behavior (using `setTimeout()` instead of `setInterval()`, but the problem applies to both timers):
119+
For non-arrow functions, the `this` context is set to the [`globalThis`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) (an alias for [`window`](/en-US/docs/Web/API/Window/window) in browsers) object.
121120

122-
```js
123-
myArray = ["zero", "one", "two"];
124-
125-
myArray.myMethod = function (sProperty) {
126-
alert(arguments.length > 0 ? this[sProperty] : this);
127-
};
128-
129-
myArray.myMethod(); // prints "zero,one,two"
130-
myArray.myMethod(1); // prints "one"
131-
setTimeout(myArray.myMethod, 1000); // Alerts "[object Window]" after 1 second
132-
setTimeout(myArray.myMethod, 1500, "1"); // Alerts "undefined" after 1.5 seconds
133-
```
121+
The following example demonstrates how this can cause unexpected behavior. Here, when we pass the method `counter.count` directly to `setInterval()`, the `this` context is lost, and the method is called on the global object instead of the `Counter` instance, resulting in a `TypeError` when the `count` method tries to access `this`:
134122

135-
You can use [arrow functions](/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) to adopt the `this` of the function in which `setTimeout()` is called (arrow functions have a lexical `this`).
123+
```js
124+
class Counter {
125+
constructor() {
126+
this.data = new Map();
127+
}
128+
129+
count(item) {
130+
this.data.set(item, (this.data.get(item) || 0) + 1);
131+
}
132+
}
136133

137-
You can test this with the following code.
134+
const counter = new Counter();
138135

139-
```js
140-
setTimeout(() => myArray.myMethod(), 1000); // Alert "zero,one,two" after 1 second
141-
setTimeout(() => myArray.myMethod(1), 1500); // Alert "one" after 1.5 seconds
142-
setTimeout(() => myArray.myMethod(2), 3000); // Alert "one" after 3 seconds
136+
counter.count("foo"); // Successfully adds "foo" to the map
137+
setInterval(counter.count, 1000, "bar");
138+
// TypeError: Cannot read properties of undefined (reading 'set')
143139
```
144140

145-
You might also use the [`Function.prototype.bind()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) method, which lets you specify the value that should be used as `this` for all calls to a given function.
146-
That lets you bypass problems where it's unclear what `this` will be, depending on the context from which your function was called.
141+
To work around this, you must make sure that the function passed to `setInterval` has the correct `this` context. There are three main ways to do this:
142+
143+
1. If you want to explicitly specify the `this` context, instead of passing the method directly, wrap the method call in another anonymous function that explicitly calls the method with the correct context:
144+
145+
```js
146+
setInterval(() => counter.count("bar"), 1000);
147+
setInterval(function () {
148+
counter.count("bar");
149+
}, 1000);
150+
```
151+
152+
2. If you want to use the `this` context of the code that calls `setInterval()`, always use an arrow function, which inherits the `this` context of its enclosing scope:
153+
154+
```js example-bad
155+
class Counter {
156+
//
157+
repeatedCount(item) {
158+
// BAD: the `this` context is lost in the callback
159+
setInterval(function () {
160+
this.data.set(item, (this.data.get(item) || 0) + 1);
161+
}, 1000);
162+
}
163+
}
164+
```
165+
166+
```js example-good
167+
class Counter {
168+
//
169+
repeatedCount(item) {
170+
// GOOD: the arrow function inherits the `this` context of `repeatedCount()`
171+
setInterval(() => {
172+
this.data.set(item, (this.data.get(item) || 0) + 1);
173+
}, 1000);
174+
}
175+
}
176+
```
177+
178+
3. If you want to avoid extra function wrappers (which increase memory usage) while explicitly specifying the `this` context, you can use the [`Function.prototype.bind()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) method to create a new function with the correct `this` context:
179+
180+
```js
181+
setInterval(counter.count.bind(counter), 1000, "bar");
182+
```
147183

148184
### Security considerations
149185

files/en-us/web/api/window/settimeout/index.md

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -111,53 +111,74 @@ If the order that an asynchronous method completes does matter, then you can use
111111

112112
### Functions are called with the global `this`
113113

114-
Methods or functions passed to `setTimeout()` do not run in the same execution context as `setTimeout()`, and hence do not have the same [`this`](/en-US/docs/Web/JavaScript/Reference/Operators/this) as the function that called `setTimeout()`.
115-
Instead the called function has a `this` keyword set to the `window` (or `global`) object.
114+
The functions passed to `setTimeout()` is run with normal function call semantics for determining the reference of [`this`](/en-US/docs/Web/JavaScript/Reference/Operators/this).
116115
This problem is explained in detail in the [JavaScript reference](/en-US/docs/Web/JavaScript/Reference/Operators/this#callbacks).
117116

118-
The following example demonstrates how this can cause unexpected behavior:
117+
For non-arrow functions, the `this` context is set to the [`globalThis`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) (an alias for [`window`](/en-US/docs/Web/API/Window/window) in browsers) object.
119118

120-
```js
121-
const myObject = {
122-
log() {
123-
console.log(`myProperty: ${this.myProperty}`);
124-
},
125-
myProperty: 12,
126-
};
127-
128-
myObject.log(); // myProperty: 12
129-
setTimeout(myObject.log, 1000); // myProperty: undefined
130-
```
131-
132-
You can use a wrapper function, such as an [arrow function](/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), to adopt the `this` of the function in which `setTimeout()` is called (arrow functions have a lexical `this`).
133-
134-
You can test this with the following code:
119+
The following example demonstrates how this can cause unexpected behavior. Here, when we pass the method `counter.count` directly to `setTimeout()`, the `this` context is lost, and the method is called on the global object instead of the `Counter` instance, resulting in a `TypeError` when the `count` method tries to access `this`:
135120

136121
```js
137-
// Arrow function callback
138-
setTimeout(() => myObject.log(), 2000); // myProperty: 12 after 2 seconds
122+
class Counter {
123+
constructor() {
124+
this.data = new Map();
125+
}
139126

140-
// Anonymous function callback
141-
setTimeout(function () {
142-
myObject.log();
143-
}, 3000); // myProperty: 12 after 3 seconds
144-
```
127+
count(item) {
128+
this.data.set(item, (this.data.get(item) || 0) + 1);
129+
}
130+
}
145131

146-
You might also use the [`Function.prototype.bind()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) method, which lets you specify the value that should be used as `this` for all calls to a given function.
147-
That lets you bypass problems where it's unclear what `this` will be, depending on the context from which your function was called:
132+
const counter = new Counter();
148133

149-
```js
150-
const myArray = ["zero", "one", "two"];
151-
const myBoundMethod = function (sProperty) {
152-
console.log(arguments.length > 0 ? this[sProperty] : this);
153-
}.bind(myArray);
154-
155-
myBoundMethod(); // prints "zero,one,two" because 'this' is bound to myArray in the function
156-
myBoundMethod(1); // prints "one"
157-
setTimeout(myBoundMethod, 1.0 * 1000); // still prints "zero,one,two" after 1 second because of the binding
158-
setTimeout(myBoundMethod, 1.5 * 1000, "1"); // prints "one" after 1.5 seconds
134+
counter.count("foo"); // Successfully adds "foo" to the map
135+
setTimeout(counter.count, 1000, "bar");
136+
// TypeError: Cannot read properties of undefined (reading 'set')
159137
```
160138

139+
To work around this, you must make sure that the function passed to `setTimeout` has the correct `this` context. There are three main ways to do this:
140+
141+
1. If you want to explicitly specify the `this` context, instead of passing the method directly, wrap the method call in another anonymous function that explicitly calls the method with the correct context:
142+
143+
```js
144+
setTimeout(() => counter.count("bar"), 1000);
145+
setTimeout(function () {
146+
counter.count("bar");
147+
}, 1000);
148+
```
149+
150+
2. If you want to use the `this` context of the code that calls `setTimeout()`, always use an arrow function, which inherits the `this` context of its enclosing scope:
151+
152+
```js example-bad
153+
class Counter {
154+
//
155+
delayedCount(item) {
156+
// BAD: the `this` context is lost in the callback
157+
setTimeout(function () {
158+
this.data.set(item, (this.data.get(item) || 0) + 1);
159+
}, 1000);
160+
}
161+
}
162+
```
163+
164+
```js example-good
165+
class Counter {
166+
//
167+
delayedCount(item) {
168+
// GOOD: the arrow function inherits the `this` context of `delayedCount()`
169+
setTimeout(() => {
170+
this.data.set(item, (this.data.get(item) || 0) + 1);
171+
}, 1000);
172+
}
173+
}
174+
```
175+
176+
3. If you want to avoid extra function wrappers (which increase memory usage) while explicitly specifying the `this` context, you can use the [`Function.prototype.bind()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) method to create a new function with the correct `this` context:
177+
178+
```js
179+
setTimeout(counter.count.bind(counter), 1000, "bar");
180+
```
181+
161182
### Non-number delay values are silently coerced into numbers
162183

163184
If `setTimeout()` is called with [_delay_](#delay) value that's not a number, implicit [type coercion](/en-US/docs/Glossary/Type_coercion) is silently done on the value to convert it to a number.

0 commit comments

Comments
 (0)