JavaScript details and some practical applications

Posted by v1ral on Fri, 12 Nov 2021 06:25:02 +0100

Cast type

Value type conversion

var a = 42;
var b = a + ""; //  Implicit cast
var c = String(a); //  Explicit cast
 Copy code

Abstract value operation

document.all   Is a false value object. that is  !! document.all   Value is   false.

Show cast

Convert date display to number:

use   Date.now()   To get the current timestamp, use   new Date(..).getTime()   To get the timestamp of the specified time.

Strange ~ operator:

~x   Roughly equivalent to  - (x+1). Strange, but relatively easy to explain:  ~ 42; // \- (42+1) ==> \-43

String in JavaScript   indexOf(..)   Method also follows this Convention. This method searches the specified substring in the string. If it is found, it returns the position of the substring (starting from 0), otherwise it returns - 1.

~   and   indexOf()   Together, you can cast the result cast type (actually just cast) to true / false values:

var a = "Hello World";
~a.indexOf("lo"); // - four  <--  It's worth it!

if (~a.indexOf("lo")) { // true
  //  Find a match!
}
Copy code

Parse non string:

Someone once make complaints about Tucao.   parseInt(..)   One of the pits:

parseInt( 1/0, 19 ); // 18
 Copy code

parseInt(1/0, 19)   Actually   parseInt("Infinity", 19). The first character is "I", and when the base number is 19, the value is 18.

In addition, there are some examples that seem strange but actually make sense:

parseInt(0.000008); //  0   ("0"   From   "0.000008")
parseInt(0.0000008); //  eight   ("8"   From   "8e-7")
parseInt(false, 16); //  two hundred and fifty   ("fa"   From   "false")
parseInt(parseInt, 16); //  fifteen   ("f"   From   "function..")
parseInt("0x10"); // 16
parseInt("103", 2); // 2
 Copy code

Implicit cast

Implicit cast between string and number

For example:

var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
 Copy code

Another example:

var a = [1,2];
var b = [3,4];
a + b; // "1,23,4"
Copy code

According to section 11.6.1 of the ES5 specification, if an operand is a string or can be converted to a string through the following steps, + will be spliced. If one of the operands is an object (including an array), first call the ToPrimitive abstract operation (section 9.1 of the specification), and then call the   [[DefaultValue]] (section 8.12.8 of the specification), with numbers as the context.

You may have noticed that this is related to   ToNumber   Abstract operations treat objects the same way (see section 4.2.2). Because of the array   valueOf()   The operation cannot get a simple base type value, so it calls instead   toString(). Therefore, the two arrays in the above example become "1,2" and "3,4"+ Splice them and return to "1,23,4".

In short, if one of the operands of + is a string (or a string can be obtained through the above steps), string splicing is performed; Otherwise, digital addition is performed.

Cast of symbols

ES6 allows explicit cast from symbol to string, but implicit cast will produce errors. The specific reasons are beyond the scope of this book.

For example:

var s1 = Symbol("cool");
String(s1); // "Symbol(cool)"
var s2 = Symbol("not cool");
s2 + ""; // TypeError
 Copy code

Symbols cannot be cast to numbers (both explicit and implicit errors), but they can be cast to Booleans (both explicit and implicit results are false)   true).

Due to the lack of consistency of rules, we should be more careful about the cast of symbols in ES6.

Fortunately, due to the special use of symbols, we will not often use its cast.

Loose equality and strict equality

The common misconception is "==   Check whether the values are equal===   Check that values and types are equal. It sounds reasonable, but it's not accurate enough. Many JavaScript books and blogs explain this, but unfortunately they are all wrong.

The correct explanation is:==   Allow cast in equality comparison, and  ===  Not allowed. "

Equality comparison between strings and numbers:

  • If Type(x) is a number and Type(y) is a string, the result of x == ToNumber(y) is returned.

  • If Type(x) is a string and Type(y) is a number, the result of ToNumber(x) == y is returned.

Equality comparison between other types and Boolean types:

  • If Type(x) is Boolean, the result of ToNumber(x) == y is returned;

  • If Type(y) is Boolean, the result of x == ToNumber(y) is returned.

null   and   undefined   Equality comparison between:

  • If x is null and y is undefined, the result is true.

  • If x is undefined and y is null, the result is true.

