preface

You wish the world, I wish you no bugs. Hello everyone! I’m Lin Dull!

A few months ago, I read three yuan big guy’s “(suggested collection) original JS soul of the ask, can you catch a few?” Series, at that time was used to commute bus time brush. Say the feeling at that time, some knowledge points still don’t know really, feel good cow batch, really have a feeling of being tortured by the soul. The most terrible thing is that I didn’t realize that my foundation was so poor at that time. I just remembered some scattered knowledge points and forgot them in a few days. I always thought about when to organize my knowledge system but I didn’t take action.

At a certain point, perhaps stimulated by a certain article, my mentality suddenly changed a lot. How can I describe that feeling… It’s like I think I know it all, but I don’t know that I don’t know it, and THEN I hope that tomorrow will be better. Kind of feels like a frog in a well. I didn’t know the importance of the word precipitation until I really knew who I was. When I say, ‘Why is this? What else? ‘And’ What if? ‘to review some of the previous knowledge, I found myself really have a lot to add…

This article was originally some notes made by myself after reading “Sanyuan -JS Soul Questions” recently, but I found that the more I wrote… This has led to a series of articles I have written, such as the JS type conversion series, JS inheritance series, this, and so on. Here to three mentioned some questions to do some supplementary explanation, so that they become more suitable for junior and intermediate partners to read it, but also a consolidation of their own this stage of learning, there are wrong places to please point out.

I’ve been writing nuggets for a year or two and haven’t broken Lv4 yet. Set a flag here, rise Lv4 and then blow up women’s “dumb girl”, put “her” in the next article to arrange wave. Already very humble…

(Inherits to the original author god three yuan of gratitude to write, but also please three yuan 19177 fans do not misunderstand ah, I am also a small fan of his…)

Lin’s dull knowledge system

All articles are included in gitHub’s “niubility-coding-js”.

First complement: JS type base

1. ‘1’.tostring () can be called, but 1.tostring () can’t?

We know that if we use:

'1'.toString()
/ / or
true.toString()
Copy the code

This is because toString is a method on Object.prototype that can be called by any element that has access to the Object prototype.

Here, for ‘1’.toString(), a layer of conversion is done to turn it into an “object” so that the toString() method can be called.

And that’s it:

var s = new Object('1');
s.toString();
s = null;
Copy the code
  • createObjectInstance,sInto theString{"1"}object
  • callObject.prototypeInstance method ontoString()
  • Destroy the instance as soon as you’re done

This part of the ternary analysis is quite extensive, but I mainly want to add why 1.tostring () does not work.

When we tried to use 1.tostring () in our code, we found that the editor had already reported an error that would not allow us to do so.

The reason why this is so weird in the first place is because we’ve all overlooked one thing, and that is. It is also part of the number 😂.

Such as 1.2, 1.3. So when you want to use 1.tostring (), the JavaScript interpreter will treat it as part of a number, which is equivalent to (1.)toString, which is obviously wrong code.

In that case, if I give the code back one. Was that enough? So I tried:

console.log(1.1.toString())
Copy the code

And found that it actually printed:

"1.1"
Copy the code

This again proves that 1.toString() will. Ascribe to 1 instead of toString().

Of course, it works if you use a variable to carry the number:

var num = 1;
console.log(num.toString()) / / "1"

/ / or
console.log((1).toString()) / / "1"
Copy the code

2. Why can we use new Number but not New Symbol?

var num = new Number(1) // Number{1}
var str = new String('1') // String{'1'}
var bol = new Boolean(true) // Boolean{true}

var symbol = new Symbol(1) // TypeError
Copy the code

Basic data types like the one above created with new Number, new String, and so on are called creating an explicit wrapper object around the original data type.

In plain English: use new to create wrapper classes of primitive types.

Var Symbol = Symbol(1) is now used without new, so it is incomplete as a constructor.

However, for historical reasons, new Number can still be used in this way, but it is not recommended.

If you really want to create a Symbol wrapper object, you can use the object () function:

var sym = Symbol(1)
console.log(typeof sym) // "symbol"
var symObj = Object(sym)
console.log(typeof symObj) // "object"
Copy the code

Second complement: JS type detection

1. Instanceof Can you determine basic data types?

What do you mean 🤔️?

Normally, instanceof is used to determine whether a constructor’s prototype object can be found on the prototype chain of an object.

Take a look at the introduction of the popular point:

a instanceof B

Instance object a instanceof constructor B

Checks if there is a B.prototype on a’s prototype chain (__proto__), returning true if there is, false otherwise.

So can it be used to determine basic data types?

Var num = 1 var num = 1 var num = 1

If you try to write:

var num = 1;
console.log(num instanceof Number) // false
Copy the code

The result is false.

At this point you can implement a custom instanceof behavior using symbol.hasInstance.

First of all, what are we trying to implement?

a instanceof B
Copy the code

A on the left is a variable, B on the right is a constructor, and class is essentially a constructor.

So in this requirement, we can define a class called MyNumber and wrap a layer inside it that exposes a static method to determine the data type:

class MyNumber {
    static [Symbol.hasInstance](instance) {
        return typeof instance === 'number'}}var num = 1;
console.log(num instanceof MyNumber) // true
Copy the code
  • In the classMyNumberIs defined with a name calledSymbol.hasInstanceStatic method of
  • This method receives an instance objectinstance
  • The return value istypeofDetermine whether or notnumbertype

Here is more difficult to understand Symbol. HasInstance, the first contact with it does not know what it is 😂. Found a wave of MDN references to it:

Used to determine whether an object is an instance of a constructor.

Then I tried to write a few cases, and found that it doesn’t have to be so complicated, you can simply understand that when we use Instanceof, we can customize the right constructor (class) and its Instanceof validation mode.

As in the 👆 example above, I override the static method symbol. hasInstance in MyNumber to validate type instance === ‘number’.

So why is a static method (that is, in front of the method combined with the static), by reading “[why not three even] is more simple than on JS inheritance – encapsulation (wheel test)” as we know, the static method is mounted in the MyNumber methods on this class, so we can take the following content in another writing:

