Some Fundamental Questions that Kill People - (Update from time to time)

Preface

Add 6 on April 27, 2018. Explain the difference between shallow copy and deep copy and implement it simply. Seventh, native JS operation DOM?
In 2008/04/30, eight new elements were added to solve the problem of calculation accuracy, such as 0.1+0.2?
2018/05/06 Modified Code Format
Added a new interview question for the encounter of closure execution environment in November/06, 2018

A Front-end JS Interview Question that is often looked down upon

Authors: Little Cang Hai
Source: http://www.cnblogs.com/xxcanghai/
This article addresses: http://www.cnblogs.com/xxcanghai/

(The type of question is the same, just use my own understanding to analyze, in order not to confuse, I slightly modified the code. I am ashamed to say that although I have only one wrong question and the last three most difficult ones are all right, but the train of thought is wrong, so I hope you don't just look at the answers, the key is to learn the principles of them.

function Person() {
  getAge = function() {
    console.log(10);
  };
  return this;
}

Person.getAge = function() {
  console.log(20);
};

Person.prototype.getAge = function() {
  console.log(30);
};

var getAge = function() {
  console.log(40);
};

function getAge() {
  console.log(50);
}

Person.getAge();
getAge();
Person().getAge();
getAge();
new Person.getAge();
new Person().getAge();
new new Person().getAge();

(Forgive me for not showing you the effect code, because I don't know why I can't output it in the editor, so you can put it in the local print and see ())

This is a topic involving too many knowledge points, including function declaration, variable promotion, this pointing, new new objects, priority, prototype chain, inheritance, object attributes and prototype attributes, etc.
(The answer is not posted. You can run by yourselves for fear that you can't help looking at the correct answer first.)

First, analyze what has been done above.

  1. Define a Person function with an anonymous getAge function
  2. Define a static property getAge function for the Person function itself
  3. Define a getAge function on the prototype of Person function
  4. Variable declares a getAge function expression
  5. Declare a getAge function directly

Person.getAge();

Look at it separately

function Person() {
  getAge = function() {
    console.log(10);
  };
  return this;
}

console.log(Person); //Look at what Person is.
Person.getAge = function() {
  console.log(20);
};

Person.getAge();

Obviously, the static property getAge of Person is called directly, and the result is 20. (For details, please refer to my previous article. Some understandings about the new operator in Javascript, inheritance and prototype chains)

getAge();

First of all, there is no object in front, so you can know that the global environment call does not take Person into account.
This question examines function declarations and function expressions.

getAge(); //50
var getAge = function() {
  console.log(40);
};

getAge(); //40
function getAge() {
  console.log(50);
}

getAge(); //40

As you can see above, getAge first points to the function declaration and is not overwritten until the step of the function expression.
This requires understanding the difference between the two types of Javascript Function: functions created with function declarations can be called after function parsing (parsing with equal logic processing); functions created with function expressions are assigned at runtime and can not be called until expression assignment is completed.

The creation of variable objects goes through the following processes in turn.

  1. Establish arguments objects. Check the parameters in the current context and establish the attributes and attribute values under the object.
  2. Check the function declaration in the current context, which is a function declared using the function keyword. In the variable object, an attribute is created with the function name, and the attribute value is a reference to the memory address of the function. If an attribute of a function name already exists, it will be overwritten by a new reference.
  3. Check the variable declaration in the current context. Whenever a variable declaration is found, an attribute is created in the variable object with the variable name and the attribute value is undefined. If the attribute of the variable name already exists, in order to prevent the function with the same name from being modified to undefined, it will be skipped directly and the original attribute value will not be modified.

So the actual sequence of this step is as follows

function getAge() {
  //Function lifting analytic assignment to getAge
  console.log(50);
}
var getAge; //Variable elevation, where getAge is undefined

getAge(); //50, which is still a function declaration

//Expression Coverage Variable Assignment
getAge = function() {
  console.log(40);
};

getAge(); //40
getAge(); //40

Person().getAge();

  1. Person() is executed directly before, and an object is returned to point to the global window, so the static property defined by the second step function itself, namely window.getAge(), is not considered. According to the previous answer, we know that the function declaration will be overwritten and will not enter the prototype chain to search for getAge function, so we can eliminate the interference of the third and fifth steps.
  2. window.getAge(), the key here is also a trap that the getAge function assignment inside the function is not declared!!!

The difference is: if you have a function declaration, when you execute Person(), because this is a local variable getAge function belonging to the Person function, and the getAge function of the global variable called from outside, the output is naturally 40.

function Person() {
  var getAge = function() {
    console.log(10);
  };
  return this;
}

var getAge = function() {
  console.log(40);
};

Person().getAge();

If there is no function declaration, when Person() is executed, because the getAge function assigned to the Person function covers the getAge function of the external global variable, the output is naturally 10.

function Person() {
  getAge = function() {
    console.log(10);
  };
  return this;
}

var getAge = function() {
  console.log(40);
};

Person().getAge();

getAge();

This step is mainly affected by the above, and is also a further demonstration of the previous step, because the global variable getAge function has been overridden, so now directly calling the global getAge output is 10.

new Person.getAge();

The difficulty here is the symbol priority.

(Screenshots from Operator priority)
It is generally believed that this is the case.

(new Person).getAge(); // 10

As you can see from the screenshot

Membership access (dot symbol) = new (with parameter list) > function call = new (without parameter list)

In fact, I was confused at that time when I looked at the priority and understood it from that point of view. If the priority is accurate, how to run would be wrong. First, run the dot symbol, then new should call the function first without parameters, and finally to the new step.

new ((Person.getAge)());  //Uncaught TypeError: Person.getAge(...) is not a constructor

Later, when I saw the comments, I understood a little more clearly. I misunderstood some things.
First of all, the new with parameters does not mean that it has to be passed on to calculate, but with parentheses behind it.

new Person()//Taking ginseng
new Person//No ginseng

In fact, it's like this. Understand it as a whole.

new (Person.getAge)();//20

Because the new priority without parameters is inferior to member access (dot symbol), Person.getAge is executed first.
Then new (with parameter lists) takes precedence over function calls, so Person.getAge functions are used as constructors to instantiate something, such as new xxx().
It's better to find out the origin and development of this problem first, then we can solve the problems that are more circumscribed later.

new Person().getAge();

After understanding the operator priority problem, it's easy to do the following
First step by step analysis,

  1. In the title, member access (dot symbol) and new (with parameter list) are the most priority, which is calculated from left to right under the same priority, so the new Person() is executed first.
  2. Because getAge in Person function is only an assignment function, all instances do not inherit this function, only getAge function can be found from the prototype chain. (Output 30 that)

(new Person()).getAge();//30

new new Person().getAge();

Continue to analyze the above steps step by step.

  1. In the title, member access (dot symbol) and new (with parameter list) are the most priority, which is calculated from left to right under the same priority, so the new Person() is executed first.
  2. Because getAge in Person function is only an assignment function, all instances do not inherit this function, and can only find getAge function from the prototype chain, (output 30 that), so the new Person().getAge is executed first.
  3. The new Person().getAge function is used as a constructor to instantiate something, such as new xxx(), to execute new ((new Person().getAge) (), and to output 30.

Maybe that's all. I wonder if I've made it clear.

Second, on the types of coercive conversion

(This is a problem that I created when I was studying stealth conversion. There are a lot of twists and turns in it, but it's enough to kill a lot of people. See if you can do it all right.)

console.log(Number(null));
console.log(Number(undefined));
console.log(Number({}));
console.log(Number({abc: 123}));

console.log(undefined == null);
console.log(NaN == null);
console.log(null == null);
console.log(NaN == NaN);

console.log([1] == true);
console.log([[1], [2], [3]] == '1,2,3');

console.log('' == false);
console.log(null == false);
console.log({} == false);
console.log({} == []);
console.log([] == false);
console.log([] == []);
console.log(![] == false);
console.log(![] == []);

console.log(new Boolean(true) == 1);
console.log(new Boolean(false) == 0);
console.log(new Boolean(true) ? true : false);
console.log(new Boolean(false) ? true : false);

This is a little bit about knowledge, but it can reflect the strange shape of javascript language. In fact, I have written all the rules, and these questions are also derived from it. (For details, you can refer to my previous articles.) Invisible Conversion of Equal Symbols in javascript)

Let's look at Number first.

  • If it is a null value, return 0.
  • If undefined, return NaN.
  • Whether {} calls the object's valueOf() method or {} first, and then calls the toString() method to output "[object Object]", resulting in a string that calls Number() again because the invalid string returns NaN
  • Ditto
console.log(Number(null)); //0
console.log(Number(undefined)); //NaN
console.log(Number({})); //NaN
console.log(Number({abc: 123})); //NaN

Next is about the incomplete equivalence between undefined, NaN and null.
Null and undefined are equal, undefined and undefined are equal, null and null are equal.
But if one of the operands is NaN, the equal operator returns false, while the unequal operator returns true. (Even if both operands are NaN, the equality operator returns false because according to the rule NaN is not equal to NaN. )

console.log(undefined == null); //true
console.log(NaN == null); //false
console.log(null == null); //true
console.log(NaN == NaN); //false

The following is about conversion types

1. If an operand is a Boolean value, it is converted to a numerical value false and true to 1 before comparing equality.
2. If one operand is a string and the other is a value, the string call Number() is converted to a value before comparing equality.
3. If one operand is an object and the other is not, the object's valueOf is invoked first, and then the object's toString is invoked to compare with the basic type. That is to say, first convert to number type, then continue to convert to string type to compare according to the previous rules.

  1. [1] Call the object's valueOf() method or [1], then call the toString() method to output "1", get the string and then call Number() to return 1,
    true calls Number() and returns 1 directly
  2. [[1], [2], [3]] Call the object's valueOf() method or [[1], [2], [3], and then call the toString() method to output "1, 2, 3" for direct comparison.
    (For toString(), if it is an Array value, each element of Array is converted to a string and spliced with commas as separators. )
console.log([1] == true); //true
console.log([[1], [2], [3]] == '1,2,3'); //true

Comparing data types with Boolean values, review the main points mentioned above, and then there are several invisible transformations that you should know about:

null and undefined cannot be converted to any other value.
false -> 0.
[] -> 0.
{} -> NaN.

Then you can do most of the questions.

  1. 0==0
  2. null==0
  3. NaN==0
  4. The puzzle is, if both operands are objects, then compare whether they are the same object. If both operands point to the same object, the equal operator returns true; otherwise, it returns false.
  5. 0==0
  6. The puzzles, though they all look the same, are not the same if the types of references point to different addresses. (For details, please refer to my previous article.) Basic Types and Reference Types of javascript)
  7. Confused questions, after adding anti-symbols, we need to consider more situations.
    First of all, according to the question of symbol priority explained in the answer to the previous question,! Priority is higher than ==, so the previous judgment is that []= true, the reverse is false.

    (![] ? true : false) == []  ->  0 == 0

    The main difficulty of this problem is to consider the priority of symbols, first to judge and then to compare.

  8. Same 7
console.log('' == false); //true
console.log(null == false); //false
console.log({} == false); //false
console.log({} == []); //false
console.log([] == false); //true
console.log([] == []); //false
console.log(![] == false); //true
console.log(![] == []); //true

Finally, it's about constructors, which are misleading (see my previous article for details) Some understandings of constructors, prototype chains and new operators in Javascript)
The constructor creates an instance of a user-defined object type or one of the built-in object types with the constructor, so the new Boolean returns not a Boolean value, but a built-in object type, as follows

console.log(typeof Boolean(true));
console.log(typeof new Boolean(true));
console.log(typeof new Boolean(true).valueOf());
console.log(typeof new Boolean(true).toString());

So I know the results are as follows:

  1. new Boolean(true) first calls the object's valueOf() method to return true, then calls the toString() method to output "true", resulting in a string and then calls Number() to return 1.
  2. The principle is as follows
  3. 3 and 4. This is not a comparison, but if judgment, because there are objects, so they all return true.
console.log(new Boolean(true) == 1); //true
console.log(new Boolean(false) == 0); //true
console.log(new Boolean(true) ? true : false); //true
console.log(new Boolean(false) ? true : false); //true

3. On z-index Hierarchical Trees

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8">
        <title></title>
        <style media="screen">
            .one {
                position: relative;
                z-index: 2;
            }
            .two {
                z-index: 3;
            }
            .three {
                position: absolute;
                top: 0;
                z-index: 2;
            }

            .four {
                position: absolute;
                z-index: -1;
            }
            .six {
                position: absolute;
                top: 0;
                z-index: 5;
            }
        </style>
    </head>

    <body>
        <div class="one">
            <div class="two"></div>
            <div class="three"></div>
        </div>
        <div class="four">
            <div class="five"></div>
            <div class="six"></div>
        </div>
    </body>

</html>

According to the structure style, give the order of hierarchy in the example. It's not very easy to say here. Let's look at the principle and try again.
z-index hierarchy in css
The Use Method of CSS z-index Attribute and the Concept of Hierarchical Tree
CSS Foundation (7): z-index Details
Deep understanding of stacked z-index in CSS positioning

Let me give you a color version for your reference.

<!DOCTYPE html>
<html>
    <head>
      <meta charset="utf-8">
      <title></title>
      <style media="screen">
        .one, .four{width: 400px; height: 100px;}
        .two, .five{width: 200px; height: 100px;}
        .three, .six{width: 100px; height: 100px;}

        .one{border:5px solid  red}
        .two{background-color: green}
        .three{background-color: blue}

        .four{border:5px solid  pink; margin-top: -20px;}
        .five{background-color: orange}
        .six{ background-color: black}

        .one{position: relative; z-index: 2}
        .two{z-index: 3}
        .three{position: absolute; top: 0; z-index: 2}

        .four{position: absolute; z-index: -1}
        .six{position: absolute; top: 0; z-index: 5}
      </style>
    </head>
    <body>
        <div class="one">
            <div class="two"></div>
            <div class="three"></div>
        </div>
        <div class="four">
            <div class="five"></div>
            <div class="six"></div>
        </div>
    </body>
</html>

Fourthly, the principle of Function.prototype.bind() method in JS: compatible writing

Generally speaking, we think that the way to change the direction of this function is to call, apply and bind.

Method name describe
call Call one method of an object to replace the current object with another object, and pass the remainder in sequence
apply Call one method of an object to replace the current object with another object, and pass the remainder as an array
bind Create a new function called a binding function. When the binding function is called, the binding function will call the first parameter of the bind() method as this when it is created, the second and subsequent parameters of the bind() method as well as the parameters of the binding function itself as the parameters of the original function in order. Primitive function

The difference between them is that the timing of invocation or the form of adding parameters are different, so we can do compatibility with the methods in it.

Writing in Prototype.js

Function.prototype.bind = function() {
  var fn = this,
    args = [].prototype.slice.call(arguments),
    object = args.shift();
  return function() {
    return fn.apply(object, args.concat([].prototype.slice.call(arguments)));
  };
};

Firefox provides a compatible implementation for bind

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      throw new TypeError(
        'Function.prototype.bind - what is trying to be bound is not callable'
      );
    }

    var aArgs = Array.prototype.slice.call(arguments, 1),
      fToBind = this,
      fNOP = function() {},
      fBound = function() {
        return fToBind.apply(
          this instanceof fNOP && oThis ? this : oThis || window,
          aArgs.concat(Array.prototype.slice.call(arguments))
        );
      };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

Fifth, the classic front-end interview question: What happened from the input URL to the page loading?

I met one of the interview questions personally. I have too much knowledge. I'll give you a plane ticket. Front-end classic interview questions: What happened from the input URL to the page load?

6. Explain the difference between shallow copy and deep copy and implement it simply

To put it simply and intuitively:

  • Shallow copy only copies the attributes of the original object sequentially, while JavaScript storage objects are all memory addresses, so shallow copy will cause deep object attributes to point to the same memory address.
  • Deep copy not only copies the attributes of the original object in turn, but also copies the attributes of the original object recursively to the new object by deep copy.

(For details, please refer to my previous article. Basic Types and Reference Types of javascript)

Simple implementation of shallow copy:

function shallowCopy(obj) {
  var _obj = {},
    key;
  //It's more convenient to use Object.keys
  for (key in obj) {
    //Copy only the first attributes of the object itself
    if (obj.hasOwnProperty(key)) {
      _obj[key] = obj[key];
    }
  }
  return _obj;
}

var obj1 = {
    a: 1,
    b: {
      c: 2,
    },
  },
  obj2;

//Copy the object and change the original object value
obj2 = shallowCopy(obj1);
obj1.a = 2;
obj1.b.c = 3;
console.log(obj1, obj2);

Shallow copy ES5 implementation:

The Object.assign() method is used to copy the values of all enumerable attributes from one or more source objects to the target object. It will return the target object

function shallowCopy(obj) {
  return Object.assign({}, obj);
}

var obj1 = {
    a: 1,
    b: {
      c: 2,
    },
  },
  obj2;

//Copy the object and change the original object value
obj2 = shallowCopy(obj1);
obj1.a = 2;
obj1.b.c = 3;
console.log(obj1, obj2);

Simple implementation of deep copy:

function deepCopy(obj) {
  var _obj = {},
    key;
  //It's more convenient to use Object.keys
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (typeof obj[key] === 'object') {
        _obj[key] = deepCopy(obj[key]);
      } else {
        _obj[key] = obj[key];
      }
    }
  }
  return _obj;
}