Equality comparison between object and non object:

  • If Type(x) is a string or number and Type(y) is an object, the result of x == ToPrimitive(y) is returned;

  • If Type(x) is an object and Type(y) is a string or number, the result of ToPromitive(x) == y is returned.

grammar

error

Use variables in advance

The ES6 specification defines a new concept called TDZ (temporary dead zone).

TDZ refers to the case where a variable in the code cannot be referenced because it has not been initialized.

The most intuitive example of this is in the ES6 specification   let   Block scope:

{
  a = 2; // ReferenceError!
  let a;
}
Copy code

a = 2   Try in   let a   initialization   a   This variable was previously used (its scope is   { .. }   Inside), this is   a   TDZ, will produce an error.

Interestingly, using typeof for undeclared variables will not produce an error (see Chapter 1), but an error will be reported in TDZ:

{
  typeof a; // undefined
  typeof b; // ReferenceError! (TDZ)
  let b;
}
Copy code

Callback

Save point callback

Construct a timeout verification tool:

function timeoutify(fn, delay) {
  var intv = setTimeout(function() {
    intv = null
    fn(new Error('Timeout!'))
  }, delay)

  return function() {
    //  Not timed out yet?
    if (intv) {
      clearTimeout(intv)
      fn.apply(this, arguments)
    }
  }
}
Copy code

Here is how to use it:

//  use  ‘ error-first   Style '   Callback design
function foo(err, data) {
  if (err) {
    console.error(err)
  }
  else {
    console.log(data)
  }
}

ajax('http://some.url.1', timeoutify(foo, 500))
Copy code

What if you're not sure if the API of interest will always execute asynchronously? You can create a version similar to this "proof of concept" version   asyncify(..)   Tools:

function asyncify(fn) {
  var orig_fn = fn,
    intv = setTimeout(function() {
      intv = null
      if (fn) fn()
    }, 0)

  fn = null

  return function() {
    //  Trigger too fast, before the timer intv trigger indicates that the asynchronous conversion occurs?
    if (intv) {
      fn = orig_fn.bind.apply(
        orig_fn,
        //  Add this of the wrapper to the parameter of the bind(..) call,
        //  And currying all incoming parameters
        [this].concat([].slice.call(arguments))
      )
    }
    //  Already asynchronous
    else {
      //  Call the original function
      orig_fn.apply(this, arguments)
    }
  }
}
Copy code

It can be used like this   asyncify(..):

function result(data) {
  console.log(a)
}

var a = 0

ajax('..pre-cached-url..', asyncify(result))
a++
Copy code

Whether the Ajax request is already in the cache and attempts to call the callback immediately, or it needs to be obtained from the network and completed asynchronously in the future, this code will always output 1 instead of 0 -- result(...) can only be called asynchronously, which means that a + + has the opportunity to run before result(...).

Promise

Promise trust issues

Callback not called

Provide a solution for timeout processing:

//  A Promise tool for timeout
function timeoutPromise(delay) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject('Timeout!')
    }, delay)
  })
}

//  Set foo() timeout
Promise.race([
  foo(),
  timeoutPromise(3000)
])
.then(
  function() {
    // foo(..) completed in time!
  },
  function(err) {
    //  Or foo() was rejected, or it just didn't finish on time
  //  Check err to see what happens
  }
)
Copy code

Chain flow

To further illustrate the link, let's generalize the delay Promise creation (no resolution message) process into a tool to reuse in multiple steps:

function delay(time) {
  return new Promise(function(resolve, reject) {
    setTimeout(resolve, time)
  })
}

delay(100) //  Step 1
  .then(function STEP2() {
    console.log("step 2 (after 100ms)")
    return delay(200)
  })
  .then(function STEP3() {
    console.log("step 3 (after another 200ms)")
  })
  .then(function STEP4() {
    console.log("step 4 (next Job)")
    return delay(50)
  })
  .then(function STEP5() {
    console.log("step 5 (after another 50ms)")
  })
Copy code

call   delay(200)   Create a promise that will be completed in 200ms, and then we start from the first one   then(..)   This promise is returned in the completion callback, which will result in a second error   then(..)   Wait for the 200ms promise.

Promise limitations

Sequence error handling

Promise's design limitations (chain call) cause a trap that people can easily get caught, that is, errors in promise chain can easily be inadvertently ignored.