console.log(num instanceof MyNumber) // true
/ / replaced by:
console.log(MyNumber[Symbol.hasInstance](num)) // true
Copy the code

See, it’s just a method name, and this method is static on MyNumber, so we can call MyNumber[symbol.hasinstance].

What if there were no static?

Without static, methods defined in a class are mounted to the class’s prototype object. If we want to use this method, we can either call myNumber.prototype directly or generate an instance using new MyNumber() :

class MyNumber {[Symbol.hasInstance](instance) { / / not static
        return typeof instance === 'number'}}var num = 1

console.log(num instanceof new MyNumber()) // true
console.log(num instanceof MyNumber.prototype) // true

// Convert to:
console.log(MyNumber.prototype[Symbol.hasInstance](num)) // true
console.log(new MyNumber()[Symbol.hasInstance](num)) // true
Copy the code

So let’s go back now:

class MyNumber {
    static [Symbol.hasInstance](instance) {
        return typeof instance === 'number'}}var num = 1;
console.log(num instanceof MyNumber) // true
Copy the code

Doesn’t it make a lot of sense?

So what if I wanted you to implement a class MyArray that uses instanceof to determine if it’s an array?

Thinking 🤔…

Well… The answer:

class MyArray {  
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance); }}console.log([] instanceof MyArray); // true
Copy the code

2. Search route of Instanceof?

Instanceof is used to determine whether a constructor’s prototype object can be found on the prototype chain of an object.

And it goes up the prototype chain layer by layer until it reaches the end of the prototype chain.

So how does this process work? Let’s look at an example 🌰 :

function Parent () {
  this.name = 'parent'
}
function Child () {
  this.sex = 'boy'
}
Child.prototype = new Parent()
var child1 = new Child()

console.log(child1 instanceof Child)
console.log(child1 instanceof Parent)
console.log(child1 instanceof Object)
Copy the code

The result is:

true
true
true
Copy the code

In this case, we use the prototype chain inheritance, Chind inherits from Parent, and all three constructor prototype objects exist in the prototype chain of child1.

In other words, child1, on the left, will constantly look through its prototype chain to see if there is a prototype object for the constructor on the right.

Child1 instanceof Child

child1 -> child1.__proto__ -> Child.prototype
Copy the code

Child1 instanceof Parent

child1 -> child1.__proto__ -> Child.prototype
-> Child.prototype.__proto__ -> Parent.prototype
Copy the code

Don’t get it?

That’s okay. I have another trick:

I added three search paths to the mind map of 👆 archetypal chain inheritance above.

1, 2, and 3 marked ⭕️ represent the prototype objects of Child, Parent, and Object respectively.

Good, one picture is neat and clear. Later encountered instanceof this kind of thing, according to my map to find the route to find it can 😁 ~

3. What does isPropertypeOf() do?

Now that we’re talking about instanceof, we have to mention the isPrototypeOf method.

It belongs to the object. prototype method, which you can print in the console.

IsPrototypeOf () is used the opposite of instanceof.

Object1 is used to determine if the specified object exists in the prototype chain of another object object2, returning true if it exists, and false otherwise.

For example, in the above 👆 question, the content we will print will be changed:

function Parent () {
  this.name = 'parent'
}
function Child () {
  this.sex = 'boy'
}
Child.prototype = new Parent()
var child1 = new Child()

console.log(Child.prototype.isPrototypeOf(child1))
console.log(Parent.prototype.isPrototypeOf(child1))
console.log(Object.prototype.isPrototypeOf(child1))
Copy the code

The output here is still three true:

true
true
true
Copy the code

The way to determine this is simply to inherit the prototype chain instanceof find mind map this map can be found in reverse.

More on instanceOf can be found here at 👇 :

💦【 why not three even 】 finish this 48 questions thoroughly understand JS inheritance (1.7W words with hot finishing – back to nature)

4. Object.is() and ===

  • for+ 0and0Are different in their judgment.
  • forNaNandNaNAre different in their judgment.
console.log(+0= = =0) // true
console.log(Object.is(+0.0)) // false
console.log(NaN= = =NaN) // false
console.log(Object.is(NaN.NaN)) // true
Copy the code

You don’t need to remember it, just understand that object.is () fixes these special cases on the basis of ===.

+0 and -0 should not be equal, as shown by the fact that 1 / +0 is Infinity and 1 / -0 is -infinity.

(But there’s no difference between +0 and 0)

NaN’s are all non-numbers, so they should be equal.

So the interior of object.is () actually does some fixing:

function is (x, y) {
  if (x === y) {
    returnx ! = =0 || 1 / x === 1 / y;
  } else {
    returnx ! == x && y ! == y; }}Copy the code

What can be understood here 🤔️?

  • whenx === yIs used to deal with+0, -0, 0The special case of.

In this statement, we will first determine whether x and y are both +0, -0, or 0 (because we know that no matter which one of these three is compared with 0, the result will be true, so x! = = 0 is false), and according to the | | short circuit principle, a false front, so the final result depends on the back.

So x! So theta is equal to theta is equal to 0, which is the same thing as pushing x, y, plus 0, minus 0, 0 to 1 / x === 1 / y, and using the result of that to determine the final result.

Return x! == 0 || y ! = = 0 | | 1 = = = 1 / x/y, than I have one more here | | y! = = 0. The comments section of PALS matteokjh hint, think can put the | | y! The judgment of == 0 is omitted, and MDN does not polyfill it with this step. I think: because it can enter x === y, then if x! If lambda = lambda = 0, then y! Lambda is equal to lambda is equal to 0, which is definitely true. In addition, I have executed the code saved, and the result is the same as that without saving, so I feel it is feasible.

Object. Is (+0, -0) is Infinity, and 1 / -0 is -infinity, so if x and y are equal to +0 or -0, then Object.

  • whenx ! == yIs used to deal withNaN, NaNThe special case of.

