preface

In JS, the + symbol is a very common one, and it has the following uses

  • Number addition, binary operation
  • String concatenation operation, binary operation, highest priority
  • The positive sign, unary operation, can be extended to cast other types of operators to numeric type

Another common use is curly braces {}, which are also common for two purposes

  • The literal definition of an object
  • Block statements

The plus operator +

In addition to the common cases described above, there are the following conversion rules in the standard, in order: operand + operand = result

  • Convert left and right operators ToPrimitive values using ToPrimitive (primitive)
  • After the first conversion, if one of the operands has a value of type “string”, the other operand is cast to a string and concatenation is performed.
  • In other cases, all the operands are converted to the “number” type of the original data type and then added mathematically.

ToPrimitive internal operation

Therefore, the plus operator can only be used with primitive data types. How do you convert values of object types to primitive data types?

JavaScript objects are converted ToPrimitive type values using the ToPrimitive algorithm, which is an internal algorithm and a set of rules that the programming language follows internally

In ECMAScript 6th Edition #7.1.1, there is an abstract ToPrimitive operation that can be used to convert objects ToPrimitive data types. This operation can be used not only for the plus operator but also for relational comparisons or value equality comparisons. Here is the explanatory syntax for ToPrimitive

ToPrimitive(input, PreferredType?) The input represents the value to be substituted, and the PreferredType can be either a Number or a String, which indicates which primitive type to convert the “preferred” or “preferred” to, and the conversion steps vary depending on the value

If the default is not provided, the hint value for the conversion is set to default. The preferred hint value for the conversion’s original type is automatically added by JS when an internal conversion is made, which is generally the default value

Transformation algorithm

When an object is converted to a value of the primitive type, the methods on the object are called using the following logic:

  • If obj[symbol.toprimitive] exists, objSymbol.toprimitive is called first

  • Otherwise, use the following rule: PreferredType indicates the Number

    • When the PreferredType is Number, the input is the value to be converted. Here are the steps to convert the input value
    • If input is a raw data type, return input directly
    • Otherwise, the valueOf() method of the object is called if input is an object, and the valueOf the original data type is returned if it can be retrieved
    • Otherwise, the object’s toString() method is called if input is an object and returns the value of the original data type, if available
    • Otherwise, TypeError is thrown

    PreferredType 为字符串 String

    • When the PreferredType is String, input is the value to be converted. Here are the steps to convert the input value:
    • Input is the raw data type, so return input directly
    • Otherwise, the object’s toString() method is called if input is an object and returns the value of the original data type, if available
    • Otherwise, the valueOf() method of the object is called if input is an object, and the valueOf the original data type is returned if it can be retrieved
    • Otherwise, TypeError is thrown

    Hint default if PreferredType is not provided

    • The procedure is the same as that when the PreferredType is Number

Symbol.toPrimitive

  • Symbol.toPrimitive is a built-in Symbol value that exists as a function value attribute of an object and is called when an object is converted to its original value
  • The function is called with a string parameter hint indicating the expected type of the original value to be converted to. The hint parameter is"number","string""default"Any one of the
  • Symbol. ToPrimitive is superior in type conversion, the first is the highest
let ab = {
    valueOf() {
        return 0;
    },
    toString() {
        return '1'; },Symbol.toPrimitive]() {
        return 2; }}console.log(1+ab); / / 3
console.log('1'+ab); / / 12
Copy the code

example


// The object with the Symbol. ToPrimitive property
var obj2 = {
  [Symbol.toPrimitive](hint) {
    if(hint == "number") {return 10;
    }
    if(hint == "string") {return "hello";
    }
    return true; }}console.log(+obj2);     //10 --hint in "number"
console.log(`${obj2}`); //hello --hint is "string"
console.log(obj2 + ""); //"true"

let obj = {
  [Symbol.toPrimitive](hint) {
    if(hint === 'number') {console.log('Number scene');
      return 123;
    }
    if(hint === 'string') {console.log('the String scene');
      return 'str';
    }
    if(hint === 'default') {console.log('the Default scene');
      return 'default'; }}}console.log(2*obj); // Number Scenario 246
console.log(3 + obj); // Default scenario 3default
console.log(obj + "");  // Default Scenario Default
console.log(String(obj)); / / the String STR
Copy the code

ValueOf and toString methods

In the Object prototype design of JS, there must be two valueOf and toString methods, so these two methods will be present in all objects, but they may change the order in which they are called during the conversion process

The use of toString and valueOf methods for primitive data

const str = "hello", n = 123, bool = true;
console.log(typeof(str.toString()) + "_" + str.toString()) // string_hello
console.log(typeof(n.toString()) + "_" + n.toString())  // string_123
console.log(typeof(bool.toString()) + "_" + bool.toString()) //string_true


console.log(typeof(str.valueOf()) + "_" + str.valueOf()) //string_hello
console.log(typeof(n.valueOf()) + "_" + n.valueOf()) //number_123
console.log(typeof(bool.valueOf()) + "_" + bool.valueOf()) //boolean_true

