You get a bonus - 1 coin for daily activity. Now you have 1 coin

Clone setTimeout and setInterval

Lecture




  1. setTimeout
    1. Cancel execution clearTimeout
  2. setInterval
  3. Recursive setTimeout with NFE
  4. Timing subtleties with setInterval
  5. Repeat nested setTimeout
  6. Min Timer Delay
  7. Actual frequency of operation
  8. Breakdown of long scripts
  9. SetTimeout trick setTimeout(func, 0)
  10. Total

Almost all implementations of JavaScript have an internal timer scheduler that allows you to set a function call after a specified period of time.

In particular, this feature is supported in browsers and in the Node.JS server.

setTimeout

Syntax:

var timerId = setTimeout(func/code, delay[, arg1, arg2...])

Options:

func/code
Function or line of code to execute.
The string is supported for compatibility, it is not recommended to use it.
delay
The delay in milliseconds, 1000 milliseconds, is 1 second.
arg1 , arg2 ...
Arguments to pass to the function. Not supported in IE9-.

The function will be executed after the time specified in the delay parameter.

For example, the following code will trigger an alert('Привет') in one second:

1 function func() {
2    alert( 'Привет' );
3 }
4 setTimeout(func, 1000);

If the first argument is a string, the interpreter creates an anonymous function from this string.

That is, such a record works in the same way:

1 setTimeout( "alert('Привет')" , 1000);

The use of strings is not recommended, as they can cause problems when minimizing code, and, in general, the very possibility of using a string is retained only for compatibility.

Instead, use anonymous functions:

1 setTimeout( function () { alert( 'Привет' ) }, 1000);

Cancel execution clearTimeout

The setTimeout function returns a timerId that can be used to cancel an action.

Syntax: clearTimeout(timerId) .

In the following example, we set a timeout and then delete (change your mind). As a result, nothing happens.

1 var timerId = setTimeout( function () { alert(1) }, 1000);
2
3 clearTimeout(timerId);

setInterval

The setInterval method has a syntax similar to setTimeout .

var timerId = setInterval(func/code, delay[, arg1, arg2...])

The meaning of the arguments is the same. But, unlike setTimeout , it starts the function execution not once, but regularly repeats it after a specified time interval. You can stop execution by calling clearInterval(timerId) .

The following example will display a message every two seconds at startup until 5 seconds has passed:

1 var timerId = setInterval( function () {
2    alert( "тик" );
3 }, 2000);
4
5 setTimeout( function () {
6    clearInterval(timerId);
7    alert( 'стоп' );
8 }, 5000);

Modal windows in Safari / Chrome / Opera block timer

What happens if you do not press OK for a long time on the alert that appears?

It depends on the browser.

In Chrome, Opera and Safari browsers, the internal timer “freezes” during the alert/confirm/prompt display and continues counting from the time it is closed. Therefore, after closing the alert in any case, it will take two seconds to the next tick.

But in IE and Firefox, the internal timer will continue to go, and if you do not close the alert long time, the next call may be appropriate. But, since the browser cannot show the new alert while this one is open, it will wait for pressing OK , and then it will show the new alert right away .

Recursive setTimeout with NFE

It so happens that the function must be performed at a certain interval, but the next interval is determined on the basis of its last result.

The real example is that we are trying to connect to the server to receive data, but this does not work, for example, because the server is overloaded (who knows, maybe an attack on it).

In order not to load the server even more, we will make the next data request at a slightly longer interval, then if there is still a problem - even more, and so on up to the maximum interval, when we stop the auto-queries and suggest the user decide what to do. Something like this, for example, Gmail.

Here, so as not to be distracted by other topics,

For example, we will display an alert with a random interval:

For example, we are writing an application that tries once a second to join the server and retrieve data from it. The connect() function will do this.

Timing subtleties with setInterval

A call to setInterval(функция, задержка) puts the функцию to be executed at a specified time interval. But there is a subtlety.

In fact, the pause between calls is less than the specified interval.

For example, take setInterval(function() { func(i++) }, 100) . It performs func every 100 ms, increasing the counter value each time.

In the picture below, the red block is the func execution time. The time between blocks is the time between starts of the function, and it is less than the set delay!

  Clone setTimeout and setInterval

That is, the browser initiates the launch of the function carefully every 100мс , without taking into account the execution time of the function itself.