Because we know NaN! == NaN, so if x and y are not equal to themselves, both are nans, and if both are NaN, object. is(NaN, NaN) is true.

Third, the toString() method is useful

1. Where does toString() exist?

I’ve read a lot about toString() up until now, mostly about its use, but where does it really exist?

Object. Prototype: Number, String, Boolean, Symbol, BigInt; Do they have the means themselves? Or are they on their prototype objects?

In the spirit of getting to the bottom, I printed out Number and Number. Prototype:

console.log(Number)
console.log(Number.prototype)
Copy the code

And THEN I discovered a couple of things:

  • NumberIt’s just a constructor that will print out the source code
  • Number.prototypeAnd it doestoString()
  • Number.prototype.__proto__That isObject.prototypeThere are alsotoString()

Then I tried String, Boolean, Symbol and found the same result.

In fact, it is not difficult to understand the “💦【 Why not 】 complete the 48 questions to understand the JS inheritance (1.7W words contain – back to nature)” friends know that all Object prototype chain will eventually point to Object. Prototype, is regarded as “inherited” Object instance. The toString() method can therefore be used, but will be overridden for different built-in objects to better suit their functional needs, so you can see it on Number.prototype as well.

So we can draw the first conclusion:

  • In addition toNull, and undefinedOther data types (base data type + reference data type) are present on the prototype object of the constructortoString()methods
  • Base datatype constructor on the prototype objecttoString()overwriteObjectOn the prototype objecttoString()methods

(Of course, you’ll see later that this isn’t exactly true, but most of the time we care about who can use it, not where it exists.)

2. Who can call toString()?

The toString() method can be called on any object except null and undefined, and usually returns the same result as String.

In fact, the most common thing to get confused about here is String and toString.

I’ve always used these two attributes haphazardly to convert a type to a string.

  • StringIs a kind ofFunctionAn object like this, it can either be used as an object, using static methods on it, or it can be used as a constructor, creating aStringobject
  • whiletoStringIt is in addition toNull, and undefinedA method that does not exist on any data type, usually returns the result andStringThe same.

3. How to use toString() to determine the specific type of data?

Perhaps the most common usage is this:

Object.prototype.toString.call({ name: 'obj' }) // '[object Object]'
Copy the code

Hard knowledge point, first Object. The prototype. The toString method according to the [[class]] internal attributes of objects, returned by the “[Object” and the class and “] “string is composed of three parts.

What you mean? [[class]] What is the internal attribute 🤔️?

You can’t really think too much about it here. Just take it literally.

Well, I’ll just assume it represents one thing.

like

  • Arrays are a class of its[[class]]isArray
  • String is a class of its[[class]]isString
  • argumentsIt’s one class, it’s[[class]]isArguments

In addition, there are many kinds of [[class]], and you don’t need to remember them all, just the common, basic, and easy to understand ones.

So back to the Object. The prototype. ToString. The call () calls in this way, you can understand it now, it can help us to accurately determine whether a data type, also is to discern is an array or digital or function, or NaN. 😊

And since it returns a string like “object object”, and the preceding “[object]” string is fixed (including the space after “t”), can we wrap a method to get only “object”?

Very simple, the code:

function getClass (obj) {
    let typeString = Object.prototype.toString.call(obj); // "[object Object]"
    return typeString.slice(8.- 1);
}
Copy the code

As you can see, I named this function getClass, which corresponds to its original purpose, to get the [[class]] internal properties of the object.

In addition, after receiving the “object object” string, a. Slice (8, -1) string truncation function is used to remove the first eight characters “[object]” and the last “]”.

Now let’s take a look at some common data types:

function getClass(obj) {
    let typeString = Object.prototype.toString.call(obj); // "[object Array]"
    return typeString.slice(8.- 1);
}
console.log(getClass(new Date)) // Date
console.log(getClass(new Map)) // Map
console.log(getClass(new Set)) // Set
console.log(getClass(new String)) // String
console.log(getClass(new Number)) // Number
console.log(getClass(true)) // Boolean
console.log(getClass(NaN)) // Number
console.log(getClass(null)) // Null
console.log(getClass(undefined)) // Undefined
console.log(getClass(Symbol(42))) // Symbol
console.log(getClass({})) // Object
console.log(getClass([])) // Array
console.log(getClass(function() {})) // Function
console.log(getClass(document.getElementsByTagName('p'))) // HTMLCollection

console.log(getClass(arguments)) // Arguments
Copy the code

"Lin dull, so much, this is the people do?"

"Just be calm and remember some common ones..."

"Pa!"

4. Tostring.call () differs from typeof.

👌 : ToString. call: tostring. call: tostring. call: tostring. call: tostring. call: tostring. call: tostring. call: tostring. call: tostring. call: tostring. call: tostring. call: tostring. call

Speaking of data types, we have a Typeof, right? How is it different from toString.call()?

A review of typeof’s display rules:

  • For primitive types (i.eNumber, a string,This kind of), exceptnullCan display the correct type
  • nullMisjudged for historical reasons"object"
  • For reference types (i.eThe object, arrayThis), except that functions are displayed as"object"
  • The function will be displayed asfunction

So the disadvantage of typeof is obvious. I now have an object and an array or a date object, and I want to be careful to distinguish between them, and typeof is definitely not possible because they all get “object”.

So, using our wrapped getClass() is an obvious choice.

5. What happens when toString() is called for different types of data?

What’s the difference between calling toString() on different data types?

Here I mainly break it down into two chunks:

  • Basic data type call
  • Reference type call

5.1 Basic Data Types Call toString

For primitive datatypes, it’s super easy to call, just think of it as a string instead of its original value:

console.log('1'.toString()) / / '1'
console.log(1.1.toString()) / / '1.1'
console.log(true.toString()) // 'true'
console.log(Symbol(1).toString()) // 'Symbol(1)'
console.log(10n.toString()) / / '10'
Copy the code

So for basic data types:

  • When a primitive data type is called, its original value is replaced with a string