// console.log(str.valueof) => ƒ valueOf() {[native code]}
console.log(str.valueOf === str) // false
// console.log(n.valueof) => ƒ valueOf() {[native code]}
console.log(n.valueOf === n) // false
// bool.valueOf() => true
console.log(bool.valueOf() === bool) // true
Copy the code

For raw data, the toString method acts as a type conversion, converting the original type to a string. The valueOf method will have the same effect as returning the original data for raw type data

Compound object type data uses toString and valueOf methods

var obj = {};
console.log(obj.toString());    // [object object] Returns the object type
console.log(obj.valueOf());     {} returns the object itself
Copy the code

Integrated case

const test = { 
 i: 10.toString: function() { 
    console.log('toString'); 
    return this.i; 
 }, 
 valueOf: function() { 
    console.log('valueOf'); 
    return this.i; 
 } 
} 
alert(test); // 10 toString 
alert(+test); // 10 valueOf 
alert(' '+test); // 10 valueOf 
alert(String(test)); // 10 toString 
alert(Number(test)); // 10 valueOf 
alert(test == '10'); // true valueOf 
alert(test === '10'); // false
Copy the code

Add toString() and String() differences

  • Both the toString() and String() methods can be converted to strings
  • toString()
    • ToString () can convert all data to a string, but null and undefined are excluded. Null and undefined call toString() will return an error
    • If the current data type is numeric, a number can be written inside the toString() parentheses to represent the base, which can be converted to the corresponding base string
    var num = 123;
    console.log(num.toString() + '_' + typeof(num.toString()));   // 123_string   
    console.log(num.toString(2) + '_' + typeof(num.toString()));    // 1111011_string
    console.log(num.toString(8) + '_' + typeof(num.toString()));    // 173_string
    console.log(num.toString(16) + '_' + typeof(num.toString()));   //7b_string
    Copy the code
  • String() String() can convert null and undefined to a String, but not to a base String

The Symbol. ToPrimitive and toString methods must return primitive values. ValueOf can return other types as well as primitive values

Numbers are actually the default preferred type, meaning that in general, when a plus object is converted, valueOf is called first and then toString is called

Note: The default preferred type for Date objects is String

JS for Object and Array design

Object pure Object type valueOf and toString methods designed in JS, their return is as follows:

  • ValueOf returns the valueOf the object itself
  • The toString method returns:"[object Object]"String value. The return value for different built-in objects is"[object type]"string
    • typeRefers to the type recognition of the object itself, for example, the Math object is returned"[object Math]"string
    • But some built-in objects override this method, so direct calls do not have this value (note: this returns the first value of the string)"object"The beginning of English is lowercase, the beginning of English is uppercase)

Therefore, toString in Object can be used to determine different objects. This is often seen in the past when JS can use a few function libraries or methods, but it needs to use the call method in function, to output the correct Object type value, for example:

Object.prototype.toString.call([])  // "[object Array]"
Object.prototype.toString.call(new Date) // "[object Date]"
Copy the code

Both methods of the object can be overridden. To see the order in which the two methods are run, use the following code:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return {}; // object
  },
  toString: function () {
      console.log('toString');
      return 'obj'; // string}}console.log(1 + obj);  //valueOf -> toString -> '1obj'
console.log(+obj); // // valueOf -> toString -> NaN
console.log(' ' + obj); // valueOf -> toString -> 'obj'
Copy the code

It is rare to call toString first, probably only when a Date object or force is being cast to a string

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return 1; // number
  },
  toString: function () {
      console.log('toString');
      return {}; // object
  }
}
alert(obj); // toString -> valueOf -> alert("1");
String(obj); // toString -> valueOf -> "1";
Copy the code