var obj1 = {
    a: 1,
    b: {
      c: 2,
    },
  },
  obj2;

//Copy the object and change the original object value
obj2 = deepCopy(obj1);
obj1.a = 2;
obj1.b.c = 3;
console.log(obj1, obj2);

Be careful:

  • for in traversal performance is too poor to include attributes on object prototype chains, even if hasOwn Property judgment is added, it only reduces operation attributes.
  • Some people will use arguments.callee method recursively, but the Strict Mode of ECMAscript5 is prohibited because arguments are huge and changeable. Why was the arguments.callee.caller property deprecated in JavaScript?

Function name call benefits:

  • This function can be called in code like any other code.
  • It does not pollute namespaces.
  • Its value will not change.
  • It performs better (accessing parameter objects is expensive).

Deep copy transcription:

The value serialized into a JSON string opens a new storage address to separate the two.

function deepCopy(obj) {
  return JSON.parse(JSON.stringify(obj));
}

var obj1 = {
    a: 1,
    b: {
      c: 2,
    },
  },
  obj2;

//Copy the object and change the original object value
obj2 = deepCopy(obj1);
obj1.a = 2;
obj1.b.c = 3;
console.log(obj1, obj2);

Be careful:

  • It can only deal with data structures (Number, String, Boolean, Array, Flat Object, etc.) that can be directly represented by json. It does not support NaN, Infinity, circular reference, function, etc.
  • If an instance is constructed, the constructor and other related attributes of the original object will be cut off.

