This commit is contained in:
Ilya Kantor 2019-08-13 22:07:33 +03:00
parent a2daa0191f
commit 4bc42e1360
3 changed files with 43 additions and 34 deletions

View file

@ -6,6 +6,6 @@ importance: 4
Create a calculator that prompts for an arithmetic expression and returns its result.
There's no need to check the expression for correctness in this task.
There's no need to check the expression for correctness in this task. Just evaluate and return the result.
[demo]

View file

@ -1,6 +1,6 @@
# Eval: run a code string
The built-in `eval` function allows to execute a string of `code`.;
The built-in `eval` function allows to execute a string of code.
The syntax is:
@ -15,7 +15,9 @@ let code = 'alert("Hello")';
eval(code); // Hello
```
A call to `eval` returns the result of the last statement.
A string of code may be long, contain line breaks, function declarations, variables and so on.
The result of `eval` is the result of the last statement.
For example:
```js run
@ -23,7 +25,12 @@ let value = eval('1+1');
alert(value); // 2
```
The code is executed in the current lexical environment, so it can see outer variables:
```js run
let value = eval('let i = 0; ++i');
alert(value); // 1
```
The eval'ed code is executed in the current lexical environment, so it can see outer variables:
```js run no-beautify
let a = 1;
@ -68,13 +75,13 @@ The reason is simple: long, long time ago JavaScript was a much weaker language,
Right now, there's almost no reason to use `eval`. If someone is using it, there's a good chance they can replace it with a modern language construct or a [JavaScript Module](info:modules).
Still, if you're sure you need to dynamically `eval` a string of code, please note that its ability to access outer variables has side-effects.
Please note that its ability to access outer variables has side-effects.
Code minifiers (tools used before JS gets to production, to compress it) replace local variables with shorter ones for brewity. That's usually safe, but not if `eval` is used, as it may reference them. So minifiers don't replace all local variables that might be visible from `eval`. That negatively affects code compression ratio.
Code minifiers (tools used before JS gets to production, to compress it) replace local variables with shorter ones for optimization. That's usually safe, but not if `eval` is used, as it may reference them. So minifiers don't replace all local variables that might be visible from `eval`. That negatively affects code compression ratio.
Using outer local variables inside `eval` is a bad programming practice, as it makes maintaining the code more difficult.
There are two ways how to evade any eval-related problems.
There are two ways how to be totally safe from such problems.
**If eval'ed code doesn't use outer variables, please call `eval` as `window.eval(...)`:**
@ -88,7 +95,7 @@ let x = 1;
}
```
**If your code needs local variables, execute it with `new Function` and pass them as arguments:**
**If eval'ed code needs local variables, change `eval` to `new Function` and pass them as arguments:**
```js run
let f = new Function('a', 'alert(a)');

View file