5.2 Reference Type Call toString

The hard part is that reference types call toString(), and we know that reference types have many classes depending on [[class]], such as Object, Array, Date, and so on.

Is toString() different from class to class 🤔️?

Yes, different versions of toString are divided into:

  • An array oftoStringThe method is to convert each item to a string and use it again","The connection
  • Ordinary objects (e.g{name: 'obj'}This type of) is converted to a string"[object Object]"
  • Function (class), reIs converted to a source string
  • The date ofA date string that will be converted to the local time zone
  • Wrapper object invocation of the original valuetoStringA string that returns the original value
  • haveSymbol.toStringTagObjects with built-in attributes change to their corresponding labels when invoked"[object Map]"

(The Symbol. ToStringTag attribute will be asked in the next question)

For example 🌰 :

console.log([].toString()) / / ""
console.log([1.2].toString()) / / 1, 2, ""

console.log({}.toString()) // "[object Object]"
console.log({name: 'obj'}.toString()) // "[object Object]"

console.log(class A {}.toString()) // "class A {}"
console.log(function () {}.toString()) // "function () {}"
console.log(/(\[|\])/g.toString()) // "/(\[|\])/g1"
console.log(new Date().toString()) "Fri Mar 27 2020 12:33:16 GMT+0800"

console.log(new Object(true).toString()) // "true"
console.log(new Object(1).toString()) / / "1"
console.log(new Object(BigInt(10)).toString()) 10 "/ /"

console.log(new Map().toString()) // "[object Map]"
console.log(new Set().toString()) // "[object Set]"
Copy the code

6. Do you understand Symbol. ToStringTag?

The Symbol. ToStringTag describes it as follows:

The symbol.toStringTag public Symbol is the string value attribute used in the default string description of the object created. It is composed of the Object. The prototype. The toString () method in internal access.

It determines what the [[class]] internal property of all the data types we mentioned earlier is.

For example, Number, we got [[class]] is Number, then I can understand the Number class and its Symbol. ToStringTag returns Number.

The Number, String, and Boolean attributes do not include symbol.toStringTag, which is returned when toString.call() is used.

Let’s print out the new Map() we just saw in question 5.

console.log(new Map())
Copy the code

We can see that symbol.toStringTag actually exists in map.prototype, meaning it is a built-in property of Map and Set, so when toString() is called directly, it returns “[Object Map]”.

Well, is that how we understand it?

  • There is noSymbol.toStringTagThe type of the built-in property is calledtoString()Is equivalent toString(obj)This call is converted to the corresponding string
  • There areSymbol.toStringTagThe type of the built-in property is calledtoString()Will return the corresponding label (i.e"[object Map]"Such a string)

Examples of common objects with symbol.toStringTag built-in attributes are:

console.log(new Map().toString()) // "[object Map]"
console.log(new Set().toString()) // "[object Set]"
console.log(Promise.resolve().toString()) // "[object Promise]"
Copy the code

Its main function is that, like symbol. hasInstance, it allows us to customize tags.

(Symbol.hasInsance is used to customize the return value of instanceof)

What are custom tags 🤔️?

That is, if we now create a class and call its instance object with toString.call(), we get the following result:

class Super {}
console.log(Object.prototype.toString.call(new Super())) // "[object Object]"
Copy the code

The resulting new Super() is an object, so it will print “[object object]”.

But now that we have symbol. toStringTag, we can change the following “Object”.

For example, let me rewrite:

class Super {
  get [Symbol.toStringTag] () {
    return 'Validator'}}console.log(Object.prototype.toString.call(new Super())) // "[object Validator]"
Copy the code

This is where symbol.toStringTag comes in handy. It allows us to customize the tag.

Symbol. ToStringTag overrides the tag of new Super(), not the tag of Super.

class Super {
  get [Symbol.toStringTag] () {
    return 'Validator'}}console.log(Object.prototype.toString.call(Super)) // "[object Function]"
console.log(Object.prototype.toString.call(new Super())) // "[object Validator]"
Copy the code

Since Super itself is still a function, only instance objects generated by Super will use our custom tags.

To summarize Symbol. ToStringTag:

  • It’s a built-in property of some particular type, for exampleMap, Set, Promise
  • The main function is to allow us to customize the tag, modifyObject.prototype.toString.call()Return result of

More on toString and symbol.toStringTag can be found here at 👇 :

【 解 析 】 Complete understanding of data type conversion from 206 console.log()

Fourth complement: JS type conversion

Because type switching can be a bit of a pain in the neck, I’ve written a series of articles on this topic, which basically cover some of the questions you might ask in an interview: Portals:

【 解 析 】 Complete understanding of data type conversion from 206 console.log()

【 原 文 】 Understand the history of data type conversion from 206 console.log()

(The next article written very good no one see afflictive 😣)

1. Basic usage of valueOf()

  • A basic data type call that returns the original value of the caller
  • Other reference type calls for non-date objectsvalueOf()The default is to return itself
  • And the date object will return oneMilliseconds since January 1, 1970(similar to the1585370128307).

Example 🌰 :

console.log('1'.valueOf()) / / '1'
console.log(1.1.valueOf()) / / 1.1

console.log([].valueOf()) / / []
console.log({}.valueOf()) / / {}
console.log(['1'].valueOf()) / / / '1'
console.log(function () {}.valueOf()) / / ƒ () {}
console.log(/(\[|\])/g.valueOf()) // /(\[|\])/g
console.log(new Date().valueOf()) / / 1585370128307
Copy the code

2. Specific conversion process of ToPrimitive?

The built-in ToPrimitive function is called when we convert an object to its original type or perform a == comparison.

Such as:

console.log(String({})) // Object to string, result is "[object object]"
console.log(Number([1.2])) // The object is converted to a number, resulting in NaN

console.log([] == ! [])// true
Copy the code

All the above results are derived from the ToPrimitive function.

Let’s look at its functional syntax first:

ToPrimitive(input, PreferredType?)
Copy the code

