Autumn trick is coming! Learn quickly, this article from JS often meet questions, combined with the basic classic JS books “JavaScript advanced programming”, “JavaScript you do not know” series and some big god’s blog for summary. The general content is as follows:
Autumn recruitment escort — JS Interview Part 1
- Js data type
- Js garbage collection mechanism
- Arrays in JS
- Functions in JS
- Js object-oriented programming
Autumn Recruitment escort — JS Interview Part ii
- Asynchronous programming of JS
- Js modular management
- ES6 new features
- DOM
- The event
Js data type
1. What are the data types in JS?
In JS, data types are divided into basic types and reference types:
(1) Basic types:
- null
- undefined
- boolean
- number
- string
- Symbol (ES6 introduced)
(2) Js reference types are subtypes of object, including the following:
- Object
- Function
- Array
- RegExp
- Date
- Wrapper classes: String, Number, Boolean
- Math
2. How are basic types and reference types stored in JS respectively?
(1) Remember the conclusion: Data types of primitive types are stored in stack space, and values of reference types are stored in the heap.
(2) Look at the topic again to strengthen understanding:
// Define four variables
var num1 = 5
var num2 = num1;
var obj1 = {
name: 'Piggy Piggy'
}
var obj2 = obj1
// Modify num1 and obj1
num1 = 4
obj1.name = 'pig'
// Output four variables
console.log(num1) / / 4
console.log(num2) / / 5
console.log(obj1.name) / / the pig
console.log(obj2.name) / / the pig
Copy the code
The output of num1 and num2 above is well understood because in JS primitive data types are stored in stack space. If a variable assigns a value of the primitive type to another variable, a new value is created on the variable object and then copied to the location allocated for the new variable.
So why is the name output of obj1 and obj2 changed? This is because the values referenced to types in JS are stored in the heap. If a variable to another variable assignment the value of a reference type, also in the variable object to create a new value, and then copy the value to the new variable for the location of the distribution, but unlike the base type, the value is a pointer, the pointer points to the same object in the heap, so any one object is modified in is to modify the same object.
3. What is the way of passing parameters in JS?
(1) Remember the conclusion: in JS, all function parameters are passed by value, that is, the value outside the function is copied to the function for internal use, just like the value from a variable to another variable. This means that both primitive and reference type values are passed in the same way as the copying process described above.
(2) Look at the topic again to strengthen understanding:
// Basic type pass
function addTen(num) {
num += 10
return num
}
var count = 20
var result = addTen(count)
alert(count) / / 20
alert(result) / / 30
// Passing of reference types
function setName(obj) {
obj.name = "Piggy Piggy"
}
var person = {}
setName(person)
console.log(person.name) // Piggy Piggy
Copy the code
There are two ways to access variables: by value and by reference. Why do you pass parameters by value?
The passing of the underlying type is easy to understand, but local changes to the passing of the reference type are reflected globally, and some students may mistakenly think that the passing of the reference type is passed as a parameter. But what really happens is this:
- An object is created and stored in the Person variable
- The setName function is called, and the person variable is passed into setName
- The value of person is copied to OBj, which copies a pointer to an object in the heap
- Modified the obj
- And that’s shown in person
As you can see from the above process, the person variable is passed by value. Let’s look at another example to illustrate this problem
function setName(obj){
obj.name = "Piggy Piggy"
obj = new Object()
obj.name = The God of three.
}
var person = {}
setName(person)
alert(person.name) // Piggy PiggyCopy the codeCopy the code
If passed by reference, the display value should be “triple god”, but the js reference type is also passed by value, so the print is “piggy piggy stupid”.
3. How to determine the various data types
(1) Why judge?
In JS, variables are loosely typed, and loosely typed means they can hold any type of data.
(2) Judgment method:
- Typeof detection
typeof undefined= = ="undefined" // true
typeof true= = ="boolean" // true
typeof 42= = ="number" // true
typeof "42"= = ="string" //true
typeof{... } = = ="object" //true
Copy the code
- Null detection, because Typeof NULL outputs object, but this is only a long-standing JS Bug.
var a = null(! a &&typeof a === "object") // true
Copy the code
- Detection of reference types
// 1
typeof function a() {... } = = ="function" //true
// 2. Array type check
// 2.1 According to the two methods of the prototype chain, but the prototype chain may be modified, so it is sometimes unreliable
arr instanceof Array= = =true
arr.proto.constructor === Array
// 2.2 Reliable method
Object. Prototype. ToString. Apply (arr) = = = "[objectArray]"// 2.3 The official method is reliable, feasible and simple
Array.isArray(arr) === true
Copy the code
4. Explain the difference between undefined and null
(1) null:
- Null refers to a null value, which has been assigned a value but currently has no value
- Null is a special keyword, not an identifier, and cannot be used or assigned as a variable
(2) is undefined
- The default value for uninitialized values is undefined
- Undefined means no value, meaning no value has ever been assigned
- The Undipay is an identifier that can be used and assigned as a variable
// Undefined can be assigned to the global identifier in non-strict mode
function foo() {
undefined = 2
}
foo()
function foo() {
"use strict";
undefined = 2;
}
foo()
// In strict and non-strict modes, you can declare a local variable of undefined
function foo() {
"use strict";
var undefined = 2;
console.log(undefined);
}
foo();
Copy the code
5. Why doesn’t 0.1+0.2 equal 0.3?
(1) Reason: 0.1 and 0.2 will loop forever after being converted to binary. Due to the limit of standard bits, the excess bits will be cut off. At this time, there has been a loss of precision.
(2) Solutions:
// First set an error range, usually called "machine precision". For js, this value is usually 2^-52
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2, -52);
}
// Compare the error values
function nbumersCloseEnoughToEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON
}
/ / to compare
var a = 0.2 + 0.1
var b = 0.3
nbumersCloseEnoughToEqual(a, b) //true
Copy the code
7. Some knowledge of packaging
A string is a primitive type, not an object. Why call a method? What happens in the following code?
var S1 = "some test"
var s2 = s1.substring(2)
Copy the code
- Create an instance of String
- The specified method is invoked on the instance
- Destroy the instance
8. Js type conversion
(1) [] ==! [] What was the result? Why is that?
- ==, both sides need to be converted to numbers and then compared.
- [] converts to a number 0.
- ! [] is first converted to a Boolean. Since [] is a reference type converted to a Boolean value of true,
- So! [] is false, and then converted to a number, becomes 0.
- 0 == 0, the result is true
!!!!!null // false!!!!!' ' // false!!!!!undefined // false!!!!!0 // false!!!!!NaN // false!!!!!1 //true!!!!! {}// true!!!!! []// true
Copy the code
(2) What is the difference between == and ===?
- For example, ‘1’===1 is false because one side is a string and the other is a number.
- == is not as strict as ===. For the general case, it returns true as long as the values are equal, but == also involves some type conversions, which are as follows:
- If the types on both sides are the same, the value is compared, for example, 1==2, return false
- Check if null and undefined, return true if so
- Check whether the type is String and Number. If yes, convert String to Number and compare
- If so, convert the Boolean to Number and compare
- If one is Object and the other is String, Number, or Symbol, the Object is converted to a String and then compared
console.log({a: 1} = =true);//false
console.log({a: 1} = ="[object Object]");//true
100= ='100' // true
0= =' ' // true
0= =false // true
false= =' ' // true
null= =undefined // true
if (obj == null) {... }/ / equivalent to
if (obj === null || obj === undefined) {... }Copy the code
(3) String splicing
const a = 100 + 10 / / 110
const b = 100 + '10' / / '110'
const c = true + '10' // 'true10'
Copy the code
Js garbage collection mechanism
See the js garbage collection mechanism in another article
(1) Garbage collection mechanism in the call stack
Data reclamation in the stack in js relies on ESP (the pointer to the current execution state) being moved down to eliminate execution context held in the stack.
(2) Garbage collection mechanism in the heap
In V8, the heap is divided into new generation and old generation regions.
- The new generation stores objects with a short lifetime, which are stored between 1 and 8M and use the sub-garbage collector in JS.
- The old generation stores objects that have been generated for a long time and has a large memory capacity. It uses the main garbage collector in JS.
The working process is as follows:
- Marks active and inactive objects in the space. Live objects are objects that are still in use, and inactive objects are objects that can be garbage collected.
- Reclaim memory occupied by inactive objects. All objects marked as recyclable in memory are cleaned uniformly after all tags are completed.
- Memory consolidation. Generally speaking, after frequent object collection, there will be a large number of discontinuous memory space, we call these discontinuous memory space memory fragmentation. When a large amount of memory fragmentation occurs in memory, it is possible to run out of memory if large contiguous memory needs to be allocated. So the last step requires defragmenting the memory, but this step is optional because some garbage collectors do not generate memory fragments, such as the sub-garbage collector.
Note:
We have seen the js garbage collection mechanism above, but because JavaScript runs on a single thread, once the garbage collection algorithm is executed, the JavaScript script that is being executed needs to be suspended and script execution can be resumed after garbage collection is completed. We call this behavior total pause.
- The new generation has smaller memory, faster collection and less impact of pauses.
- In order to reduce the lag phenomenon caused by old generation, incremental marking algorithm is used. A complete garbage collection is broken down into smaller garbage collections to reduce the lag phenomenon.
Arrays in JS
1. What are the detection methods of arrays?
// According to the two methods of the prototype chain, but the prototype chain can be modified, so it is sometimes unreliable
arr instanceof Array= = =true
arr.__proto__.constructor === Array
// A reliable method
Object. Prototype. ToString. Apply (arr) = = = "[objectArray]"// The official method is reliable, feasible and simple
Array.isArray(arr) === true
Copy the code
(1) Detailed detection through the prototype
See what’s on the Array prototype?
As you can see, there are a number of methods defined on the Array prototype. We’ll see what these methods do and how to implement them later.
Let’s get back to how we can judge arrays from prototypes. The __proto__ attribute of an Array instance refers to the Array prototype, which may not be too specific because of the knowledge of the prototype. We can see directly from the following figure that the __proto__ property of ARR is the same as the Array prototype seen above.
The constructor property of the prototype Array points to the Array constructor, and the constructor of the prototype Array points to the Array constructor, so arr.__proto__. Constructor points to the Array constructor, so we can make a judgment.
2. Array stack and queue methods
Stack and queue related apis are often used in algorithm problems, and the related theory corresponds to stack and queue in data deconstruction, which will not be described here.
- The push() method takes any number of arguments, adds them one by one to the end of the array, and returns the modified array length
- The pop() method removes the last item at the end of the array, reduces the length value of the array, and returns the removed item
- The shift() method removes the first item in the array while subtracting the array length by one to return the removed item
- The unshift() method adds the parameters to the front of the array and returns the length of the new array
3. Array conversion method
- The toLocaleString() method that concatenates each element of the output array with a comma.
- ToString (), same as toLocaleString.
- The value() method prints the array itself.
- The join() method joins each element of the array with an argument passed in. If you pass null, you convert an array to a string.
4. Reorder the Array
When it comes to reversing arrays and sorting, these are also two very classic programming problems. Sorting is more so, several common sorting methods, to carry out skilled programming in the back.
- The reverse() method reverses the order of the array, noting that changing the method changes the original array
- The sort() method sorts the items in ascending order — the smallest value is first and the largest value is last. The sort() function can take a comparison function as an argument so that we can decide which number comes first
- If the first argument comes before the second, return a negative number
- If two arguments are equal, return 0
- If the first argument comes after the second argument, return a positive number
5. Array operation methods
-
The concat() method, used to merge multiple arrays. It adds the new array member to the back of the original array member, and returns a new array, unchanged.
-
The slice() method, used to extract a portion of the destination array, returns a new array, unchanged.
- Its first argument is the starting position (starting at 0 and included in the new array returned)
- The second argument is the termination position (but the element at that position itself is not included). If you omit the second argument, you are returned to the last member of the original array
- Returns a copy of the array with no arguments.
-
The splice() method deletes a part of the original array and adds a new array member at the deleted location, returning the deleted element. Note that this method changes the original array.
- The first argument is the starting location of the deletion (starting from 0)
- The second parameter is the number of deleted elements.
- If there are more arguments, these are the new elements to be inserted into the array
6. Array location method
- IndexOf () method that returns the first occurrence of a given element in the array, or -1 if none occurs
- The lastIndexOf() method returns the last occurrence of a given element in the array, or -1 if none occurred.
7. Array iteration method
(1) Map () method
Manually implement map:
Array.prototype.map = function(callback, thisArg) {
const res = []
const O = Object(this);
const len = O.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in O) {
res.push(callback.call(thisArg, O[i], i, this))}}return res;
}
const arr = [1.2.3.4]
const arr1 = arr.map((item) = > {
return item + 1
})
console.log(arr1) // [2, 3, 4, 5]
Copy the code
(2) forEach() method
Implement forEach manually:
// Similar to map, but without the return value
Array.prototype.forEach = function (callback, thisArg) {
const O = Object(this)
const len = O.length >>> 0
for (let i = 0; i < len; i++) {
if (i in O) {
callback.call(thisArg, O[i], i, this); }}}Copy the code
(3) Fliter () method
Array.prototype.filter = function (callback, thisArg) {
const res = []
const O = Object(this)
const len = O.length >>> 0
for (let i = 0; i < len; i++) {
if (i in O) {
if (callback.call(thisArg, O[i], i, this)) {
res.push(O[i])
}
}
}
return res;
}
Copy the code
(4) Some () and every() methods
- The some method returns true as long as one member returns true, and false otherwise.
- The every method returns true for all members, and false otherwise.
8. Merge method
(1) Reduce () method
Each member of the array is processed from left to right, eventually accumulating to a value.
Handwritten reduce:
Array.prototype.reduce = function (callback, initialValue) {
const O = Object(this)
const len = O.length >>> 0
if (len == 0) {
return false;
}
let accumulator = initialValue ? initialValue : O[0]
for (let i = 1; i < len; i++) {
if (i in O) {
accumulator = callback.call(undefined, accumulator, O[i], i, this)}}return accumulator
}
const arr = [1.2.3.4]
const result = arr.reduce((accumulator, item) = > {
return accumulator + item;
})
console.log(result) / / 10
Copy the code
(2) reduceRight() method
Each member of the array is processed from right to left, eventually accumulating to a value.
9. Array deduplication
// Array decrement
// let arr = [1, 2, 2, 1, 3, 4, 5, 6, 4, 2]
function arrDelRepeat1(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1); }}}return arr;
}
function arrDelRepeat2(arr) {
return [...new Set(arr)]
}
function arrDelRepeat3(arr) {
let myArr = []
for (let i = 0; i < arr.length; i++) {
if (myArr.indexOf(arr[i]) == -1) { myArr.push(arr[i]); }}return myArr;
}
Copy the code
10. Array flattening
// Array flattening
let arr = [1[2[3[4.5]]].6];
/ / es6 syntax
function flat1(arr) {
return arr.flat(Infinity);
}
// JSON + re
function flat2(arr) {
let str = JSON.stringify(arr);
str = str.replace(/(\[|\])/g.' ');
str = '[' + str + '] ';
return JSON.parse(str);
}
/ / recursion
function flat3(arr) {
let result = []
let fn = function (ary) {
for (let i = 0; i < ary.length; i++) {
let item = ary[i];
if (Array.isArray(ary[i])) {
fn(item)
} else {
result.push(item)
}
}
}
fn(arr)
return result;
}
/ / reduce iteration
function flat4(arr) {
return arr.reduce((pre, cur) = > {
return pre.concat(Array.isArray(cur) ? flat4(cur) : cur); }}, [])console.log(flat4(arr));
Copy the code
Functions in JS
1. Is argument an array? How do I convert it to an array?
In JS, the arguments to the function, the list of elements returned by the DOM query, are not strictly arrays, they just act like arrays, but are essentially objects. Sometimes you need to convert an array of classes into a real array. There are several ways to do this:
// Arguments is the function argument list
/ / silce method
var arr = Array.prototype.slice.call(arguments)
// concat
var arr = Array.prototype.concat.apply([], arguments)
/ / ES6 syntax
var arr = [...arguments]
// Built-in functions
var arr = Array.from(arguments)
Copy the code
2. What happens to a new function?
- Create an entirely new object
- This object will be linked by [[Prototype]], linking the [[Prototype]] of the new object to the object that Prototype points to
- Make this of the function point to the object, and execute the constructor code (add attributes to the new object).
- Determine the return value type of the function. If the function returns no other object, the function call in the new expression automatically returns the new object
Implement a new manually:
function myNew(fn, args) {
if (typeoffn ! = ='function') {
return false;
}
// Create a new object
let newObject = Object.create(fn.prototype);
// Execute constructor code to add attributes to the object
letresult = fn.apply(newObject, ... args);// Determine whether a function object or the newly created newObject is returned
let flag = result && (typeof result === "object" || typeof result === "function");
return flag ? result : newObject;
}
Copy the code
3. Talk about scopes and scope chains
(1) What is global context?
Execution context creation falls into three categories:
- Execute global code, compile global code, create global context, and only one
- When a function is called, the code inside the function is compiled, creating a function context that is destroyed after the function is executed
- Use the eval function, which is rarely encountered and will not be discussed here.
(2) What about variable promotion?
In JS, context is managed by the call stack, one of the three memory Spaces in JS execution. Let’s see how it works:
showName() / / the pig
console.log(myName) // undefiend
var myName = "Piggy Piggy"
function showName() {
console.log("Pig")}Copy the code
- Js compiles the global code, creates the global context and pushes it to the bottom of the stack
- The global code executes console.log, printing undefined
- Assign the myName variable to piggy Piggy
- ShowName is called, and js compiles it to create the execution context of showName
- After executing showName, the execution context of showName pops out of the stack and is destroyed
- Global code execution is completed, the stack is popped, and the code is finished
So this is where we can answer our previous question. The so-called variable promotion is that during the execution of JS code, the code will be compiled first. During the compilation process, the declaration of variables and functions will be put into the call stack to form the context call stack, and the remaining ones will generate the execution code. This leads to the phenomenon of variable escalation.
(3) What is the output of the following problem?
var name = "Piggy Piggy"
function showName() {
console.log(name);
if (0) {
var name = "Pig"
}
console.log(name)
}
showName()
// undefined
// undefiend
Copy the code
This topic is about scope and variable promotion. We have learned about variable promotion above.
There are three scopes in JS. Before ES6, there were only two:
- Global scope
- Function scope
- Block-level scopes (new in ES6)
At first, the global context is generated, that is, the global scope, with the internal variable name = “Piggy Piggy”. When the function showName is executed later, the execution context of showName function will be formed, that is, the scope of showName. In the scope of showName, because the name declared by var is used, the block-level scope is not formed, so variable promotion will occur. So the first console didn’t print “Piggy Piggy”, and the second one printed “undefined” because the statement inside the if statement was not executed.
(4) What is the output of the following problem?
function bar() {
console.log(name)
}
function foo() {
var name = "Piggy Piggy"
bar()
}
var name = "Pig"
foo() / / the pig
Copy the code
It’s easy to think that this code will print “Peepy Piggy”, which has to do with another concept we’ll cover next: scope chains
We will look at the scope chain in conjunction with the execution context:
- The variable environment of each execution context contains an external reference to the external execution context, which we call outer.
- When a piece of code uses a variable, the JS engine looks for it in the current execution context, and if it doesn’t find it, it continues to look for it in the execution context of outer’s execution. Such level by level lookups form a scope chain.
- The generation of the scope chain is determined by the code, regardless of the call. So when bar is compiled, outer points to the global context, so instead of printing piggy Inside foo()
(5) Talk about the formation principle of block-level scope
In all kinds of execution context is divided into environment variables and lexical environment, environment variables to store some var statement, and statement of such as lexical deposit environment let block-level scoped variable, equivalent to inside the lexical environment has created a new call stack, the query variable will query lexical environment first, then go to query variable environment.
Let’s look at the detailed flow of this problem in execution context:
function foo() {
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
Copy the code
-
The first step is to compile and create the execution context
- All variables declared by var inside the function are stored in the variable environment at compile time.
- Variables declared by let are deposited in the Lexical Environment at compile time.
- Inside the function’s scoped block, variables declared by let are not stored in the lexical environment.
-
Execute to code block
- The let declarations inside the code block are stored in a new area
- Perform the console. The log (a)
- When the scoped block completes execution, its internally defined variables are popped from the top of the lexical environment’s stack
The new scope chain created above is the implementation of JS support for both variable promotion and block-level scope.
(6) How to solve the following cyclic output problem?
for(var i = 1; i <= 5; i ++){
setTimeout(function timer(){
console.log(i)
}, 0)}Copy the code
- Cause: setTimeout is a macro task. After the synchronization task is completed, I is 6, so five 6’s will be output
- Workaround: Use let to form block-level scopes
for(let i = 1; i <= 5; i ++){
setTimeout(function timer(){
console.log(i)
}, 0)}Copy the code
4. Talk about closures
(1) What is a closure?
ES5 exist in the two scopes: global scope, function, scope, function scope will be automatically destroyed at the end of the function scope chain: find a variable when starting from its own scope along the scope chain has been up for closure: use of scope, function can be within the scope of variables to access
(2) How are closures generated
- Return function (common)
const a = 2
function out () {
let a = 1
return function b () {
console.log(a)
}
}
const b = out()
b() / / 1
Copy the code
- Functions passed as arguments: Functions as arguments have access to the internal scope of the function body
var a = 1
function bar(fn) {
var a = 2
console.log(fn)
}
function baz() {
console.log(a)
}
bar(baz) / / 1
Copy the code
- In timers, event listeners, Ajax requests, cross-window communication, Web Workers, or any asynchrony, whenever you use a callback function, which in fact is the case above, you’re using a closure by treating the function as an argument.
/ / timer
setTimeout(function timeHandler(){
console.log('111');
}, 100)
// Event listener
$('#app').click(function(){
console.log('DOM Listener');
})
Copy the code
- Execute the function immediately:
var a = 2;
(function IIFE(){
2 / / output
console.log(a); }) ();Copy the code
IIFE(execute function expressions immediately) creates closures that hold both the global scope window and the scope of the current function, so variables can be global.
for(var i = 1; i <= 5; i ++){
(function(j){
setTimeout(function timer(){
console.log(j)
}, 0)
})(i)
}
Copy the code
(3) Application scenarios of closures
- Corey:
Add (1)(2)(3)(4) == 10
// The function is currified
// The parameter is fixed
function add(fn) {
let args = []
return function _c(. newArgs) {
if (args.length < fn.length - 1) {
args = [...args, ...newArgs];
return _c;
} else {
args = [...args, ...newArgs];
return fn.call(this. args) } } }function x (a, b, c, d, e) {
return a + b + c + d + e;
}
let func = add(x)
console.log(func(1) (2) (3) (4) (5)) / / 15
// The parameter is not fixed
function add() {
let args = []
return function _c(. newArgs) {
if (newArgs.length) {
args = [...args, ...newArgs]
return _c
} else {
return args.reduce((sum, item) = >sum + item); }}}let func = add()
func(1) (2) (3) (4.5) ()/ / 15
Copy the code
(4) Disadvantages of closures
Using closures globally causes memory leaks, so use them sparingly
(5) The orientation of this
This: who calls, to whom
- Default binding: In the context of global execution, this refers to a global object. In browsers, this refers to the Window object.
- Implicit binding: In the context of function execution, the value of this depends on how the function is called. If it is called by a reference object, this is set to that object, otherwise this is set to global or undefined (in strict mode).
- Show bindings: apply, call, bind
- Arrow function: depends only on how the function is called, where it is called, and where it is called. Is determined by the outer (function or global) scope.
Ps: Although bar is a reference to obj.foo, it is referring to foo itself, and the calling environment is global
function foo() {
console.log(this.a)
}
var obj = {
a: 2.foo: foo
}
var bar = obj.foo
var a = 1
bar() / / 1
Copy the code
Similar to the principle above, implicit binding depends on how the function is called.
function foo() {
console.log(this.a)
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2.foo: foo
}
var a = 1
doFoo(obj.foo) / / 1
function doFoo(fn) {
fn();
}
var obj = {
a: 2.foo: function () {
console.log(this.a)
}
}
var a = 1
doFoo(obj.foo) / / 1
Copy the code
The arrow function is determined by the outer (function or global) scope.
var obj = {
a: 2.foo: () = > {
console.log(this.a)
}
}
var a = 1
obj.foo() / / 1
Copy the code
The arrow function’s this is determined by the outer (function or global) scope!
var obj = {
a: 2.foo: () = > {
console.log(this.a)
}
}
function doFoo(fn) {
this.a = 3
fn()
}
doFoo(obj.foo) / / 3
Copy the code
(6) Bind, call, apply
Call, apply, and bind are all methods of functions that change the direction of this
- The difference between Call and Apply: The Call method accepts a list of arguments, while Apply accepts an array of arguments.
- Bind differs from apply and call in that bind does not execute functions immediately but returns them
Manually implement call:
Function.prototype.myCall = function(context) {
context = context || window;
let fn = Symbol(a); context[fn] =this;
let args = [...arguments]
console.log(args)
args.shift()
letres = context[fn](... args);delete context[fn];
return res;
}
let Obj = {
age: 18
}
function getAge() {
console.log(this.age)
}
getAge.myCall(Obj) / / 18
Copy the code
Implement apply manually:
Function.prototype.apply = function(context, args) {
context = context || window;
context.fn = this;
let result;
if(args) { result = context.fn(... args) }else {
result = context.fn()
}
delete context.fn;
return result
}
let Obj = {
age: 18
}
function getAge() {
console.log(this.age)
}
getAge.apply(Obj)
Copy the code
Manually implement bind:
Function.prototype.bind = function(context) {
let self = this
let args = Array.prototype.slice.call(arguments.1)
context = context || window
let func = function() {
let funArgs = Array.prototype.slice.call(arguments)
// Bind this differently depending on how func is used
// If this is an instance of self, then func is instantiated with new, and this is the call environment
// Otherwise it is just normal to bind the context
return self.call(this instanceof self ? this: context, ... args, ... funArgs) } func.prototype =Object.create(this.prototype);
return func
}
Copy the code
(6) Arrow function
Arrow functions are defined using what is called the “fat arrow” operation =>. Arrow functions do not apply the four rules of the normal function this binding, but determine this based on the outer (function or global) scope, and the binding of the arrow function cannot be modified (nor can new).
- Arrow functions are often used in callback functions, including event handlers or timers
- The arrow function’s this object is the object at which it is defined, not used
- Arrow functions do not have their own this, so they cannot use call, apply, or bind to change this
- No archetypes, no this, no super, no arguments, no new.target
- Cannot be called by the new keyword. When a function is new, the prototype of the returned object points to the prototype of the function, whereas the arrow function has no prototype, so an error is reported.
5. Say the output of the following code
function Foo() {
getName = function () { alert(1); };
return this;
}
Foo.getName = function () { alert(2); };
Foo.prototype.getName = function () { alert(3); };
var getName = function () { alert(4); };
function getName() { alert(5); }
// Write the following output:
Foo.getName(); / / 2
getName(); / / 4
Foo().getName(); / / 1
getName(); / / 1
new Foo.getName(); / / 2
new Foo().getName(); / / 3
Copy the code
The first time might be a little tricky, so let’s analyze it.
(1) First, the first oneFoo.getName()
The output is 2
function Foo() {
getName = function () { alert(1); };
return this;
}
Foo.getName = function () { alert(2); };
Foo.getName(); // So foo.getName () outputs 2
Copy the code
- Properties added inside a function are added to objects created using the function properties as constructors during the execution of new
- Properties added outside the constructor are only properties of that function, that is, object properties, and are not added to the object
(2) The secondgetName()
The output is 4
This has to do with the variable promotion we mentioned above
var getName = function () { alert(4); };
function getName() { alert(5); }
/ / equivalent to
var getName = undefined;
function getName() { alert(5); }
getName = function () { alert(4); };
// The final output is 4
getName();
Copy the code
(3) The thirdFoo().getName()
And the fourthgetName()
The principle of the same
- The execution context of JS is divided into two types: global and function. After the function is executed, the execution context of the function will be destroyed
Foo()
Internal during the execution of a functiongetName
Because there is no declaration, the variable will be promoted to global and then assignedfunction () { alert(4); }
Foo()
The function returns this, which is global by this’s pointing rule, and executesthis.getName()
It’s called globallygetName()
And we know in the last step that this is the global situationgetName()
The output is 4- Execute statement 4, execute function global
getName()
, the output is still 4.
Foo().getName();
Copy the code
GetName () : Foo. GetName () : Foo. GetName () : Foo. If there are some this statements inside the function, this is returned as an attribute of a new object
(5) The last one has to do with the prototype
Js object-oriented programming
1. What is a prototype? What is a prototype chain?
-
Prototype objects and constructors
- For every function defined in js, there is a built-in prototype that points to the function’s prototype object
- Object.isprototypeof () can be used to determine whether an Object is a prototype of the passed parameter.
- Object.getprototype() returns prototpe (not available before ES5)
- The new function that becomes a constructor returns a new instance object with a __proto__ attribute pointing to the constructor’s prototype function
- Object.hasownproperty () checks whether a property exists in an instance or prototype
- The IN operator returns true whenever the corresponding property is queried in the instance or stereotype of the object
- A stereotype object, on the other hand, will have a constructor property, which points to the original function.
- For every function defined in js, there is a built-in prototype that points to the function’s prototype object
-
Talk about the prototype chain
- JavaScript instance objects point to superclass objects through __proto__ until they point to Object objects, whose __proto__ points to NULL, thus forming a chain of prototype-pointing, the prototype chain.
- Instanceof determines the relationship between prototypes and instances
// person is an instance of Object, Child, or Parent person instanceof Object // true person instanceof Child // true person instanceof Parent // true [] instanceof Array // true [] instanceof Object // true {} instanceof Object // true Copy the code
- IsPrototypeOf determines the relationship between a prototype and an instance
// person is an instance of Object, Child, or Parent Object.prototype.isPrototypeOf(person) //true Parent.prototype.isPrototypeOf(person) //true Child.prototype.isPrototypeOf(person) //true Copy the code
What stages does the constructor go through?
- Create a new object
- Assign the constructor’s scope to the new object, and this naturally refers to the new object
- The constructor uses new to refer this to the newly created object.
- In a normal function call, this points to Global, the browser’s Window object.
- Execute the code in the constructor to add attributes to the new object
- Return a new object
Interpret the following output:
- So let’s define one
Foo
function - There’s no global
name
, so print “” - To perform the
new Foo()
Since a new function returns an object, it has no global impact.name
Is “” - The global executive
Foo()
This refers to global, so globalthis.name = "wk"
- print
name
forwk
3 How to create objects with JS?
-
Object constructors or Object literals can be used to create a single Object
var person = { name:"Piggy Piggy".age:20.job:"students".sayName:function(){ alert(this.name) } }; Copy the code
Disadvantages: Poor reusability. If you want to create multiple objects, it will generate a lot of repeated code. For example, if there are 100 people’s information to enter, you need to repeat the above code 100 times and assign different information values.
-
Factory pattern: Considering classes cannot be created in ECMAScript, developers invented a function that encapsulates the details of creating objects with a specific interface.
function createPerson (name,age,job) { var o = new Object(a); o.name = name; o.age = age; o.job = job; o.sayName =function() { alert(this.name); }; return o; } var person1 = createPerson("wk".20."student"); Copy the code
Disadvantages: The factory pattern solves the problem of creating multiple similar objects, but it does not solve the problem of object recognition (determining the type of an object)
-
Constructor pattern: We know that constructors can create objects of specified types. In addition to native constructors such as Object and Array, we can also create our own definition constructors. As follows:
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { alert(this.name); }; } var person1 = Person("Piggy Piggy".20."student"); var person2 = Person("Holy trinity".18."student"); Copy the code
Disadvantages: Each method recreates one side on each instance, resulting in different scope chains and identifier resolution, and unequal functions of the same name on different instances. Because each instance of the constructor new is passed, the code in the constructor is executed once. For field attributes, the fields of each instance should be independent, of course; But for method properties, we want all instances to share the same.
-
Prototype pattern: All functions have a prototype property, which is a pointer to an object. This solves the problem posed by the constructor model.
function Person() { } Person.prototype.name = "Piggy Piggy"; Person.prototype.age = 20; Person.prototype.job = "student"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName();//" piggy piggy" var person2 = new Person(); person2.sayName();//" piggy piggy" alert(person1.sayName == person2.sayName);//true Copy the code
Disadvantages: The biggest advantage of the prototype pattern is its sharing, which is also its disadvantage. Sometimes we don’t want to share some data with other instances. For example, everyone has a different name, but in the prototype pattern, all instances have the same name.
-
Composite pattern: The constructor pattern and the stereotype pattern are combined, with public properties written to the stereotype and incoming properties written to the constructor.
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; } Person.prototype = { constructor:Person, sayName = function(){ alert(this.name); }}Copy the code
4. Copy objects
(1) Shallow copy
Shallow copy Copies non-reference type values and addresses (Pointers to objects) for reference type values
/ / shallow copy
function shallowCopy(obj) {
if (typeofobj ! = ='object') {
return false;
}
let newObj = Array.isArray(obj) ? [] : {}
for (let i in obj) {
// obj. hasOwnProperty Determines whether the property is instance or prototype
if (obj.hasOwnProperty(i)) {
newObj[i] = obj[i]
}
}
}
Copy the code
(2) Deep copy
For a value of a reference type, instead of simply copying the address of the object, create a brand new object in the heap and copy each item in it
-
Using the JSON method
function jsonCopy(obj) { return JSON.parse(JSON.stringify(obj)) } Copy the code
Disadvantages: This method cannot copy functions and re
-
The recursive implementation
function jsonCopy(obj) { return JSON.parse(JSON.stringify(obj)) } function deepCopy(target) { if (typeoftarget ! = ='object' || obj == null) { return target } let cloneObj = Array.isArray(target) ? [] : {}; for (let i in target) { if (target.hasOwnProperty(i)) { cloneObj[i] = deepCopy(target[i]) } } return cloneObj; } Copy the code
This implementation simply Outlines the principles of deep copy, and if you want to take it one step further, check out the blog I refer to: How to Write a Deep copy that impresses interviewers.
5. How does JS implement inheritance?
-
With prototype chain
function Parent() { this.name = "wk"; } Parent.prototype.getName = function() { console.log(this.name); } function Child(age) { this.age = age } Child.prototype = new Parent() Child.prototype.getAge = function() { console.log(this.age) } let child = new Child(18) child.getName() // wk child.getAge() / / 18 Copy the code
Advantages:
- Superclass methods can be reused
Disadvantages:
-
All reference attributes (info) of the parent class are shared by all subclasses. Changing a reference attribute of one subclass affects the other subclasses as well
-
Subtype instances cannot pass arguments to parent type constructors
-
Borrow constructor: Parent1 is called in Child1 with the call() method. Each instance of Child1 has a copy of Parent’s name property.
function Parent(name, age) { this.name = name; this.age = age; } Parent.prototype.getName = function() { console.log(this.name); } function Child(name, age, job) { Parent.call(this, name, age) this.job = job; } let child = new Child('wk'.18.'coder'); child.getName() // TypeError: child.getName is not a function Copy the code
Advantages:
- Arguments can be passed to the parent class in the subclass constructor
- Reference properties of the parent class are not shared
Disadvantages:
- A subclass does not have access to methods defined on its parent class prototype
-
Composition: combines the advantages of prototype chains and borrowed constructors, becoming the most commonly used inheritance pattern in JS.
function Parent(name, age) { this.name = name; this.age = age; } Parent.prototype.getName = function() { console.log(this.name); } function Child(name, age, job) { Parent.call(this, name, age) this.job = job; } Child.prototype = new Parent(); let child = new Child('wk'.18.'coder'); child.getName(); // wk Copy the code
Advantages:
- Methods of the parent class can be reused
- You can pass parameters to the Parent constructor from the Child constructor
- Referenced properties in the parent constructor are not shared
Disadvantages:
- An extra execution of the new Parent() internal function resulted in a performance penalty
-
Primitive inheritance: A shallow copy of a parameter object
function objectCopy(obj) { function Fun() {}; Fun.prototype = obj;return new Fun() } let person = { name: "wk".age: 18.friends: ["jack"."tom"."rose"].sayName:function() { console.log(this.name); }}let person1 = objectCopy(person); person1.name = "wxb"; person1.friends.push("lily"); person1.sayName(); // wxb let person2 = objectCopy(person); person2.name = "gsr"; person2.friends.push("kobe"); person2.sayName(); // "gsr" console.log(person.name); //wk console.log(person.friends); // ["jack", "tom", "rose", "lily", "kobe"] Copy the code
Advantages:
- Superclass methods are reusable
Disadvantages:
- References to the parent class are shared by all subclasses
- A subclass instance cannot pass arguments to its parent class
-
Parasitic inheritance: Use primitive inheritance to make a shallow copy of a target object, enhancing the capability of the shallow copy
function objectCopy(obj) { function Fun() {}; Fun.prototype = obj;return new Fun(); } function createAnother(original) { let clone = objectCopy(original); clone.getName = function () { console.log(this.name); }; return clone; } let person = { name: 'wk'.friend: ["rose"."tom"."jack"],}let person1 = createAnother(person); person1.friends.push("lily"); console.log(person1.friends); person1.getName(); // wk let person2 = createAnother(person); console.log(person2.friends); // ["rose", "tom", "jack", "lily"] Copy the code
-
Parasitic combinatorial inheritance (combinatorial optimization) : Parasitic combinatorial inheritance is probably the best model for reference type inheritance
function Parent(name) { this.name = name; this.friends = ["rose"."lily"."tom"] } Parent.prototype.getName = function () { console.log(this.name); } function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = Object.create(Parent.prototype); // the effect is similar to that of objectCopy in parasitic inheritance Child.prototype.constructor = Child; // Changing prototype changes constructor and refers it back to Child Child.prototype.getAge = function () { console.log(this.age); } let child1 = new Child("wk".18); child1.getAge(); / / 18 child1.getName(); // wk child1.friends.push("jack"); console.log(child1.friends); // ["rose", "lily", "tom", "jack"] let child2 = new Child("cy".21); child2.getAge(); / / 21 child2.getName(); // cy console.log(child2.friends); // ["rose", "lily", "tom"] Copy the code
Advantages:
- The superclass constructor is called only once
- Child can pass parameters to Parent
- Superclass methods can be reused
- Reference properties of the parent class are not shared
6. class
(1) Basic usage
- constructor
- attribute
- methods
class Student {
constructor(name, number){
this.name = name
this.number = number
}
sayName() {
console.log(this.name)
}
sayNumber() {
console.log(this.number)
}
}
let a = new Student("wk".18)
a.sayName()
a.sayNumber()
Copy the code
(2) Inheritance
- extends
- super
- Extend or override methods
/ / parent class
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(this.name + ' eat food')}}1 / / subclass
class Teacher extends People {
constructor(name, id) {
super(name)
this.id = id
}
sayId() {
console.log(this.id)
}
}
/ / subclass 2
class Coder extends People {
constructor(name, company) {
super(name)
this.company = company
}
sayCompany() {
console.log(this.company)
}
}
let b = new Teacher("wk".12)
b.eat()
b.sayId()
let c = new Coder("cy"."tencent")
c.eat()
c.sayCompany()
Copy the code
(3) Class principle
Classes are syntactic sugar, and the essence is still a constructor. Properties are put in the constructor body, and methods are written in the function prototype
7. Look at the following code to say output
var A = function() {};
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2.m: 3
}
var c = new A();
console.log(b.n); / / 1
console.log(b.m); // undefined
console.log(c.n); / / 2
console.log(c.m); / / 3
Copy the code
The __proto__ attribute points to {n = 1}. After that, the prototype of the constructor A is changed to {n:2, m :3}, but it does not affect the __proto__ pointing to B.