Introducing problems

This article has been updated to the public account: want to keep a cat front

Today, we made a weekly surprise visit to the intern members of the front end group of the studio to inspect their mastery of JS basic skills. I gave this question:

const bool = new Boolean(false);
console.log(bool);

if(! bool) {console.log('ok');
} else {
    console.log('okk');
}
Copy the code

The first line instantiates an object and assigns it to bool by calling the JS built-in function with new. So the second line should print out as an object. In the conditional statement, bool is an object, so! Bool should be implicitly converted to false. So the second output should be OKk.

Everything seems smooth and reasonable, but on second thought, why? We seem to memorize rules all the time, but rarely explore the principles that hold them up.

For example, what do we think of when we look at this code?

  const str = "Uni";
  
  console.log(str.length);
Copy the code

Most of you are probably thinking about the console printing out the length of this string, and we’re doing an operation to get the length of the string. Yes, have we thought about str.length? STR is a basic type. Its value is stored in the stack, and the stack memory space is fixed. So why is it possible to do something like dot length to get an object property? STR is not an object. What’s going on here?

Such as:

  const foo = function() {
    console.log(this);
  }
  
  const obj = 'Uni';
  
  foo.call(obj);
Copy the code

We all know it works

Function.prototype.call([thisArg[,arg1,...,argN]])

Method to display binding this pointer to solve many of the problems of this pointing unknown. But what if we pass the base type of data to thisArg? What’s the result? What’s going to happen?

The reason we are confused about the above operation is that we do not know enough about boxing and unboxing in JS.

packing

Boxing is simply the operation of converting a base data type to the corresponding reference type.

And the packing is divided into two kinds: implicit packing and display packing.

According to packing

The display boxing is very simple, and it is a matter of manipulating the primitive data types with built-in objects or primitive wrapper types.

Such as:

  const name = new String("Uni");
Copy the code

In this case, our name is an object that can call the corresponding method or method on the prototype chain. Such as:

  const name = new String("Uni");
  String.prototype.age = "20";
  
  console.log(name.age);
Copy the code

We can also print this object to see:

  const name = new String("Uni");
  console.log(name);
Copy the code

We can see that the browser console prints something like this:

Where Length is a non-enumerable property.

Implicit packing

We just looked at the display boxing example and saw that length is an unenumerable property of String instantiation (of course String also has length itself). So why can we call the length property of a string that we declare as a literal?

For this code:

  const name = "Uni";
  const len = name.length;
Copy the code

In the JS engine it looks like this:

  const name = "Uni";
  let newName = new Object(name);
  const len = newName.length;
  newName = null;
Copy the code

Except that all of this is done immediately after name.length is accessed, and the instance generated to get Length is destroyed immediately.

To summarize the steps for implicit boxing:

  • Create an instance of the corresponding type
  • Invoke the desired method or property in the instance
  • Destroy the instance

This explains why we normally access a bit of a string with the [] form and the literal length of the string with the.length form. Take a closer look at the illustration that shows the example of boxing:

Getting the length of a string is a common scenario because of the constant planning method. It occurred to me that if we were implicitly boxing the length property of a string literal like this every time we accessed it, wouldn’t it be too performance costly? Such as:

  const str = "abc";
  
  for (let i = 0; i < str.length; ++i) {
    console.log(str[i]);
  }
Copy the code

Implicitly box the length of STR each time you try to determine I. Should I show the packing length first and then take it out and save it so that the performance can be reduced? Like this:

  const str = "abc";
  const len = new String(str);
  for (let i = 0; i < len; ++i) {
    console.log(str[i]);
  }
Copy the code

To test my idea, I did some research on the Internet and found that I was actually degrading performance! It turns out that the browser has some pre-processing for some of these commonly used implicit boxing, in order to reduce performance costs. So we operate like this will instead be clever clever mistake, will reduce JS execution efficiency…

Split open a case

Unboxing, the reverse of boxing, refers to the conversion of a reference type to the corresponding base type. ValueOf and toString methods of reference type are commonly used.

Here’s a quick question I asked at the beginning of the article:

const bool = new Boolean(false);
console.log(bool);

if(! bool) {console.log('ok');
} else {
    console.log('okk');
}
Copy the code

How do we get the console to print OK by unpacking it? Take a look:

const bool = new Boolean(false);
console.log(bool.valueOf());    // false

if(! bool.valueOf()) {console.log('ok');        // ok
} else {
    console.log('okk');
}
Copy the code