Parameters:

  • A parameter:inputRepresents the input value to be processed
  • Parameters of the two:PerferredType, the type to be converted. You can see that the syntax has a question mark after it to indicate that it is not required. It has only two optional values,NumberandString.

The processing of incoming parameters is more complicated. Let’s take a look at the flow chart:

According to the flow chart, we get the following information:

  1. When the PreferredType is not passed, it is equivalent to a String if the input is of date type, and a Number otherwise.
  2. If ToPrimitive(obj, Number) is used, the procedure is as follows:
  • If obj is a basic type, return it directly
  • Otherwise, the valueOf method is called, and if a raw value is returned, JavaScript returns it.
  • Otherwise, the toString method is called, and if a raw value is returned, JavaScript returns it.
  • Otherwise, JavaScript throws a type error exception.
  1. If it is ToPrimitive(obj, String), the processing is as follows:
  • If obj is a basic type, return it directly
  • Otherwise, the toString method is called, and if a raw value is returned, JavaScript returns it.
  • Otherwise, the valueOf method is called, and if a raw value is returned, JavaScript returns it.
  • Otherwise, JavaScript throws a type error exception.

(Summary source: Yuba Hu -JavaScript Deep Headache of type conversion (1))

The diagram above at 👆 is actually quite complicated. If you are careful, you may notice that only the toString() and valueOf() methods are executed in different order in the red frame.

If the PreferredType is String, the toString() method is executed first

If the PreferredType is Number, the valueOf() method is executed first

(Lin Naidai suggests that you draw this process on the draft paper yourself.)

2.1 Object to String

To reinforce the point:

console.log(String({})) // "[object Object]"
Copy the code

For this simple conversion we can replace it with toPrimitive pseudocode:

toPrimitive({}, 'string')
Copy the code

OK👌, to review the transformation rules:

  1. inputis{}, is a reference type,PerferredTypeforstring
  2. So calltoString()The method, which is equal to{}.toString()
  3. {}.toString()As the result of the"[object Object]", is a string, is the basic data type, and returns, end of story.

Wow ~

Is everything said to make sense, it seems not difficult 😁.

It is true that the order of execution in the JS engine is the same when using String(), except that the last basic data type is converted to a String when the result is returned.

That is to say, we have to split the third step of 👆 into two steps:

  1. {}.toString()As the result of the"[object Object]"Is a string of basic data types
  2. Will this"[object Object]"String does one more string conversion and returns. (because the"[object Object]"It is already a string, so it is returned as is, there is no difference.)

Converting the final result to a string and returning it is easy to understand. If you want to return a number or null, you can call the String method to get a String

2.2 Converting objects to Numbers

We just said that the object to string case is toPrimitive(Object, ‘string’),

Then an object to a number is toPrimitive(object, ‘number’).

The difference is that toString() is called after valueOf().

Example 🌰 :

console.log(Number({}))
console.log(Number([]))
console.log(Number([0]))
console.log(Number([1.2]))

console.log(Number(new Date()))
Copy the code

The Number ({}) :

  • We pass in an object{}, so callvalueOf()Method, the method is in the problem7.1As already mentioned, all referential type calls except the date object return itself, so the object is still returned{}
  • valueOf()The value returned is still an object, so continue callingtoString()Method, and{}calltoString()The result is a string"[object Object]"Is a basic data type
  • Now that I have the underlying data type, it’s time to return it, but I have to convert it to a number before I return it, so"[object Object]"Convert to figureNaN, so the result isNaN

The Number ([]) :

  • We pass in an array[], so callvalueOf()Method that returns itself[]
  • []Continue to calltoString()Method, while an empty array to a string is""
  • Finally, empty string""Converted to digital0return

The Number ([0]) :

  • because[0]Converting to a string is"0", and finally turning into numbers0return

For Number([1, 2]) :

  • We pass in an array[1, 2], so callvalueOf()Method returns the array itself[1, 2]
  • So go ahead and calltoString()Method, which is then converted to"1, 2,"string
  • "1, 2,"The string is finally converted to a numberNaN, so the result isNaN

For Number(new Date()) :

  • We pass in an object of type datenew Date(), so callvalueOf()In the title7.2Date type calls, as already stated invalueOf()Yes returns a millisecond
  • If the number of milliseconds is numeric, which is the basic data type, then the value is returned1585413652137

Results:

console.log(Number({})) // NaN
console.log(Number([])) / / 0
console.log(Number([0])) / / 0
console.log(Number([1.2])) // NaN

console.log(Number(new Date())) / / 1585413652137
Copy the code

3. Why use “,” concatenation when converting an array to a string?

We all know that when you convert an array to a string, you convert every item in it to a string and then you concatenate it back.

So why is there a “,” concatenation? Will join() be called when toString() is called?

To test my idea 💡, I did an experiment by rewriting the join() method on an array:

var arr = [1.2]
arr['join'] = function () {
  let target = [...this]
  return target.join('~')}console.log(String(arr))
Copy the code

In the overridden join function, this represents the array arr being called.

Then change the return value to “~” concatenation, the result is:

"1 ~ 2"
Copy the code

That is, it does implicitly call the join method during String(ARr).

But when we overwrite toString(), we don’t care about the overwritten join:

var arr = [1.2]
arr['toString'] = function () {
  let target = [...this]
  return target.join(The '*')
}
arr['join'] = function () {
  let target = [...this]
  return target.join('~')}console.log(String(arr)) / / "1 * 2"
Copy the code

As you can see, toString() has a higher priority than join().

Now we can draw another conclusion:

If an object is an array, the default implementation is to use the return value of the join() method as the return value of toString() when converted to a string without overriding its toString() method.

4. Do you know Symbol. ToPrimitive? What does it do?

When we convert an object to its original value, we implicitly call the internal ToPrimitive method to do the conversion as described above.

The Symbol. ToPrimitive property helps us rewrite toPrimitive to change the result of the conversion.

