This article is part of my modern JavaScript development: Fundamentals of Syntax and Engineering Practice series. It mainly discusses the rules and implicit type conversion in JavaScript common operations such as addition, subtraction, multiplication, division, and comparison. All references covered in this article are listed in the JavaScript Data Structure Learning & Practice Reference Index.

In JavaScript, when we compare operations or add, subtract, multiply and divide operations, we often trigger the implicit type conversion mechanism of JavaScript. And that’s where it’s often confusing. For example, the console.log operation in the browser usually converts any value to a string and displays it, while the math operation first converts the value to a numeric type (except for a Date object) and then operates on it.

Let’s start by looking at a couple of typical JavaScript operator results. Hopefully, by the end of this section, we’ll have a reasonable explanation for each item:

// compare [] ==! [] / /trueNaN ! == NaN //true1 = =true // true2 = =true // false
"2"= =true // flase

null > 0 // false
null < 0 // false
null == 0 // false
null >= 0 // true/ / addtrue + 1 // 1
undefined + 1 // NaN

letobj = {}; {} + 1 // 1, where {} is treated as code block {1 + 1} + 1 // 1 obj + 1 // [object object]1 {} + {} // Chrome display"[object Object][object Object]"Firefox displays NaN [] + {} // [object object] [] + a // [object object] + [] // equivalent to +""= > {0} + + [] [] / / 0 a / / [object object] [2, 3] + [1, 2] / /'2,31,2'[2] / / + 1'21'
[2] + (-1) // "2-1"// Subtraction or other operations cannot concatenate strings, so NaN [2] -1 // 1 [2,3] -1 // NaN {} -1 // -1 is returned in the wrong string formatCopy the code

Conversion between primitive types

Primitive types in JavaScript include numeric types, strings, Booleans, and null types. The common conversion functions between primitive types are String, Number, and Boolean:

// String
let value = true;
console.log(typeof value); // boolean

value = String(value); // now value is a string "true"
console.log(typeof value); // string

// Number
let str = "123";
console.log(typeof str); // string

let num = Number(str); // becomes a number 123

console.log(typeof num); // number

let age = Number("an arbitrary string instead of a number");

console.log(age); // NaN, conversion failed

// Boolean
console.log( Boolean(1) ); // true
console.log( Boolean(0) ); // false

console.log( Boolean("hello")); //true
console.log( Boolean("")); //falseCopy the code

As a result, we have the following table of JavaScript primitives (including examples of conversions from compound types to primitives) :

The original value Convert to a numeric type To a string type Convert to Boolean type
false 0 “false” false
true 1 “true” true
0 0 “0” false
1 1 “1” true
“0” 0 “0” true
“1” 1 “1” true
NaN NaN “NaN” false
Infinity Infinity “Infinity” true
-Infinity -Infinity “-Infinity” true
“” 0 “” false
“20” 20 “20” true
“twenty” NaN “twenty” true
[] 0 “” true
[20] 20 “20” true
[10] NaN “10, 20” true
[“twenty”] NaN “twenty” true
[“ten”,”twenty”] NaN “ten,twenty” true
function(){} NaN “function(){}” true
{} NaN “[object Object]” true
null 0 “null” false
undefined NaN “undefined” false

ToPrimitive

In comparison operation and addition operation, the operation object on both sides of the operator is transformed into the original object. In JavaScript, this conversion is actually done by the ToPrimitive function. In fact, JavaScript automatically calls ToPrimitive to convert an object to its original type when it appears in a context that requires its original type to operate on. For example, the alert function, mathematical operator, and key as an object are typical scenarios. The signature of this function is as follows:

ToPrimitive(input, PreferredType?)Copy the code

To better understand how this works, we can simply implement it in JavaScript:

var ToPrimitive = function(obj,preferredType){
  var APIs = {
    typeOf: function(obj){
      return Object.prototype.toString.call(obj).slice(8,-1);
    },
    isPrimitive: function(obj){
      var _this = this,
          types = ['Null'.'Undefined'.'String'.'Boolean'.'Number'];
      returntypes.indexOf(_this.typeOf(obj)) ! = = 1; }}; // if obj is already the original object, return it directlyif(APIs.isPrimitive(obj)) {returnobj; } // For the Date type, its toString method is preferred; Otherwise, preferredType = (preferredType ===)'String' || APIs.typeOf(obj) === 'Date')?'String' : 'Number';
  if(preferredType==='Number') {if(APIs.isPrimitive(obj.valueOf())) { return obj.valueOf()};
    if(APIs.isPrimitive(obj.toString())) { return obj.toString()};
  }else{
    if(APIs.isPrimitive(obj.toString())) { return obj.toString()};
    if(APIs.isPrimitive(obj.valueOf())) { return obj.valueOf()};
  }
  throw new TypeError('TypeError');
}Copy the code

We can simply overwrite the valueOf method of an object and see that the result of the operation changes:

let obj = {
    valueOf:(a)= > {
        return 0;
    }
}

obj + 1 / / 1Copy the code

If we force both the valueOf and toString methods of an object to be overridden as methods that return an object, an exception is thrown directly.

obj = {
        valueOf: function () {
            console.log("valueOf");
            return {}; // not a primitive
        },
        toString: function () {
            console.log("toString");
            return {}; // not a primitive
        }
    }

obj + 1
// error
Uncaught TypeError: Cannot convert object to primitive value
    at <anonymous>:1:5Copy the code

It is worth noting that the result of the call to valueOf() is still an array, so the implicit conversion of an array type is a string. With the introduction of the Symbol type in ES6, JavaScript calls the object’s [symbol.toprimitive] method first to convert the object to its original type, and the order of method calls becomes:

  • whenobj[Symbol.toPrimitive](preferredType)If a method exists, it is called first.
  • If the preferredType parameter is String, try it in sequenceobj.toString()obj.valueOf();
  • If the preferredType parameter is Number or the default value, try it in sequenceobj.valueOf()obj.toString().

The signature of the [Symbol. ToPrimitive] method is:

obj[Symbol.toPrimitive] = function(hint) {
  // return a primitive value
  // hint = one of "string"."number"."default"
}Copy the code

We can also override this method to modify the performance of the object:

user = {
  name: "John",
  money: 1000,

  [Symbol.toPrimitive](hint) {
    console.log(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.money; }}; // conversions demo: console.log(user); // hint: string -> {name:"John"}
console.log(+user); // hint: number -> 1000
console.log(user + 500); // hint: default -> 1500Copy the code

Comparison operations

JavaScript provides two modes for strict comparison and cast comparison. Strict comparison (===) returns true only if the objects on both sides of the operator are of the same type and content, and false otherwise. The more widely used == operator first converts objects to the same type and then compares them. The <= and other operations are first converted to Primitives and then compared.

The standard equality operator (== and! =) uses the Abstract Equality Comparison Algorithm to compare the operation objects (x == y) on both sides of the operator. The main points of the Algorithm flow are extracted as follows:

  • If either x or y is NaN, false is returned;
  • Return true if x and y are either null or undefined (null == undefined // true); Otherwise return false (null == 0 // false);
  • If the x and y types are inconsistent and x and y are one of String, Number, or Boolean types, the toNumber function is used to convert x and y toNumber type for comparison.
  • If one of x and y is Object, the ToPrimitive function is used to convert it to the original type, and then the comparison is performed.

Let’s review [] ==! The ToPrimitive function is called to convert the [] object to a string “”; For the right! [], an explicit type conversion is first performed to convert it to false. The comparison then converts both sides of the operator to numeric type, that is, to 0, so the final comparison result is true. The result of null >= 0 being true was also described above, and ECMAScript states that >= is true if < is false.

Add operation

For addition, JavaScript first converts the objects around the operator to the Primitive type. JavaScript then performs the implicit type conversion first, and then the operation, if the appropriate implicit type conversion yields a meaningful value. For example, the expression value1 + value2 first calls ToPrimitive to convert the two operands ToPrimitive types:

prim1 := ToPrimitive(value1)
prim2 := ToPrimitive(value2)Copy the code

The valueOf method of an object other than the Date type will be called preferentially, and since the valueOf method of an array still returns an array type, its string representation will be returned. If either of priM1 and PRIM2 is a string after conversion, string concatenation is preferred. Otherwise, add.