It happens that the execution of a function takes more time than the delay. For example, the function is complex, and the delay is small. Or the function contains alert/confirm/prompt statements that block the flow of execution. In this case, interesting things start.

If the launch of the function is not possible, because the browser is busy, it will be queued and executed as soon as the browser is free.

The image below illustrates what is happening for a function that takes a long time to execute.

The function call initiated by setInterval is added to the queue and immediately occurs when this becomes possible:

  Clone setTimeout and setInterval

The second launch of the function occurs immediately after the end of the first:

  Clone setTimeout and setInterval

More than once in the queue execution is not set.

If the execution of the function takes more time than several scheduled executions, then it will still stand in the queue once. So there is no “accumulation” of launches.

In the image below, setInterval tries to execute the function at 200 ms and puts the call in a queue. At 300 ms and 400 ms, the timer wakes up again, but nothing passes.

  Clone setTimeout and setInterval

Let's look at an example of how this works.

Modal windows in Safari / Chrome / Opera block timer

The internal timer in Safari / Chrome browsers does not tick during alert/confirm/prompt . If there are 3 seconds left before the execution, then even when the alert shown for a minute - the delay remains 3 seconds.

Therefore, the example below is not reproduced in these browsers. Other browsers are fine.

  1. Run the example below in any browser except Chrome / Safari and wait for the pop-up window. Please note that this is an alert . While the modal window is displayed, the execution of JavaScript is blocked. Wait a bit and click OK.
  2. You should see that the second launch will be right there, and the third after a short time from the second, less than 2000 ms.
  3. To stop the repetition, click the Стоп button.

1 < input type = "button" onclick = "clearInterval(timer)" value = "Стоп" >
2
3 <script>
4    var i = 1;
5    var timer = setInterval( function () { alert(i++) }, 2000);
6 </script>

The following happens.

  1. The browser performs a function every 2 seconds.
  2. When the alert window pops up - the execution is blocked and remains blocked all the time while the alert displayed.
  3. If you wait long enough, then the internal watch goes. The browser puts the next execution in the queue, once (in Chrome / Safari, the internal timer does not go! This is an error in the browser).
  4. When you click OK , the execution that was in the queue is instantly called.
  5. The next performance will be called up with less delay than specified. This is because the scheduler wakes up every 2000ms. And if the alert was closed at the time point corresponding to 3500 ms from the beginning, then the next execution is assigned to 4000 ms, i.e. will happen in 500ms.

Calling setInterval(функция, задержка) does not guarantee a real delay between executions.

There are cases when the real delay is more or less than the specified one. In general, not the fact that there will be at least some delay.

Importance: 5

Write a function that sequentially outputs numbers from 1 to 20 in the console, with the interval between the numbers 100ms. That is, all output should occupy 2000ms, during which every 100ms the next number appears in the console.

Click on the button, opening the console, to demonstrate:

Problem solving should use setInterval .

Decision

01 function printNumbersInterval20_100() {
02    var i = 1;
03    var timerId = setInterval( function () {
04      console.log(i);
05      if (i == 20) clearInterval(timerId);
06      i++;
07    }, 100);
08 }
09
10 // вызов
11 printNumbersInterval20_100();

[Open task in new window]

Repeat nested setTimeout

In cases when not just regular repetition is necessary, but a delay between starts is required, the re-setting of setTimeout used each time the function is executed.

Below is an example that issues an alert at intervals of 2 seconds between them .

01 < input type = "button" onclick = "clearTimeout(timer)" value = "Стоп" >
02
03 <script>
04    var i = 1;
05
06    var timer = setTimeout( function run() {
07      alert(i++);
08      timer = setTimeout(run, 2000);
09    }, 2000);
10
11 </script>

On the timeline, there will be fixed delays between starts. Illustration for 100ms delay:

  Clone setTimeout and setInterval

Importance: 5

Do the same as in the task Output numbers every 100ms, but using setTimeout instead of setInterval .

Decision

01 function printNumbersTimeout20_100() {
02    var i = 1;
03    var timerId = setTimeout( function go() {
04      console.log(i);
05      if (i < 20) setTimeout(go, 100);
06      i++;
07    }, 100);
08 }
09
10 // вызов
11 printNumbersTimeout20_100();

[Open task in new window]

Min Timer Delay

