Summary of JS basic questions

Posted by lordofgore on Mon, 28 Feb 2022 13:26:12 +0100

Summary of JS basic questions

[refer to the "question of the original JS soul" of the three great gods of nuggets, JS advanced programming, web front-end interview - interviewer series, etc.]

1. The object is passed as a function parameter

Say the running result of the following code and explain the reason

function test(person) {
  person.age = 26
  person = {
    name: 'hzj',
    age: 18
  }
  return person
}
const p1 = {
  name: 'fyq',
  age: 19
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?

First, we trace back to variables: variables contain two different types of data: original values and reference values. The original value is the simplest data, and the reference value is an object composed of multiple values. When assigning a value to a variable, the variable that holds the original value is accessed by value. What we operate is the actual value stored in the variable. The reference value is an object stored in memory. JS does not allow direct access to the memory location. When operating an object, it is actually the reference of the object; In addition to different storage methods, the original value and reference value are also different when copied through variables. The original value will be copied to the location of the new variable. The two stored values are the same, but they are completely independent of each other. When assigning a reference value from one variable to another, the copied value is a pointer to the object stored in heap memory, so the changes on one object will be reflected on another object. That is, instances of two objects point to the same object.

Next, when it comes to passing parameters, the parameters of all functions in ECMAScript are passed by value. Take the above code as an example: p1 object is passed to the test() method as a parameter and copied to the parameter person. Inside the function, p1 and person point to the same reference, and the object they point to is saved in the heap memory on the global scope When age changes, p1 Age will change naturally. p2 variable holds the pointer of person rewritten.

result:

p1: {name: "fyq", age: 26}
p2: {name: "hzj", age: 18}

2.typeof and instanceof

  • Manually implement instanceof function
    instanceof is mainly used to judge whether an instance belongs to a certain type. The implementation principle is that the prototype of the variable on the right is on the prototype chain of the variable on the left. In the process of searching, the prototype chain of the variable on the left will be traversed until the prototype of the variable on the right is found. If the search fails, false will be returned
function new_instanceof(leftValue,rightValue) {
 // Here, use typeof to judge the basic data type. If yes, return false directly
    if(typeof leftValue !== 'object' || leftValue === null) return false;
	let left = leftValue._proto_;
	let right = rightValue.prototype;
	while(true) {
		if(left=null) { return false;}
		if(left=right) { return true;}
		left=left._proto_ 
}
}
  • The difference between the two
    typeof and instanceof are both methods for judging data types. The differences are as follows:

    typeof Will return the basic type of a variable, instanceof Returns a Boolean value
    
    instanceof Complex reference data types can be accurately judged, but basic data types cannot be correctly judged
    
    and typeof There are also disadvantages, although it can judge the basic data type( null Except), but in the reference data type, except function Other than the type, others cannot be judged
    
    It can be seen that the above two methods have disadvantages and can not meet the needs of all scenarios
    

    If you need a general detection data type, you can use object prototype. ToString, call this method to uniformly return the string in the format "[object Xxx]"
    The code is as follows:

Object.prototype.toString({})        // "[object Object]"
Object.prototype.toString.call({})  // The result is the same as above. It's ok to add call
Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call('1')  // "[object String]"
Object.prototype.toString.call(true)  // "[object Boolean]"
Object.prototype.toString.call(function(){})  // "[object Function]"
Object.prototype.toString.call(null)   //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g)    //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([])       //"[object Array]"
Object.prototype.toString.call(document)  //"[object HTMLDocument]"
Object.prototype.toString.call(window)   //"[object Window]"

3. Array conversion [] = =! [] what is the result?

Any object converted to Boolean value is true, but "!" is encountered during comparison Convert value to Boolean first, not encountered '!' First convert to string, [] directly convert to Boolean value is true, so! [] is converted to a Boolean value of false, [] is converted to a string of "" and then converted to a Boolean value of false. Finally, they are converted to the number 0. 0 = = 0, the result is true

4. Convert object to original value

First, judge whether the object has a symbol Toprimitive (hint) method, if any, executes this method without following steps:
Convert object to string: if the object has toString() method, JS will convert this value to string and return a string result. If there is no toString () method, the valueof() method will be called.
When the object is converted to the default type or numeric type, the valueof() method will be executed first. If there is no valueof() method, but the toString() method is defined, the toString() method will be executed.

give an example:
How to make if (a = = 1 & & A = = 2) condition hold

var a={
	value:0,
	valueOf:function() {
		this.value++;
		return this.value;
	}
};
console.log(a==1&&a==2);

When two operators are represented by two equals signs, the two operators will first carry out type conversion, usually forced type conversion. If one operator is an object and the other is not, call the valueOf() method of the object to obtain its original value.

5.NaN represents a non numeric value, which is special: it is not equal to any value, including itself. How to judge NaN: X= X returns true

6.jQuery siblings(), next(), find() method

1. The siblings () method returns all sibling elements of the selected element.

The following example returns all sibling elements of < H2 >:

example

$(document).ready(function(){
  $("h2").siblings();
});

You can also use optional parameters to filter searches for sibling elements.

The following example returns all < p > elements belonging to sibling elements of < H2 >:

example

$(document).ready(function(){
  $("h2").siblings("p");
});

2.jQuery next() method
The next() method returns the next sibling element of the selected element. This method returns only one element.

The following example returns the next sibling element of < H2 >:

example

$(document).ready(function(){
  $("h2").next();
});

3.jQuery find() method
The find() method returns the descendant element of the selected element, all the way down to the last descendant.

The following example returns all < span > elements that belong to < div > descendants:

example

$(document).ready(function(){
  $("div").find("span");
});

7.JS variable promotion and function promotion

It means that if var declares variables or declares variables with function function name () {}, it will be promoted to the top in js pre parsing stage. Secondly, function promotion takes precedence over variable promotion

console.log(foo);
var foo = 1  //Variable promotion
console.log(foo)
foo()
function foo(){ //Function promotion
   console.log('function')
}

Equivalent to:

function foo(){ //Mention the top
   console.log('function')
}
var foo 
console.log(foo) //Output the function foo, because foo is not assigned, and foo is still the original value 
foo = 1;  //Assignment will not be promoted. After assignment, foo is no longer a function type, but a number type
console.log(foo) //Output 1
foo() //An error will be reported here because foo is not a function

8. Closure

The concept of a closure refers to the concept of a function in another scope.
Understanding the scope chain is very important to the understanding of closures: when calling a function, an execution context will be created for the function call, a scope chain will be created, and then the active object of the function will be initialized with arguments and other named parameters. The active object is pushed to the front of the scope chain. The active object of the external function is the second object on the scope chain of the internal function. This scope chain has been connecting all the active objects containing the function outward until the global execution context is terminated. When a function is executed, there will be an object containing variables in each execution context. Variables in the global context are called variable objects. It will always exist during code execution. The object in the local context of a function is called an active object, which exists only during the execution of the function.

function creatCompare(propertyName) {
	return function (obj1,obj2) {
		let value1 = obj1[propertyName];
		let value2 = obj2[propertyName];
		if(value1<value2) {return -1;}
		else if(value1>value2) {return 1;}
		else {return 0;}
	};
}
let compare = creatCompare('name');
let result = compare({name:'Nicholas'},{name:'Matt'});

In this example, a function is returned inside the function. The returned function calls the variable propertyName of the external function. After being returned in this internal function and used elsewhere, it still refers to this variable. This function adds its active object containing the function to its scope chain, in the creatCompare () function. The scope chain of the anonymous function actually contains the active object of createcompare(), as follows:

From JS advanced programming P311
Characteristics of closures:
Make it possible for external access to internal variables of functions;
It can avoid the use of global variables and prevent the pollution of global variables;
Local variables can be resident in memory;
It will cause memory leakage (a piece of memory space is occupied for a long time without being released)

Application scenario:

1. Buried point (a common data acquisition method for website analysis). In timers, event listeners, AJAX requests, cross window communication, Web Workers, or any asynchronous, as long as a callback function is used, it is actually using closures.

function count(){
	var num = 0;
	return function() {
		return ++num;
	}
}
var getNum = count();
var getNewNum = count();
document.querySelectorAll('button')[0].onclick = function (){
	console.log('Number of clicks to add to shopping cart:' + getNum());
}
document.querySelectorAll('button')[1].onclick = function (){
	console.log('Click payment times:' + getNewNum());
}

2. Execute the function immediately, create a closure, and save the global scope and the scope of the current function
3. Pass function as parameter
4. Return a function (example above)

The most classic example of closure problem:
How to solve the following loop output problem?

for(var i = 1;i <= 5;i++) {
	setTimeout(function timer(){
		console.log(i)
	},0)
}

Why all outputs are 6? If you understand the closure mechanism at the beginning, it is not difficult to see that setTimeout is a macro task. The single thread eventLoop mechanism in JS executes the macro task only after the main thread synchronization task is executed. Therefore, after the loop is completed, there is an i in the global variable with a value of 6. When executing a macro task, i is not found in the scope of the current callback function. Go to the upper layer of the scope chain and find i. all printed out in turn are 6

How to solve it?
1. Use the immediate execution function to pass the i variable to the timer every time the for loop

for(i = 1;i <=5;i++) {
	(function (j){
		setTimeout(function timer(){
		console.log(j)
	},0)
	})(i)
}

2. Pass the third parameter to the timer as the first function parameter of timer function

for(i = 1;i <=5;i++) {}
		setTimeout(function timer(j){
		console.log(j)
	},0,i)
}

