Skip to content
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

Decorators and forwarding, call/apply #208

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
// sử dụng ... args thay vì đối số để lưu trữ array "thực" trong wrapper.calls
wrapper.calls.push(args);
return func.apply(this, args);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function spy(func) {
// your code
// mã của bạn
}


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.
Wrapper được trả về bởi `spy(f)` nên lưu trữ tất cả các đối số và sau đó sử dụng `f.apply` để chuyển tiếp cuộc gọi.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ importance: 5

---

# Spy decorator
# Decorator gián điệp

Create a decorator `spy(func)` that should return a wrapper that saves all calls to function in its `calls` property.
Tạo một decorator `spy(func)` sẽ trả về một wrapper lưu tất cả các lệnh gọi để hoạt động trong thuộc tính `calls` của nó.

Every call is saved as an array of arguments.
Mỗi cuộc gọi được lưu dưới dạng một array đối số.

For instance:
Ví dụ:

```js
function work(a, b) {
alert( a + b ); // work is an arbitrary function or method
alert( a + b ); // công việc là một hàm hoặc phương thức tùy ý
}

*!*
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.
Tái bút: Decorator đó đôi khi hữu ích cho thử nghiệm đơn vị. Dạng nâng cao của nó là `sinon.spy` trong thư viện [Sinon.JS](http://sinonjs.org/).
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The solution:
Lời giải:

```js run demo
function delay(f, ms) {
Expand All @@ -11,22 +11,22 @@ function delay(f, ms) {

let f1000 = delay(alert, 1000);

f1000("test"); // shows "test" after 1000ms
f1000("kiểm tra"); // hiển thị "kiểm tra" sau 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.
Hãy lưu ý cách sử dụng arrow function ở đây. Như chúng ta đã biết, các arrow function không có `this` `arguments` riêng, vì vậy `f.apply(this, arguments)` lấy `this` `arguments` từ wrapper.

If we pass a regular function, `setTimeout` would call it without arguments and `this=window` (assuming we're in the browser).
Nếu chúng ta chuyển một hàm thông thường, `setTimeout` sẽ gọi nó mà không có đối số và `this=window` (giả sử chúng ta đang ở trong trình duyệt).

We still can pass the right `this` by using an intermediate variable, but that's a little bit more cumbersome:
Chúng ta vẫn có thể chuyển `this` bên phải bằng cách sử dụng một biến trung gian, nhưng điều đó hơi rườm rà hơn một chút:

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

return function(...args) {
let savedThis = this; // store this into an intermediate variable
let savedThis = this; // lưu cái này vào một biến trung gian
setTimeout(function() {
f.apply(savedThis, args); // use it here
f.apply(savedThis, args); // sử dụng nó ở đây
}, ms);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@ importance: 5

---

# Delaying decorator
# Decorator trì hoãn

Create a decorator `delay(f, ms)` that delays each call of `f` by `ms` milliseconds.
Tạo một decorator `delay(f, ms)` để trì hoãn mỗi lần gọi `f` bằng `ms` mili giây.

For instance:
Ví dụ:

```js
function f(x) {
alert(x);
}

// create wrappers
// tạo các wrapper
let f1000 = delay(f, 1000);
let f1500 = delay(f, 1500);

f1000("test"); // shows "test" after 1000ms
f1500("test"); // shows "test" after 1500ms
f1000("kiểm tra"); // hiển thị "kiểm tra" sau 1000ms
f1500("kiểm tra"); // hiển thị "kiểm tra" sau 1500ms
```

In other words, `delay(f, ms)` returns a "delayed by `ms`" variant of `f`.
Nói cách khác, `delay(f, ms)` trả về một biến thể "trễ bởi `ms`" của `f`.

In the code above, `f` is a function of a single argument, but your solution should pass all arguments and the context `this`.
Trong đoạn mã trên, `f` là một hàm của một đối số duy nhất, nhưng giải pháp của bạn phải chuyển tất cả các đối số và ngữ cảnh `this`.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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:
Hàm <code>trình xử lý</code> được gọi trên đầu vào này:
<br>
<input id="input1" placeholder="type here">
<input id="input1" placeholder="gõ ở đây">

<p>

Debounced function <code>debounce(handler, 1000)</code> is called on this input:
Hàm gỡ lỗi <code>debounce(handler, 1000)</code> được gọi trên đầu vào này:
<br>
<input id="input2" placeholder="type here">
<input id="input2" placeholder="gõ ở đây">

<p>
<button id="result">The <code>handler</code> puts the result here</button>
<button id="result"><code>Trình xử lý</code> đặt kết quả ở đây</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.
Lệnh gọi `debounce` trả về một wrapper. Khi được gọi, nó lên lịch cuộc gọi hàm ban đầu sau `ms` đã cho và hủy bỏ thời gian chờ như vậy trước đó.

Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,50 @@ importance: 5

---

# Debounce decorator
# Decorator gỡ lỗi

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.
Kết quả của decorator `debounce(f, ms)` là một wrapper tạm dừng các lệnh gọi đến `f` cho đến khi có `ms` mili giây không hoạt động (không có lệnh gọi, "thời gian hồi"), sau đó gọi `f` một lần với các đối số mới nhất .

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`).
Nói cách khác, `debounce` giống như một thư ký chấp nhận "các cuộc điện thoại" và đợi cho đến khi có `ms` mili giây để im lặng. Và chỉ khi đó nó mới chuyển thông tin cuộc gọi mới nhất cho "ông chủ" (gọi `f` thực tế).

For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`.
Chẳng hạn, chúng ta có một hàm `f` và thay thế nó bằng `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.
Sau đó, nếu hàm được gói được gọi ở 0ms, 200ms 500ms, sau đó không có lệnh gọi nào, thì `f` thực tế sẽ chỉ được gọi một lần, ở 1500ms. Đó là: sau khoảng thời gian hồi 1000ms kể từ lần gọi cuối cùng.

![](debounce.svg)

...And it will get the arguments of the very last call, other calls are ignored.
...Và nó sẽ nhận được các đối số của cuộc gọi cuối cùng, các cuộc gọi khác sẽ bị bỏ qua.

Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)):
Đây là mã cho nó (sử dụng decorator gỡ lỗi từ [thư viện Lodash](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")
// hàm gỡ lỗi đợi 1000 mili giây sau cuộc gọi cuối cùng rồi chạy: 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.
Bây giờ là một ví dụ thực tế. Giả sử, người dùng nhập nội dung nào đó và chúng ta muốn gửi yêu cầu đến máy chủ khi quá trình nhập kết thúc.

There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result.
Không có lý do nào để gửi yêu cầu cho mọi ký tự được nhập. Thay vào đó, chúng ta muốn đợi và sau đó xử lý toàn bộ kết quả.

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.
Trong trình duyệt web, chúng ta có thể thiết lập trình xử lý sự kiện -- một hàm được gọi trên mọi thay đổi của vùng nhập vào. Thông thường, một trình xử lý sự kiện được gọi rất thường xuyên cho mỗi phím được nhập. Nhưng nếu chúng ta `debounce` nó sau 1000 mili giây, thì nó sẽ chỉ được gọi một lần, sau 1000 mili giây sau lần nhập cuối cùng.

```online

In this live example, the handler puts the result into a box below, try it:
Trong ví dụ trực tiếp này, trình xử lý đặt kết quả vào hộp bên dưới, hãy thử:

[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.
Nhìn thấy? Đầu vào thứ hai gọi hàm gỡ lỗi, vì vậy nội dung của nó được xử lý sau 1000 mili giây kể từ đầu vào cuối cùng.
```

So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else.
Vì vậy, `debounce` là một cách tuyệt vời để xử lý một chuỗi sự kiện: có thể là chuỗi các lần nhấn phím, di chuyển chuột hoặc thứ gì đó khác.

It waits the given time after the last call, and then runs its function, that can process the result.
Nó đợi thời gian nhất định sau cuộc gọi cuối cùng, rồi chạy hàm của nó, có thể xử lý kết quả.

The task is to implement `debounce` decorator.
Nhiệm vụ là triển khai decorator `debounce`.

Hint: that's just a few lines if you think about it :)
Gợi ý: đó chỉ là một vài dòng nếu bạn nghĩ về nó :)
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
// ghi nhớ đối số cuối cùng để gọi sau thời gian hồi
savedArgs = arguments;
savedThis = this;
return;
}

// otherwise go to cooldown state
// nếu không thì chuyển sang trạng thái hồi
func.apply(this, arguments);

isThrottled = true;

// plan to reset isThrottled after the delay
// kế hoạch thiết lập lại isThrottled sau sự chậm trễ
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
// nếu có cuộc gọi, thì saveThis/savedArgs có cuộc gọi cuối cùng
//cuộc gọi đệ quy chạy hàm và đặt lại thời gian hồi
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}

return wrapper;
}
}
Loading