The browser timer has the lowest possible delay. It varies from approximately zero to 4ms in modern browsers. In older it can be more and reach 15ms.

According to the standard, the minimum delay is 4ms. So there is no difference between setTimeout(..,1) and setTimeout(..,4) .

See the minimum resolution "live" by the following example.

In the example below, there are DIV'ы , each extended by a call to setInterval with the delay specified in it - from 0ms (top) to 20ms (bottom).

Run it in various browsers, in particular, in Chrome and Firefox. You will surely notice that the first few DIV'ов are animated at the same speed. This is precisely because the timer does not distinguish too small delays.

Open in new window Open in sandbox

The zero delay setTimeout and setInterval behavior has browser features.

  • In Opera, setTimeout(.., 0) is the same as setTimeout(.., 4) . It is performed less frequently than setTimeout(.. ,2) . This is a feature of this browser.
  • In Internet Explorer, the zero delay setInterval(.., 0) will not work. This concerns setInterval , i.e. setTimeout(.., 0) works fine.

The example below implements the same animation, but via setTimeout . If you look at it in various browsers, you can notice the differences from setInterval .

Open in new window Open in sandbox

Actual frequency of operation

Tripping may be much less common.

In some cases, the delay may not be 4ms, but 30ms or even 1000ms.

  • Most browsers (desktop first) continue to execute setTimeout/setInterval , even if the tab is inactive.

    At the same time, a number of them (Chrome, FF, IE10) reduce the minimum frequency of the timer, to 1 time per second. It turns out that in the “background” tab the timer will work, but rarely.

  • When running on battery, in a laptop, browsers can also reduce the frequency in order to run the code less often and save battery power. IE is particularly known for this. Reduction can reach several times, depending on the settings.
  • If the processor load is too high, JavaScript may not have time to process timers on time. However, some setInterval starts will be missed.

Conclusion: the 4ms frequency should be oriented, but you should not count.

Let's look at reducing the frequency in action on a small example.

When you click on the button below, setInterval(..., 90) launched, which lists the time intervals between the last 25 timer triggers. Run it. Go to another tab and return.


If your browser increases the timeout with background tab execution, you will see extended intervals marked in red.

In addition, you will definitely see that the timer is not perfectly accurate.

Displays spacing in the console

The code that is used in the example above and considers the time intervals between calls looks like this:

01 var timeMark = new Date;
02 setTimeout( function go() {
03    var diff = new Date - timeMark;
04
05    // вывести очередную задержку в консоль вместо страницы
06    console.log(diff);
07
08    // запомним время в самом конце,
09    // чтобы измерить задержку именно между вызовами
10    timeMark = new Date;
11
12    setTimeout(go, 100);
13 }, 100);

Breakdown of long scripts

Zero or short timeouts are also used to break the execution flow of “heavy” scripts.

For example, the script for syntax highlighting should analyze the code, create many color elements for highlighting and add them to the document - on a large file it will take a lot of time.

The browser will first eat 100% of the processor, and then it may indicate that the script runs too long.

In order to avoid this, the complex task is divided into parts, the execution of each part is started at a mini-interval after the previous one, to give the browser time. For example, it is planned to highlight 20 lines every 10ms.

Importance: 5

The task is to implement syntax highlighting in long code with the help of JavaScript, for an online code editor. This requires complex calculations, especially the processor loads the generation of additional page elements that visually highlight.

Therefore, we decide to process not all the code at once, which would lead to the hang of the script, but to break the work into parts: highlight 20 lines every 10 ms.

As we know, there are two options for implementing such a backlight:

  1. Via setInterval , with a stop at the end of the work:
    1 timer = setInterval( function () {
    2    if (есть еще что подсветить) highlight();
    3    else clearInterval(timer);
    4 }, 10);
  2. Via nested setTimeout :
    1 setTimeout( function go() {
    2    highlight();
    3    if (есть еще что подсветить) setTimeout(go, 10);
    4 }, 10);

Which one is worth using? Why?

Decision

You need to choose option 2, which guarantees the browser free time between running highlight .

The first option can load the processor by 100% if highlight takes time close to 10ms or, moreover, more than 10ms, because the timer does not take into account the execution time of the function.

Interestingly, in both cases, the browser will not display a warning that the script takes a lot of time. But from 100% of the processor load, braking of other operations is possible. In general, this is not what we want, so option 2.

