diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js
index d5a09efb3..5815767c2 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js
@@ -1,7 +1,7 @@
function spy(func) {
function wrapper(...args) {
- // using ...args instead of arguments to store "real" array in wrapper.calls
+ // bruger ...args i stedet for arguments for at gemme et "rigtigt" array i wrapper.calls
wrapper.calls.push(args);
return func.apply(this, args);
}
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js
index 38da0105f..503a59023 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js
@@ -1,5 +1,5 @@
function spy(func) {
- // your code
+ // din kode
}
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js
index 5adfcb978..1e7cc9d37 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js
@@ -1,6 +1,6 @@
-describe("spy", function() {
- it("records calls into its property", function() {
- function work() {}
+describe("spy", function () {
+ it("optager kald og gemmer dem i dens egenskab calls", function () {
+ function work() { }
work = spy(work);
assert.deepEqual(work.calls, []);
@@ -17,7 +17,7 @@ describe("spy", function() {
]);
});
- it("transparently wraps functions", function() {
+ it("transperant wrap af funktioner", function () {
let sum = sinon.spy((a, b) => a + b);
@@ -28,7 +28,7 @@ describe("spy", function() {
});
- it("transparently wraps methods", function() {
+ it("transparent wrap af metoder", function () {
let calc = {
sum: sinon.spy((a, b) => a + b)
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md
index 0c8a211b4..1aa877fda 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md
@@ -1 +1 @@
-The wrapper returned by `spy(f)` should store all arguments and then use `f.apply` to forward the call.
+Wrapperen der returneres af `spy(f)` skal gemme alle argumenter og derefter bruge `f.apply` til at videregive kaldet.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md
index a3843107c..1473a92f4 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md
@@ -4,15 +4,15 @@ importance: 5
# Spy decorator
-Create a decorator `spy(func)` that should return a wrapper that saves all calls to function in its `calls` property.
+Opret en decorator `spy(func)` der returnerer en wrapper der gemmer alle kald til funktionen i dens `calls` egenskab.
-Every call is saved as an array of arguments.
+Hvert kald er gemt som et array af argumenter.
-For instance:
+For eksempel:
```js
function work(a, b) {
- alert( a + b ); // work is an arbitrary function or method
+ alert( a + b ); // work er en tilfældig funktion eller metode
}
*!*
@@ -27,4 +27,4 @@ for (let args of work.calls) {
}
```
-P.S. That decorator is sometimes useful for unit-testing. Its advanced form is `sinon.spy` in [Sinon.JS](http://sinonjs.org/) library.
+P.S. Denne decorator kan være brugbar ved unit-testing. En avanceret form er `sinon.spy` i [Sinon.JS](http://sinonjs.org/) biblioteket.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js
index d9295da51..bf17a03c6 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js
@@ -1,13 +1,13 @@
-describe("delay", function() {
- before(function() {
+describe("delay", function () {
+ before(function () {
this.clock = sinon.useFakeTimers();
});
- after(function() {
+ after(function () {
this.clock.restore();
});
- it("calls the function after the specified timeout", function() {
+ it("kalder funktionen efter den givne timeout", function () {
let start = Date.now();
function f(x) {
@@ -18,15 +18,15 @@ describe("delay", function() {
let f1000 = delay(f, 1000);
f1000("test");
this.clock.tick(2000);
- assert(f.calledOnce, 'calledOnce check fails');
+ assert(f.calledOnce, 'calledOnce check fejlede');
});
- it("passes arguments and this", function() {
+ it("videregiver argumenter og this", function () {
let start = Date.now();
let user = {
- sayHi: function(phrase, who) {
+ sayHi: function (phrase, who) {
assert.equal(this, user);
- assert.equal(phrase, "Hello");
+ assert.equal(phrase, "Hej");
assert.equal(who, "John");
assert.equal(Date.now() - start, 1500);
}
@@ -37,10 +37,10 @@ describe("delay", function() {
let spy = user.sayHi;
user.sayHi = delay(user.sayHi, 1500);
- user.sayHi("Hello", "John");
+ user.sayHi("Hej", "John");
this.clock.tick(2000);
- assert(spy.calledOnce, 'calledOnce check failed');
+ assert(spy.calledOnce, 'calledOnce check fejlede');
});
});
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md
index 24bb4d448..061bb13ec 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md
@@ -1,4 +1,4 @@
-The solution:
+Løsningen:
```js run demo
function delay(f, ms) {
@@ -11,22 +11,22 @@ function delay(f, ms) {
let f1000 = delay(alert, 1000);
-f1000("test"); // shows "test" after 1000ms
+f1000("test"); // viser "test" efter 1000ms
```
-Please note how an arrow function is used here. As we know, arrow functions do not have own `this` and `arguments`, so `f.apply(this, arguments)` takes `this` and `arguments` from the wrapper.
+Bemærk hvordan en arrow function er brugt her. Som vi ved, har arrow functions ingen egen `this` og `arguments`, så `f.apply(this, arguments)` tager `this` og `arguments` fra wrapperen.
-If we pass a regular function, `setTimeout` would call it without arguments and `this=window` (assuming we're in the browser).
+Hvis vi sender en almindelig funktion, vil `setTimeout` kalde den uden argumenter og med `this=window` (hvis vi er i browseren).
-We still can pass the right `this` by using an intermediate variable, but that's a little bit more cumbersome:
+Vi kan stadig videregive det rigtige `this` ved at bruge en midlertidig variabel, men det er lidt mere besværligt:
```js
function delay(f, ms) {
return function(...args) {
- let savedThis = this; // store this into an intermediate variable
+ let savedThis = this; // gem this som en midlertidig variabel
setTimeout(function() {
- f.apply(savedThis, args); // use it here
+ f.apply(savedThis, args); // brug den her
}, ms);
};
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md
index c04c68d7e..0504284c5 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md
@@ -2,25 +2,25 @@ importance: 5
---
-# Delaying decorator
+# Forsinkelses decorator
-Create a decorator `delay(f, ms)` that delays each call of `f` by `ms` milliseconds.
+Opret en decorator `delay(f, ms)` der forsinker hvert kald af `f` med `ms` millisekunder.
-For instance:
+For eksempel:
```js
function f(x) {
alert(x);
}
-// create wrappers
+// Opret wrappers der forsinker kaldet af f med 1000ms og 1500ms
let f1000 = delay(f, 1000);
let f1500 = delay(f, 1500);
-f1000("test"); // shows "test" after 1000ms
-f1500("test"); // shows "test" after 1500ms
+f1000("test"); // viser "test" efter 1000ms
+f1500("test"); // viser "test" efter 1500ms
```
-In other words, `delay(f, ms)` returns a "delayed by `ms`" variant of `f`.
+Med andre ord: `delay(f, ms)` returner en udgave af `f` der er "forsinket med `ms` millisekunder".
-In the code above, `f` is a function of a single argument, but your solution should pass all arguments and the context `this`.
+I koden ovenfor er `f` en funktion med et enkelt argument, men din løsning skal videregive alle argumenter og konteksten `this`.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js
index 750e649f8..468cc1132 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js
@@ -7,33 +7,33 @@ describe('debounce', function () {
this.clock.restore();
});
- it('for one call - runs it after given ms', function () {
+ it('for et kald - kører den én gang efter givent antal millisekunder', function () {
const f = sinon.spy();
const debounced = debounce(f, 1000);
debounced('test');
- assert(f.notCalled, 'not called immediately');
+ assert(f.notCalled, 'ikke kaldt med det samme');
this.clock.tick(1000);
- assert(f.calledOnceWith('test'), 'called after 1000ms');
+ assert(f.calledOnceWith('test'), 'kaldt efter 1000ms');
});
- it('for 3 calls - runs the last one after given ms', function () {
+ it('for 3 kald - kører den sidste efter givent antal millisekunder', function () {
const f = sinon.spy();
const debounced = debounce(f, 1000);
debounced('a');
- setTimeout(() => debounced('b'), 200); // ignored (too early)
- setTimeout(() => debounced('c'), 500); // runs (1000 ms passed)
+ setTimeout(() => debounced('b'), 200); // ignoreret (for tidligt)
+ setTimeout(() => debounced('c'), 500); // kører (1000 ms passeret)
this.clock.tick(1000);
- assert(f.notCalled, 'not called after 1000ms');
+ assert(f.notCalled, 'ikke kaldt efter 1000ms');
this.clock.tick(500);
- assert(f.calledOnceWith('c'), 'called after 1500ms');
+ assert(f.calledOnceWith('c'), 'kaldt efter 1500ms');
});
- it('keeps the context of the call', function () {
+ it('beholder konteksten af kaldet', function () {
let obj = {
f() {
assert.equal(this, obj);
@@ -44,5 +44,5 @@ describe('debounce', function () {
obj.f('test');
this.clock.tick(5000);
});
-
+
});
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html
index e3b4d5842..df9e5c0c0 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html
@@ -1,24 +1,32 @@
-Function handler is called on this input:
-
-
+Funktion
+handler
+kaldes på dette input:
+
+
-
-Debounced function debounce(handler, 1000) is called on this input:
-
-
+ Debounced funktion
+ debounce(handler, 1000)
+ kaldes på dette input:
+
+
+
- + - \ No newline at end of file + input1.oninput = handler; + input2.oninput = _.debounce(handler, 1000); + +
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md index 83e75f315..3c1d643cc 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md @@ -9,5 +9,5 @@ function debounce(func, ms) { ``` -A call to `debounce` returns a wrapper. When called, it schedules the original function call after given `ms` and cancels the previous such timeout. +Et kald til `debounce` returnerer en wrapper. Når den kaldes, planlægger den et kald til den originale funktion efter `ms` millisekunder og annullerer tidligere timeouts hvis de findes. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 5b0fcc5f8..c68e0b27f 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -4,19 +4,19 @@ importance: 5 # Debounce decorator -The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments. +Resultatet af en `debounce(f, ms)` decorator er en wrapper der suspenderer kaldet til `f` indtil der er `ms` millisekunder af inaktivitet (ingen kald, "cooldown period"), så kalder den `f` én gang med de seneste argumenter. -In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`). +Med andre ord er `debounce` som en sekretær der tager imod "telefonopkald" og venter ind til der har været `ms` millisekunder af inaktivitet. Og først da overfører den de seneste opkaldsoplysninger til "chefen" (kalder den faktiske `f`). -For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`. +For eksempel, vi havde en funktion `f` og erstattede den med `f = debounce(f, 1000)`. -Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call. +Hvis den omgivende funktion kaldes ved 0ms, 200ms og 500ms, og der ikke er flere kald efter det, vil den faktiske `f` kun blive kaldt én gang, ved 1500ms. Det vil sige: efter cooldown-perioden på 1000ms fra det sidste kald.  -...And it will get the arguments of the very last call, other calls are ignored. +...og vi vil få argumenterne fra det sidste kald, andre kald ignoreres. -Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)): +Her er koden for det (som bruger debounce decorator fra [Lodash biblioteket](https://lodash.com/docs/4.17.15#debounce)): ```js let f = _.debounce(alert, 1000); @@ -24,28 +24,28 @@ let f = _.debounce(alert, 1000); f("a"); setTimeout( () => f("b"), 200); setTimeout( () => f("c"), 500); -// debounced function waits 1000ms after the last call and then runs: alert("c") +// debounced funktion venter 1000ms efter det sidste kald og kalder så alert("c") ``` -Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished. +Nu til et praktisk eksempel. Lad os sige, at brugeren skriver noget, og vi vil sende en forespørgsel til serveren når inputtet er færdigt. -There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result. +Der er ingen pointe i at sende forespørgslen for hver tastetryk. I stedet vil vi vente og så behandle hele resultatet. -In a web-browser, we can setup an event handler -- a function that's called on every change of an input field. Normally, an event handler is called very often, for every typed key. But if we `debounce` it by 1000ms, then it will be only called once, after 1000ms after the last input. + en webbrowser kan vi opsætte en event handler -- en funktion der kaldes ved hver ændring af et inputfelt. Normalt kaldes en event handler meget ofte, for hver tastetryk. Men hvis vi `debounce` den med 1000ms, vil den kun blive kaldt én gang, efter 1000ms efter det sidste input. ```online -In this live example, the handler puts the result into a box below, try it: +I dette live eksempel sætter handleren resultatet i en boks nedenfor, prøv det: [iframe border=1 src="debounce" height=200] -See? The second input calls the debounced function, so its content is processed after 1000ms from the last input. +Kan du se effekten? Det andet input kalder den debounced funktion, så dens indhold behandles efter 1000ms fra det sidste input. ``` -So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else. +Så `debounce` er en god måde at processere en række af events: en sekvens at tastetryk, musbevægelser eller andet. -It waits the given time after the last call, and then runs its function, that can process the result. +Den venter en given tid efter det sidste kald, og så kører den sin funktion, som kan behandle resultatet. -The task is to implement `debounce` decorator. +Din opgave er at implementere en `debounce` decorator. -Hint: that's just a few lines if you think about it :) +Hint: det er egentlig kun et par linjer hvis man tænker over det :) diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js index 8071be9d4..7d2f3bfff 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js @@ -7,23 +7,23 @@ function throttle(func, ms) { function wrapper() { if (isThrottled) { - // memo last arguments to call after the cooldown + // memoriser sidste sæt argumenter der skal kaldes efter cooldown savedArgs = arguments; savedThis = this; return; } - // otherwise go to cooldown state + // ellers gå til cooldown state func.apply(this, arguments); isThrottled = true; - // plan to reset isThrottled after the delay + // planlæg at nulstille isThrottled efter forsinkelsen setTimeout(function() { isThrottled = false; if (savedArgs) { - // if there were calls, savedThis/savedArgs have the last one - // recursive call runs the function and sets cooldown again + // hvis der har været yderligere kald har savedThis/savedArgs de sidste + // rekursivt kald kører funktionen og sætter cooldown igen wrapper.apply(savedThis, savedArgs); savedArgs = savedThis = null; } diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js index e671438f6..64f413997 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js @@ -1,4 +1,4 @@ -describe("throttle(f, 1000)", function() { +describe("throttle(f, 1000)", function () { let f1000; let log = ""; @@ -6,41 +6,41 @@ describe("throttle(f, 1000)", function() { log += a; } - before(function() { + before(function () { this.clock = sinon.useFakeTimers(); f1000 = throttle(f, 1000); }); - it("the first call runs now", function() { - f1000(1); // runs now + it("det første kald kører nu", function () { + f1000(1); // kører nu assert.equal(log, "1"); }); - it("then calls are ignored till 1000ms when the last call works", function() { - f1000(2); // (throttling - less than 1000ms since the last run) - f1000(3); // (throttling - less than 1000ms since the last run) - // after 1000 ms f(3) call is scheduled + it("kald ignoreres indtil 1000ms er gået, og det sidste kald kører", function () { + f1000(3); // (throttling - mindre end 1000ms siden sidste kørsel) + f1000(2); // (throttling - mindre end 1000ms siden sidste kørsel) + // efter 1000 ms planlægges kald til f(3) - assert.equal(log, "1"); // right now only the 1st call done + assert.equal(log, "1"); // lige nu er første kald overstået - this.clock.tick(1000); // after 1000ms... - assert.equal(log, "13"); // log==13, the call to f1000(3) is made + this.clock.tick(1000); // efter 1000ms... + assert.equal(log, "13"); // log==13, kaldet til f1000(3) sker }); - it("the third call waits 1000ms after the second call", function() { + it("the third call waits 1000ms after the second call", function () { this.clock.tick(100); - f1000(4); // (throttling - less than 1000ms since the last run) + f1000(4); // (throttling - mindre end 1000ms siden sidste kørsel) this.clock.tick(100); - f1000(5); // (throttling - less than 1000ms since the last run) + f1000(5); // (throttling - mindre end 1000ms siden sidste kørsel) this.clock.tick(700); - f1000(6); // (throttling - less than 1000ms since the last run) + f1000(6); // (throttling - mindre end 1000ms siden sidste kørsel) - this.clock.tick(100); // now 100 + 100 + 700 + 100 = 1000ms passed + this.clock.tick(100); // nu er 100 + 100 + 700 + 100 = 1000ms passeret - assert.equal(log, "136"); // the last call was f(6) + assert.equal(log, "136"); // det sidste kald var f(6) }); - after(function() { + after(function () { this.clock.restore(); }); @@ -48,7 +48,7 @@ describe("throttle(f, 1000)", function() { describe('throttle', () => { - it('runs a forwarded call once', done => { + it('kører et videresendt kald én gang', done => { let log = ''; const f = str => log += str; const f10 = throttle(f, 10); diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index 6950664be..236d05c72 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -29,10 +29,10 @@ function throttle(func, ms) { } ``` -A call to `throttle(func, ms)` returns `wrapper`. +Et kald til `throttle(func, ms)` returnerer `wrapper`. -1. During the first call, the `wrapper` just runs `func` and sets the cooldown state (`isThrottled = true`). -2. In this state all calls are memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call. -3. After `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`) and, if we had ignored calls, `wrapper` is executed with the last memorized arguments and context. +1. Ved første kald kører `wrapper` funktionen `func` og sætter cooldown-tilstanden (`isThrottled = true`). +2. I denne tilstand gemmes alle kald i `savedArgs/savedThis`. Bemærk at både konteksten og argumenterne er lige så vigtige og skal gemmes. Vi har brug for begge dele samtidigt for at kunne genskabe kaldet. +3. Efter `ms` millisekunder har gået, udløser `setTimeout`. Cooldown-tilstanden fjernes (`isThrottled = false`) og, hvis der var ignorerede kald, køres `wrapper` med de sidste gemte argumenter og kontekst. -The 3rd step runs not `func`, but `wrapper`, because we not only need to execute `func`, but once again enter the cooldown state and setup the timeout to reset it. +Det tredje trin kører ikke `func`, men `wrapper`, fordi vi ikke kun skal køre `func`, men også igen indtaste cooldown-tilstanden og opsætte timeout'en til at nulstille den. \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index cbd473196..d02f9f7b9 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -4,50 +4,51 @@ importance: 5 # Throttle decorator -Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper. +Opret en "throttling" decorator `throttle(f, ms)` -- der returnerer en wrapper. -When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. +Når den kaldes flere gange, sender den kaldet til `f` maksimalt én gang pr. `ms` millisekunder. -Compared to the debounce decorator, the behavior is completely different: -- `debounce` runs the function once after the "cooldown" period. Good for processing the final result. -- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often. +Sammenlignet med debounce decorator er adfæren helt anderledes: +- `debounce` kører funktionen én gang efter "cooldown" perioden. Godt til at behandle det endelige resultat. +- `throttle` kører den ikke oftere end givet `ms` tid. Godt til regelmæssige opdateringer der ikke bør ske for ofte. -In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds. +Med andre ord er `throttle` en slags sekretær der accepterer telefonopkald uden at ville genere chefen (calls the actual `f`) mere end én ger per `ms` millisekunder. -Let's check the real-life application to better understand that requirement and to see where it comes from. +Lad os se på et eksempel fra en realistisk situation for bedre at forstå metoden. -**For instance, we want to track mouse movements.** +**Tracking af musens bevægelser.** -In a browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms). -**We'd like to update some information on the web-page when the pointer moves.** +I en browser kan vi opsætte en funktion der modtager musens kooridnater og kaldes hver gang musen bevæger sig. I praksis vil sådan en funktion blive kaldt ret ofte - noget i stil med 100 gange i sekundet (hvert 10. millisekund). -...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms. +**Vi vil gerne opdatere informationen på siden når musen bevæger sig.** -So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but forward the call to `update()` at maximum once per 100ms. +...Men funktionen `update()` der skal stå for opdateringen er alt for tung til at køre ved hver mikro-bevægelse. Der giver nok heller ingen mening at opdatere oftere end én gang pr. 100ms. -Visually, it will look like this: +Derfor pakker vi den ind i en decorator: brug `throttle(update, 100)` som funktionen der skal køres ved hver mus-bevægelse i stedet for den originale `update()`. Decoratoren vil blive kaldt ofte, men videregive kaldet til `update()` maksimalt én gang pr. 100ms. -1. For the first mouse movement the decorated variant immediately passes the call to `update`. That's important, the user sees our reaction to their move immediately. -2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls. -3. At the end of `100ms` -- one more `update` happens with the last coordinates. -4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, quite important, the final mouse coordinates are processed. +Det vil se ud i stil med dette: -A code example: +1. For den første bevægelse med musen vil den dekorerede variant med det samme videregive kaldet til `update`. Det er vigtigt, brugeren ser vores reaktion på bevægelsen. +2. Derefter, mens musen bevæger sig videre *og* indtil `100ms` er gået, sker der intet. Den dekorerede variant ignorerer kaldene. +3. Ved slutningen af `100ms` -- en ekstra `update` sker med de sidste koordinater. +4. Til sidst, når musen stopper et sted, venter den dekorerede variant til `100ms` er udløbet og kører derefter `update` med de sidste koordinater. Så er det vigtigt at de sidste koordinater bliver behandlet. + +Et kodet eksempel: ```js function f(a) { console.log(a); } -// f1000 passes calls to f at maximum once per 1000 ms +// f1000 videregiver kald til f maks én gang per 1000 ms let f1000 = throttle(f, 1000); -f1000(1); // shows 1 -f1000(2); // (throttling, 1000ms not out yet) -f1000(3); // (throttling, 1000ms not out yet) +f1000(1); // vider 1 +f1000(2); // (throttling, 1000ms ikke endnu) +f1000(3); // (throttling, 1000ms ikke endnu) -// when 1000 ms time out... -// ...outputs 3, intermediate value 2 was ignored +// når 1000 ms er gået... +// ...output'er 3, den midterste værdi 2 ignoreres ``` -P.S. Arguments and the context `this` passed to `f1000` should be passed to the original `f`. +P.S. Argumenterne og konteksten `this` der gives til `f1000` skal videregives til den originale `f`. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index c5d785493..89bbb0bdc 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -1,21 +1,21 @@ -# Decorators and forwarding, call/apply +# Decorators og videresendelse, call/apply -JavaScript gives exceptional flexibility when dealing with functions. They can be passed around, used as objects, and now we'll see how to *forward* calls between them and *decorate* them. +JavaScript giver enestående fleksibilitet, når det kommer til funktioner. De kan sendes rundt, bruges som objekter, og nu vil vi se, hvordan man *videresender* kald mellem dem og *dekorerer* dem. ## Transparent caching -Let's say we have a function `slow(x)` which is CPU-heavy, but its results are stable. In other words, for the same `x` it always returns the same result. +Lad os sige, at vi har en funktion `slow(x)`, som er CPU-tung, men dens resultater er stabile. Med andre ord returnerer den altid det samme resultat for den samme `x`. -If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations. +Hvis funktionen kaldes ofte, kan vi ønske at cache (huske) resultaterne for at undgå at bruge ekstra tid på genberegninger. -But instead of adding that functionality into `slow()` we'll create a wrapper function, that adds caching. As we'll see, there are many benefits of doing so. +Men i stedet for at tilføje den funktionalitet direkte i `slow()`, vil vi oprette en wrapper-funktion, der tilføjer caching. Som vi vil se, er der mange fordele ved at gøre det. -Here's the code, and explanations follow: +Her er koden. Forklaringen følger nedenfor: ```js run function slow(x) { - // there can be a heavy CPU-intensive job here - alert(`Called with ${x}`); + // her kan der være et CPU-tungt job + alert(`Kaldt med ${x}`); return x; } @@ -23,65 +23,64 @@ function cachingDecorator(func) { let cache = new Map(); return function(x) { - if (cache.has(x)) { // if there's such key in cache - return cache.get(x); // read the result from it + if (cache.has(x)) { // hvis der er sådan en nøgle i cache + return cache.get(x); // læs resultatet fra cache } - let result = func(x); // otherwise call func + let result = func(x); // ellers kald func - cache.set(x, result); // and cache (remember) the result + cache.set(x, result); // og "cache" (husk) resultatet return result; }; } slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1) is cached and the result returned -alert( "Again: " + slow(1) ); // slow(1) result returned from cache +alert( slow(1) ); // slow(1) er gemt i cache og resultatet returneres +alert( "Again: " + slow(1) ); // slow(1) resultatet returnes fra cache -alert( slow(2) ); // slow(2) is cached and the result returned -alert( "Again: " + slow(2) ); // slow(2) result returned from cache +alert( slow(2) ); // slow(2) er gemt i cache og resultatet returneres +alert( "Again: " + slow(2) ); // slow(2) resultatet returnes fra cache ``` -In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior. +I koden ovenfor er `cachingDecorator` en *decorator*: en speciel funktion, der tager en anden funktion og ændrer dens adfærd. -The idea is that we can call `cachingDecorator` for any function, and it will return the caching wrapper. That's great, because we can have many functions that could use such a feature, and all we need to do is to apply `cachingDecorator` to them. +Ideen er at vi kalder `cachingDecorator` for en hvilken som helst funktion, og den returnerer en caching-wrapper. Det er godt, fordi vi kan have mange funktioner, der kunne bruge denne funktionalitet, og alt hvad vi behøver at gøre er at anvende `cachingDecorator` på dem. -By separating caching from the main function code we also keep the main code simpler. - -The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic: +Ved at separere caching fra hovedfunktionens kode holder vi også hovedkoden enkel. +Resultatet af `cachingDecorator(func)` er en "wrapper": `function(x)` som "wrapper" (omkranser) kaldet af `func(x)` i caching-logik:  -From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior. +Set udefra vil den nye "wrapped" funktion opføre sig som den originale funktion, men med tilføjet caching-funktionalitet. -To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself: +Kort sagt, der er flere fordele ved at bruge en separat `cachingDecorator` i stedet for at ændre koden i `slow` selv: -- The `cachingDecorator` is reusable. We can apply it to another function. -- The caching logic is separate, it did not increase the complexity of `slow` itself (if there was any). -- We can combine multiple decorators if needed (other decorators will follow). +- `cachingDecorator` er genbrugelig. Vi kan anvende den på en anden funktion. +- Caching-logikken er separat, den øgede ikke kompleksiteten i `slow` selv (hvis der var nogen). +- Vi kan kombinere flere decorators hvis det er nødvendigt (andre decorators vil følge). -## Using "func.call" for the context +## Brug "func.call" for at få kontekst -The caching decorator mentioned above is not suited to work with object methods. +Caching decoratoren nævnt ovenfor er ikke egnet til at arbejde med objektmetoder. -For instance, in the code below `worker.slow()` stops working after the decoration: +For eksempel, i koden nedenfor stopper `worker.slow()` efter dekoration med en fejl: ```js run -// we'll make worker.slow caching +// Vi laver worker.slow om til en udgave med caching let worker = { someMethod() { return 1; }, slow(x) { - // scary CPU-heavy task here - alert("Called with " + x); + // CPU-tung opgave her + alert("Kaldt med " + x); return x * this.someMethod(); // (*) } }; -// same code as before +// samme kode som før function cachingDecorator(func) { let cache = new Map(); return function(x) { @@ -96,49 +95,49 @@ function cachingDecorator(func) { }; } -alert( worker.slow(1) ); // the original method works +alert( worker.slow(1) ); // den oprindelige metode virker -worker.slow = cachingDecorator(worker.slow); // now make it caching +worker.slow = cachingDecorator(worker.slow); // lad os lave den om til en udgave med cache *!* -alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined +alert( worker.slow(2) ); // Ups! Error: Cannot read property 'someMethod' of undefined */!* ``` -The error occurs in the line `(*)` that tries to access `this.someMethod` and fails. Can you see why? +Fejlen sker i linjen med `(*)` der prøver at tilgå `this.someMethod` men fejler. Kan du se hvorfor? -The reason is that the wrapper calls the original function as `func(x)` in the line `(**)`. And, when called like that, the function gets `this = undefined`. +Grunden til det er at wrapperen kalder den originale funktion som `func(x)` i linjen `(**)`. Og, når den bliver kaldt på den måde, får funktionen `this = undefined`. -We would observe a similar symptom if we tried to run: +Vi vil se det samme ske hvis vi prøvede at køre: ```js let func = worker.slow; func(2); ``` -So, the wrapper passes the call to the original method, but without the context `this`. Hence the error. +Så wrapperen videregiver kaldet til den originale metode, men uden konteksten `this`. Derfor fejler det. -Let's fix it. +Lad os fikse det. -There's a special built-in function method [func.call(context, ...args)](mdn:js/Function/call) that allows to call a function explicitly setting `this`. +Der er en indbygget funktion kaldet [func.call(context, ...args)](mdn:js/Function/call) der er skabt specielt til lejligheden. Den tillader at kalde en funktion eksplicit og sætte `this`. -The syntax is: +Syntaksen er: ```js func.call(context, arg1, arg2, ...) ``` -It runs `func` providing the first argument as `this`, and the next as the arguments. +Den kører `func` og giver det en henvisning til `this` med som første argument, og derefter eventuelt de næste argumenter. -To put it simply, these two calls do almost the same: +Sat lidt simpelt op gør disse to kald næsten det samme: ```js func(1, 2, 3); func.call(obj, 1, 2, 3) ``` -They both call `func` with arguments `1`, `2` and `3`. The only difference is that `func.call` also sets `this` to `obj`. +De kalder begge `func` med argumenterne `1`, `2` and `3`. Den eneste forskel er at `func.call` også sætter `this` til `obj`. -As an example, in the code below we call `sayHi` in the context of different objects: `sayHi.call(user)` runs `sayHi` providing `this=user`, and the next line sets `this=admin`: +I koden nedenfor kalder vi `sayHi` med forskellige objekter som kontekst: `sayHi.call(user)` kører `sayHi` men leverer `this=user` som kontekst mens den næste linje sætter `this=admin`: ```js run function sayHi() { @@ -148,12 +147,12 @@ function sayHi() { let user = { name: "John" }; let admin = { name: "Admin" }; -// use call to pass different objects as "this" +// brug call for at videregive forskellige objekter som "this" sayHi.call( user ); // John sayHi.call( admin ); // Admin ``` -And here we use `call` to call `say` with the given context and phrase: +og her bruger vi `call` til at kalde `say` med den givne kontekst og frase: ```js run @@ -163,11 +162,11 @@ function say(phrase) { let user = { name: "John" }; -// user becomes this, and "Hello" becomes the first argument -say.call( user, "Hello" ); // John: Hello +// user bliver til this og "Hej" bliver det første argument +say.call( user, "Hej" ); // John: Hej ``` -In our case, we can use `call` in the wrapper to pass the context to the original function: +I vores oprindelige eksempel kan vi bruge `call` i wrapperen til at videregive konteksten til den originale funktion: ```js run let worker = { @@ -176,7 +175,7 @@ let worker = { }, slow(x) { - alert("Called with " + x); + alert("Kaldt med " + x); return x * this.someMethod(); // (*) } }; @@ -188,57 +187,57 @@ function cachingDecorator(func) { return cache.get(x); } *!* - let result = func.call(this, x); // "this" is passed correctly now + let result = func.call(this, x); // "this" liver sendt rigtigt nu */!* cache.set(x, result); return result; }; } -worker.slow = cachingDecorator(worker.slow); // now make it caching +worker.slow = cachingDecorator(worker.slow); // omdan den til en version med cache -alert( worker.slow(2) ); // works -alert( worker.slow(2) ); // works, doesn't call the original (cached) +alert( worker.slow(2) ); // virker +alert( worker.slow(2) ); // virker, kalder ikke originalen med den cachede version ``` -Now everything is fine. +Nu virker alt fint. -To make it all clear, let's see more deeply how `this` is passed along: +For at gøre det helt tydeligt, lad os gå mere i dybden med hvordan `this` videregives i det sidste eksempel: -1. After the decoration `worker.slow` is now the wrapper `function (x) { ... }`. -2. So when `worker.slow(2)` is executed, the wrapper gets `2` as an argument and `this=worker` (it's the object before dot). -3. Inside the wrapper, assuming the result is not yet cached, `func.call(this, x)` passes the current `this` (`=worker`) and the current argument (`=2`) to the original method. +1. Efter dekorationen af `worker.slow` er den nu omdannet til wrapperen `function (x) { ... }`. +2. Så når `worker.slow(2)` eksekveres vil wrapperen få `2` som argument og `this=worker` (det er objektet før punktum). +3. Inde i wrapperen, forudsat at det ikke er i chachen, vil `func.call(this, x)` videregive den aktuelle `this` (`=worker`) og det aktuelle argument (`=2`) til den originale metode. -## Going multi-argument +## Hvad med flere argumenter? -Now let's make `cachingDecorator` even more universal. Till now it was working only with single-argument functions. +Lad os gøre `cachingDecorator` endnu mere universiel. Indtil videre fungerer den kun med funktioner med ét argument. -Now how to cache the multi-argument `worker.slow` method? +Hvordan skal vi cache en multi-argument `worker.slow` metode? ```js let worker = { slow(min, max) { - return min + max; // scary CPU-hogger is assumed + return min + max; // Skræmmende tung CPU-opgave her } }; -// should remember same-argument calls +// skal huske kald med de samme argumenter worker.slow = cachingDecorator(worker.slow); ``` -Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key. +Tidligere kunne vi for et enkelt argument `x` bare kalde `cache.set(x, result)` for at gemme resultatet og `cache.get(x)` for at hente det. Men nu skal vi huske resultatet for en *kombination af argumenter* `(min,max)`. Den indbyggede `Map` tager kun en enkelt værdi som nøgle. -There are many solutions possible: +Der er flere mulige løsninger: -1. Implement a new (or use a third-party) map-like data structure that is more versatile and allows multi-keys. -2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`. -3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many. +1. Implementer en ny (eller brug en 3de-parts) map-lignende datastruktur der er mere fleksibel og tillader brug af flere nøgler. +2. Brug indlejrede maps: `cache.set(min)` vil bleve et `Map` der gemmer parret `(max, result)`. På den måde kan vi hente `resultatet` som `cache.get(min).get(max)`. +3. Forbind to værdier til et. I vores tilfælde vil vi kunne bruge en simpel streng `"min,max"` som `Map`-nøgle. For fleksibilitet kan vi levere en *hashing funktion* til vores decorator, der ved hvordan man laver en værdi ud af mange. -For many practical applications, the 3rd variant is good enough, so we'll stick to it. +For de fleste praktiske anvendelser er variant 3 god nok, så vi holder os til den. -Also we need to pass not just `x`, but all arguments in `func.call`. Let's recall that in a `function()` we can get a pseudo-array of its arguments as `arguments`, so `func.call(this, x)` should be replaced with `func.call(this, ...arguments)`. +Endelig skal vi huske at vi ikke bare skal sende `x`, men alle argumenter i `func.call`. Lad os huske at i en `function()` kan vi få et pseudo-array af dens argumenter som `arguments`, så `func.call(this, x)` bør erstattes med `func.call(this, ...arguments)`. -Here's a more powerful `cachingDecorator`: +Her er en mere kraftfuld `cachingDecorator`: ```js run let worker = { @@ -273,50 +272,50 @@ function hash(args) { worker.slow = cachingDecorator(worker.slow, hash); -alert( worker.slow(3, 5) ); // works -alert( "Again " + worker.slow(3, 5) ); // same (cached) +alert( worker.slow(3, 5) ); // virker, slow(3, 5) er gemt i cache +alert( "Igen " + worker.slow(3, 5) ); // det samme her (cachet resultat returneres) ``` -Now it works with any number of arguments (though the hash function would also need to be adjusted to allow any number of arguments. An interesting way to handle this will be covered below). +Nu virker det med et givet antal argumenter (selvom hashing-funktionen også skal justeres for at tillade et vilkårligt antal argumenter. En interessant måde at håndtere dette vil blive beskrevet nedenfor). -There are two changes: +Der er to ændringer i `cachingDecorator`: -- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions. -- Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function. +- I linjen `(*)` kaldes funktionen `hash` som opretter en enkelt tekststreng som nøgle fra `arguments`. Det er en simpel "join" funktion der omdanner argumenterne `(3, 5)` til strengen `"3,5"`. Mere komplekse tilfælde kan kræve andre hashing-funktioner. +- Senere i `(**)` bruger `func.call(this, ...arguments)` til at videregive både context og alle argumenter som wrapperen fik (ikke kun det første) til den originale funktion. ## func.apply -Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`. +I stedet for `func.call(this, ...arguments)` kunne vi bruge `func.apply(this, arguments)`. -The syntax of built-in method [func.apply](mdn:js/Function/apply) is: +Syntaksen for den indbyggede metode [func.apply](mdn:js/Function/apply) er: ```js func.apply(context, args) ``` -It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments. +Den kører `func` og sætter `this=context` og bruger et array-lignende objekt `args` som en liste af argumenter. -The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them. +Den eneste forskel på syntaksen mellem `call` og `apply` er at `call` forventer en liste af argumenter, mens `apply` tager et array-lignende objekt med dem. -So these two calls are almost equivalent: +Så disse to kald er næsten ens: ```js func.call(context, ...args); func.apply(context, args); ``` -They perform the same call of `func` with given context and arguments. +De udfører det samme kald af `func` med en givet kontekst og argumenter. -There's only a subtle difference regarding `args`: +Der er kun en lille forskel i håndtering af `args`: -- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. -- The `apply` accepts only *array-like* `args`. +- Spread syntaksen `...` tillader at videregive *itererbare* `args` som en liste til `call`. +- `apply` tillader kun *array-lignende* `args`. -...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. +...og for objekter der både er itererbare og array-lignende, så som rigtige arrays, kan vi bruge begge. Men `apply` vil sikkert være hurtigere, fordi de fleste JavaScript-motorer internt optimerer det bedre. -Passing all arguments along with the context to another function is called *call forwarding*. +Et videregive alle argumenter sammen med konteksten til en anden funktion kaldes *call forwarding*. -That's the simplest form of it: +Dette er den simpleste form af det: ```js let wrapper = function() { @@ -324,11 +323,11 @@ let wrapper = function() { }; ``` -When an external code calls such `wrapper`, it is indistinguishable from the call of the original function `func`. +Når en ekstern kode kalder en sådan `wrapper`, er det ikke muligt at skelne mellem kaldet af denne fra den originale funktion `func`. -## Borrowing a method [#method-borrowing] +## Lån en metode [#method-borrowing] -Now let's make one more minor improvement in the hashing function: +Lad os nu lave en mindre forbedring til hashing-funktionen. ```js function hash(args) { @@ -336,9 +335,9 @@ function hash(args) { } ``` -As of now, it works only on two arguments. It would be better if it could glue any number of `args`. +Som det er nu virker det kun på to argumenter. Det ville være bedre hvis den kunne sammenkæde et vilkårligt antal `args`. -The natural solution would be to use [arr.join](mdn:js/Array/join) method: +Den naturlige løsning vil være at bruge metoden [arr.join](mdn:js/Array/join): ```js function hash(args) { @@ -346,9 +345,9 @@ function hash(args) { } ``` -...Unfortunately, that won't work. Because we are calling `hash(arguments)`, and `arguments` object is both iterable and array-like, but not a real array. +...uheldigvis virker det ikke. Det er fordi vi kalder `hash(arguments)` og `arguments` objektet er både itererbart og array-lignende - men ikke et *rigtigt* array. -So calling `join` on it would fail, as we can see below: +Så kaldet til `join` på det vil fejle, som vi ser nedenfor: ```js run function hash() { @@ -360,7 +359,7 @@ function hash() { hash(1, 2); ``` -Still, there's an easy way to use array join: +Men, der er et nemt trick til at kunne bruge array join: ```js run function hash() { @@ -372,48 +371,47 @@ function hash() { hash(1, 2); ``` -The trick is called *method borrowing*. - -We take (borrow) a join method from a regular array (`[].join`) and use `[].join.call` to run it in the context of `arguments`. +Dette trick kaldes*method borrowing*. -Why does it work? +Vi tager (låner) join metoden fra et regulært array (`[].join`) og bruger `[].join.call` til at køre det i kontekst af `arguments`. -That's because the internal algorithm of the native method `arr.join(glue)` is very simple. +Hvorfor virker det? -Taken from the specification almost "as-is": +Det er fordi den interne algoritme for metoden `arr.join(glue)` er ret simpel. -1. Let `glue` be the first argument or, if no arguments, then a comma `","`. -2. Let `result` be an empty string. -3. Append `this[0]` to `result`. -4. Append `glue` and `this[1]`. -5. Append `glue` and `this[2]`. -6. ...Do so until `this.length` items are glued. -7. Return `result`. +Taget fra specifikationen som det står ordret: -So, technically it takes `this` and joins `this[0]`, `this[1]` ...etc together. It's intentionally written in a way that allows any array-like `this` (not a coincidence, many methods follow this practice). That's why it also works with `this=arguments`. +1. Lad `glue` være det første argument eller, hvis ingen argumenter, så en komma `","`. +2. Lad `result` være en tom streng. +3. Tilføj `this[0]` til `result`. +4. Tilføj `glue` og `this[1]`. +5. Tilføj `glue` og `this[2]`. +6. ...fortsæt indtil `this.length` elementer er sammenkædet. +7. Returner `result`. -## Decorators and function properties +Så teknisk set tager den `this` og kæder `this[0]`, `this[1]` ...osv sammen. Det er bevidst skrevet på en måde der tillader ethvert array-lignende `this` (ikke en tilfældighed, mange metoder følger denne praksis). Derfor virker det også med `this=arguments`. -It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them. +## Decorators og egenskaber af funktioner -E.g. in the example above if `slow` function had any properties on it, then `cachingDecorator(slow)` is a wrapper without them. +Det er normalt uproblematisk at erstatte en funktion eller en metode med en dekoreret version, bortset fra ét lille punkt. Hvis den originale funktion havde egenskaber på sig, som `func.calledCount` eller lignende, så vil den dekorerede funktion tage dem i betragtning. Set i det lys skal man være opmærksom på at kopiere egenskaberne fra den originale funktion til wrapperen, hvis de findes. -Some decorators may provide their own properties. E.g. a decorator may count how many times a function was invoked and how much time it took, and expose this information via wrapper properties. +E.g. i eksemplet ovenfor, hvis `slow` funktionen havde nogle egenskaber på sig, så vil `cachingDecorator(slow)` være en wrapper uden dem. -There exists a way to create decorators that keep access to function properties, but this requires using a special `Proxy` object to wrap a function. We'll discuss it later in the article