up
This commit is contained in:
parent
ab9ab64bd5
commit
97c8f22bbb
289 changed files with 195 additions and 172 deletions
|
@ -0,0 +1,13 @@
|
|||
function makeCounter() {
|
||||
let count = 0;
|
||||
|
||||
function counter() {
|
||||
return count++;
|
||||
}
|
||||
|
||||
counter.set = value => count = value;
|
||||
|
||||
counter.decrease = () => count--;
|
||||
|
||||
return counter;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
function makeCounter() {
|
||||
let count = 0;
|
||||
|
||||
// ... your code ...
|
||||
}
|
||||
|
||||
let counter = makeCounter();
|
||||
|
||||
alert( counter() ); // 0
|
||||
alert( counter() ); // 1
|
||||
|
||||
counter.set(10); // set the new count
|
||||
|
||||
alert( counter() ); // 10
|
||||
|
||||
counter.decrease(); // decrease the count by 1
|
||||
|
||||
alert( counter() ); // 10 (instead of 11)
|
|
@ -0,0 +1,41 @@
|
|||
describe("counter", function() {
|
||||
|
||||
it("increases from call to call", function() {
|
||||
|
||||
let counter = makeCounter();
|
||||
|
||||
assert.equal( counter(), 0 );
|
||||
assert.equal( counter(), 1 );
|
||||
assert.equal( counter(), 2 );
|
||||
});
|
||||
|
||||
|
||||
describe("counter.set", function() {
|
||||
it("sets the count", function() {
|
||||
|
||||
let counter = makeCounter();
|
||||
|
||||
counter.set(10);
|
||||
|
||||
assert.equal( counter(), 10 );
|
||||
assert.equal( counter(), 11 );
|
||||
});
|
||||
});
|
||||
|
||||
describe("counter.decrease", function() {
|
||||
it("decreases the count", function() {
|
||||
|
||||
let counter = makeCounter();
|
||||
|
||||
counter.set(10);
|
||||
|
||||
assert.equal( counter(), 10 );
|
||||
|
||||
counter.decrease();
|
||||
|
||||
assert.equal( counter(), 10 );
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
The solution uses `count` in the local variable, but addition methods are written right into the `counter`. They share the same outer lexical environment and also can access the current `count`.
|
|
@ -0,0 +1,15 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Set and decrease for counter
|
||||
|
||||
Modify the code of `makeCounter()` so that the counter can also decrease and set the number:
|
||||
|
||||
- `counter()` should return the next number (as before).
|
||||
- `counter.set(value)` should set the `count` to `value`.
|
||||
- `counter.decrease(value)` should decrease the `count` by 1.
|
||||
|
||||
See the sandbox code for the complete usage example.
|
||||
|
||||
P.S. You can use either a closure or the function property to keep the current count. Or write both variants.
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
1. For the whole thing to work *anyhow*, the result of `sum` must be function.
|
||||
2. That function must keep in memory the current value between calls.
|
||||
3. According to the task, the function must become the number when used in `==`. Functions are objects, so the conversion happens as described in the chapter <info:object-toprimitive>, and we can provide our own method that returns the number.
|
||||
|
||||
Now the code:
|
||||
|
||||
```js run
|
||||
function sum(a) {
|
||||
|
||||
let currentSum = a;
|
||||
|
||||
function f(b) {
|
||||
currentSum += b;
|
||||
return f;
|
||||
}
|
||||
|
||||
f.toString = function() {
|
||||
return currentSum;
|
||||
};
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
alert( sum(1)(2) ); // 3
|
||||
alert( sum(5)(-1)(2) ); // 6
|
||||
alert( sum(6)(-1)(-2)(-3) ); // 0
|
||||
alert( sum(0)(1)(2)(3)(4)(5) ); // 15
|
||||
```
|
||||
|
||||
Please note that the `sum` function actually works only once. It returns function `f`.
|
||||
|
||||
Then, on each subsequent call, `f` adds its parameter to the sum `currentSum`, and returns itself.
|
||||
|
||||
**There is no recursion in the last line of `f`.**
|
||||
|
||||
Here is what recursion looks like:
|
||||
|
||||
```js
|
||||
function f(b) {
|
||||
currentSum += b;
|
||||
return f(); // <-- recursive call
|
||||
}
|
||||
```
|
||||
|
||||
And in our case, we just return the function, without calling it:
|
||||
|
||||
```js
|
||||
function f(b) {
|
||||
currentSum += b;
|
||||
return f; // <-- does not call itself, returns itself
|
||||
}
|
||||
```
|
||||
|
||||
This `f` will be used in the next call, again return itself, so many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion.
|
|
@ -0,0 +1,17 @@
|
|||
importance: 2
|
||||
|
||||
---
|
||||
|
||||
# Sum with an arbitrary amount of brackets
|
||||
|
||||
Write function `sum` that would work like this:
|
||||
|
||||
```js
|
||||
sum(1)(2) == 3; // 1 + 2
|
||||
sum(1)(2)(3) == 6; // 1 + 2 + 3
|
||||
sum(5)(-1)(2) == 6
|
||||
sum(6)(-1)(-2)(-3) == 0
|
||||
sum(0)(1)(2)(3)(4)(5) == 15
|
||||
```
|
||||
|
||||
P.S. Hint: you may need to setup custom object to primitive conversion for your function.
|
354
1-js/06-advanced-functions/06-function-object/article.md
Normal file
354
1-js/06-advanced-functions/06-function-object/article.md
Normal file
|
@ -0,0 +1,354 @@
|
|||
|
||||
# Function object, NFE
|
||||
|
||||
As we already know, functions in JavaScript are values.
|
||||
|
||||
Every value in JavaScript has the type. What type of value is a function?
|
||||
|
||||
In JavaScript, a function is an object.
|
||||
|
||||
A good way to imagine functions is as callable "action objects". We can not only call them, but also treat them as objects: add/remove properties, pass by reference etc.
|
||||
|
||||
|
||||
## The "name" property
|
||||
|
||||
Function objects contain few sometimes-useable properties.
|
||||
|
||||
For instance, a function name is accessible as the "name" property:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert("Hi");
|
||||
}
|
||||
|
||||
alert(sayHi.name); // sayHi
|
||||
```
|
||||
|
||||
What's more funny, the name-assigning logic is smart. It also sticks the right name to function that are used in assignments:
|
||||
|
||||
```js run
|
||||
let sayHi = function() {
|
||||
alert("Hi");
|
||||
}
|
||||
|
||||
alert(sayHi.name); // sayHi (works!)
|
||||
```
|
||||
|
||||
Also works if the assignment is done via a default value:
|
||||
|
||||
```js run
|
||||
function f(sayHi = function() {}) {
|
||||
alert(sayHi.name); // sayHi (works!)
|
||||
}
|
||||
|
||||
f();
|
||||
```
|
||||
|
||||
In the specification, this feature is called a "contextual name". If the function does not provide one, then in an assignment is figured out from the context.
|
||||
|
||||
Object methods have names too:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
|
||||
sayHi() {
|
||||
// ...
|
||||
},
|
||||
|
||||
sayBye: function() {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
alert(user.sayHi.name); // sayHi
|
||||
alert(user.sayBye.name); // sayBye
|
||||
```
|
||||
|
||||
There's no magic though. There are cases when there's no way to figure out the right name.
|
||||
|
||||
Then it's empty, like here:
|
||||
|
||||
```js
|
||||
// function created inside array
|
||||
let arr = [function() {}];
|
||||
|
||||
alert( arr[0].name ); // <empty string>
|
||||
// the engine has no way to set up the right name, so there is none
|
||||
```
|
||||
|
||||
In practice, most functions do have a name.
|
||||
|
||||
## The "length" property
|
||||
|
||||
There is another built-in property "length" that returns the number of function parameters, for instance:
|
||||
|
||||
```js run
|
||||
function f1(a) {}
|
||||
function f2(a, b) {}
|
||||
function many(a, b, ...more) {}
|
||||
|
||||
alert(f1.length); // 1
|
||||
alert(f2.length); // 2
|
||||
alert(many.length); // 2
|
||||
```
|
||||
|
||||
Here we can see that rest parameters are not counted.
|
||||
|
||||
The `length` property is sometimes used for introspection in functions that operate on other functions.
|
||||
|
||||
For instance, in the code below `ask` function accepts a `question` to ask and an arbitrary number of `handler` functions to call.
|
||||
|
||||
When a user answers, it calls the handlers. We can pass two kinds of handlers:
|
||||
|
||||
- A zero-argument function, then it is only called for a positive answer.
|
||||
- A function with arguments, then it is called in any case and gets the answer.
|
||||
|
||||
The idea is that we have a simple no-arguments handler syntax for positive cases (most often variant), but allow to provide universal handlers as well.
|
||||
|
||||
To call `handlers` the right way, we examine the `length` property:
|
||||
|
||||
```js run
|
||||
function ask(question, ...handlers) {
|
||||
let isYes = confirm(question);
|
||||
|
||||
for(let handler of handlers) {
|
||||
if (handler.length == 0) {
|
||||
if (isYes) handler();
|
||||
} else {
|
||||
handler(isYes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// for positive answer, both handlers are called
|
||||
// for negative answer, only the second one
|
||||
ask("Question?", () => alert('You said yes'), result => alert(result));
|
||||
```
|
||||
|
||||
This is a particular case of so-called [polymorphism](https://en.wikipedia.org/wiki/Polymorphism_(computer_science)) -- treating arguments differently depending on their type or, in our case depending on the `length`. The idea does have a use in JavaScript libraries.
|
||||
|
||||
## Custom properties
|
||||
|
||||
We can also add properties of our own.
|
||||
|
||||
Here we add the `counter` property to track the total calls count:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert("Hi");
|
||||
|
||||
*!*
|
||||
// let's count how many times we run
|
||||
sayHi.counter++;
|
||||
*/!*
|
||||
}
|
||||
sayHi.counter = 0; // initial value
|
||||
|
||||
sayHi(); // Hi
|
||||
sayHi(); // Hi
|
||||
|
||||
alert( `Called ${sayHi.counter} times` ); // Called 2 times
|
||||
```
|
||||
|
||||
```warn header="A property is not a variable"
|
||||
A property assigned to a function like `sayHi.counter = 0` does *not* define a local variable `counter` inside it. In other words, a property `counter` and a variable `let counter` are two unrelated things.
|
||||
|
||||
We can treat a function as an object, store properties in it, but that has no effect on its execution. Variables never use function properties and vise versa. These are just parallel words.
|
||||
```
|
||||
|
||||
Function properties can replace the closure sometimes. For instance, we can rewrite the counter example from the chapter <info:closure> to use a function property:
|
||||
|
||||
```js run
|
||||
function makeCounter() {
|
||||
// instead of:
|
||||
// let count = 0
|
||||
|
||||
function counter() {
|
||||
return counter.count++;
|
||||
};
|
||||
|
||||
counter.count = 0;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
let counter = makeCounter();
|
||||
alert( counter() ); // 0
|
||||
alert( counter() ); // 1
|
||||
```
|
||||
|
||||
The `count` is now stored in the function directly, not in its outer Lexical Environment.
|
||||
|
||||
Is it worse or better than using the closure?
|
||||
|
||||
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:
|
||||
|
||||
```js run
|
||||
function makeCounter() {
|
||||
|
||||
function counter() {
|
||||
return counter.count++;
|
||||
};
|
||||
|
||||
counter.count = 0;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
let counter = makeCounter();
|
||||
|
||||
*!*
|
||||
counter.count = 10;
|
||||
alert( counter() ); // 10
|
||||
*/!*
|
||||
```
|
||||
|
||||
So it depends on our aims which variant to choose.
|
||||
|
||||
## Named Function Expression
|
||||
|
||||
Named Function Expression or, shortly, NFE, is a term for Function Expressions that have a name.
|
||||
|
||||
For instance, let's take an ordinary Function Expression:
|
||||
|
||||
```js
|
||||
let sayHi = function(who) {
|
||||
alert(`Hello, ${who}`);
|
||||
};
|
||||
```
|
||||
|
||||
...And add a name to it:
|
||||
|
||||
```js
|
||||
let sayHi = function *!*func*/!*(who) {
|
||||
alert(`Hello, ${who}`);
|
||||
};
|
||||
```
|
||||
|
||||
Did we do anything sane here? What's the role of that additional `"func"` name?
|
||||
|
||||
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.
|
||||
|
||||
Adding such a name also did not break anything.
|
||||
|
||||
The function is still available as `sayHi()`:
|
||||
|
||||
```js run
|
||||
let sayHi = function *!*func*/!*(who) {
|
||||
alert(`Hello, ${who}`);
|
||||
};
|
||||
|
||||
sayHi("John"); // Hello, John
|
||||
```
|
||||
|
||||
There are two special things about the name `func`:
|
||||
|
||||
1. It allows to reference the function from inside itself.
|
||||
2. It is not visible outside of the function.
|
||||
|
||||
For instance, the function `sayHi` below re-calls itself with `"Guest"` if no `who` is provided:
|
||||
|
||||
```js run
|
||||
let sayHi = function *!*func*/!*(who) {
|
||||
if (who) {
|
||||
alert(`Hello, ${who}`);
|
||||
} else {
|
||||
*!*
|
||||
func("Guest"); // use func to re-call itself
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
sayHi(); // Hello, Guest
|
||||
|
||||
// But this won't work:
|
||||
func(); // Error, func is not defined (not visible outside of the function)
|
||||
```
|
||||
|
||||
Why do we use `func`? Maybe just use `sayHi` for the nested call?
|
||||
|
||||
|
||||
Actually, in most cases we can:
|
||||
|
||||
```js
|
||||
let sayHi = function(who) {
|
||||
if (who) {
|
||||
alert(`Hello, ${who}`);
|
||||
} else {
|
||||
*!*
|
||||
sayHi("Guest");
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```js run
|
||||
let sayHi = function(who) {
|
||||
if (who) {
|
||||
alert(`Hello, ${who}`);
|
||||
} else {
|
||||
*!*
|
||||
sayHi("Guest"); // Error: sayHi is not a function
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
let welcome = sayHi;
|
||||
sayHi = null;
|
||||
|
||||
welcome(); // Error, the nested sayHi call doesn't work any more!
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
The optional name which we can put into the Function Expression is exactly meant to solve this kind of problems.
|
||||
|
||||
Let's use it to fix the code:
|
||||
|
||||
```js run
|
||||
let sayHi = function *!*func*/!*(who) {
|
||||
if (who) {
|
||||
alert(`Hello, ${who}`);
|
||||
} else {
|
||||
*!*
|
||||
func("Guest"); // Now all fine
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
let welcome = sayHi;
|
||||
sayHi = null;
|
||||
|
||||
welcome(); // Hello, Guest (nested call works)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
The outer code still has it's variable `sayHi` or `welcome` later. And `func` is an "internal function name", how it calls itself privately.
|
||||
|
||||
```smart header="There's no such thing for Function Declaration"
|
||||
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.
|
||||
|
||||
Sometimes, when we need a reliable internal name, it's the reason to rewrite a Function Declaration to Named Function Expression form.
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
Functions are objects.
|
||||
|
||||
Here we covered their properties:
|
||||
|
||||
- `name` -- the function name. Exists not only when given in the function definition, but also for assignments and object properties.
|
||||
- `length` -- the number of arguments in the function definition. Rest parameters are not counted.
|
||||
|
||||
If the function is declared as a Function Expression (not in the main code flow), and it carries the name, then it is called Named Function Expression. The name can be used inside to reference itself, for recursive calls or such.
|
||||
|
||||
Also, functions may carry additional properties. Many well-known JavaScript libraries make a great use of this feature.
|
||||
|
||||
They create a "main" function and attach many other "helper" functions to it. For instance, the [jquery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`. And then adds `_.clone`, `_.keyBy` and other properties to (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to less pollute the global space, so that a single library gives only one global variable. That lowers the chance of possible naming conflicts.
|
||||
|
||||
So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.
|
Loading…
Add table
Add a link
Reference in a new issue