[Open task in new window]

SetTimeout trick setTimeout(func, 0)

This trick is worthy of entering the annals of JavaScript hacks.

The function is wrapped in setTimeout(func, 0) if they want to start it after the end of the current script.

The fact is that setTimeout never performs a function immediately. He only plans its implementation. But the JavaScript interpreter will start performing scheduled functions only after the execution of the current script.

By standard, setTimeout cannot perform a function with a delay of 0 anyway. As we said before, the delay is usually 4ms. But the main thing here is that the execution in any case will be after the execution of the current code.

For example:

01 var result;
02
03 function showResult() {
04    alert(result);
05 }
06
07 setTimeout(showResult, 0);
08
09 result = 2*2;
10
11 // выведет 4

Later, in the chapter Managing the Processing Order, setTimeout (... 0), we will look at the various uses of this trick when dealing with events.

Total

The setInterval(func, delay) and setTimeout(func, delay) methods allow you to run func regularly / once after a delay milliseconds.

Both methods return a timer identifier. It is used to stop a call to clearInterval/clearTimeout .

Features
setInterval setTimeout
Timing There is a call strictly on the timer. If the interpreter is busy, one call is queued.

The execution time of the function is not taken into account, so the time from the end of one run to the beginning of another may be different.

The recursive setTimeout call is used instead of setInterval where a fixed pause between executions is needed.
Delay Minimum delay: 4ms. Minimum delay: 4ms.
The minimum delay for these methods in modern browsers is different and ranges from approximately zero to 4 ms. In older browsers, it can reach up to 15ms.
Browser features In IE, the delay 0 does not work. In Opera, zero delay is equivalent to 4ms, the remaining delays are handled accurately, including non-standard 1ms, 2ms and 3ms.

Importance: 5

There are two runners:

var runner1 = new Runner();
var runner2 = new Runner();

Everyone has a step() method that takes a step.

Which of the two runners will be faster?

01 // первый?
02 setInterval( function () {
03    runner1.step();
04 }, 15);
05
06 // или второй?
07 setTimeout( function go() {
08    runner2.step();
09    setTimeout(go, 15);
10 }, 15);
11
12 setTimeout( function () {
13    alert(runner1.steps);
14    alert(runner2.steps);
15 }, 5000);

Who will take more steps? Why do you think so?

Decision

Usually the first runner will be faster.

But it is also possible that the time coincides.

Create real Runner objects and run them to check:

01 function Runner() {
02    this .steps = 0;
03
04    this .step = function () {
05      doSomethingHeavy();
06      this .steps++;
07    }
08
09    function doSomethingHeavy() {
10      for ( var i=0; i<10000; i++) {
11        this [i] = this .step + i;
12      }
13    }
14
15 }
16
17 var runner1 = new Runner();
18 var runner2 = new Runner();
19
20 // запускаем бегунов
21 setInterval( function () {
22    runner1.step();
23 }, 15);
24
25 setTimeout( function go() {
26    runner2.step();
27    setTimeout(go, 15);
28 }, 15);
29
30 // кто сделает больше шагов?
31 setTimeout( function () {
32    alert(runner1.steps);
33    alert(runner2.steps);
34 }, 5000);

  • If in step step() there was no call to doSomethingHeavy() , then the number of steps would be equal, since very little time is needed for such a step.

    The JavaScript interpreter tries to optimize such frequently repeated “bottlenecks” as much as possible. In this case, the step call would be reduced to a single microprocessor operation. This is negligible compared to the rest of the code.

  • Since we have a step, nevertheless, it does something, and the doSomethingHeavy() function is specially written in such a way that it cannot be reduced to a single operation, the first runner will have time to take more steps.

    After all, in setTimeout pause of 15 ms will be between steps, and setInterval step evenly, every 15 ms. It turns out more often.

  • Finally, there are browsers (IE9) in which the timer “does not tick” when executing JavaScript. For them, both options will behave like setTimeout , so the number of steps will be the same.
[Open task in new window]

Importance: 5

The execution of the function f takes about 1 second.

What will alert in the code below?

When will setTimeout work? Select the desired option:

  1. Before performing f .
  2. At runtime f .
  3. Immediately upon termination of f .
  4. 10ms after the end of f .