3. Using the let in ES6, ES6 adds a block level scope, and the declared variables are only valid in the current block. After using let, the scope chain no longer exists

for(let i = 1;i <=5;i++) {
	setTimeout(function timer(){
		console.log(i)
	},0)
}

9. Naming of variables:

(1) The first character must be a letter, underscore () Or a dollar sign ($); Other characters can be letters, underscores, dollar symbols, or numbers.

(2) Variable names cannot include spaces, plus or minus symbols;

(3) Keywords in JS cannot be used as variable names, such as int,new, etc;

(4) JS variable names are strictly case sensitive;

10. Succession

Implementation inheritance is the only inheritance method supported by ECMAScript, which is mainly realized through prototype chain

First: with the help of prototype chain

When the prototype contains reference values, the reference values in the prototype will be shared among all instances. When prototype inheritance is used, the prototype will actually become an instance of another prototype`

 function Parent1() {
    this.play = [1, 2, 3]
  }
  function Child1() {
  }
  Child1.prototype = new Parent1();
  var s1 = new Child1();
  var s2 = new Child1();
  s1.play.push(4);
  console.log(s1.play, s2.play);//[1,2,3,4],[1,2,3,4]

All instances of Parent1 have the property of play. After being inherited by Child1 through the prototype chain, Child1 Prototype becomes an instance of Parent1, and all get their own play attribute. As a result, all instances on Child1 will share this attribute. So basically, prototype chain inheritance will not be used alone.

The second is to steal constructors

 function Parent2(){
    this.play = [1, 2, 3]
  }
  function Child2(){
    Parent2.call(this);
  }
 Child2.prototype = new Parent2();
  var s1 = new Child2();
  var s2 = new Child2();
  s1.play.push(4);
  console.log(s1.play, s2.play);//[1,2,3,4],[1,2,3]

In order to solve the inheritance problem caused by the reference value of the prototype, call the constructor of the parent class in the subclass constructor and use the call() or apply() method. As a result, each instance has its own play attribute. However, subclasses cannot access methods defined on the parent class prototype. Stealing constructors can hardly be used alone.

The third kind: combination inheritance

function Parent3 (name) {
    this.name = name;
    this.play = [1, 2, 3];
  }
  Parent3.prototype.sayName = function(){
 		console.log(this.name);
	}
   function Child3() {
    Parent3.call(this,name);
  }
  Child3.prototype = new Parent3();
  var s3 = new Child3("a");
  var s4 = new Child3("b");
  s3.play.push(4);
  console.log(s3.play, s4.play);
  console.log(s3.sayName(), s4.sayName();
 


There is an efficiency problem in combinatorial inheritance, that is, the constructor of the parent class is always invoked two times. Once it is called when creating the subclass prototype, it is called in the subclass constructor second times.

Fourth: parasitic combination

  function Parent4(name) {
            this.play = [1, 2, 3];
        }
        function Child4() {
            Parent4.call(this);
        }
        Child4.prototype = Object.create(Parent4.prototype);
        Child4.prototype.constructor = Child4;
        var s3 = new Child4();
        s3.play.push(4);
        console.log(s3);

Object. The create () method essentially clones two Parent4 and only calls the Parent4 constructor once, Child4 prototype. Constructor = Child4 also makes the instance object point to Child4 again, which solves the problem that the default constructor is lost due to rewriting the prototype. Parasitic composite inheritance can be regarded as the best mode of reference type inheritance.
It is mentioned here that the extensions keyword in es6 directly inherits the inheritance of js

class Person {
  constructor(name) {
    this.name = name
  }
  // Prototype method
  // Person prototype. getName = function() { }
  // It can be abbreviated as getName() {...}
  getName = function () {
    console.log('Person:', this.name)
  }
}
class Gamer extends Person {
  constructor(name, age) {
    // If there is a constructor in the subclass, you need to call super() before using "this".
    super(name)//super() is equivalent to person call(this)
    this.age = age
  }
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // Successfully accessed the method of the parent class

Extensions actually adopts parasitic combination inheritance
Problems of inheritance itself
It is impossible to decide which attributes to inherit. All attributes should be inherited. The code coupling between parent and son is too high. Solution: combination oriented design.

11,*


The attribute name added in js uses the hump method, and the connection line used in css returns the node list except id and query

The result is: document getElementsByClassName(“test”)[0].style.backgroundColor=“red”;

12. * like other IEEE 754 programming languages that represent floating-point numbers, the number of JavaScript has accuracy problems. For example, the result of 0.2 + 0.4 is 0.600 billion 00000 1. Which of the following options can get 0.6?


Resolution:

13,*


Analysis: the answer is "error"

In JS, there are only two ways to declare functions:
Type 1: function foo() {...} (function declaration)
Type 2: var foo = function() {...} (anonymous function must be followed by the equal sign, which is essentially a function expression)
In addition, things like var foo = function bar() {...} are handled in a unified way according to 2, that is, the function cannot be accessed through bar outside the function, because it has become an expression.
But why not "undefined"?
If you ask for typeof g, it will return undefined, but if you ask for g(), you will call function G first, and an exception will be thrown directly here, so it is Error.

14. * what is the NOSCRIPT tag used for?

Used to define alternative content when the script is not executed:

<body>  
...
  ...

  <script type="text/javascript">
    <!--
    document.write("Hello World!")
    //-->
  </script><noscript>Your browser does not support JavaScript!</noscript>...
  ...
</body> 

15. The undefined value is derived from null. When = =, it will be automatically converted to null, so it returns true. However, if the strict comparison character = = =, no conversion occurs and false will be returned.

16. Flattening of arrays

Requirement: convert multidimensional array into one-dimensional array
Suppose a function named flatten can flatten the array, and the effect will be as follows:

var arr = [1, [2, [3, 4]]];
console.log(flatten(arr)) // [1, 2, 3, 4]

First: recursion

let result = [];
function flatten(arr) {
    for (let i = 0; i < arr.length; i++) {
           if (Array.isArray(arr[i])) {
                flatten(arr[i]);
                } else {
                    result.push(arr[i]);
                }
            }
    return result;
 arr = [1, [2, 3],[4, 5, 6]];
console.log(flatten(arr));

The second type: toString(),split(','), map(callback)

let arr = [1, [2, 3], [4, 5, 6]];
 //toString() replaces the array with a flattened string
 //split('') converts a string into an array of strings divided by
 //The map() method returns a new array. The elements in the array are the values of the original array elements after calling the function. The map() method processes the elements in order of the original array elements.
 function flatten(arr) {
       return arr.toString().split(',').map((item) => {
           return +item;//Quickly convert strings to numbers
  })
 }
console.log(flatten(arr));

Third: reduce

let arr = [1, [2, [3, 4]]];
 function flatten(arr) {
      return arr.reduce((pre, cur) => {
           return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
            }, [])
        }
console.log(flatten(arr));

Fourth: extension operator

let arr = [1, [2, [3, 4]]];
let result = [];
function flatten(arr) {
      while (arr.some(item => Array.isArray(item))) {
            arr = [].concat(...arr)
            }
         return arr;
        };
console.log(flatten(arr));

The fifth type: ES6 flat()

let arr = [1, [2, [3, 4]]];
ary = arr.flat(Infinity)
console.log(ary);

17. this point in Javascript timer

var name = 'my name is window';
var obj = {
    name: 'my name is obj',
    fn: function () {
        var timer = null;
        clearInterval(timer);
        timer = setInterval(function () {
            console.log(this.name);  //my name is window
        }, 1000)
    }
}
obj.fn()

Here, from this Name can see that this points to window.

If there is no special point, the point of this in the callback functions of setInterval and setTimeout is window. This is because the timer method of JS is defined under window. However, in many scenarios, the direction of this needs to be modified. Several are summarized here:

The first: the most commonly used method: save this as a variable in the external function, and use this variable in the callback function instead of using this directly.

var name = 'my name is window';
var obj = {
    name: 'my name is obj',
    fn: function () {
        var that = this;
        var timer = null;
        clearInterval(timer);
        timer = setInterval(function () {
            console.log(that.name);   //my name is obj
        }, 1000)
    }
}

Add var that = this in fn; Use that instead of this in the callback function. This method is the most common and widely used.

The second is to use the bind() method (bind() is the standard of ES5. There is a compatibility problem under the lower version of IE, so ES5 shim can be introduced JS solution)

The function of bind() is similar to call and apply. Both of them modify the point of this. However, call and apply are functions that will be executed immediately after this point is modified, while bind returns a new function, which will create a new function with the same body as the original function, and this in the new function points to the incoming object.

var name = 'my name is window';
var obj = {
    name: 'my name is obj',
    fn: function () {
        var timer = null;
        clearInterval(timer);
        timer = setInterval(function () {
            console.log(this.name);   //my name is obj
        }.bind(this), 1000)
    }
}

The reason why call and apply cannot be used here is that call and apply do not return functions, but execute functions immediately. Then, the function of timer will be lost.

Third: use the arrow function of es6: the greatest function of the arrow function is this pointing.

var name = 'my name is window';
var obj = {
    name: 'my name is obj',
    fn: function () {
        var timer = null;
        clearInterval(timer);
        timer = setInterval(() => {
            console.log(this.name);  //my name is obj
        }, 1000)
    }
}

The arrow function does not have its own this, which inherits from the scope of the external function. Therefore, in this example, this in the timer callback function inherits fn. Of course, the arrow function also has compatibility problems. If it is compatible with low version ie, it needs to be compiled with babel and Es5 shim JS.

Quoted in: blog Garden: snow white wind https://www.cnblogs.com/443855539-wind/p/6480673.html

18. Shallow copy

Shallow copy refers to the creation of new data. This data has an accurate copy of the attribute value of the original data. If the attribute is a basic type, the copied value is the value of the basic type. If the attribute is a reference type, the copy is the memory address, that is, the shallow copy is the copy layer, and the deep reference type is the shared memory address. The following example shows that the shallow copy is the memory address:

var obj = {
            age: 18,
            nature: ['smart', 'good'],
            names: {
                name1: 'fx',
                name2: 'xka'
            },
            love: function() {
                console.log('fx is a great girl')
            }
        }
        var newObj = Object.assign({}, obj);
        newObj.age = 20;
        console.log(newObj);
        console.log(obj);

 var obj = {
            age: 18,
            nature: ['smart', 'good'],
            names: {
                name1: 'fx',
                name2: 'xka'
            },
            love: function() {
                console.log('fx is a great girl')
            }
        }
        var newObj = Object.assign({}, obj);
        newObj.names.name1 = 'a';
        console.log(newObj);
        console.log(obj);


Implementation of shallow copy:

First: slice()

        let arr = [1, [2, [3, 4]]];
        let newArr = arr.slice();
        newArr[0] = 100;
        console.log(arr);//[1, [2, [3, 4]]]
        console.log(newArr);//[100, [2, [3, 4]]]

Second: manual implementation

let arr = [1, [2, [3, 4]]];
        const shallClone = function(target) {
            if (typeof target === 'object' && target != null) {
                const cloneTarget = Array.isArray(target) ? [] : {};
                for (let prop in target) {
                    console.log(prop);//0 1
                    if (target.hasOwnProperty(prop)) {
                        cloneTarget[prop] = target[prop];
                    }
                }
                return cloneTarget;
            } else {
                return target;
            }
        }
        console.log(shallClone(arr));//1,[2, [3, 4]]

The third type: object assign()

const target = { a: 1, b: 2 };
const source = {b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

Object.assign() is only applicable to shallow copy. If the source value is a reference to an object, it will only copy its reference value. It copies the reference of the object's attribute, not the object itself

const log = console.log;

function test() {
  'use strict';
  let obj1 = { a: 0 , b: { c: 0}};
  let obj2 = Object.assign({}, obj1);
  log(JSON.stringify(obj2));
  // { a: 0, b: { c: 0}}

  obj1.a = 1;
  log(JSON.stringify(obj1));
  // { a: 1, b: { c: 0}}
  log(JSON.stringify(obj2));
  // { a: 0, b: { c: 0}}

  obj2.a = 2;
  log(JSON.stringify(obj1));
  // { a: 1, b: { c: 0}}
  log(JSON.stringify(obj2));
  // { a: 2, b: { c: 0}}

  obj2.b.c = 3;
  log(JSON.stringify(obj1));
  // { a: 1, b: { c: 3}}
  log(JSON.stringify(obj2));
  // { a: 2, b: { c: 3}}

  // Deep Clone
  obj1 = { a: 0 , b: { c: 0}};
  let obj3 = JSON.parse(JSON.stringify(obj1));
  obj1.a = 4;
  obj1.b.c = 4;
  log(JSON.stringify(obj3));
  // { a: 0, b: { c: 0}}
}

test();

*[see MDN for details]

Fourth: concat shallow copy array

       let arr = [1, [2, [3, 4]]];
        let newArr = arr.concat();
        newArr[0] = 100;
        console.log(arr); //[1, [2, [3, 4]]]
        console.log(newArr); //[100, [2, [3, 4]]]

Fifth:... Expansion operator

        let arr = [1, [2, [3, 4]]];
        let newArr = [...arr]
        newArr[0] = 100;
        console.log(arr); //[1, [2, [3, 4]]]
        console.log(newArr); //[100, [2, [3, 4]]]

19. Deep copy

Deep copy opens up a new stack. The properties of two objects are exactly the same, but corresponding to two different addresses, modifying the properties of one object will not change the properties of the other object. The following examples illustrate the difference between deep copy and shallow copy

var obj = {
            age: 18,
            nature: ['smart', 'good'],
            names: {
                name1: 'fx',
                name2: 'xka'
            },
            love: function() {
                console.log('fx is a great girl')
            }
        }

        function deepClone(obj, hash = new WeakMap()) {
            if (obj === null) return obj; // If it is null or undefined, I will not copy
            if (obj instanceof Date) return new Date(obj);
            if (obj instanceof RegExp) return new RegExp(obj);
            // It may be an object or ordinary value. If it is a function, it does not need deep copy
            if (typeof obj !== "object") return obj;
            // If it is an object, you need to make a deep copy
            if (hash.get(obj)) return hash.get(obj);
            let cloneObj = new obj.constructor();
            // What is found is the constructor on the prototype of the class, and the constructor on the prototype points to the current class itself
            hash.set(obj, cloneObj);
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    // Implement a recursive copy
                    cloneObj[key] = deepClone(obj[key], hash);
                }
            }
            return cloneObj;
        }
        //After changing the second layer attribute value name1 after copying, the value in obj does not change, indicating the difference between deep copy and shallow copy
        deepClone(obj).names.name1 = 'a';
        console.log(obj);


Implementation of deep copy:

First: cloneDeep()

const _ = require('lodash');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

The second: jQuery extend()

const $ = require('jquery');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false

The third kind: JSON stringify()

const obj2=JSON.parse(JSON.stringify(obj1));

However, this method has disadvantages. undefined, symbol and function are ignored

const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

Fourth: recursive loop at the beginning

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // If it is null or undefined, I will not copy
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // It may be an object or ordinary value. If it is a function, it does not need deep copy
  if (typeof obj !== "object") return obj;
  // If it is an object, you need to make a deep copy
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // What is found is the constructor on the prototype of the class, and the constructor on the prototype points to the current class itself
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // Implement a recursive copy
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

Quote from -- interviewer https://vue3js.cn/interview/JavaScript/copy.html#%E4%B8%89%E3%80%81%E6%B7%B1%E6%8B%B7%E8%B4%9D

20. V8 executes a section of JS code

  • Firstly, AST (abstract syntax tree) is generated through lexical analysis and syntax analysis
  • The V8 interpreter converts AST to bytecode
  • The interpreter executes the bytecode line by line. When encountering the hot code, the compiler starts to compile and generate the corresponding machine code to optimize the execution efficiency. This bytecode technology combined with the interpreter and compiler is called just in time compilation (JIT)

21. "= =" operator is implicitly converted to equal operator. In comparison, type conversion will be carried out first, and then determine whether the operands are equal

  • Both are simple types, and both string and Boolean values are converted to numeric values for comparison

  • The simple type is compared with the reference type, and the object is transformed into the value of its original type, and then compared

  • If both are reference types, compare whether they point to the same object

  • null and undefined are equal

  • Returns false if NaN exists

22. Points needing attention in this

  • This function contains multiple objects. Although this function is called by the outermost object, this only points to the object at its upper level
var o = {
    a:10,
    b:{
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

In the above code, the upper level object of this is b, and there is no definition of a variable in b, so the output is undefined

  • Another special case
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

At this point, this points to window, and here we need to remember that this always points to the object that finally calls it, although fn is the way of object b, but fn assignment is not implemented to j, so it finally points to window.

  • The new process encounters a return object. At this time, this points to the returned object
function fn()  
{  
    this.user = 'xxx';  
    return {};  
}
var a = new fn();  
console.log(a.user); //undefined

If a simple type is returned, this points to the instance object

function fn()  
{  
    this.user = 'xxx';  
    return 1;
}
var a = new fn();  
console.log(a.user); //xxx

Note that although null is also an object, new still points to the instance object at this time

function fn()  
{  
    this.user = 'xxx';  
    return null;
}
var a = new fn();  
console.log(a.user); //xxx
  • This has different behaviors in standard functions and arrow functions. In standard functions, this refers to the context object in which the function is regarded as a method call. At this time, it is usually called this value. The function defined in the global context refers to this object. Which object this refers to must be determined when the function is called; In the arrow function, this refers to the context that defines the arrow function
window.color = 'red';
let o = {
    color: 'blue'
}
let sayColor = () => console.log(this.color);
sayColor(); //'red'
o.sayColor1 = sayColor;
o.sayColor1(); //'red'

The function name is only the variable that holds the pointer, so the globally defined sayColor() and o.sayColor1() are the same function, but the execution context is different.

When calling a function in event callback or timer callback, the this value does not refer to the desired object. At this time, the callback function is written as an arrow function to solve the problem, because the this in the arrow function preserves the context when defining the function.

function King() { 
 this.royaltyName = 'Henry'; 
 // this refers to an instance of King
 setTimeout(() => console.log(this.royaltyName), 1000); 
}
function Queen() { 
 this.royaltyName = 'Elizabeth'; 
 // this refers to the window object
 setTimeout(function() { console.log(this.royaltyName); }, 1000); 
} 
new King(); // Henry 
new Queen(); // undefined

23. Event agent (event entrustment)

Delegate the function of one element in response to an event to another element, and the event delegation is completed in the bubbling phase. When the event responds to the target element, it will trigger its outer bound event through the event bubble mechanism, and then execute the function on the outer element.
If we have a list with a large number of list items, we need to respond to an event when we click on the list item

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>

If each list item is bound with a function one by one, it will consume a lot of memory

// Get target element
const lis = document.getElementsByTagName("li")
// Loop through binding events
for (let i = 0; i < lis.length; i++) {
    lis[i].onclick = function(e){
        console.log(e.target.innerHTML)
    }
}

At this time, you can delegate the event, bind the click event to the parent element ul, and then match the target element when executing the event

// Bind events to parent elements
document.getElementById('list').addEventListener('click', function (e) {
    // Compatibility processing
    var event = e || window.event;
    var target = event.target || event.srcElement;
    // Determine whether to match the target element
    if (target.nodeName.toLocaleLowerCase === 'li') {
        console.log('the content is: ', target.innerHTML);
    }
});

Another scenario is that the above list items are not many. We bind events to each list item

However, if users can dynamically add or remove list item elements at any time, they need to re bind events to the newly added elements and unbind events to the elements to be deleted every time they change

If the event delegate is used, there is no such trouble, because the event is bound in the parent layer and has nothing to do with the increase or decrease of the target element. The execution to the target element is matched in the process of actually responding to the execution of the event function

for instance:

In the following html structure, click input to dynamically add elements

<input type="button" name="" id="btn" value="add to" />
<ul id="ul1">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    <li>item 4</li>
</ul>

Use event delegation

const oBtn = document.getElementById("btn");
const oUl = document.getElementById("ul1");
var num = 4;

//Event delegate, and the added child elements also have events
oUl.onclick = function (ev) {
    ev = ev || window.event;
    const target = ev.target || ev.srcElement;
    if (target.nodeName.toLowerCase() == 'li') {
        console.log('the content is: ', target.innerHTML);
    }

};

//Add new node
oBtn.onclick = function () {
    num++;
    const oLi = document.createElement('li');
    oLi.innerHTML = `item ${num}`;
    oUl.appendChild(oLi);
};

The events suitable for event delegation are: click, mousedown, mouseup, keydown, keyup and keypress

From the above application scenario, we can see that using event delegation has two advantages:

Reduce the memory required for the whole page and improve the overall performance
Dynamic binding to reduce repetitive work
However, using event delegation also has limitations:

There is no event bubbling mechanism for focus and blur events, so it is impossible to delegate binding events

Although events such as mousemove and mouseout have event bubbles, they can only be calculated and located by location continuously, which consumes high performance. Therefore, they are not suitable for event delegation

If you use event proxies for all events, event misjudgment may occur, that is, events that should not have been triggered are bound to events

quote: https://vue3js.cn/interview/JavaScript/event_agent.html#%E4%B8%89%E3%80%81%E6%80%BB%E7%BB%93

24. new operator

In JavaScript, the new operator is used to create an instance object of a given constructor
The new operator mainly does the following work:

  • Create a new object obj
  • Connect the object and constructor through the prototype chain
  • Bind this in the constructor to the newly created object obj
  • Judge according to the return type of the constructor. If it is the original value, it will be ignored. If it is the return object, the content of the instance object will become the content of the return object
    For example:
function Test(name) {
  this.name = name
  console.log(this) // Test { name: 'xxx' }
  return { age: 26 }
}
const t = new Test('xxx')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'
function Test(name) {
  this.name = name
  return 1
}
const t = new Test('xxx')
console.log(t.name) // 'xxx'

Handwritten new operator:

function mynew(Func, ...args) {
    // 1. Create a new object
    const obj = {}
    // 2. The new object prototype points to the constructor prototype object
    obj.__proto__ = Func.prototype
    // 3. Point this of the build function to the new object
    let result = Func.apply(obj, args)
    // 4. Judge according to the return value
    return result instanceof Object ? result : obj}

Test:
1. The return type of the constructor is the original value

function mynew(func, ...args) {
    const obj = {}
    obj.__proto__ = func.prototype
    let result = func.apply(obj, args)
    return result instanceof Object ? result : obj
}
function Person(name, age) {
    this.name = name;
    this.age = age;
    return 1
}
Person.prototype.say = function () {
    console.log(this.name)
}

let p = mynew(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123}
p.say() // huihui

2. The return value of the constructor is an object

function mynew(Func, ...args) {
    // 1. Create a new object
    const obj = {}
        // 2. The new object prototype points to the constructor prototype object
    obj.__proto__ = Func.prototype
        // 3. Point this of the build function to the new object
    let result = Func.apply(obj, args)

    // 4. Judge according to the return value
    return result instanceof Object ? result : obj
}

function Person(name, age) {
    this.name = name;
    this.age = age;
    //Constructor return type is object
    return {
        sex: 'woman'
    }
}
Person.prototype.say = function() {
    console.log(this.name)
}

let p = mynew(Person, "huihui", 123)
console.log(p) //{sex: 'woman'}
p.say() // Uncaught TypeError: p.say is not a function

25. ajax principle and packaging

1. Definition

Asynchronous javascript and XML is a web development technology for creating interactive web applications. It can exchange data with the server and update some web pages without reloading the whole web page

2. Principle

Create an XmlHTTPRequest object to send an asynchronous request to the server, obtain data from the server, and then use JS to operate the DOM to update the page

3. Implementation process:

  • Create the XMLHTTPRequest object, the core object of Ajax
  • Establish a connection with the server through the open() method of the object
  • Build the required content and send it to the server through the send() method of the object
  • Listen for your communication status on the server side through the onreadstatechange event provided by the object
  • Receive and process the data results of the response from the server to the client
  • Update results to HTML page

4. Encapsulation

//Encapsulate an ajax request
function ajax(options) {
    //Create XMLHttpRequest object
    const xhr = new XMLHttpRequest()


    //Contents of initialization parameters
    options = options || {}
    options.type = (options.type || 'GET').toUpperCase()
    options.dataType = options.dataType || 'json'
    const params = options.data

    //Send request
    if (options.type === 'GET') {
        xhr.open('GET', options.url + '?' + params, true)
        xhr.send(null)
    } else if (options.type === 'POST') {
        xhr.open('POST', options.url, true)
        xhr.send(params)

    //Receive request
    xhr.onreadystatechange = function () {
    //XMLHttpRequest. When the readyState property status is 4, it indicates that the whole request process has been completed
        if (xhr.readyState === 4) {
            let status = xhr.status
            if (status >= 200 && status < 300) {
            //XMLHttpRequest. The responseText property is used to receive the response result from the server
                options.success && options.success(xhr.responseText, xhr.responseXML)
            } else {
                options.fail && options.fail(status)
            }
        }
    }}

The usage is as follows:

ajax({
    type: 'post',
    dataType: 'json',
    data: {},
    url: 'https://xxxx',
    success: function(text,xml){//Callback function after successful request
        console.log(text)
    },
    fail: function(status){Callback function after request failure
        console.log(status)
    }
})

26. Anti shake and throttling

1. Definition
It is essentially a means of optimizing high-frequency code execution

For example, when the browser's resize, scroll, keypress, mousemove and other events are triggered, they will constantly call the callback function bound to the event, which greatly wastes resources and reduces the front-end performance

In order to optimize the experience, we need to limit the number of calls for such events. For this, we can reduce the call frequency by means of throttle and debounce

  • Throttling: it only runs once in n seconds. If it is triggered repeatedly in n seconds, it will take effect only once
  • Anti shake: this event will be executed after n seconds. If it is triggered repeatedly within n seconds, it will be timed again

2. Anti shake

// js anti shake core code

function debounce (fn, delay) {
  let timer = null
  // closure
  return () => {
    // If you are currently in a timing process, clear this timing and start the next timing,
    // Otherwise, it will not be cleared, and the next timing will be started directly. The fn function will not be triggered until the delay milliseconds have elapsed.
    if (timer) {
      clearTimeout(timer)
    }
    // Start next timing
    timer = setTimeout(fn, delay)
  }
}

function handleScroll () {
  console.log('If I don't slide within one second after sliding, I will execute it once', Math.random())
}

window.addEventListener('scroll', debounce(handleScroll, 1000))

throttle

// js throttling core code

// time stamp
function throttle1 (fn, delay) {
  let prev = Date.now()
  // closure
  return () => {
    let now = Date.now()
    if (now - prev >= delay) {
      fn()
      prev = Date.now()
    }
  }
}

// timer
function throttle2 (fn, delay) {
  let timer = null
  // closure
  return () => {
    if (!timer) {
      timer = setTimeout(() => {
        fn()
        timer = null
      }, delay)
    }
  }
}

function handleScroll () {
  console.log('I only execute it once a second during the sliding process', Math.random())
}

window.addEventListener('scroll', throttle1(handleScroll, 1000))
// window.addEventListener('scroll', throttle2(handleScroll, 1000))

difference:

  • Function anti shake. After a continuous operation, handle the callback, which is realized by clearTimeout and setTimeout. Function throttling, which is executed only once in a period of continuous operation, is used in events with high frequency to improve performance
  • Function anti chattering focuses on events triggered continuously for a certain period of time. It is only executed once in the last time, while function throttling is only executed once in a period of time
    For example, the time frequency is set to 500ms. Within 2 seconds, the function and throttling are frequently triggered and executed every 500ms. Anti shake, no matter how many times the method is adjusted, it will only be executed once after 2s

Topics: Javascript Front-end