How to Sweep Javascript Interview Core Test Sites in Spring Recruitment Season (Basic Edition)?

  Front end, html, javascript, node.js, Programmer


Javascript is the focus of the front-end interview. This article focuses on sorting out the common knowledge points in Javascript, and then analyzes some easy-to-appear topics. Limited to the length of the articles, it is impossible to explain all the knowledge points. This article only lists some important and difficult points.If you want to know more, please clickMy blog.

I. Variable Types

1.JS data type classification

According to the way variable types are passed in JavaScript, they are divided into basic data types and reference data types. Among them, the basic data types include Undefined, Null, Boolean, Number, String, Symbol (ES6 added, representing unique values), while the reference data types are collectively called Object objects, mainly including objects, arrays and functions.

There are differences in the way parameters are passed:

  • If the parameter of the function is of simple type, a numerical copy of a value type will be transferred to the inside of the function, and the inside of the function will not affect the parameter variable transferred from the outside of the function.
  • If a parameter is a reference type, the address value of the reference type will be copied to the parameter of the incoming function, and the internal modification of the function will affect the transfer.

Topic: Differences between Basic Types and Reference Types

Basic types and reference types are stored in different locations in memory. Basic types are stored directly in the stack, while objects of reference types are stored in the heap. At the same time, pointers are stored in the stack, and the pointers point to the starting locations of entities in the heap. Let’s look at the main difference between the two through a small topic:

//Basic Type
 var a = 10
 var b = a
 b = 20
 console.log(a)  // 10
 console.log(b)  // 20

In the above code, a b is a value type, and the two modify the assignment respectively without any influence on each other. Let’s look at examples of reference types:

//Reference Type
 var a = {x: 10, y: 20}
 var b = a
 b.x = 100
 b.y = 200
 console.log(a)  // {x: 100, y: 200}
 console.log(b)  // {x: 100, y: 200}

In the a bove code, ab is the reference type. After b = a is executed, the attribute value of B is modified, and that of A changes accordingly. Because both a and b are reference types, pointing to the same memory address, that is, both refer to the same value, so when b modifies the attribute, the value of a changes accordingly

2. Judgment of data type


Typeof returns a string representing the data type, the returned results include: number, boolean, string, symbol, object, undefined, function and other seven data types, but cannot determine null, array and so on

typeof Symbol();  // symbol valid
 typeof '';  // string valid
 typeof 1;  // number is valid
 typeof true;  //boolean valid
 typeof undefined;  //undefined valid
 typeof new Function();  // function valid
 typeof null;  //object is invalid
 typeof [] ;  //object is invalid
 typeof new Date();  //object is invalid
 typeof new RegExp();  //object is invalid


Instanceof is an instance used to determine whether a is b. the expression is: ainstance of b. if a is an instance of b, it returns true, otherwise it returns false.The instanceof operator is used to test whether an object has a prototype property of a constructor in its prototype chain, but it cannot detect null and undefined

[] instanceof Array;  //true
 {} instanceof Object;  //true
 new Date() instanceof Date;  //true
 new RegExp() instanceof RegExp//true
 Null instanceof Null// error reported
 Undefined instanceof undefined// error reported


The role of the constructor is very similar to that of instanceof.However, unlike instanceof, constructor detection Object can also handle detection of basic data types.
However, the constructor of the function is unstable. This is mainly reflected in rewriting the prototype of the class. In the process of rewriting, it is likely to cover the former constructor, thus the detected result is inaccurate.

4) () is the most accurate and common way.'') ;  // [object String] ;  // [object Number] ;  // [object Boolean] ;  // [object Undefined] ;  // [object Null] Function()) ;  // [object Function] Date()) ;  // [object Date][]) ;  // [object Array] RegExp()) ;  // [object RegExp] Error()) ;  // [object Error]

3. Shallow copy and deep copy

Shallow copy only copies the pointer to an object, not the object itself. The old and new objects still share the same memory.

The implementation of shallow copy (seeShallow copy and deep copy):

  • Object.assign (): note that when the target object has only one layer, it is a deep copy
  • Array.prototype.concat()
  • Array.prototype.slice()

Deep copy is to copy all the reference structures of the data when copying the data.. In short, there are two data structures in memory that are identical and independent of each other. Instead of copying only their reference relationships, reference types are copied.