01 setTimeout( function () {
02    alert(i);
03 }, 10);
04
05 var i;
06
07 function f() {
08    // точное время выполнения не играет роли
09    // здесь оно заведомо больше задержки setTimeout
10    for (i=0; i<1e8; i++) f[i%10] = i;
11 }
12
13 f();

Decision

Calling alert(i) in setTimeout will print 100000000 , since the trigger will be guaranteed after the end of the current code.

The queue to scheduled calls always comes only after the end of the current script.

You can check this by running:

01 setTimeout( function () {
02    alert(i);
03 }, 10);
04
05 var i;
06
07 function f() {
08    // точное время выполнения не играет роли
09    // здесь оно заведомо больше задержки setTimeout
10    for (i=0; i<1e8; i++) f[i%10] = i;
11 }
12
13 f();

Answer to the second question: 3 (immediately after).

The call is scheduled for 10мс from the call time setTimeout , but the function is executed more than 10мс , so by the time it ends, the time has come and the call is on hold right there.

[Open task in new window]

Importance: 5

The execution of the function f takes about 1 second.

What will alert in the code below?

When will setInterval work? Select the desired option:

  1. Before executing f , during and after, interspersed with executing f .
  2. Run f , one time.
  3. During the execution of f , it is possible several times.
  4. Immediately after the end of f once.
  5. Immediately after the end of f , maybe several times.
  6. 10ms after the end of f , once.
  7. 10ms after the end of f , maybe several times.

Is this behavior cross-browser?

01 var timer = setInterval( function () {
02    i++;
03 }, 10);
04
05 setTimeout( function () {
06    clearInterval(timer);
07    alert(i);
08 }, 50);
09
10 var i;
11
12 function f() {
13    // точное время выполнения не играет роли
14    // здесь оно заведомо больше 50мс
15    for (i=0; i<1e8; i++) f[i%10] = i;
16 }
17
18 f();

Decision

Call alert(i) in setTimeout will enter 100000001 . Why - it will be clear from the answer to the second question.

You can check this by running:

01 var timer = setInterval( function () {
02    i++;
03 }, 10);
04
05 setTimeout( function () {
06    clearInterval(timer);
07    alert(i);
08 }, 50);
09
10 var i;
11
12 function f() {
13    // точное время выполнения не играет роли
14    // здесь оно заведомо больше 50мс
15    for (i=0; i<1e8; i++) f[i%10] = i;
16 }
17
18 f();

The answer to the second question: 4 (immediately after the end fonce).

Scheduling setIntervalwill call the function every 10мсtime after the current time. But since the interpreter is busy with a long function, no call takes place until the end of its work.

During the execution time, it fmay take time for which several calls are scheduled setInterval, but in this case only one remains, i.e. no call accumulation occurs. That is the logic of the work setInterval.

After the end of the current script, the interpreter accesses the queue of scheduled calls, sees in it setIntervaland executes. And then immediately executed setTimeout, the turn of which immediately came.

So, we see that it has been setIntervalexecuted exactly 1 time after the end of the function. This behavior is cross-browser.

[Open task in new window]

Importance: 5

Write a function delay(f, ms)that returns a wrapper around fthat delays the call by msmilliseconds.

For example:

1 function f(x) {
2    alert(x);
3 }
4
5 var f1000 = delay(f, 1000);
6 var f1500 = delay(f, 1500);
7
8 f1000( "тест" ); // выведет "тест" через 1000 миллисекунд
9 f1500( "тест2" ); // выведет "тест2" через 1500 миллисекунд

In other words, f1000this is a call delayed for 1000ms f.

In the example above, the function has only one argument, but delaymust be universal: transfer any number of arguments and context this.

Decision

01 function delay(f, ms) {
02
03    return function () {
04      var savedThis = this ;
05      var savedArgs = arguments;
06
07      setTimeout( function () {
08        f.apply(savedThis, savedArgs);
09      }, ms);
10    };
11
12 }
13
14 function f(x) {
15    alert(x);
16 }
17
18 var f1000 = delay(f, 1000);
19 var f1500 = delay(f, 1500);
20
21 f1000( "тест" ); // выведет "тест" через 1000 миллисекунд
22 f1500( "тест2" ); // выведет "тест2" через 1500 миллисекунд

Pay attention to how the wrapper works:

1 return function () {
2    var savedThis = this ;
3    var savedArgs = arguments;
4
5    setTimeout( function () {
6      f.apply(savedThis , savedArgs);
7    }, ms);
8 };

