Skip to content

Decorators and forwarding, call/apply #163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
19a98b3
Translate a part of article
mahdiHash Oct 10, 2021
f392414
Translate a part of article
mahdiHash Oct 11, 2021
fef1f2d
Merge branch 'javascript-tutorial:master' into master
mahdiHash Oct 12, 2021
1c9611e
Translate a part of article
mahdiHash Oct 12, 2021
2186c43
Translate a part of article
mahdiHash Oct 14, 2021
c91d2d1
Change "ترکیب‌کننده" to "ترکیب‌سازی"
mahdiHash Oct 14, 2021
9a8fa65
Translate a part of article
mahdiHash Oct 15, 2021
b2f22fc
Translate a part of article
mahdiHash Oct 16, 2021
d8b9e94
Translate a part of article
mahdiHash Oct 16, 2021
1ac2396
Translate article
mahdiHash Oct 16, 2021
e24f7d8
Translate task of "spy-decorator"
mahdiHash Oct 17, 2021
f9ddfe7
Translate solution of "spy-decorator"
mahdiHash Oct 17, 2021
8390a85
Translate a comment in solution of "_js.view"
mahdiHash Oct 17, 2021
e84e68f
Translate task of "delay"
mahdiHash Oct 17, 2021
eae3fed
Translate solution of "delay"
mahdiHash Oct 17, 2021
22769b3
Translate task of "debounce"
mahdiHash Oct 17, 2021
ebb396d
Translate "index.html", make it look good in Farsi
mahdiHash Oct 17, 2021
ba95035
Translate solution of "debounce"
mahdiHash Oct 17, 2021
0e0e265
Change "منع" to "معلق"
mahdiHash Oct 17, 2021
6515af3
Translate task of "throttle"
mahdiHash Oct 17, 2021
091e9a0
Remove an untranslated line
mahdiHash Oct 17, 2021
5ab57fd
Translate solution of "throttle"
mahdiHash Oct 17, 2021
5791c2d
Translate solution in "_js.view"
mahdiHash Oct 17, 2021
ecce1be
Apply suggestions from code review
mahdiHash Oct 18, 2021
42f7f19
Remove additional HTML tag
mahdiHash Oct 18, 2021
43944aa
Change "کول‌داون" to "آرام‌شدن"
mahdiHash Oct 18, 2021
de05b91
Change "کول‌داون" to "آرام‌شدن"
mahdiHash Oct 18, 2021
340f593
Change "کول‌داون" to "آرام‌شدن"
mahdiHash Oct 18, 2021
b19ada0
Change "کول‌داون" to "آرام‌شدن"
mahdiHash Oct 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function spy(func) {

function wrapper(...args) {
// using ...args instead of arguments to store "real" array in wrapper.calls
// wrapper.calls برای ذخیره کردن آرایه «واقعی» درون arguments به جای ...args استفاده از
wrapper.calls.push(args);
return func.apply(this, args);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
The wrapper returned by `spy(f)` should store all arguments and then use `f.apply` to forward the call.
دربرگیرنده که توسط `spy(f)` برگردانده می‌شود باید تمام آرگومان‌ها را ذخیره و سپس از `f.apply` برای ارسال کردن فراخوانی استفاده کند.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ importance: 5

---

# Spy decorator
# دکوراتور جاسوس

Create a decorator `spy(func)` that should return a wrapper that saves all calls to function in its `calls` property.
یک دکوراتور `spy(func)` بسازید که باید دربرگیرنده‌ای را برگرداند که تمام فراخوانی‌های تابع را درون ویژگی `calls` خودش ذخیره کند.

Every call is saved as an array of arguments.
هر فراخوانی به عنوان آرایه‌ای از آرگومان‍‌ها ذخیره می‌شود.

For instance:
برای مثال:

```js
function work(a, b) {
alert( a + b ); // work is an arbitrary function or method
alert( a + b ); // یک تابع یا متد داخواه است work تابع
}

*!*
Expand All @@ -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.
پی‌نوشت: این دکوراتور بعضی اوقات در انجام یونیت تست (unit-testing) کاربرد دارد. شکل پیشرفته آن `sinon.spy` در کتابخانه [Sinon.JS](http://sinonjs.org/) است.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The solution:
راه‌حل:

```js
function delay(f, ms) {
Expand All @@ -10,14 +10,14 @@ function delay(f, ms) {
}
```

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.
لطفا به چگونگی استفاده از تابع کمانی در اینجا توجه کنید. همانطور که می‌دانیم، تابع‌های کمانی `this` و `arguments` خودشان را ندارند پس `f.apply(this, arguments)` مقدار `this` و `arguments` را از دربرگیرنده می‌گیرند.

If we pass a regular function, `setTimeout` would call it without arguments and `this=window` (in-browser), so we'd need to write a bit more code to pass them from the wrapper:
اگر ما یک تابع معمولی را قرار دهیم، `setTimeout` آن را بدون آرگومان‌ها و `this=window` (در مرورگر) فراخوانی خواهد کرد، پس ما باید کمی بیشتر کد بنویسیم تا آن‌ها از طریق دربرگیرنده رد و بدل کنیم:

```js
function delay(f, ms) {

// added variables to pass this and arguments from the wrapper inside setTimeout
// قرار دهیم setTimeout و آرگومان‌ها را از طریق دربرگیرنده درون this متغیرهایی اضافه کردیم تا
return function(...args) {
let savedThis = this;
setTimeout(function() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ importance: 5

---

# Delaying decorator
# دکوراتور تأخیر انداز

Create a decorator `delay(f, ms)` that delays each call of `f` by `ms` milliseconds.
یک دکوراتور `delay(f, ms)` بسازید که هر فراخوانی `f` را به اندازه `ms` میلی‌ثانیه به تأخیر می‌اندازد.

For instance:
برای مثال:

```js
function f(x) {
Expand All @@ -17,10 +17,10 @@ function f(x) {
let f1000 = delay(f, 1000);
let f1500 = delay(f, 1500);

f1000("test"); // shows "test" after 1000ms
f1500("test"); // shows "test" after 1500ms
f1000("test"); // را بعد از 1000 میلی‌ثانیه نشان می‌دهد "test"
f1500("test"); // را بعد از 1500 میلی‌ثانیه نشان می‌دهد "test"
```

In other words, `delay(f, ms)` returns a "delayed by `ms`" variant of `f`.
به عبارتی دیگر، `delay(f, ms)` یک نوع از `f` که «به اندازه `ms` تأخیر دارد» را برمی‌گرداند.

In the code above, `f` is a function of a single argument, but your solution should pass all arguments and the context `this`.
در کد بالا، `f` تایعی است که یک آرگومان دارد اما راه‌حل شما باید تمام آرگومان‌ها و زمینه `this` را در فراخوانی قرار دهد.
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<!doctype html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Function <code>handler</code> is called on this input:
تابع <code>handler</code> روی این ورودی فراخوانی می‌شود:
<br>
<input id="input1" placeholder="type here">
<input id="input1" placeholder="اینجا تایپ کنید">

<p>

Debounced function <code>debounce(handler, 1000)</code> is called on this input:
تابع معلق <code>debounce(handler, 1000)</code> روی این ورودی فراخوانی می‌شود:
<br>
<input id="input2" placeholder="type here">
<input id="input2" placeholder="اینجا تایپ کنید">

<p>
<button id="result">The <code>handler</code> puts the result here</button>
<button id="result">تابع <code>handler</code> نتیجه را اینجا قرار می‌دهد</button>

<script>
function handler(event) {
Expand All @@ -21,4 +21,4 @@

input1.oninput = handler;
input2.oninput = _.debounce(handler, 1000);
</script>
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -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.
فراخوانی `debounce` یک دربرگیرنده را برمی‌گرداند. زمانی که فرا خوانده شد، زمان‌بندی می‌کند که تابع اصلی بعد از مدت `ms` داده شده فراخوانی شود و زمان‌بندی قبلی را لغو می‌کند.

Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,50 @@ 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.
نتیجه دکوراتور `debounce(f, md)` یک دربرگیرنده است که تا `ms` میلی‌ثانیه عدم فعالیت وجود نداشته باشد، فراخوانی‌های `f` را به حالت تعلیق در می‌آورد (فراخوانی انجام نمی‌شود، «مدت زمان آرام‌شدن») سپس `f` را با آخرین آرگومان‌ها فراخوانی می‌کند.

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`).
به عبارتی دیگر، `debounce` مانند یک منشی است که «تماس‌های تلفنی» را می‌پذیرد و تا زمانی که `ms` میلی‌ثانیه سکوت برقرار شود صبر می‌کند. و فقط بعد از این مدت اطلاعات آخرین تماس را به «رئیس» منتقل می‌کند (تابع واقعی `f` را فرا می‌خواند).

For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`.
برای مثال، ما یک تابع `f` داشتیم و آن را با `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.
سپس اگر تابع دربرگرفته شده در زمان‌های 0، 200، 500 میلی‌ثانیه فراخوانی شد، و پس از آن هیچ فراخوانی‌ای وجود نداشت، سپس تابع واقعی `f` فقط یک بار بعد از 1500 میلی‌ثانیه فراخوانی می‌شود. یعنی اینکه: پس از 1000 میلی‌ثانیه زمان آرام‌شدن از زمان آخرین فراخوانی.

![](debounce.svg)

...And it will get the arguments of the very last call, other calls are ignored.
...و این تابع آرگومان‌های آخرین فراخوانی را دریافت می‌کند و بقیه فراخوانی‌ها نادیده گرفته می‌شوند.

Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)):
اینجا کدی برای آن داریم (از دکوراتور معلق‌کننده در کتابخانه [Lodash library](https://lodash.com/docs/4.17.15#debounce) استفاده می‌کند):

```js
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")
// alert("c") :تابع معلق 1000 میلی‌ثانیه بعد از آخرین فراخوانی صبر می‌کند و سپس اجرا می‌شود
```

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.
حالا یک مثال عملی. بیایید فرض کنیم که کاربر چیزی تایپ می‌کند و ما می‌خواهیم یک زمانی که وارد کردن تمام شد یک درخواست به سرور ارسال کنیم.

There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result.
دلیلی برای فرستادن درخواست برای هر کاراکتر تایپ شده وجود ندارد. به جای آن ما می‌خواهیم صبر کنیم و سپس تمام نتیجه را پردازش کنیم.

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.
در یک مرورگر وب، ما می‌توانیم یک کنترل‌کننده رویداد ترتیب دهیم -- تابعی که برای هر تغییر در قسمت ورودی فراخوانی می‌شود. در حالت عادی، یک کنترل‌کننده رویداد خیلی فراخوانی می‌شود، برای هر کلید تایپ شده. اما اگر ما آن را به مدت 1000 میلی‌ثانیه `debounce`(معلق) کنیم، پس از 1000 میلی‌ثانیه بعد از آخرین ورودی، فقط یک بار فراخوانی می‌شود.

```online

In this live example, the handler puts the result into a box below, try it:
در این مثال زنده، کنترل‌کننده نتیجه را درون جعبه بیرون می‌گذارد، امتحانش کنید:

[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.
می‌بینید؟ ورودی دوم، تابع معلق را فراخوانی می‌کند پس محتوای آن پس از 1000 میلی‌ثانیه بعد از آخرین ورودی پردازش می‌شود.
```

So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else.
پس `debounce` راهی عالی برای پردازش دنباله‌ای از رویدادها است: چه دنباله‎ای از فشار دادن کلیدها باشد، چه حرکت مَوس(mouse) یا هر چیز دیگری.

It waits the given time after the last call, and then runs its function, that can process the result.
این تابع به اندازه زمان داده شده پس از آخرین فراخوانی صبر می‌کند و سپس تابع خودش که می‌تواند نتیجه را پردازش کند را فرا می‌خواند.

The task is to implement `debounce` decorator.
تمرین، پیاده‌سازی دکوراتور `debounce` است.

Hint: that's just a few lines if you think about it :)
راهنمایی: اگر درباره آن فکر کنید، فقط چند خط می‌شود :)
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,28 @@ function throttle(func, ms) {
function wrapper() {

if (isThrottled) {
// memo last arguments to call after the cooldown
// بخاطر سپردن آخرین آرگومان‌ها برای فراخوانی بعد از آرام‌شدن
savedArgs = arguments;
savedThis = this;
return;
}

// otherwise go to cooldown state
// در غیر این صورت به حالت آرام‌شدن برو
func.apply(this, arguments);

isThrottled = true;

// plan to reset isThrottled after the delay
// بعد از تأخیر isThrottled زمان‌بندی برای تنظیم مجدد
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
// آخرین آن‌ها را دارند savedThis/savedArgs ،اگر فراخوانی‌ای وجود داشت
// فراخوانی بازگشتی تابع را اجرا می‌کند و حالت آرام‌شدن را دوباره تنظیم می‌کند
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}

return wrapper;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ function throttle(func, ms) {
}
```

A call to `throttle(func, ms)` returns `wrapper`.
فراخوانی `throttle(func, ms)` تابع `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. در حین اولین فراخوانی، تابع `wrapper` فقط `func` را اجرا می‌کند و وضعیت آرام‌شدن را تنظیم می‌کند (`isThrottled = true`).
2. در این حالت، تمام فراخوانی‌ها در `savedArgs/savedThis` ذخیره می‌شوند. لطفا در نظر داشته باشید که هم زمینه و هم آرگومان‌ها به یک اندازه مهم هستند و باید به یاد سپرده شوند. ما برای اینکه فراخوانی جدید بسازیم به هر دوی آن‌ها نیاز داریم.
3. بعد از اینکه `ms` میلی‌ثانیه طی شد، `setTimeout` فعال می‌شود. حالت آرام‌شدن حذف می‌شود (`isThrottled = false`) و اگر ما فراخوانی نادیده‌گرفته‌شده‌ای داشتیم، `wrapper` همراه با آخرین آرگومان‌ها و زمینه ذخیره شده اجرا می‌شود.

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.
مرحله سوم `wrapper` را اجرا می‌کند نه `func` را، چون ما نه تنها نیاز داریم که `func` را اجرا کنیم بلکه باید دوباره به حالت آرام‌شدن برگردیم و زمان‌بندی را برای تنظیم مجدد آن پیاده‌سازی کنیم.
Loading