Let’s take a look at the summary:

  • If you overwrite an object or a constructorToString, valueOf, symbol.toprimitiveMethod,Symbol.toPrimitiveIs the highest priority
  • ifSymbol.toPrimitiveAn error is reported if the function returns a value that is not the underlying data type (that is, the original value)
  • Symbol.toPrimitiveAccepts a string argumenthint, which represents the expected type of the original value to be converted to'number', 'string', 'default'Three options
  • useString()Call,hintfor'string'; useNumber()When,hintfor'number'
  • hintThe value of the parameter is determined from the beginning of the call

Here’s an example 🌰 :

var b = {
  toString () {
    console.log('toString')
    return '1'
  },
  valueOf () {
    console.log('valueOf')
    return [1.2[]},Symbol.toPrimitive] (hint) {
    console.log('symbol')
    if (hint === 'string') {
      console.log('string')
      return '1'
    }
    if (hint === 'number') {
      console.log('number')
      return 1
    }
    if (hint === 'default') {
      console.log('default')
      return 'default'}}}console.log(String(b))
console.log(Number(b))
Copy the code

The toString, valueOf, and symbol. toPrimitive attributes are overwritten. We already know from 👆 above that the first two attributes are ignored as long as symbol. toPrimitive is present, so we do not care about them.

For symbol. toPrimitive, I add all three hint cases. If I had expected String(b) to print String, Number(b) to print Number, and the result is exactly what I expected:

'string'
'1'
'number'
1
Copy the code

5. == type conversion in comparison?

In fact, what we’re probably getting tested for most is using == to compare whether two different types of variables are equal or not.

However, the case of congruence === is relatively simple and generally not very likely to be tested, because the condition of congruence is: if the types are equivalent and equal, they are considered congruent, and type conversion is not involved.

== == == == == ==

console.log([] == ! [])// true
console.log({} == true) // false
console.log({} == "[object Object]") // true
Copy the code

How to? These questions are often seen 😁, let us see one by one below.

First of all, there are a few things we need to know, and this is a hard and fast rule, and we can’t continue without it.

When using == for comparison, there are the following conversion rules (judgment rules) :

  1. If the two sides have the same type and the same value, they are equal2 = = 3Must be forfalseOf the
  2. Both sides of the comparison are basic data types:
  • If one party isNull, and undefined, the other party must beNull or undefinedJust fortrue, that is,null == undefinedfortrueornull == nullfortrueBecause theundefinedDerived fromnull
  • One party isStringIf it is, it isStringtoNumberAgain to compare
  • One party isBooleanIf it is, it willBooleantoNumberAgain to compare
  1. The side of the comparison has a reference type:
  • The reference type will followToNumberTo compare (actually itshintisdefault, that is,toPrimitive(obj, 'default'), butdefaultConversion rules andnumberMuch like)
  • If both are reference types, determine whether they refer to the same object

In some articles, it says:

If one is Object and the other is String, Number, or Symbol, the Object is converted to a String and then compared