There are other things to consider about Promise errors. Because a Promise chain is only a member Promise connected together, it does not identify the whole chain as an individual entity, which means that there is no external method to observe possible errors.

If a promise chain without an error handler is built, any error anywhere in the chain will continue to propagate in the chain until the reject handler is registered at a certain step. In this particular example, it is sufficient to have a reference to the last promise in the chain (p in the following code), because you can register the rejection handler there, and this handler can be notified of all propagated errors:

//  foo(..),   STEP2(..) and STEP3(..) are tools that support promise
var p = foo(42)
  .then(STEP2)
  .then(STEP3);
Copy code

Although it may be confusing here, the   p   Does not point to the first promise() call in the chain   foo(42)   The generated one), but points to the last promise, that is, from the call   then(STEP3)   The one in the.

Also, no step in the Promise chain explicitly handles its own errors. That means you can   p   Register a reject error handler on the. This handler will be notified of any error at any position in the chain:

p.catch(handleErrors); 
Copy code

However, if any step in the chain actually carries out its own error handling (possibly in a hidden or abstract invisible way), your   handleErrors(..)   You won't be notified. This may be what you want - after all, it's a "handled rejection" - but it may not be. Failure to get clear error notification (for a specific "processed" rejection) is also a defect, which limits the functionality of some use cases.

Basically, this is equivalent to the limitation of try... Catch: try... Catch may catch an exception and simply swallow it. So this is not a unique limitation of Promise, but it may be a trap we want to bypass.

Unfortunately, many times there are no references reserved for intermediate steps of Promise chain sequence. Therefore, without such a reference, you cannot associate an error handler to reliably check for errors.

Single value

Promise can only have one completion value or one rejection reason by definition. In simple examples, this is not a problem, but in more complex scenarios, you may find that this is a limitation.

The general recommendation is to construct a value encapsulation (such as an object or array) to hold such multiple information. This solution can work, but it is very ugly and cumbersome to encapsulate and unseal every step of the Promise chain.

  1. Split value

Sometimes you can use this as a signal that you should break down the problem into two or more promises.

Imagine you have a tool   foo(..), which can asynchronously generate two values (x and y):

function getY(x) { 
  return new Promise(function(resolve, reject){ 
    setTimeout(function(){ 
      resolve((3 * x) - 1); 
    }, 100); 
  });
} 

function foo(bar, baz) { 
  var x = bar * baz; 
  return getY(x).then(function(y){ 
    //  Encapsulate the two values into a container
    return [x, y]; 
  }); 
} 

foo(10, 20).then(function(msgs){ 
  var x = msgs[0]; 
  var y = msgs[1]; 
  console.log(x, y); // 200 599 
}); 
Copy code

First, let's reorganize   foo(..)   Returns the content of the, so that there is no need to   x   and   y   Encapsulated into an array value for transmission through promise. Instead, we can encapsulate each value into its own promise:

function foo(bar, baz) { 
  var x = bar * baz; 
  
  //  Return two   promise
  return [ 
    Promise.resolve(x), 
    getY(x) 
  ]; 
} 

Promise.all( 
  foo(10, 20) 
).then(function(msgs){ 
  var x = msgs[0]; 
  var y = msgs[1]; 
  console.log(x, y); 
}); 
Copy code

Is a promise array really better than a value array passed to a single promise? From a grammatical point of view, this is not an improvement.

However, this method is more in line with promise's design concept. If you need to refactor the code later   x   and   y   This method is much simpler if the calculations are separated. It is up to the calling code to decide how to arrange the two promises, rather than putting such details in   foo(..)   Internal abstraction, which is cleaner and more flexible. Used here   Promise.all([..]), of course, this is not the only option.

  1. Transfer parameters

var x = ..   and   var y = ..   The assignment operation is still a troublesome overhead. We can use some kind of function technique in auxiliary tools:

function spread(fn) { 
  return Function.apply.bind(fn, null); 
} 

Promise.all( 
  foo(10, 20) 
).then(spread(function(x, y){ 
  console.log(x, y); // 200 599 
})) 
Copy code

This will be better! Of course, you can make this function trick online to avoid additional AIDS:

Promise.all( 
  foo(10, 20) 
).then(Function.apply.bind( 
  function(x, y){ 
    console.log(x, y); // 200 599 
  },
  null
)); 
Copy code