Deep copy Object.create:

Create an object that has a specified prototype and optionally contains specified properties

function deepCopy(obj) {
  var _obj = {},
    key;
  //It's more convenient to use Object.keys
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (typeof obj[key] === 'object') {
        _obj[key] = Object.create(obj[key]);
      } else {
        _obj[key] = obj[key];
      }
    }
  }
  return _obj;
}

var obj1 = {
    a: 1,
    b: {
      c: 2,
    },
  },
  obj2;

//Copy the object and change the original object value
obj2 = deepCopy(obj1);
obj1.a = 2;
obj1.b.c = 3;
console.log(obj1, obj2);

Be careful:

Attributes are not in the attributes of the replicated object itself, but in the prototype chain.
(For details, please refer to my previous article. Three ways to create objects - literal quantities, new constructors and Object.create())

Without considering compatibility, you can write as follows:

function deepCopy(obj) {
  var _obj = obj.constructor === Array ? [] : {};

  if (window.JSON) {
    _obj = JSON.parse(JSON.stringify(obj));
  } else {
    Object.keys(obj).map(key => {
      _obj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
    });
  }

  return _obj;
}

var obj1 = {
    a: 1,
    b: {
      c: 2,
    },
  },
  obj2;

//Copy the object and change the original object value
obj2 = deepCopy(obj1);
obj1.a = 2;
obj1.b.c = 3;
console.log(obj1, obj2);