@ -2,9 +2,9 @@
We can not only assign handlers, but also generate events from JavaScript.
Custom events can be used to create "graphical components". For instance, a root element of the menu may trigger events telling what happens with the menu: `open` (menu open), `select` (an item is selected) and so on.
Custom events can be used to create "graphical components". For instance, a root element of our own JS-based menu may trigger events telling what happens with the menu: `open` (menu open), `select` (an item is selected) and so on. Another code may listen to the events and observe what's happening with the menu.
Also we can generate built-in events like `click`, `mousedown` etc, that may be good for testing.
We can generate not only completely new events, that we invent for our own purposes, but also built-in ones, such as `click`, `mousedown` etc. That may be helpful for automated testing.
## Event constructor
@ -27,9 +27,9 @@ Arguments:
## dispatchEvent
After an event object is created, we should "run" it on an element using the call `elem.dispatchEvent(event)`.
After an event object is created, we should "run" it on an element using the call `elem.dispatchEvent(event)`.
Then handlers react on it as if it were a regular built-in event. If the event was created with the `bubbles` flag, then it bubbles.
Then handlers react on it as if it were a regular browser event. If the event was created with the `bubbles` flag, then it bubbles.
In the example below the `click` event is initiated in JavaScript. The handler works same way as if the button was clicked:
@ -129,11 +129,11 @@ alert(event.clientX); // undefined, the unknown property is ignored!
Technically, we can work around that by assigning directly `event.clientX=100` after creation. So that's a matter of convenience and following the rules. Browser-generated events always have the right type.
The full list of properties for different UI events is in the specification, for instance [MouseEvent](https://www.w3.org/TR/uievents/#mouseevent).
The full list of properties for different UI events is in the specification, for instance, [MouseEvent](https://www.w3.org/TR/uievents/#mouseevent).
## Custom events
For our own, custom events like `"hello"` we should use `new CustomEvent`. Technically [CustomEvent](https://dom.spec.whatwg.org/#customevent) is the same as `Event`, with one exception.
For our own, completely new events types like `"hello"` we should use `new CustomEvent`. Technically [CustomEvent](https://dom.spec.whatwg.org/#customevent) is the same as `Event`, with one exception.
In the second argument (object) we can add an additional property `detail` for any custom information that we want to pass with the event.
@ -158,23 +158,25 @@ For instance:
The `detail` property can have any data. Technically we could live without, because we can assign any properties into a regular `new Event` object after its creation. But `CustomEvent` provides the special `detail` field for it to evade conflicts with other event properties.
The event class tells something about "what kind of event" it is, and if the event is custom, then we should use `CustomEvent` just to be clear about what it is.
Besides, the event class describes "what kind of event" it is, and if the event is custom, then we should use `CustomEvent` just to be clear about what it is.
## event.preventDefault()
We can call `event.preventDefault()` on a script-generated event if `cancelable:true` flag is specified.
Many browser events have a "default action", such as nagivating to a link, starting a selection, and so on.
Of course, for custom events, with names unknown for the browser, there are no "default browser actions". But our code may plan its own actions after `dispatchEvent`.
For new, custom events, there are definitely no default browser actions, but a code that dispatches such event may have its own plans what to do after triggering the event.
The call of `event.preventDefault()` is a way for the handler to send a signal that those actions should be canceled.
By calling `event.preventDefault()`, an event handler may send a signal that those actions should be canceled.
In that case the call to `elem.dispatchEvent(event)` returns `false`. And the event-generating code knows that the processing shouldn't continue.
In that case the call to `elem.dispatchEvent(event)` returns `false`. And the code that dispatched it knows that it shouldn't continue.
For instance, in the example below there's a `hide()` function. It generates the `"hide"` event on the element `#rabbit`, notifying all interested parties that the rabbit is going to hide.
Let's see a practical example - a hiding rabbit (could be a closing menu or something else).
A handler set by `rabbit.addEventListener('hide',...)` will learn about that and, if it wants, can prevent that action by calling `event.preventDefault()`. Then the rabbit won't hide:
Below you can see a `#rabbit` and `hide()` function that dispatches `"hide"` event on it, to let all interested parties know that the rabbit is going to hide.
```html run refresh
Any handler can listen to that event with `rabbit.addEventListener('hide',...)` and, if needed, cancel the action using `event.preventDefault()`. Then the rabbit won't disappear:
```html run refresh autorun
<pre id="rabbit">
|\ /|
\|_|/
@ -182,6 +184,7 @@ A handler set by `rabbit.addEventListener('hide',...)` will learn about that and
=\_Y_/=
{>o<}
</pre>
<button onclick="hide()">Hide()</button>
<script>
// hide() will be called automatically in 2 seconds
@ -190,7 +193,7 @@ A handler set by `rabbit.addEventListener('hide',...)` will learn about that and
cancelable: true // without that flag preventDefault doesn't work
});
if (!rabbit.dispatchEvent(event)) {
alert('the action was prevented by a handler');
alert('The action was prevented by a handler');
} else {
rabbit.hidden = true;
}
@ -201,13 +204,10 @@ A handler set by `rabbit.addEventListener('hide',...)` will learn about that and
event.preventDefault();
}
});
// hide in 2 seconds
setTimeout(hide, 2000);
</script>
```
Обратите внимание: событие должно иметь флаг `cancelable: true`, иначе вызов `event.preventDefault()` будет проигнорирован.
## Events-in-events are synchronous
@ -219,11 +219,10 @@ Then the control jumps to the nested event handler, and after it goes back.
For instance, here the nested `menu-open` event is processed synchronously, during the `onclick`:
```html run
```html run autorun
<button id="menu">Menu (click me)</button>
<script>
// 1 -> nested -> 2
menu.onclick = function() {
alert(1);
@ -239,17 +238,18 @@ For instance, here the nested `menu-open` event is processed synchronously, duri
</script>
```
Please note that the nested event `menu-open` bubbles up and is handled on the `document`. The propagation of the nested event is fully finished before the processing gets back to the outer code (`onclick`).
The output order is: 1 -> nested -> 2.
Please note that the nested event `menu-open` fully bubbles up and is handled on the `document`. The propagation and handling of the nested event must be fully finished before the processing gets back to the outer code (`onclick`).
That's not only about `dispatchEvent`, there are other cases. JavaScript in an event handler can call methods that lead to other events -- they are too processed synchronously.
If we don't like it, we can either put the `dispatchEvent` (or other event-triggering call) at the end of `onclick` or wrap it in zero-delay `setTimeout`:
If we don't like it, we can either put the `dispatchEvent` (or other event-triggering call) at the end of `onclick` or, maybe better, wrap it in zero-delay `setTimeout`:
```html run
<button id="menu">Menu (click me)</button>
<script>
// Now the result is: 1 -> 2 -> nested
menu.onclick = function() {
alert(1);
@ -267,13 +267,15 @@ If we don't like it, we can either put the `dispatchEvent` (or other event-trigg
Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `mouse.onclick`, so event handlers are totally separate.
The output order becomes: 1 -> 2 -> nested.
## Summary
To generate an event from code, we first need to create an event object.
The generic `Event(name, options)` constructor accepts an arbitrary event name and the `options` object with two properties:
- `bubbles: true` if the event should bubble.
- `cancelable: true` if the `event.preventDefault()` should work.
- `bubbles: true` if the event should bubble.
- `cancelable: true` if the `event.preventDefault()` should work.
Other constructors of native events like `MouseEvent`, `KeyboardEvent` and so on accept properties specific to that event type. For instance, `clientX` for mouse events.