Skip to content

Commit 1c0bebe

Browse files
committed
ok
1 parent bef864a commit 1c0bebe

File tree

23 files changed

+424
-347
lines changed

23 files changed

+424
-347
lines changed

1-js/3-object-basics/1-object/article.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,22 +235,16 @@ let obj = {
235235
alert( obj.for + obj.let + obj.return ); // 6
236236
```
237237
238-
Basically, any name is allowed, with one exclusion: `__proto__`.
239-
240-
The built-in property named `__proto__` has a special functionality (we'll cover it later), and it can't be set to a non-object value:
238+
Basically, any name is allowed, but there's a special one: `"__proto__"`. We can't set it to a non-object value:
241239
242240
```js run
243-
let obj = {};
244-
obj.__proto__ = 5;
241+
let obj = { __proto__: 5 };
245242
alert(obj.__proto__); // [object Object], didn't work as intended
246243
```
247244
248-
As you we see from the code, an assignment to a primitive is ignored. If we want to store *arbitrary* (user-provided) keys, then such behavior can be the source of bugs and even vulnerabilities, because it's unexpected. There's another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in the chapter <info:map-set-weakmap-weakset>, which supports arbitrary keys.
245+
Later we'll learn more about that `__proto__` and see how to work around it [todo Object.create(null)]. Also we'll learn another data structure [Map](info:map-set-weakmap-weakset) that doesn't have such problems and supports arbitrary keys.
249246
````
250247

251-
252-
253-
254248
## Existance check
255249

256250
A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined:

1-js/6-data-structures/3-string/article.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,9 @@ Let's recap the methods to avoid any confusion:
451451

452452

453453
```smart header="Which one to choose?"
454-
All of them can do the job. The author finds himself using `slice` almost all the time.
454+
All of them can do the job. Formally, `substr` has a minor drawback: it is described not in the core Javascript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere.
455+
456+
The author finds himself using `slice` almost all the time.
455457
```
456458

457459
## Comparing strings
@@ -526,7 +528,7 @@ The characters are compared by their numeric code. The greater code means that t
526528

527529
### Correct comparisons
528530

529-
The "right" algorithm to do string comparisons is more complex than it may seem. Because the alphabets are different for different languages. So the same letter may be located differently in different alphabets, that is -- even if it looks the same, different alphabets put it in different place.
531+
The "right" algorithm to do string comparisons is more complex than it may seem. Because alphabets are different for different languages. The same-looking letter may be located differently in different alphabets.
530532

531533
So, the browser needs to know the language to compare.
532534

1-js/7-deeper/2-closure/article.md

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,129 @@ There are other ways to do that:
420420
}();
421421
```
422422

423+
## Garbage collection
424+
425+
Lexical Environment objects that we've been talking about follow same garbage collection rules as regular ones.
426+
427+
So, a Lexical Environment exists while there's a nested function referencing it with its `[[Environment]]`.
428+
429+
- Usually, Lexical Environment is cleaned up after the function run. Even if it has a nested function, for instance:
430+
431+
```js
432+
function f() {
433+
let value = 123;
434+
435+
function g() {} // g is local
436+
}
437+
438+
f();
439+
```
440+
441+
Here both `value` and `g` become unreachable after the end of `f()`, and so even though `g` references its outer lexical environment, that doesn't matter.
442+
443+
- But if `g` were returned and kept reachable, then that its reference keeps the outer lexical environment alive as well:
444+
445+
```js
446+
function f() {
447+
let value = 123;
448+
449+
function g() {}
450+
451+
*!*
452+
return g;
453+
*/!*
454+
}
455+
456+
let g = f(); // function g will live and keep the outer lexical environment in memory
457+
```
458+
459+
- If `f()` is called many times, and resulting functions are saved, then the corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
460+
461+
```js
462+
function f() {
463+
var value = Math.random();
464+
465+
return function() {};
466+
}
467+
468+
// 3 functions in array, every of them links to LexicalEnvrironment
469+
// from the corresponding f() run
470+
let arr = [f(), f(), f()];
471+
```
472+
- A Lexical Environment object lives until it is reachable. In the code below, after `g` becomes unreachable, it dies with it:
473+
474+
```js
475+
function f() {
476+
var value = 123;
477+
478+
function g() {}
479+
480+
return g;
481+
}
482+
483+
let g = f(); // while g is alive
484+
// there corresponding Lexical Environment lives
485+
486+
g = null; // ...and now the memory is cleaned up
487+
```
488+
489+
### Real-life optimizations
490+
491+
As we've seen, in theory while a function is alive, all outer variabels are also retained.
492+
493+
But in practice, JS engines try to optimize that. They analyze variable usage and if it's easy to see that an outer variable is not used -- it is removed.
494+
495+
In the code above `value` is not used in `g`. So it will be cleaned up from the memory.
496+
497+
**Important side effect in V8 (Chrome, Opera) is that such variable will become unavailable in debugging!**
498+
499+
Try running the example below with the open Developer Tools in Chrome.
500+
501+
When it pauses, in console type `alert(value)`.
502+
503+
```js run
504+
function f() {
505+
var value = Math.random();
506+
507+
function g() {
508+
debugger; // in console: type alert( value ); No such variable!
509+
}
510+
511+
return g;
512+
}
513+
514+
var g = f();
515+
g();
516+
```
517+
518+
As you could see -- there is no such variable! The engine decided that we won't need it and removed it.
519+
520+
That may lead to funny (if not such time-consuming) debugging issues, especially when we can see instead of *our* variable the more outer one:
521+
522+
```js run global
523+
let value = "Surprise!";
524+
525+
function f() {
526+
let value = "the closest value";
527+
528+
function g() {
529+
debugger; // in console: type alert( value ); Surprise!
530+
}
531+
532+
return g;
533+
}
534+
535+
let g = f();
536+
g();
537+
```
538+
539+
```warn header="See ya!"
540+
This feature of V8 is good to know. If you are debugging with Chrome/Opera, sooner or later you will meet it.
541+
542+
That is not a bug of debugger, but a special feature of V8. Maybe it will be changed sometimes.
543+
You always can check for it by running examples on this page.
544+
```
545+
423546

424547
## The old "var"
425548

@@ -586,7 +709,6 @@ The features described above make using `var` inconvenient most of time. First,
586709

587710

588711

589-
590712
## Global object
591713

592714
A *global object* is the object that provides access to built-in functions and values, defined by the specification and the environment.

1-js/7-deeper/3-function-object/article.md

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,11 @@ alert( counter() ); // 0
170170
alert( counter() ); // 1
171171
```
172172

173-
Unlike the closures, the `count` is now bound to the function directly, not to its outer Lexical Environment.
173+
Unlike examples from chapter <info:closure>, the `count` is now stored in the function directly, not in its outer Lexical Environment.
174174

175175
Is it worse or better than using the closure?
176176

177-
The main difference is that if the value of `count` lives in an outer variable, then an external code is unable to access it. Only the nested function may modify it. And if it's bound to function, then such thing is possible:
177+
The main difference is that if the value of `count` lives in an outer variable, then an external code is unable to access it. Only nested functions may modify it. And if it's bound to function, then such thing is possible:
178178

179179
```js run
180180
function makeCounter() {
@@ -196,108 +196,136 @@ alert( counter() ); // 10
196196
*/!*
197197
```
198198

199-
Sometimes such possibility can be a plus, but usually we want more control over `count`, and hence the closure is more often used.
199+
So it depends on our aims which variant to choose.
200200

201201
## Named Function Expression
202202

203-
Compare these two function definitions:
203+
Named Function Expression or, shortly, NFE, is a term a Function Expression that has a name.
204+
205+
For instance, let's take an ordinary Function Expression:
204206

205207
```js
206-
let sayHi = function() { // (1)
207-
alert('Hello');
208+
let sayHi = function(who) {
209+
alert(`Hello, ${who}`);
208210
};
211+
```
212+
213+
...And add a name to it:
209214

210-
let sayHi = function *!*func*/!*() { // (2)
211-
alert('Hello');
215+
```js
216+
let sayHi = function *!*func*/!*(who) {
217+
alert(`Hello, ${who}`);
212218
};
213219
```
214220

215-
Both create a function and put it into the variable `sayHi`. And usually we use the first variant is fine.
221+
What's the role of that additional `"func"` name?
216222

217-
But if we specify a name right in the Function Expression (2), then it becomes an "internal function name", only visible from inside the function.
223+
First let's note, that we still have a Function Expression. Adding the name `"func"` after `function` did not make it a Function Declaration, because it is still created as a part of an assignment expression.
218224

219-
Let's see why we may need it.
225+
Adding such a name also did not break anything.
220226

221-
As we've seen it's easy to copy a function and maybe replace the previous value with something else:
227+
The function is still available as `sayHi()`:
222228

223-
```js run
224-
let sayHi = function() {
225-
alert('Hello');
229+
```js run
230+
let sayHi = function *!*func*/!*(who) {
231+
alert(`Hello, ${who}`);
226232
};
227233

228-
// oh maybe another word is better? replace it!
229-
let oldSayHi = sayHi; // keep the old variant here
230-
sayHi = function() { // replace with a newer one
231-
alert("What's up dude?");
234+
sayHi("John"); // Hello, John
235+
```
236+
237+
There are two special things about the name `func`:
238+
239+
1. It allows to reference the function from inside itself.
240+
2. It is not visible outside of the function.
241+
242+
For instance, the function `sayHi` below re-calls itself with `"Guest"` if no `who` is provided:
243+
244+
```js run
245+
let sayHi = function *!*func*/!*(who) {
246+
if (who) {
247+
alert(`Hello, ${who}`);
248+
} else {
249+
*!*
250+
func("Guest"); // use func to re-call itself
251+
*/!*
252+
}
232253
};
233254

255+
sayHi(); // Hello, Guest
234256

235-
oldSayHi(); // Hello
236-
sayHi(); // What's up dude?
257+
// But this won't work:
258+
func(); // Error, func is not defined (not visible outside of the function)
237259
```
238260

239-
The problem may occur if a function references *itself* from inside. It happens when the function wants to access its properties (`sayHi.counter` in the example above), or it wants to recursively call itself one more time.
261+
Why do we use `func`? Maybe just use `sayHi` for the nested call?
240262

241-
But if the function has moved, then the old name becomes irrelevant! There will be an error.
242263

243-
Here's the code:
264+
Actually, in most cases we can:
244265

245-
```js run
246-
// create a function
247-
let sayHi = function() {
248-
sayHi.counter++;
249-
alert('Hi ' + sayHi.counter);
266+
```js
267+
let sayHi = function(who) {
268+
if (who) {
269+
alert(`Hello, ${who}`);
270+
} else {
271+
*!*
272+
sayHi("Guest");
273+
*/!*
274+
}
250275
};
251-
sayHi.counter = 0;
276+
```
252277

253-
// move it
254-
let movedSayHi = sayHi;
255-
256-
// overwrite the old name to make things more obvious
257-
sayHi = null;
278+
The problem with that code is that the value of `sayHi` may change. The function may go to another variable, and the code will start to give errors:
258279

280+
```js run
281+
let sayHi = function(who) {
282+
if (who) {
283+
alert(`Hello, ${who}`);
284+
} else {
259285
*!*
260-
movedSayHi(); // Error: Cannot read property 'counter' of null
286+
sayHi("Guest"); // Error: sayHi is not a function
261287
*/!*
288+
}
289+
};
290+
291+
let welcome = sayHi;
292+
sayHi = null;
293+
294+
welcome(); // Error, the nested sayHi call doesn't work any more!
262295
```
263296

264-
The optional name which we can put into the Function Expression is exactly meant to solve this kind of problems.
297+
That happens because the function takes `sayHi` from its outer lexical environment. There's no local `sayHi`, so the outer variable is used. And at the moment of the call that outer `sayHi` is `null`.
265298

266-
- It is only visible from inside the function.
267-
- It always references the current function.
299+
The optional name which we can put into the Function Expression is exactly meant to solve this kind of problems.
268300

269301
Let's use it to fix the code:
270302

271303
```js run
272-
// now with the internal name "say"
273-
let sayHi = function *!*say*/!*() {
274-
*!*say*/!*.counter++;
275-
alert('Hi ' + *!*say*/!*.counter); // and use it everywhere inside
304+
let sayHi = function *!*func*/!*(who) {
305+
if (who) {
306+
alert(`Hello, ${who}`);
307+
} else {
308+
*!*
309+
func("Guest"); // Now all fine
310+
*/!*
311+
}
276312
};
277-
sayHi.counter = 0;
278-
279-
let movedSayHi = sayHi;
280313

314+
let welcome = sayHi;
281315
sayHi = null;
282316

283-
movedSayHi(); // Hi 1
284-
movedSayHi(); // Hi 2 (works)
285-
286-
alert(say); // Error (say is undefined, that's normal)
317+
welcome(); // Hello, Guest (nested call works)
287318
```
288319

289-
Please note that:
290-
291-
- The name `say` exists only inside the function. The last line demonstrates that.
292-
- The name `say` inside the function is always the current function, no matter in which variable it is. That's why it works.
320+
Now it works, because the name `"func"` is function-local. It is not taken from outside (and not visible there). The specification guarantees that it always references the current function.
293321

294-
So the outer code has it's variable `sayHi` or `movedSayHi` later to call the function. The `say` is an "internal function name", how it calls itself privately.
295-
296-
A Function Expression with a name is called *Named Function Expression*, often abbreviated as NFE.
322+
The outer code still has it's variable `sayHi` or `welcome` later. And `func` is an "internal function name", how it calls itself privately.
297323

324+
```smart header="No such thing for Function Declaration"
298325
The "internal name" feature described here is only available for Function Expressions, not to Function Declarations. For Function Declarations, there's just no syntax possibility to add a one more "internal" name for them.
299326
300327
Sometimes, when we need a reliable internal name, it's the reason to rewrite a Function Declaration to Named Function Expression form.
328+
```
301329

302330
## Summary
303331

0 commit comments

Comments
 (0)