Seven, native JS operation DOM?

It's mostly tears. From native to JQ to MV* framework, the ancestors are blurred.

Find Nodes

Method Effect
document.getElementById Find elements by ID, case sensitive, and return only the first if there are multiple results
document.getElementsByClassName Find elements based on class names, and multiple class names are separated by spaces, returning an HTMLCollection. Note that the compatibility is IE9+ (including). In addition, not only document, but other elements also support the getElementsByClassName method
document.getElementsByTagName Find elements based on tags, * means querying all tags and returning an HTMLCollection
document.getElementsByName Returns a NodeList based on the name attribute of the element
document.querySelector Specifies a CSS selector for one or more matching elements. You can use their id s, classes, types, attributes, attribute values, and so on to select elements. Returns a single Node, IE8+ (including), and only returns the first if multiple results are matched.
document.querySelectorAll Specifies a CSS selector for one or more matching elements. You can use their id s, classes, types, attributes, attribute values, and so on to select elements. Returns a NodeList, IE8+ (including)
document.forms Get all form s of the current page and return an HTMLCollection
<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8">
        <title></title>
    </head>

    <body>
        <ul id="id">
            <li class="list1">
                <input type="text" name="name" value="">
            </li>
            <li class="list1"></li>
            <li class="list1"></li>
            <li class="list1 list2"></li>
            <li class="list1 list2"></li>
        </ul>
        <script type="text/javascript">
            console.log(document.getElementById('id'));
            console.log(document.getElementsByClassName('list1'));
            console.log(document.getElementsByClassName('list1 list2'));
            console.log(document.getElementsByTagName('*'));
            console.log(document.getElementsByTagName('li'));
            console.log(document.getElementsByName('name'));
            console.log(document.querySelector('#id'));
            console.log(document.querySelector('.list1'));
            console.log(document.querySelectorAll('.list1'));
        </script>
    </body>