The following example causes an error because you can’t get the values of the original data types regardless of the order. The error message is “TypeError: Cannot convert object to primitive value”, Cannot convert object to primitive value”

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return {}; // object
  },
  toString: function () {
      console.log('toString');
      return {}; // object}}console.log(obj + obj); // valueOf -> toString -> error!
Copy the code

Array is a very common Array. Although it is an Object type, it is different from Object in design. Its toString is overridden.

  • ValueOf returns the Object itself (like Object)
  • The toString method returns: equivalent to the string returned by join(‘,’) with the array value, that is [1,2,3]. ToString () would be “1,2,3”, note this

Function is rarely used, and its toString is overridden, so it is not the toString of Object. ValueOf of Function and toString return values:

  • ValueOf returns the Object itself (like Object)
  • The toString method returns a string value for the code contained in the function

Number, String, and Boolean wrap objects

A wrapper object is an object designed by JS for the primitive data types number, string, and Boolean. All the attributes and methods used by these three primitive data types are provided in this above

The valueOf and toString methods that wrap objects are overridden on the prototype, so their return values are different from the normal Object design:

  • ValueOf method return value: the corresponding original data type value
  • The toString method returns the corresponding original data type value, the string value when converted to a string

The toString method is special. The toStrings in these three wrapper objects are explained as follows:

  • Number wraps the toString method of an object: It can have a pass parameter that determines the carry (2, 8, 16) when converted to a string.
  • ToString method of String wrapped object: returns the same result as valueOf in String wrapped object
  • Boolean Wraps the object’s toString method: returns"true""false"string

Note that the use of the cast functions Number(), String(), and Boolean() is often confused, as opposed to wrapping objects that must be instantiated using the new keyword, such as new Number(123), Number(‘123’) is a function that casts other types to numeric types

The Number(), String(), and Boolean() casts correspond to the ToNumber, ToString, and ToBoolean internal conversions in the ECMAScript standard. ToPrimitive is used to convert an object to its original data type and then to the value of the desired type

The instance

String + other primitive type

Strings have the highest precedence among the plus operations, and the addition of strings must be a concatenation. For all other primitive data types converted to strings, refer to the ToString comparison table in the ECMAScript standard. Here are some simple examples

'1' + 123 / / "1123"
'1' + false // "1false"
'1' + null // "1null"
1' + undefined // "1undefined"
Copy the code

Number + other primitive data types that are not strings

When adding numbers to other types, all other types take precedence over numbers except string concatenation. Therefore, all primitive data types except string must be converted to numbers for mathematical addition

1 + true // True to 1, false to 0 -> 2
1 + null // null converts to 0 -> 1
1 + undefined // undefined to NaN -> NaN
Copy the code

Primitive data types other than numbers/strings for addition

When a number is used directly with a primitive data type other than a string, it is converted to a number, independent of the string at all

true + true / / 2
true + null / / 1
undefined + null // NaN
Copy the code

[] + []

[] + [] / / ""
Copy the code
  • The two arrays are added in valueOf -> toString order
  • ValueOf returns the array itself, so the return valueOf toString is the original data type, whereas [] is converted to a string""
  • Therefore, the last operation is equivalent to adding two empty strings. According to the rule of addition, the second step is concatenation, where two empty strings are concatenated to produce an empty string

{} + {}

{} + {} // "[object Object][object Object]"
Copy the code
  • Two empty objects are added, again in valueOf -> toString order
  • ValueOf returns the object itself, so the return valueOf toString is the original data type, that is, {} is converted to a string"[object Object]"
  • So this last operation is equal to two"[object Object]"String addition, step 2 of the addition rules, is concatenation.

Special note: {} + {} has different results in different browsers

  • Some browsers, such as Firefox and The Edge browser, use the{} + {}Literally equivalent to+ {}Statement, because they think it starts with curly braces({)Is the beginning of a block statement rather than an object literal, so the first one will be skipped{}, think of the entire statement as a+ {}The statement of
  • Equivalent to forcing a numerical valueNumber({})The function call operation, i.eNumber("[object Object]")And what you end up with isNaN

If we add parentheses (()) to the first empty object, so that JS will think that the preceding object is an object, we can get the same result:

({}) + {} // "[object Object][object Object]"
Copy the code

The same result can be obtained by declaring the variable value of the object separately, as follows:

let foo = {}, bar = {};
foo + bar;
Copy the code

{a:1, b:2} {a:1, b:2} {a:1, b:2} {a:1, b:2} {a:1, b:2} {a:1, b:2} {a:1, b:2

{} + []

{} is treated as a block statement, as described above, but this time all browsers have the same result. If {} comes first and [] comes after, the preceding (left) operand is treated as a block statement rather than an object literal

So {} +[] is equivalent to the +[] statement, which is equivalent to the Number([]) operation that forces the value of the Number, that is, the Number(“”) operation, and the result is a 0 Number

{} + [] / / 0
{a: 1} + [1.2] / / + "1, 2 - > NaN
Copy the code

Note that if the first {} is followed by something else like an array, a number, or a string, the plus becomes a unary plus, which is a forced conversion to a number. This is a trap

[] + {}

[] + {} // "" + "[object Object]" -> "[object Object]"
[1.2] + {a:1}  / / "1, 2 [object object]." "
Copy the code

The Date object

The return values of valueOf and toString methods on the Date object:

  • ValueOf: The given time converted to UNIX time (since 1 January 1970 00:00:00 UTC), but a numeric value in microseconds
  • The toString method returns a string of localized times

The Date object is mentioned as an exception of the preferred type “string”. This differs from other objects (valueOf is called first and then toString is called) in that it takes precedence over toString to convert the plus sign. The result must be a string concatenation, as follows:

1 + (new Date()) // "1Mon May 17 2021 02:06:39 GMT+0800"
Copy the code

To get the valueOf return value from a Date object, use unary plus + to cast it to a numeric type, as in the following code:

+new Date(a)/ / 1621188445596
Copy the code

Symbol type

The newly added Symbol data type in ES6 is neither a general value nor an object. It does not have an internal automatic transformation design, so it cannot be directly used for addition operations