3. What is the difference between == and ===? (from “God three -(Suggested collection) native JS soul ask, how many can you catch?

Think of it this way, too, because consider the procedure for toPrimitive(obj, ‘number’) :

  • If the input value is a reference data type, call firstvalueOf()methods
  • ifvalueOf()A method returns a primitive datatype, or continues the call if it does nottoString()
  • If the calltoString()Is returned if the return value of is a primitive data type, otherwise an error is reported.

As you can see, valueOf() is executed first, but a reference type executes a valueOf() method. Except for the date type, it returns itself, that is, after valueOf() is executed, it is still a reference type and itself. So we can omit the valueOf() step and say it executes toString() directly, which would make the problem much faster.

6. How many unary operators can be converted?

Type conversions for several common operators:

  1. -, *, /, %All four operate by converting both sides of the sign to numbers
  2. +Since it is not only a numeric operator, but also a concatenator of a string, there are two cases:
  • If both ends of the number are numerically calculated (unary plus sign+bThis is equivalent to converting to numbers.)
  • If one end is a string, the other end is converted to a string for concatenation

Object + type conversion:

  • Object in progress+When the string is connected,toPrimitiveThe parameters of thehintisdefault, butdefaultThe execution sequence andnumberIt’s always a matter of judgmentvalueOfIf so, executevalueOfAnd then judgevalueofIf the value is a reference type, execution continuestoString. (similar to the topic4.5and4.6)
  • Date in progress+Number strings are called preferentially when connectedtoString()Methods. (similar to the topic4.7)
  • The unary plus sign is the fastest way to convert other objects to values, and it is the most recommended because it does not perform any unnecessary operations on values.

7. How many ways can you make if(a == 1&&a == 2&&a == 3) true?

I’m sure you’ll see a lot of this, but what else can you do besides rewrite valueOf()?

Solution one: rewritevalueOf()

This solution uses: ValueOf () is executed first, if the value returned by valueOf() is a primitive data type. ToString () is called if it is not a reference type. ToString () is returned if it is a primitive data type. Otherwise, an error is reported.

ValueOf () now returns a numeric type at a time, so it returns directly.

/ / 1
var a = {
  value: 0,
  valueOf () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('set up')}Copy the code

Solution two: RewritevalueOf()andtoString()

var a = {
  value: 0,
  valueOf () {
    return {}
  },
  toString () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('set up')}Copy the code

The principle is the same as solution 1, except that toString() is continued when valueOf() returns a reference.

You don’t even have to override valueOf() here, because every other object except the date object returns itself when valueOf() is called.

That means you can also do this:

var a = {
  value: 0,
  toString () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('set up')}Copy the code

Solution 3: RewriteSymbol.toPrimitive

Symbol. ToPrimitive Symbol.

When the object is compared ==, the hint received by symbol. toPrimitive is “defalut”.

var a = {
  value: 0[Symbol.toPrimitive] (hint) {
    if (hint === 'default') {
      return ++this.value
    }
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('set up')}Copy the code

And that’s ok.

Define class and rewrite valueOf()

You can also use class:

class A {
  constructor () {
    this.value = 0
  }
  valueOf () {
    return ++this.value
  }
}
var a = new A()
if (a == 1 && a == 2 && a == 3) {
  console.log('set up')}Copy the code

Solution 5: Converting an array to a string is called implicitlyjoin()

What? Is there another way to do it? And I’m a little confused about solution five.

Let’s go back to problem 4.3, where we mentioned that when an array is converted to a string, the result of calling toString() is actually the result of calling JOIN.

What does that have to do with this problem? Take a look at the answer:

let a = [1.2.3]
a['join'] = function () {
  return this.shift()
}
if (a == 1 && a == 2 && a == 3) {
  console.log('set up')}Copy the code

As we know, if an object is an array, the default implementation is to use the return value of the join() method as the return value of toString() when converted to a string without overriding its toString() method.

So here we rewrite a’s join method, and this rewrite does two things:

  1. The arrayaperforma.shift()Method, which we know affects the original arrayaPhi, let’s get rid of the first term
  2. Let’s go back to the first term that we just removed

So when we perform a == 1, we implicitly call a[‘join’], so we do the two things 👆 said above, a == 2 and a == 3.

Define class to inherit Array and rewrite join()

We can also use class for solution 5

class A extends Array {
  join = this.shift
}
var a = new A(1.2.3)
if (a == 1 && a == 2 && a == 3) {
  console.log('set up')}Copy the code

This writing method is relatively cool 🆒, but the first time to see may not be able to understand.

  • First of all,AThis class passesextendsInheritance inArrayAnd so throughnew AI’m just creating an array
  • thenARewrite thejoinMethod,join = this.shiftIs equivalent tojoin = function () { return this.shift() }
  • So when every calla == xxx“, will implicitly call our customjoinMethod, perform the same operation as method 5.

If (a === 1 &&a == 2 &&a === 3)

This one looks a little bit like the one above, but it’s congruent.

We know the conditions for congruence:

  1. The left and right types must be equal. If the types are not equal, returnfalse, and= =Different,= =Implicit type conversions occur
  2. The values are not equal

For the solution of the above 👆 problem, we all take advantage of the fact that == will have implicit type conversion. Obviously, if we use it to solve this problem, it cannot be realized.

Consider that when we make a === XXX judgment, we actually call the data a, that is to say, we need to do something before calling the data to achieve our purpose.

I wonder if this makes you think of something 🤔️? You might think of Vue’s famous data hijacking 😁.

Considering that the object.defineProperty () method was used in Vue2. X to redefine all attributes in data, we can also use it to hijack A and change the GET attribute of a.

var value = 1;
Object.defineProperty(window."a", {
  get () {
    return this.value++; }})if (a === 1 && a === 2 && a === 3) {
  console.log('set up')}Copy the code

Here are a few things that actually happen:

  • useObject.defineProperty()Method hijacks global variableswindowOn the properties of thea
  • And every time I callaWhen willvalueIncrement, and returns the increment value

(In fact, I would like to use Proxy to do data hijacking, Proxy window, new Proxy(), but the window object does not seem to have effect…)

Method 2

How to do 😂, one encounters this kind of problem I thought of array again…

var arr = [1.2.3];
Object.defineProperty(window."a", {
  get () {
    return this.arr.shift()
  }
})
if (a === 1 && a === 2 && a === 3) {
  console.log('set up')}Copy the code

Poisoned by Shift ()… Of course, this is also possible.

Method three

There is EnoYao big guy there look SAO operation:

Original link: juejin.cn/post/684490…

varA ㅤ =1;
var a = 2;
varㅤ a =3;
if(a ㅤ = =1 && a == 2&& ㅤ = = a3) {
  console.log("Set up");
}
Copy the code

Shame to say… I can’t type 😂…

9. What happens when the console type {}+[]?

[]+ {} =? /{} +[] =? (implicit conversion of the plus sign)

(PS: pick a wave of exquisite, the little sister’s articles are quite good, but the heat is not high, we can support it 😁)

OK👌, take a look at the title:

On a console (such as a browser’s console) type:

{} + []Copy the code

What will be the result 🤔️?

{} = “[object object]” {} = “[object object]” {] = “[object object]” {} = “[object object]” {} = “[object object]”

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

But notice that the problem here is to print it on the console.

When I output this code on the console, the answer is different from what I expected:

{} + []0
Copy the code

That is, {} is ignored and +[] is executed, resulting in 0.

ToString () is similar to 1.toString(), because JS interprets the code. In console or terminal, JS will think that the opening of braces {} is an empty code block, so it will ignore it when there is no content in it.

So +[] is just done, converting it to 0.

We don’t have this problem if we change the order:

(Photo credit: juejin.cn/post/684490…)

To verify this, we can call some object methods as if {} were empty and see what happens:

(Console or terminal)

{}.toString()
Copy the code

{} is still considered a block of code rather than an object, so an error is reported:

Uncaught SyntaxError: Unexpected token '. '
Copy the code

The solution is to augment it with a () :

({}).toString
Copy the code

If you want to define the default value of one of the properties as an empty object, you need to use: props

props: {
    target: {
        type: Object.default: (a)= >({})}}Copy the code

Eighth complement: eight KINDS of JS inheritance

For JS inheritance I also wrote a series of “encapsulation | | polymorphism of hereditary monarchy”, here is the portal:

  • encapsulation
  • inheritance
  • polymorphism

Specific examples and titles in the article have been very clear, here I will just list the advantages and disadvantages of each inheritance and pseudocode.

1. Prototype chain inheritance

Pseudo code:

Child.prototype = new Parent()
Copy the code

Mind mapping:

Advantages:

  • Inheriting the template of the parent class, and inheriting the prototype object of the parent class

Disadvantages:

  • If you want to add properties and methods to a subclass’s prototype, you must put theChild.prototype = new Parent()After such a statement
  • Multiple inheritance cannot be implemented (because the prototype object is already specified)
  • All attributes from the stereotype object are shared, so that if you accidentally modify a reference type attribute in the stereotype object, all instance objects created by subclasses will be affected
  • Cannot pass arguments to the parent constructor when creating a subclass

2. Structural inheritance

Pseudo code:

function Child () {
    Parent.call(this. arguments) }Copy the code

Advantages:

  • It solves the problem that the subclass instance shares the reference object of the parent class in the prototype chain inheritance, realizes multiple inheritance, and can pass parameters to the parent class when creating the subclass instance

Disadvantages:

  • Construction inheritance can only inherit the instance properties and methods of the parent class, not the properties and methods of the parent class prototype
  • An instance is not an instance of a parent class, only an instance of a subclass
  • Function reuse is not possible, each subclass has a copy of the parent class instance function, affecting performance

3. Combinatorial inheritance

Pseudo code:

// Construct inheritance
function Child () {
  Parent.call(this. arguments) }// Prototype chain inheritance
Child.prototype = new Parent()
/ / correct constructor
Child.prototype.constructor = Child
Copy the code

Mind mapping:

Implementation method:

  • Use stereotype chain inheritance to ensure that subclasses inherit properties and methods from their parent class stereotypes
  • Construct inheritance is used to ensure that subclasses inherit instance properties and methods from their parent classes

Advantages:

  • You can inherit parent instance properties and methods as well as parent stereotype properties and methods
  • The problem of reference attribute sharing in prototype chain inheritance is solved
  • Parameter can be transmitted, reusable

Disadvantages:

  • When using composite inheritance, the parent class constructor is called twice
  • It also generates two instances, and the attributes and methods in the subclass instance overwrite the attributes and methods on the subclass prototype (the superclass instance), thus adding unnecessary memory.

4. Parasitic combination inheritance

Pseudo code:

// Construct inheritance
function Child () {
  Parent.call(this. arguments) }// Original type inheritance
Child.prototype = Object.create(Parent.prototype)
/ / correct constructor
Child.prototype.constructor = Child
Copy the code

Mind mapping:

Parasitic combination inheritance was a perfect way to inherit before ES6.

It avoids the disadvantage of calling the superclass constructor twice and initializing the instance properties twice in composite inheritance.

So it has all the advantages of inheritance:

  • The parent constructor is called only once, and only one copy of the parent property is created
  • Subclasses can use properties and methods from the parent class’s prototype chain
  • Can be used normallyinstanceOfandisPrototypeOfmethods

5. Original type inheritance

Pseudo code:

var child = Object.create(parent)
Copy the code

Implementation method:

The idea is to create a constructor whose prototype points to an object, then call the new operator to create an instance and return it, essentially a shallow copy.

After ES5, you can implement this directly using the object.create () method, whereas before that you had to implement one manually (see section 6.2).

Advantages:

  • Without the need to create constructors, the prototype chain inheritance is implemented, reducing the amount of code.

Disadvantages:

  • Some referential data operations are problematic, and both instances inherit the instance’s referential data class in common
  • Take care to define methods so that they do not inherit the same method name as the object stereotype
  • You cannot use arguments to parent constructors directly

Parasitic inheritance

Pseudo code:

function createAnother (original) {
    var clone = Object.create(original);; // Create a new Object by calling object.create ()
    clone.fn = function () {}; // Enhance objects in some way
    return clone; // Return this object
}
Copy the code

Implementation method:

  • Encapsulate another layer on top of the original type inheritance to enhance the object, and then return the object.

Advantages:

  • Without the need to create constructors, the prototype chain inheritance is implemented, reducing the amount of code.

Disadvantages:

  • Some referential data operations are problematic, and both instances inherit the instance’s referential data class in common
  • Take care to define methods so that they do not inherit the same method name as the object stereotype
  • You cannot use arguments to parent constructors directly

7. Mix-in inheritance

Pseudo code:

function Child () {
    Parent.call(this)
    OtherParent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype)
Child.prototype.constructor = Child
Copy the code

Mind mapping:

Inheritance in class

Pseudo code:

class Child extends Parent {
    constructor(... args) {super(... args) } }Copy the code

Inheritance in ES6:

  • Mainly depend onextendsKeyword to implement inheritance, and the effect of inheritance is similar toParasitic combinatorial inheritance
  • Using theextendsImplementing inheritance doesn’t have to beconstructorandsuperBecause they are not generated and called by default
  • extendsThe next goal doesn’t have to beclassAs long as there is aprototypeProperty function will do

ES5 inheritance differs from ES6 inheritance:

  • inES5Inheritance in (e.gStructural inheritance, parasitic combination inheritance), essentially creating instance objects of the subclass firstthis, and then add the attributes and methods of the parent class tothisIs used withParent.call(this)).
  • And in theES6But that’s not the case in ChineseStart by creating an instance object of the parent classthis(That is, usesuper()), and then use the subclass constructor to modify itthis.

Refer to the article

Knowledge is priceless, support original.

Reference article:

  • JavaScript ES6 Symbol. HasInstance.
  • “(suggested collection) native JS soul of the question, how many can you catch? (1)”

After the language

You wish the world, I wish you no bugs. So much for this article.

Because too much has been said in the opening paragraph, I will not say 🙊.

The guy who likes Lin Dull also hopes to follow Lin’s public account LinDaiDai or scan the following QR code 👇👇👇.

I will update some front-end knowledge content and my original article 🎉 from time to time

Your encouragement is the main motivation for my continuous creation 😊.

Related recommendations:

The most detailed BPMN.js textbook in the whole Web

If you don’t understand Babel, I’ll send you a mask.

“[Suggested stars] To come to 45 Promise interview questions a cool end (1.1W words carefully arranged)”

“[suggestion 👍] 40 more this interview questions sour cool continue (1.2W word by hand)”

“[why not three even] more simple than inheritance of JS inheritance – packaging (small test)”

【 Why not three times 】 finish these 48 questions to thoroughly understand JS inheritance (1.7W words including hot finishing – back to nature)

Data type conversion from 206 console.log()