</html>

Create Nodes

Method Effect
document.createElement Create elements
document.createTextNode Create text
document.cloneNode Clone the element and receive a bool parameter to indicate whether the child element is copied or not.
document.createDocumentFragment Create document fragments
document.createComment Create annotation nodes
//Create and insert text
var ele = document.createElement('div'),
  txt = document.createTextNode('123'),
  cmt = document.createComment('comments');
ele.appendChild(txt);
ele.appendChild(cmt);
//Cloning Elements
var clone_ele1 = ele.cloneNode(),
  clone_ele2 = ele.cloneNode(true);

console.log(ele);
console.log(txt);
console.log(clone_ele1);
console.log(clone_ele2);

Modify Nodes

Method Effect
parent.appendChild(child) Append child to the end of parent's child node
parentNode.insertBefore(newNode, refNode) Insert one node in front of another
parent.removeChild(child) Delete the specified child node and return the child node
parent.replaceChild(child) Replace one node with another
parent.insertData(child) Inserting data into existing text nodes
//Create and insert text
var ele = document.createElement('div'),
  txt = document.createTextNode('123'),
  txt2 = document.createTextNode('456'),
  cmt = document.createComment('comments');

ele.appendChild(txt);
ele.insertBefore(cmt, txt);
ele.removeChild(cmt);
ele.replaceChild(txt2, txt);
txt.insertData(0, '789');
console.log(ele);