Implementation of deep copy:

  • The popular function library lodash also provides _.cloneDeep for making deep copies.
  • Jquery provides a $.extend that can be used to make deep copies.
  • JSON.parse(JSON.stringify())
  • Handwriting recursion method

Recursive implementation of the principle of deep copy: to copy a data, we must go through its attributes. if the attribute of this object is still an object, continue to use this method, and so on and so forth.

//Define the function of detecting data type
 function checkedType(target) {
 return, -1)
 //Implement Deep Cloning-Objects/Arrays
 function clone(target) {
 //Judge the copied data type
 //Initialize variable result to become final cloned data
 let result,
 targetType = checkedType(target)
 if (targetType === 'Object') {
 result = {}
 } else if (targetType === 'Array') {
 result = []
 } else {
 return target
 //Traverse the target data
 for (let i in target) {
 //Get every value that traverses the data structure.
 let value = target[i]
 //Judge whether each value in the target structure has an object/array
 if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
 //Objects/Arrays Nested in Objects/Arrays
 //Continue traversing to obtain the value value
 result[i] = clone(value)
 } else {
 //The value obtained is a basic data type or a function.
 result[i] = value
 return result

Scope and Closure

1. Execution Context and Execution Stack

The execution context is the abstract concept of the environment in which the current JavaScript code is parsed and executed. Any code running in JavaScript runs in the execution context.
The life cycle of the execution context includes three phases: creation phase → execution phase → recycling phase. We will focus on the creation phase.

The creation phase (before the function is called but does not execute any of its internal code) does the following three things:

  • Create a variable object: First initialize the arguments of the function, promote the function declaration and variable declaration.
  • Creating scope chain: It will be described below
  • Determine the direction of this: as described below
function test(arg){
 // 1. formal parameter arg is "hi"
 // 2. arg is function at this time because function declaration has higher priority than variable declaration
 var arg = 'hello';  // 3.var arg variable declaration ignored, arg = 'hello' executed
 function arg(){
 console.log('hello world')
 /* Output:
 function arg() {
 console.log('hello world');

This is because when the function is executed, a new private scope will be formed first, and then the following steps will be followed in sequence:

  • If there is a tangible parameter, assign a value to the formal parameter first.
  • For pre-interpretation in private scope, the priority of function declaration is higher than that of variable declaration, and finally the latter will be overwritten by the former, but can be re-assigned.
  • Code in private scope executes from top to bottom

When there are more functions, there are multiple function execution contexts. Every time a function is called, a new execution context is created. How do you manage so many execution contexts created?

The JavaScript engine creates an execution stack to manage the execution context.You can think of the execution stack as a stack structure for storing function calls, following the principle of first in and then out..

From the above flowchart, we need to remember several key points:

  • JavaScript is executed on a single thread, and all code is queued for execution.
  • At first, when the browser executes the global code, it first creates the global execution context and pushes it to the top of the execution stack.
  • Every time a function is executed, the execution context of the function is created and pushed to the top of the execution stack. After the execution of the current function is complet ed, the execution context of the current function is pushed out of the stack and waits for garbage collection.
  • The browser’s JS execution engine always accesses the execution context at the top of the stack.
  • There is only one global context, which is pushed out of the stack when the browser is closed.

2. Scope and scope chain

JavaScript from ES6 has global scope, function scope and block-level scope (ES6 is newly added). We can understand it this way:The scope is an independent site to prevent variables from leaking out and being exposed. That is to say, the greatest use of scope is to isolate variables, and variables with the same name will not conflict under different scopes..
Before introducing scope chain, first understand free variables. In the following code, console.log(a) needs to obtain a variable, but a is not defined in the current scope (compare with b). There are no variables defined in the current scope, which becomes free variables.

var a = 100
 function fn() {
 var b = 200
 Log (a)//A here is a free variables here.

How do you get the value of free variables-look for the parent scope (the parent scope that created the function). What if there is no parent? Look up layer by layer until the global scope is found or not found, then announce to give up. This layer-by-layer relationship is scope chain.

function F1() {
 var a = 100
 return function () {
 function F2(f1) {
 var a = 200
 var f1 = F1()
 F2(f1) // 100

In the above code, the value of free variables A is found in function F1 instead of F2 becauseWhen free variables looked for it from scope chain, it was based on scope chain when the function was defined, not when the function was executed.

What is closure

Closure is also a relatively abstract concept in JavaScript. Personally, I understand that closure is a function in a function (which is not the case in other languages). Functions inside can access variables of functions outside, and variables outside are part of this internal function.

The role of closures:

  • You can use closures to access variables in functions.
  • The variable can be stored in the memory for a long time, and the life cycle is relatively long..

Closures cannot be misused, otherwise memory leaks will occur and the performance of web pages will be affected. After the closure is used up, to release the resource immediately, point the reference variable to null.

Closures have two main application scenarios:

  • Functions are passed as parameters (see some examples of scope)
  • Function as return value (example below)
function outer() {
 Var num = 0 // internal variable
 return function add() {
 //You can access it outside the outer function by returning the add function.
 Num++ // internal functions have references as part of the add function
 var func1 = outer() //
 Func1() // is actually calling the add function and outputting 1
 Func1() // output2
 var func2 = outer()
 Func2() // output1
 Func2() // output2

4.this is a comprehensive analysis.

First understand a very important concept-The value of this can only be confirmed when it is executed, and cannot be confirmed when it is defined!Why? Because this is part of the execution context, and the execution context needs to be determined before the code is executed, not when it is defined. Look at the following example:

//Case 1
 function foo() {
 console.log(this.a) //1
 var a = 1
 //Case 2
 function fn(){
 var obj={fn:fn};
 obj.fn();  //this->obj
 //Case 3
 function CreateJsPerson(name,age){
 //this is an instance p1 of the current class;  //=>
 this.age=age;  //=>p1.age=age
 Varp1 = newcreatejsohn ("yin huazhi", 48);
 //Case 4
 function add(c, d){
 return this.a + this.b + c + d;
 var o = {a:1, b:3};, 5, 7);  // 1 + 3 + 5 + 7 = 16
 add.apply(o, [10, 20]);  // 1 + 3 + 10 + 20 = 34
 //Case 5
 < button id="btn1 "> arrow function this</button >
 <script type="text/javascript">
 let btn1 = document.getElementById('btn1');
 let obj = {
 name: 'kobe',
 age: 39,
 getName: function () {
 btn1.onclick = () => {
 console.log(this);  //obj

Next, let’s explain the above situations one by one.

  • For calling foo directly, this must be window no matter where the foo function is placed.
  • For (), we just need to remember that whoever calls the function is this, so this in the foo function is the obj object in this scenario
  • In constructor mode, this in, which appears in the class (in the function body), is an instance of the current class
  • Call, apply, and bind:this is the first parameter
  • Arrow function this points to: the arrow function does not have its own this, see if there is a function on its outer layer. if there is, this of the outer layer function is this of the inner arrow function, if not, this is window.

Third, asynchronous

1. synchronous vs asynchronous

Synchronization, as I understand it, is a linear way of execution, and the process of execution cannot span. For example, if you eat after talking and watch your cell phone after eating, you must wait until the last thing is finished before you can carry out the following things.

Asynchronous is a method of parallel processing, which can perform other tasks without waiting for a program to finish execution. For example, a person eating, watching his mobile phone and talking at the same time is the asynchronous processing method. Results that are processed asynchronously in a program usually use callback functions to process the results.

 console.log(300)  //100 200 300
 console.log(300) //100 300 200

2. Asynchronous and Single Thread

The basic reason why JS needs asynchrony is that JS runs on a single thread, that is, it can only do one thing at a time, and cannot “use two things at one time”. In order to take advantage of the computing power of multi-core CPU, HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but the sub-threads are completely controlled by the main thread and cannot operate DOM. Therefore, this new standard does not change the nature of JavaScript single threading.

An Ajax request takes 5 seconds because the network is slow. If it is synchronized, the page will be stuck here for 5 seconds and nothing can be done. If it is asynchronous, it will be much better. Wait for 5 seconds, and other things will not be delayed. As for the 5-second waiting, the net speed is too slow, not because of JS.

3. Asynchronous front-end scenario

The front end uses asynchronous scenarios

  • Timed tasks: setTimeout, setInterval
  • Network request: ajax request, dynamic loading
  • Event binding

4.Event Loop

A complete Event Loop process can be summarized as the following stages:

  • At first the execution stack is empty, we can put theThe execution stack is considered as a stack structure for storing function calls and follows the principle of first in and then out.. The micro queue is empty, and there is only one script script (whole code) in the macro queue.
  • The global context (script tag) is pushed to the execution stack to synchronize code execution. In the process of execution, it will determine whether it is a synchronous task or an asynchronous task. By calling some interfaces, new macro-task and micro-task can be generated, which will be pushed into their respective task queues respectively. After the synchronization code is executed, the script script will be moved out of the macro queue. This process is essentially the execution and dequeuing of the macro-task in the queue.
  • In the previous step, we went out of the team with a macro-task. In this step, we dealt with a micro-task. However, it should be noted that when macro-task leaves the team, the task isOne by oneImplemented; When micro-task leaves the team, the task isTeam one, team oneExecuted. Therefore, when we deal with the micro queue step, we will execute the tasks in the queue one by one and dequeue it until the queue is emptied.
  • Perform rendering operations and update the interface
  • Check if there is a Web worker task, and if so, process it.
  • The above process cycles until both queues are empty

Next, let’s look at an example to introduce the above process:


The final output is Promise1, setTimeout1, Promise2, setTimeout2

  • At the beginning, when the synchronization task of the execution stack (which belongs to macro task) is finished, it will check whether there is a micro task queue, and there is (and only one) in the above question. then it will execute all tasks in the micro task queue to output Promise1 and generate a macro task setTimeout2 at the same time.
  • Then check the macro task queue. Before setTimeout2, macro task setTimeout1 executes macro task setTimeout1 and outputs setTimeout1.
  • When macro task setTimeout1 is executed, micro task Promise2 will be generated and placed in the micro task queue, then all tasks in the micro task queue will be cleared first and Promise2 will be output.
  • After all the tasks in the micro task queue are cleared, one will be taken from the macro task queue. This time, setTimeout2 will be executed.

IV. Prototype Chain and Inheritance

1. Prototype and prototype chain

Prototype: In JavaScript, a prototype is a prototype object that represents the relationship between types.

Prototype Chain: Everything in JavaScript is an object, and there is also a relationship between objects and objects. They do not exist in isolation. In JavaScript, the inheritance relationship between Objects refers to the parent class object through the prototype object until it points to the object object, thus forming a chain of prototype points, which is called prototype chain in technical terms.

var Person = function() {
 this.age = 18
 Name =' anonymous'
 var Student = function() {}
 //Create inheritance relationship, with parent class instance as subclass prototype
 Student.prototype = new Person()
 var s1 = new Student()

Prototype diagram:

When trying to get an attribute of an object, if the object itself does not have this attribute, it will be deleted__proto__(i.e. prototype of its constructor). If the top layer has been found but not found, it will be declared a failure and returned to undefined. What is the top layer-Object.prototype.__proto__ === null

2. Succession

Introduce several common inheritance methods (for more information, please clickSix Common Inheritance Methods of JavaScript):

  • Combination Inheritance of Prototype Chain+Borrowing Constructor
function Parent(value) {
 this.val = value
 Parent.prototype.getValue = function() {
 function Child(value) {, value)
 Child.prototype = new Parent()
 const child = new Child(1)
 child.getValue() // 1
 child instanceof Parent // true

The core of the above inheritance method is to pass through the constructor of the the properties of the parent class, and then change the prototype of the subclass tonew Parent()To inherit the functions of the parent class.

The advantage of this inheritance method is that the constructor can pass parameters, not be shared with the reference attributes of the parent class, and the functions of the parent class can be reused. However, there is also a disadvantage that the constructor of the parent class is called when inheriting the functions of the parent class, resulting in more unnecessary attributes of the parent class on the prototype of the subclass, thus wasting memory.

  • Parasitic Combination Inheritance: This inheritance method optimizes the inheritance of the previous combination
function Parent(value) {
 this.val = value
 Parent.prototype.getValue = function() {
 function Child(value) {, value)
 Child.prototype = Object.create(Parent.prototype, {
 constructor: {
 value: Child,
 enumerable: false,
 writable: true,
 configurable: true
 const child = new Child(1)
 child.getValue() // 1
 child instanceof Parent // true

The core of the above inheritance implementation is to assign the prototype of the parent class to the subclass, and set the constructor as the subclass, thus not only solving the useless attribute problem of the parent class, but also finding the constructor of the subclass correctly.

  • Inheritance of class in ES6

Class keyword is introduced in ES6. class can be inherited through Extensions keyword and static method of class can be defined through static keyword, which is much clearer and more convenient than ES5′ s inheritance through modifying prototype chain. It should be noted that,The class keyword is only the syntax sugar of the prototype, and JavaScript inheritance is still based on the prototype..

class Parent {
 constructor(value) {
 this.val = value
 getValue() {
 class Child extends Parent {
 constructor(value) {
 this.val = value
 let child = new Child(1)
 child.getValue() // 1
 child instanceof Parent // true

The core of class implementation inheritance is to use extensions to indicate from which parent class, and super must be called in the subclass constructor, because this code can be seen, value).

Five, DOM operation and BOM operation

1.DOM operations

When a web page is loaded, the browser will create a document object model (DOM) of the page. We can think of DOM as an HTML structure recognized by JS, a common JS object or array. Next we will introduce common DOM operations:

  • New nodes and mobile nodes
var div1 = document.getElementById('div1')
 //Add a new node
 var p1 = document.createElement('p')
 p1.innerHTML = 'this is p1'
 Div1.appendChild(p1) // adds the newly created element
 //Move the existing node.  Note that this is "move", not copy
 var p2 = document.getElementById('p2')
  • Gets the parent element
var div1 = document.getElementById('div1')
 var parent = div1.parentElement
  • Get child elements
var div1 = document.getElementById('div1')
 var child = div1.childNodes
  • Delete node
var div1 = document.getElementById('div1')
 var child = div1.childNodes

2.DOM Event Model and Event Flow

DOM event model is divided into capture and bubbling. After an event occurs, it propagation between the child element and the parent element. The spread is divided into three stages.

(1) Capture phase: the phase in which events propagate from the window object to the target node from top to bottom;

(2) target stage: the stage when the real target node is handling the event;

(3) Bubble stage: the stage in which the event propagates from the target node to the window object from bottom to top.

DOM Event Capture Process

The capture is from top to bottom. The event first goes from the window object, then to the document (object), then to the html tag (the html tag is obtained through the document.documentElement), then to the body tag (the body tag is obtained through the document.body), then goes down layer by layer according to the ordinary html structure, and finally to the target element.

Next, let’s look at an example of an incident bubbling:

//Incident Bubble
 <div id="outer">
 <div id="inner"></div>
 window.onclick = function() {
 document.onclick = function() {
 document.documentElement.onclick = function() {
 document.body.onclick = function() {
 outer.onclick = function(ev) {
 inner.onclick = function(ev) {

How to prevent bubbling?

viaevent.stopPropagation()The method prevents the event from bubbling to the parent element and prevents any parent event handler from being executed.
We can add to the click event of the inner element in the above exampleevent.stopPropagation()After this sentence, the execution of the parent event was blocked, and finally only’ inner’ was printed.

inner.onclick = function(ev) {

3. Event Agent (Event Delegation)

Since the event will propagate up to the parent node in the bubbling phase, the monitoring function of the child node can be defined on the parent node, and the monitoring function of the parent node uniformly processes the events of multiple child elements. This method is called event proxy.

We set up a scenario, the following code, one<div>It contains several<a>, but also can continue to increase. How can it be quick and convenient for all<a>What about binding events?

<div id="div1">
 <a href="#">a1</a>
 <a href="#">a2</a>
 <a href="#">a3</a>
 <a href="#">a4</a>
 < button > click to add an a tag < /button >

If you give each<a>Tags are bound to an event one by one, which is very large for memory consumption. With the help of event proxy, we only need to bind the method to the div of the parent container, so that no matter which descendant element is clicked, the click behavior of the parent container will be triggered according to the transmission mechanism of bubbling propagation, and then the corresponding method will be executed. according to the event source, we can know who is clicked, thus completing different things.

var div1 = document.getElementById('div1')
 div1.addEventListener('click', function (e) {
 // can monitor which element triggers the click event
 var target =
 if (e.nodeName === 'A') {
 //click on < a > element

Finally, the advantages of using agents are as follows:

  • Keep the code simple
  • Reduce memory footprint of browser

4.BOM operation

BOM (Browser Object Model) is the setting and acquisition of some information of the browser itself, such as the width and height of the browser, and the address to which the browser will jump.

  • Window.screen object: contains information about the user’s screen
  • Window.location object: used to obtain the address (URL) of the current page and redirect the browser to a new page
  • Window.history object: browsing history’s advance and retreat, etc.
  • Window.navigator Object: It is often used to obtain browser information, whether to access mobile terminal, etc.

Gets the width and height of the screen


Get web address, protocol, path, parameters, hash, etc.

//For example, the current website is  a=10&b=10#some
 console.log(location.href)  //  a=10&b=10#some
 console.log(location.protocol) // https:
 console.log(location.pathname) // /timeline/frontend
 console.log( // ?  a=10&b=10
 console.log(location.hash) // #some

In addition, there are forward and backward functions of calling the browser, etc.


Get browser characteristics (commonly known as UA) and then identify the client, for example, determine whether it is Chrome browser or not

var ua = navigator.userAgent
 var isChrome = ua.indexOf('Chrome')

5.Ajax and Cross-Domain

Ajax is a technology that requests data asynchronously, which is very helpful to improve the user experience and program performance.
Simply put, Ajax loads background data through asynchronous requests and renders it on the web page without refreshing the page again. Common application scenarios include whether the form was successfully registered, Baidu search drop-down box prompt and express order number query, etc.Ajax aims to improve user experience and reduce the amount of network data transmission..

How to Handwrite XMLHttpRequest Without any Library

var xhr = new XMLHttpRequest()
 xhr.onreadystatechange = function () {
 //The functions here are executed asynchronously
 if (xhr.readyState == 4) {
 if (xhr.status == 200) {
 }"GET", "/api", false)

Because browsers have homologous policies for security reasons. In other words, if there is one difference between the protocol, domain name or port that is cross-domain, Ajax requests will fail.

So it isFor what security reasons will this mechanism be introduced?? In fact, it is mainly used to prevent CSRF attacks. To put it simply, CSRF attack is to use the login status of users to initiate malicious requests.

Then let’s consider a question.The request is cross-domain, so has the request been sent out? The request must have been sent, but the browser blocked the response.

Several common cross-domain solutions (seeImplementation Principles of Nine Cross-Domain Methods (Full Version)):

  • JSONP: Using Homologous Policy Pairs<script>Tags are not restricted, but only GET requests are supported.
  • CORS: The key to CORS communication is the setting of backend and server.Access-Control-Allow-OriginIt can be started. The highly respected cross-domain solution is much simpler than JSONP
  • Node Middleware Agent or nginx Reverse Agent: The server is not restricted mainly through homologous policies

6. Storage

Differences between sessionStorage, localStorage, and cookie

  • Common ground: all of them are stored on the browser side and follow the same source policy.
  • The difference is that the life cycle and scope are different.

Scope: localStorage can read/modify the same copy of localStorage data as long as it is under the same protocol, the same host name and the same port. SessionStorage is a bit stricter than localStorage. besides the protocol, host name and port, it is also required to be under the same window (i.e. the tab page of the browser)

Lifecycle: localStorage is a persistent local storage. The data stored in it will never expire. The only way to make it disappear is to delete it manually. SessionStorage is temporary local storage. It is session level storage. When the session ends (the page is closed), the storage content is released.


VI. Modularization

Brief introduction of several common modular specifications (for details, please clickDetails of Front End Modularization (Full Edition)):

  • CommonJS specification is mainly used for server-side programming, and the loading modules are synchronous, which is not suitable for browser environment, because synchronization means blocking loading, and browser resources are loaded asynchronously, so AMD CMD solution is available.
  • AMD specifications load modules asynchronously in a browser environment and can load multiple modules in parallel. However, AMD specification development cost is high, code reading and writing are difficult, and the semantics of module definition are not smooth.
  • CMD specification is very similar to AMD specification, both of which are used for browser programming, rely on proximity, delay execution, and can be easily run in Node.js However, depending on SPM packaging, the module loading logic is biased
  • ES6 has realized the module function on the level of language standard, and it is quite simple to realize. It can completely replace the CommonJS and AMD specifications and become a common module solution for browsers and servers.

Welcome to pay attention to the public number:Front end craftsmanWe witness your growth together!

High quality communication group wechat public accounts
2 1

Reference material