It is the wrapper that is returned by the decorator delayand will be called. To pass an argument and a context to a function called in msmilliseconds, they are copied into local variables savedThisand savedArgs.

This is one of the easiest, and at the same time convenient ways to pass something to a function called via setTimeout.

[Open task in new window]

Importance: 5

Write a function debounce(f, ms)that returns a wrapper that transfers a call fno more than once every msmillisecond.

"Extra" calls are ignored. All arguments and context are passed.

For example:

01 function f() { ... }
02
03 var f = debounce(f, 1000);
04
05 f(1); // выполнится сразу же
06 f(2); // игнор
07
08 setTimeout( function () { f(3) }, 100); // игнор (прошло только 100мс)
09 setTimeout( function () { f(4) }, 1100); // выполнится
10 setTimeout( function () { f(5) }, 1500); // игнор

The original document with a more detailed test: tutorial / timers / debounce-src.html

Decision

Solution: tutorial / timers / debounce.html

The call debouncereturns a wrapper function. All necessary data for it is stored in the circuit.

When you call, a timer is set and the state statechanges to a constant COOLDOWN(“in the process of cooling”).

Subsequent calls are ignored until the timer clears the state.

[Open task in new window]

Importance: 5

Напишите функцию throttle(f, ms) — «тормозилку», которая возвращает обёртку, передающую вызов f не чаще, чем раз в ms миллисекунд.

У этой функции должно быть важное существенное отличие от debounce : если игнорируемый вызов оказался последним, т.е. после него до окончания задержки ничего нет — то он выполнится.

Чтобы лучше понять, откуда взялось это требование, и как throttle должна работать — разберём реальное применение, на которое и ориентирована эта задача.

Например, нужно обрабатывать передвижения мыши. В JavaScript это делается функцией, которая будет запускаться при каждом микро-передвижении мыши и получать координаты курсора. По мере того, как мышь двигается, эта функция может запускаться очень часто, может быть 100 раз в секунду (каждые 10мс).

Функция обработки передвижения должна обновлять некую информацию на странице. При этом обновление — слишком «тяжёлый» процесс, чтобы делать его при каждом микро-передвижении. Имеет смысл делать его раз в 100мс, не чаще.

Пусть функция, которая осуществляет это обновление по передвижению, называется onmousemove .

Вызов throttle(onmousemove, 100) , по сути, предназначен для того, чтобы «притормаживать» обработку onmousemove . Технически, он должен возвращать обёртку, которая передаёт все вызовы onmousemove , но не чаще чем раз в 100мс.

При этом промежуточные движения можно игнорировать, но мышь в конце концов где-то остановится. И это последнее, итоговое положение мыши обязательно нужно обработать!

Визуально это даст следующую картину обработки перемещений мыши:

  1. Первое обновление произойдёт сразу (это важно, посетитель тут же видит реакцию на своё действие).
  2. Дальше будет много вызовов (микро-передвижений) с разными координатами, но пока не пройдёт 100мс — ничего не будет.
  3. По истечении 100мс — опять обновление, с последними координатами. Промежуточные микро-передвижения игнорированы.
  4. В конце концов мышь где-то остановится, обновление по окончании очередной паузы 100мс (иначе мы не знаем, последнее оно или нет) сработает с последними координатами.

Ещё раз заметим — задача из реальной жизни, а в ней принципиально важно, что последнее передвижение обрабатывается. Пользователь должен увидеть, где остановил мышь.

Чтобы было удобнее такой throttle писать, в исходном документе содержатся еще два теста: tutorial/timers/throttle-src.html

Decision

Решение: tutorial/timers/throttle.html

Вызов throttle возвращает функцию-обёртку. Все необходимые данные для неё хранятся в замыкании.

В первую очередь — это состояние state , которое вначале не назначено ( null ), при первом вызове получает значение COOLDOWN («в процессе охлаждения»), а при следующем вызове CALL_SCHEDULED .

При таймауте состояние проверяется, и если оно равно CALL_SCHEDULED — происходит новый вызов.

created: 2014-10-07
updated: 2024-11-14
435



Rating 9 of 10. count vote: 2
Are you satisfied?:



Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Scripting client side JavaScript, jqvery, BackBone

Terms: Scripting client side JavaScript, jqvery, BackBone