Eighth, to solve the problem of calculation accuracy, such as 0.1 + 0.2?

toFixed() problem:

  • The string is returned.
  • It will force reservations of limited decimal places.
  • Some browsers have a slightly different carry for decimal numbers.
console.log((0.1 + 0.2).toFixed(2));

The idea of online circulation is to convert digits into integers and then divide them back to their original positions.

/**
 * floatTool Four methods of addition, subtraction, multiplication and division are included to ensure that the accuracy of floating-point arithmetic is not lost.
 *
 * We know that floating-point computation in computer programming languages has the problem of loss of precision (or rounding error). The fundamental reason is that some numbers can not be expressed in finite quantities due to binary and implementation digit constraints.
 * Here is the binary representation of decimal decimal
 *      0.1 >> 0.0001 1001 1001 1001...(1001 Infinite cycle)
 *      0.2 >> 0.0011 0011 0011 0011...(0011 Infinite cycle)
 * The storage of each data type in a computer is limited in width, such as JavaScript, which uses 64 bits to store numeric types, so the excess is eliminated. The missing part is the missing part of accuracy.
 *
 * ** method **
 *  add / subtract / multiply /divide
 *
 * ** explame **
 *  0.1 + 0.2 == 0.30000000000000004 (It's 0.00000000004 more.
 *  0.2 + 0.4 == 0.6000000000000001  (0.0000000001 more)
 *  19.9 * 100 == 1989.9999999999998 (Less than 0.0000000002)
 *
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
var floatTool = (function() {
  /*
   * Determine whether obj is an integer
   */
  function isInteger(obj) {
    return Math.floor(obj) === obj;
  }

  /*
   * Converts a floating-point number to an integer, returning integers and multiples. If 3.14 > > 314, the multiple is 100
   * @param floatNum {number} decimal
   * @return {object}
   *   {times:100, num: 314}
   */
  function toInteger(floatNum) {
    var ret = {
      times: 1,
      num: 0,
    };
    if (isInteger(floatNum)) {
      ret.num = floatNum;
      return ret;
    }
    var strfi = floatNum + '';
    var dotPos = strfi.indexOf('.');
    var len = strfi.substr(dotPos + 1).length;
    var times = Math.pow(10, len);
    var intNum = parseInt(floatNum * times + 0.5, 10);
    ret.times = times;
    ret.num = intNum;
    return ret;
  }

  /*
   * Core method to realize addition, subtraction, multiplication and division operation to ensure that accuracy is not lost
   * Idea: Enlarge decimal to integer (multiply), perform arithmetic operation, and then reduce to decimal (divide)
   *
   * @param a {number} Operator 1
   * @param b {number} Operator 2
   * @param digits {number} Precision, reserved decimal points, such as 2, reserved for two decimal places
   * @param op {string} Operational type, add/subtract/multiply/divide
   *
   */
  function operation(a, b, op) {
    var o1 = toInteger(a);
    var o2 = toInteger(b);
    var n1 = o1.num;
    var n2 = o2.num;
    var t1 = o1.times;
    var t2 = o2.times;
    var max = t1 > t2 ? t1 : t2;
    var result = null;
    switch (op) {
      case 'add':
        if (t1 === t2) {
          // Two decimal digits are the same
          result = n1 + n2;
        } else if (t1 > t2) {
          // O 1 decimal digit greater than O 2
          result = n1 + n2 * (t1 / t2);
        } else {
          // O 1 decimal less than O 2
          result = n1 * (t2 / t1) + n2;
        }
        return result / max;
      case 'subtract':
        if (t1 === t2) {
          result = n1 - n2;
        } else if (t1 > t2) {
          result = n1 - n2 * (t1 / t2);
        } else {
          result = n1 * (t2 / t1) - n2;
        }
        return result / max;
      case 'multiply':
        result = (n1 * n2) / (t1 * t2);
        return result;
      case 'divide':
        return (result = (function() {
          var r1 = n1 / n2;
          var r2 = t2 / t1;
          return operation(r1, r2, 'multiply');
        })());
    }
  }

  // Four interfaces for addition, subtraction, multiplication and division
  function add(a, b) {
    return operation(a, b, 'add');
  }

  function subtract(a, b) {
    return operation(a, b, 'subtract');
  }

  function multiply(a, b) {
    return operation(a, b, 'multiply');
  }

  function divide(a, b) {
    return operation(a, b, 'divide');
  }

  // exports
  return {
    add: add,
    subtract: subtract,
    multiply: multiply,
    divide: divide,
  };
})();