These techniques may be clever, but ES6 gives a better answer: deconstruction. The array deconstruction assignment form looks like this:

Promise.all( 
  foo(10, 20) 
).then(function(msgs){ 
  var [x, y] = msgs; 
  console.log(x, y); // 200 599 
}); 
Copy code

Best of all, ES6 provides the deconstruction form of array parameters:

Promise.all( 
  foo(10, 20) 
) 
.then(function([x, y]){ 
  console.log(x, y); // 200 599 
}); 
Copy code

Now, we are in line with the concept of "one value for each Promise" and keep the amount of duplicate template code to a minimum!

Single resolution

The most essential feature of Promise is that Promise can only be decided once (completed or rejected). In many asynchronous situations, you only get a value once, so this works well.

However, there are many asynchronous situations that are suitable for another pattern - a pattern similar to events or data flows. On the surface, it is unclear whether promise can be well used for such use cases, if not completely unavailable. Promise cannot support multi value resolution processing at all without building a significant abstraction on promise.

Imagine a scenario where you might start a series of asynchronous steps in response to a stimulus that may occur many times (like an event), such as a button click.

This may not work as you expect:

//  click(..)   Bind the "click" event to a   DOM   element
//  request(..)   Is the support defined earlier   Promise   of   Ajax  
var p = new Promise(function(resolve, reject){ 
  click("#mybtn", resolve); 
}); 

p.then(function(evt){ 
  var btnID = evt.currentTarget.id; 
  return request("http://some.url.1/?id=" + btnID); 
}).then(function(text){ 
  console.log(text); 
}); 
Copy code

This works only if your application only needs to respond to one click of the button. If this button is clicked a second time, promise p has been decided, so the second one   resolve(..)   The call is ignored.

Therefore, you may need to transform this example to create a new Promise chain for each event:

click("#mybtn", function(evt){ 
  var btnID = evt.currentTarget.id; 
  request("http://some.url.1/?id=" + btnID).then(function(text){ 
    console.log(text); 
  }); 
}); 
Copy code

This method works because a new Promise sequence is started for each "click" event on the button.

This is ugly because you need to define the entire Promise chain in the event handler. In addition, this design destroys the idea of separation of concerns and functions (SoC) to some extent. You probably want to put the definition of the event handler function and the definition of the response to the event (the Promise chain) in different places in the code. If there is no auxiliary mechanism, it is difficult to achieve this in this mode.

Click on the bottom card / WeChat search to pay attention to the official account of "ID:gh_cc865e4c536b" (Tian Yuwen).

It's said that those who praise and pay attention to this number have found a beautiful little sister, and they will enter a million years later!!

Previous recommendation

[

Spark JS open source low code scenario workbench, which automatically generates code for you

](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487682&idx=2&sn=26d15f07ffccf6454c6aa08c04e00230&chksm=ebb6f3a1dcc17ab7973d3844f15161bcbec19c7ef85a32ce0ae590dd96df76a5d3b6327e3097&scene=21#wechat_redirect)

[

Front end code optimization, and daily use skills

](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487682&idx=4&sn=7dc9a838847145f31ebfb2a8cca46960&chksm=ebb6f3a1dcc17ab7e58fc1a20f60843e3ff656c066f22fc616f9ecf5adbc1dce8baa5523cfae&scene=21#wechat_redirect)

[

nvm installation and use

](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487379&idx=1&sn=fb06b04c517731e2ec2b2baa43635a9f&chksm=ebb6ecf0dcc165e6ef1c9b889a3f42ede75c84f9c3b8359d9e39587aed6c1534f7119631e316&scene=21#wechat_redirect)

[

One click to generate the design draft of the UI little sister, and the development efficiency is improved by 100%

](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487088&idx=3&sn=e977dc4c7a2b5e681bd621b20023caaf&chksm=ebb6ed13dcc164056f688f7c4fd2cfc4ed045bec27049efc2c75c01189dcb992ee61c781098a&scene=21#wechat_redirect)

[

Spring Boot + Vue development example music playback app

](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247486140&idx=2&sn=db97688d38832894fb5fc4cc1808461a&chksm=ebb6e9dfdcc160c9a6b83afc98e98d21ce1e1ae835c2ac2da3749b32f4c5b0e4310a86f4f33c&scene=21#wechat_redirect)

Topics: Javascript ECMAScript