9. Recursive closure function

function fun(n, o) {
  console.log(o);
  return {
    fun: function(m) {
      return fun(m, n);
    },
  };
}

Situation I

var a = fun(0) // undefined
a.fun(1) // 0
a.fun(2) // 0
a.fun(3) // 0

Because recursion can seem confusing, we try to split it up and show it.

n = 0;
o = undefined;
a = {
  fun: function(m) {
    return fun(m, n);
  }
};
--------------------
m = 1;
n = 0;
a = {
  fun: function(m) {
    return fun(m, n);
  }
};
--------------------
m = 2;
n = 0;
a = {
  fun: function(m) {
    return fun(m, n);
  }
};
--------------------
m = 3;
n = 0;
a = {
  fun: function(m) {
    return fun(m, n);
  }
};

There's a lot of interference code, but in fact only the first time it's executed, it's assigned to o, and subsequent calls just change the n value.

Situation II

var b = fun(0).fun(1).fun(2).fun(3);
// undefined
// 0
// 1
// 2

At first glance, this is not different from the above, but the result is quite surprising. I have been thinking for a long time. Another problem is that I did not remember the execution method at that time. Operators whose priority is higher.

n = 0;
o = undefined;
a = {
  fun: function(m) {
    return fun(m, n);
  }
};
--------------------
m = 1;
n = 0;
a = {
  fun: function(m) {
    return fun(m, n);
  }
};
--------------------
m = 2;
n = 1;
a = {
  fun: function(m) {
    return fun(m, n);
  }
};
--------------------
m = 3;
n = 2;
a = {
  fun: function(m) {
    return fun(m, n);
  }
};

Finally, it comes to mind that case one is actually the use of closures, and also involves the knowledge of execution environment and scope, because it belongs to the first variable o after execution, so except for the first normal assignment, the follow-up is only assignment m, so the output result is 0.
(For details, please refer to my previous article. Application of Javascript Difficult Knowledge - Recursion, Closure, Curitization, etc. (Update from time to time))
The second case is more special, because he can access the change o in the whole process of running the execution environment, and it depends on his own understanding. I don't know how to express it.

Situation III

var c = fun(0).fun(1)
c.fun(2)
c.fun(3)

// undefined
// 0
// 1
// 1

If you understand the above two situations, the problem will be simple.

Keywords: Javascript Attribute JSON less

Added by bqheath on Mon, 05 Aug 2019 11:20:29 +0300