JavaScript
The data type
String, Number, Boolean, Null, Undefined, Symbol, BigInt, Object
Heap, stack
Both are places to store data.
A stack is an automatically allocated memory space that holds values of primitive types and memory addresses of reference types.
The heap is a dynamically allocated memory space that holds values of reference types.
JavaScript does not allow direct manipulation of objects in the heap space. When manipulating objects, the actual operation is the reference of the object, and the memory address stored in the stack space serves as a point to find the value of the corresponding reference type in the heap space.
Implicit type conversion
As a weakly typed language, JavaScript automatically converts types in some scenarios due to its flexibility.
There are three common implicit type conversion scenarios: operation, negation, and comparison
operation
The implicit type conversion of an operation converts the operation’s member to type number.
Primitive type conversion:
true + false / / 1
null + 10 / / 10
false + 20 / / 20
undefined + 30 // NaN
1 + '2' / / "12"
NaN + ' ' // "NaN"
undefined + ' ' // "undefined"
null + ' ' // "null"
' ' - 3 // -3
Copy the code
null
,false
,' '
conversionnumber
They’re all of type 0undefined
conversionnumber
Type isNaN
, soundefined
And other primitive type operationsNaN
- Strings are strong in addition (actually string concatenation), and adding to any type produces a string (
symbol
Except), even if it isNaN
,undefined
. Other operations are converted normallynumber
I’m going to do it.
Reference type conversion:
[1] + 10 / / "110"
[] + 20 20 "/ /"
[1.2] + 20 // "1,220"
[20] - 10 / / 10
[1.2] - 10 // NaN
({}) + 10 // "[object Object]10"({})10 // NaN
Copy the code
- Is called first when a reference type operation is performed
valueOf
Determines whether the returned value is the original data type and performs the operation if so. If not, calltoString
To convertstring
Operation again - Same conclusion, except for the addition of the output string, the other cases are first
string
Turn againnumber
Parse the reference type conversion process:
[1.2] + 20
/ / process:
[1.2].toString() / / '1, 2,'
'1, 2,' + 20 // '1,220'
[20] - 10
/ / process
[20].toString() / / '20'
Number('20') / / 20
20 - 10 / / 10
Copy the code
The not
The inverted implicit type conversion converts the operation’s member to a Boolean type.
The implicit type conversion is relatively simple, which is to convert a value to a Boolean value and invert it:
! []// false! {}// false
!false // true
Copy the code
Usually to quickly get the Boolean type of a value, we can invert it twice:
!!!!! []// true!!!!!0 // false
Copy the code
To compare
Comparison is divided into strict comparison === and non-strict comparison ==, because === will compare types, no type conversion. I’m only talking about theta = theta.
The implicit type conversion of the comparison basically converts the member of the operation to type number.
undefined= =null // true
' '= =0 // true
true= =1 // true
'1'= =true // true
[1] = ='1' // true
[1.2] = ='1, 2,' // true= = ({})'[object Object]' // true
Copy the code
undefined
Is equal to thenull
- String, Boolean,
null
When they compare, they all turnnumber
- Reference types are cast to implicitly
string
Compare, if not equal then convertnumber
To compare
precompiled
Precompilation occurs before JavaScript code is executed, parsing and generating the code, initializing the creation and storing variables, and preparing the code for execution.
Precompilation process:
- Create GO/AO objects (GO is global, AO is active)
- Assign the parameter and variable declaration to
undefined
- Arguments and parameters are unified
- Function declaration promotion (assigning variables to function bodies)
Example:
function foo(x, y) {
console.log(x)
var x = 10
console.log(x)
function x(){}
console.log(x)
}
foo(20.30)
Copy the code
// 1. Create AO objects
AO {}
// 2. Find the parameter and variable declaration assigned to undefined
AO {
x: undefined
y: undefined
}
// 3. The arguments are consistent with the parameters
AO {
x: 20
y: 30
}
// 4. Function declaration is improved
AO {
x: function x(){}
y: 30
}
Copy the code
The first x is evaluated from the AO, and the output is function x; X is assigned 10, and the second x prints 10; The function x has been declared promoted, there is no further assignment of x, and the third x prints 10.
scope
A scope guarantees orderly access to all variables and functions that it has access to, and is a rule for code to look up variables at run time.
Function scope
Functions create their own scope at run time, “hiding” internal variables and function definitions, and the outer scope cannot access anything inside the wrapper function.
Block-level scope
Block-level scopes were created prior to ES6 using either with or try/catch. Block-level scope declarations are made easier with the introduction of the let keyword in ES6. The let keyword binds variables to any scope they are in (usually {… } inside).
{
let num = 10
}
console.log(num) // ReferenceError: num is not defined
Copy the code
Parameter scope
Once the default values of parameters are set, the parameters form a separate scope when the function is declared initialized. After initialization, the scope will disappear. This syntactic behavior does not occur if the parameter defaults are not set.
let x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) / / 2
Copy the code
The default value of argument y is equal to variable x. When f is called, the arguments form a separate scope. In this scope, the default variable x refers to the first parameter x, not the global variable x, so the output is 2.
let x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}
foo() / / 2
x / / 1
Copy the code
Y defaults to an anonymous function where x points to the first argument x of the same scope. The internal variable x of the function foo refers to the first parameter x, which is the same as the internal x of the anonymous function. The y function reassigns the value to x and outputs 2, while the outer global variable x remains intact.
closure
The nature of closures is a scoping problem. Closures occur when a function can remember and access its scope, and the function is called outside its scope.
Simply put, a function refers to a variable in its scope, and it is saved for execution in other scopes. The scope of the reference variable does not disappear, but follows the function. When this function is executed, variables can be found through the scope chain.
let bar
function foo() {
let a = 10
// The function is saved externally
bar = function () {
// reference variable A that is not currently scoped
console.log(a)
}
}
foo()
The bar function is not executed in its own scope
bar() / / 10
Copy the code
Advantages: Private variables or methods, caching
Disadvantages: Closures do not release scoped chains, which can lead to memory leaks
Prototype chain
Objects in JavaScript have a special built-in property, prototype, which is a reference to other objects. When a variable is searched, it will first look for its own Object. If it cannot find a variable, it will look for the Object’s prototype, and so on, and finally Object. Prototype. Multiple prototypes linked together are called prototype chains.
Prototype inheritance
There are many methods of stereotype inheritance, and I won’t cover all of them here, but only two commonly used methods.
The holy grail mode
function inherit(Target, Origin){
function F() {};
F.prototype = Origin.prototype;
Target.prototype = new F();
/ / reduction constuctor
Target.prototype.constuctor = Target;
// From whom records are inherited
Target.prototype.uber = Origin.prototype;
}
Copy the code
The benefit of the Holy Grail mode is that with intermediate object isolation, any attributes added by children are added to the object without affecting the parent. And the search attribute is along __proto__ search, can smoothly find the parent level of the attribute, inheritance.
Use:
function Person() {
this.name = 'people'
}
Person.prototype.sayName = function () { console.log(this.name) }
function Child() {
this.name = 'child'
}
inherit(Child, Person)
Child.prototype.age = 18
let child = new Child()
Copy the code
ES6 Class
class Person {
constructor() {
this.name = 'people'
}
sayName() {
console.log(this.name)
}
}
class Child extends Person {
constructor() {
super(a)this.name = 'child'
}
}
Child.prototype.age = 18
let child = new Child()
Copy the code
Classes can be inherited through the extends keyword, which is much cleaner and more convenient than ES5’s inheritance through modified prototype chains.
Basic packing type
let str = 'hello'
str.split(' ')
Copy the code
Primitive types have no properties or methods, but in practice we can call methods from primitive types, just as a string can call a split method.
To facilitate manipulation of primitive type values, each time a primitive type value is read, an object of the corresponding primitive wrapper type is created behind the scenes, allowing us to call methods to manipulate the data. The general process is as follows:
- create
String
Type instance - Invokes the specified method on the instance
- Destroy the instance
let str = new String('hello')
str.split(' ')
str = null
Copy the code
this
This is the binding that occurs when the function is called, and what it points to depends entirely on where the function is called. When you use this inside a function, you can access properties and methods on the caller object.
There are four cases of the this binding:
- The new binding.
new
instantiation - Show bindings.
call
,apply
,bind
Manually change pointing - Implicit binding. Called by a context object, as in
obj.fn()
.this
Point to theobj
- Default binding. Global objects are bound by default, and in strict mode to
undefined
The new binding has the highest priority and is last to the default binding.
The process of new
- Create an empty object
- Set the prototype to the object
__proto__
Object pointing to the constructorprototype
- Constructor
this
Executes the object and executes the constructor to add properties and methods to the empty object - Return instance object
If the basic type is returned, the construction process is terminated prematurely and the instance object is returned. If a reference type is returned, that reference type is returned.
// Return the base type
function Foo(){
this.name = 'Joe'
return 123
this.age = 20
}
new Foo() // Foo {name: "Joe"}
// Return the reference type
function Foo(){
this.name = 'Joe'
return [123]
this.age = 20
}
new Foo() / / [123]
Copy the code
Call, apply, bind
All three of them change the direction of this.
Call and apply change this to point to and call a function. The difference between call and apply is that they pass arguments one by one, while call and apply pass a list of arguments of array type.
Bind changes this and returns a function reference. Multiple calls to bind are invalid, and the this reference it changes only takes the first call.
Write a call
Function.prototype.mycall = function () {
if(typeof this! = ='function') {throw 'caller must be a function'
}
let othis = arguments[0] | |window
othis._fn = this
let arg = [...arguments].slice(1)
letres = othis._fn(... arg)Reflect.deleteProperty(othis, '_fn') // Delete the _fn attribute
return res
}
Copy the code
For the apply implementation, modify the parameter format
Write a bind
Function.prototype.mybind = function (oThis) {
if(typeof this! ='function') {throw 'caller must be a function'
}
let fThis = this
/ / Array. Prototype. Slice. Call the class Array into an Array
let arg = Array.prototype.slice.call(arguments.1)
let NOP = function(){}
let fBound = function(){
let arg_ = Array.prototype.slice.call(arguments)
// The new binding level is higher than the explicit binding
When called as a constructor, the pointer is left unchanged
// Use instanceof to determine if it is a constructor call
return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_))
}
// Maintain the prototype
if(this.prototype){
NOP.prototype = this.prototype
fBound.prototype = new NOP()
}
return fBound
}
Copy the code
Knowledge of ES6 syntax
Common: let, const, extension operator, template string, object destruct, arrow function, default argument, Promise
Data structure: Set, Map, Symbol
Others: Proxy and Reflect
Set, Map, WeakSet, WeakMap
Set:
- The values of the members are unique and have no duplicate values, similar to an array
- Can traverse
WeakSet:
- The member must be of reference type
- Members are weak references and can be garbage collected. After the external reference to which a member points is reclaimed, the member can also be reclaimed
- Can’t traverse
Map:
- A collection of key-value pairs that can be of any type
- Can traverse
WeakMap:
- Only the reference type is accepted as the key name
- The key name is a weak reference, and the key value can be any value and can be garbage collected. If the external reference to the key name is reclaimed, the corresponding key name can also be reclaimed
- Can’t traverse
The difference between an arrow function and a normal function
- Arrow function
this
The point is determined at code writing, that is, the scope in which the arrow function itself resides; Normal functions are determined at call timethis
. - Arrow function doesn’t have any
arguments
- Arrow function doesn’t have any
prototype
attribute
Promise
Promise is a new asynchronous programming solution in ES6 that avoids the callback hell problem. The Promise object is realized by changing the state. The asynchronous operation is represented by the synchronous process. As long as the state changes, the corresponding function will be automatically triggered.
Promise objects have three states, which are:
- pending:Default, as long as it is not told
promise
The mission is a success or a failurepending
state - fulfilled:As long as the call
resolve
Delta function, the state is going to be zerofulfilled
“Indicates that the operation succeeds - rejected:As long as the call
rejected
Delta function, the state is going to be zerorejected
“Indicates that the operation fails
Once a state change is irreversible, a function can be used to monitor the change of the Promise state. A callback to the then function is executed on success, and a callback to the catch function is executed on failure
Shallow copy
A shallow copy is a copy of a value. For a copy of an object that is a memory address, the reference to the target object and the reference to the source object point to the same memory space. If one object changes, it affects the other.
Common shallow copy methods:
- Array.prototype.slice
let arr = [{a:1}, {b:2}]
let newArr = arr1.slice()
Copy the code
- Extended operator
let newArr = [...arr1]
Copy the code
Deep copy
A deep copy is a complete copy of an object from the memory. Instead of sharing the memory with the object, a new space is created in the heap memory to store the object. Therefore, modifying the new object does not affect the original object.
Common deep copy methods:
- JSON.parse(JSON.stringify())
JSON.parse(JSON.stringify(obj))
Copy the code
- Handwritten deep copy
function deepClone(obj, map = new WeakMap(a)) {
if (obj === null || typeofobj ! = ="object") return obj;
const type = Object.prototype.toString.call(obj).slice(8, -1)
let strategy = {
Date: (obj) = > new Date(obj),
RegExp: (obj) = > new RegExp(obj),
Array: clone,
Object: clone
}
function clone(obj){
// Prevent circular references, resulting in stack overflow, the same reference object directly returned
if (map.get(obj)) return map.get(obj);
let target = new obj.constructor();
map.set(obj, target);
for (let key in obj) {
if(obj.hasOwnProperty(key)) { target[key] = deepClone(obj[key], map); }}return target;
}
return strategy[type] && strategy[type](obj)
}
Copy the code
Event delegation
Event delegation, also known as event broker, is a means of DOM event optimization. Event delegates manage all events of a certain type by specifying only one event handler using the event bubbling mechanism.
Suppose you have a list where each child element has a click event. Event delegates can be used to optimize scenarios where the memory footprint of event bindings increases linearly as the number of child elements increases. Broker events are usually bound to parent elements rather than having to add events to each child element.
<ul @click="clickHandler">
<li class="item">1</li>
<li class="item">2</li>
<li class="item">3</li>
</ul>
Copy the code
clickHandler(e) {
// Click on the child element
let target = e.target
// Outputs the child element content
consoel.log(target.textContent)
}
Copy the code
Image stabilization
Anti-shake is used to reduce the number of function calls, and for frequent calls, only the last of those calls is executed.
/ * * *@param {function} func- Executes the function *@param {number} wait- Waiting time *@param {boolean} immediate- Whether to execute * immediately@return {function}* /
function debounce(func, wait = 300, immediate = false){
let timer, ctx;
let later = (arg) = > setTimeout(() = >{
func.apply(ctx, arg)
timer = ctx = null
}, wait)
return function(. arg){
if(! timer){ timer = later(arg) ctx =this
if(immediate){
func.apply(ctx, arg)
}
}else{
clearTimeout(timer)
timer = later(arg)
}
}
}
Copy the code
The throttle
Throttling is used to reduce the number of function requests, and unlike stabilization, throttling is performed once in a period of time.
/ * * *@param {function} func- Executes the function *@param {number} delay- Delay time *@return {function}* /
function throttle(func, delay){
let timer = null
return function(. arg){
if(! timer){ timer =setTimeout(() = >{
func.apply(this, arg)
timer = null
}, delay)
}
}
}
Copy the code
Currie,
Currying is a technique that converts a function that takes multiple arguments into a function that takes a single argument, and returns a new function that takes the remaining arguments and returns a result.
General Coriolization functions:
function currying(fn, arr = []) {
let len = fn.length
return (. args) = > {
let concatArgs = [...arr, ...args]
if (concatArgs.length < len) {
return currying(fn, concatArgs)
} else {
return fn.call(this. concatArgs) } } }Copy the code
Use:
let sum = (a,b,c,d) = > {
console.log(a,b,c,d)
}
let newSum = currying(sum)
newSum(1) (2) (3) (4)
Copy the code
Advantages:
- Parameter multiplexing, since the parameters can be passed separately, we can reuse the function after passing the parameters
- Delay execution, just like
bind
You can also receive arguments and return a reference to a function without calling it
The garbage collection
The heap is divided into the new generation and the old generation, respectively by the secondary garbage collector and the main garbage collector to be responsible for garbage collection.
The new generation
Generally, the newly used objects will be placed in the New generation, which is relatively small, only dozens of MB, and the new generation will be divided into two Spaces: form space and to space.
Objects are first allocated to the form space, and in the garbage collection phase, the surviving objects in the form space are copied to the TO space, and the surviving objects are recycled, and then the two Spaces are swapped. This algorithm is called “Scanvage”.
The new generation of memory reclamation is very frequent and fast, but space utilization is low because half of the memory space is left “idle”.
The old generation
The old generation has a large space, and the objects that are still alive after multiple recycling of the new generation will be sent to the old generation.
The old generation used a “tag sweep” approach to mark surviving objects by traversing them from the root element. When the marking is complete, unmarked objects are reclaimed.
The memory space cleared by the mark will create a lot of discontinuous fragmentation space, causing some large objects to be unable to fit in. So after the recycling is done, these discontinuous debris Spaces are sorted out.
JavaScript Design pattern
The singleton pattern
Definition: Ensures that a class has only one instance and provides a global access point to access it.
As a classless language, the traditional singleton pattern concept is not applicable in JavaScript. Thinking a little differently: The singleton pattern ensures that there is only one object and provides global access.
The common application scenario is the popover component, using the singleton mode to encapsulate the global popover component method:
import Vue from 'vue'
import Index from './index.vue'
let alertInstance = null
let alertConstructor = Vue.extend(Index)
let init = (options) = >{
alertInstance = new alertConstructor()
Object.assign(alertInstance, options)
alertInstance.$mount()
document.body.appendChild(alertInstance.$el)
}
let caller = (options) = >{
// singleton judgment
if(! alertInstance){ init(options) }return alertInstance.show(() = >alertInstance = null)}export default {
install(vue){
vue.prototype.$alert = caller
}
}
Copy the code
The component is instantiated only once, no matter how many times it is called, and you end up getting the same instance.
The strategy pattern
Definition: Define a set of algorithms, encapsulate them one by one, and make them interchangeable.
The policy pattern is the most commonly used design pattern in development, and in some scenarios where there are a large number of if/else and each branch point is functionally independent, the policy pattern can be considered for optimization.
For example, the strategy mode is used for deep copy:
function deepClone(obj, map = new WeakMap(a)) {
if (obj === null || typeofobj ! = ="object") return obj;
const type = Object.prototype.toString.call(obj).slice(8, -1)
// Policy object
let strategy = {
Date: (obj) = > new Date(obj),
RegExp: (obj) = > new RegExp(obj),
Array: clone,
Object: clone
}
function clone(obj){
// Prevent circular references, resulting in stack overflow, the same reference object directly returned
if (map.get(obj)) return map.get(obj);
let target = new obj.constructor();
map.set(obj, target);
for (let key in obj) {
if(obj.hasOwnProperty(key)) { target[key] = deepClone(obj[key], map); }}return target;
}
return strategy[type] && strategy[type](obj)
}
Copy the code
This code looks much cleaner, maintaining only one policy object and adding a policy when new functionality is needed. Because policy items are individually encapsulated methods, they are also easier to reuse.
The proxy pattern
Definition: Provide a proxy for an object to control access to it.
When it is inconvenient to access an object directly or when the need is not met, a proxy object is provided to control the access to the object. It is the proxy object that actually accesses the object. The proxy object processes the request and then passes it to the ontology object.
Request data using caching proxy:
function getList(page) {
return this.$api.getList({
page
}).then(res= > {
this.list = res.data
return res
})
}
/ / agent getList
let proxyGetList = (function() {
let cache = {}
return async function(page) {
if (cache[page]) {
return cache[page]
}
let res = await getList.call(this, page)
return cache[page] = res.data
}
})()
Copy the code
The above scenario is a common paging requirement. The same page data needs to be fetched in the background only once, and the retrieved data is cached. The next time the same page is requested, the previous data can be used directly.
Publish and subscribe model
Definition: It defines a one-to-many dependency between objects, and all dependent objects are notified when an object’s state transmission changes.
The main advantage of the publish-subscribe model is to solve the decoupling between objects. It is widely used in asynchronous programming as well as to help us complete loosely coupled code writing. Communication methods like eventBus are publish-subscribe.
let event = {
events: [].on(key, fn){
if(!this.events[key]) {
this.events[key] = []
}
this.events[key].push(fn)
},
emit(key, ... arg){
let fns = this.events[key]
if(! fns || fns.length ==0) {return false
}
fns.forEach(fn= > fn.apply(this, arg))
}
}
Copy the code
The above is a simple implementation of the publish-subscribe pattern, to which you can also add the off method to cancel listening events. In a Vue, it is common to instantiate a new Vue instance to act as a publish and subscribe hub for component communication. In the small program, you can manually implement the publish and subscribe mode to solve the problem of page communication.
Decorator mode
Definition: To dynamically add additional responsibilities to an object without affecting the object itself.
The decorator pattern is also a popular design pattern in development, as it makes it easy to extend properties and methods without affecting the source code. For example, the following application scenario is submitting a form.
methods: {
submit(){
this.$api.submit({
data: this.form
})
},
// Add validation to submit forms
validateForm(){
if(this.form.name == ' ') {return
}
this.submit()
}
}
Copy the code
Imagine if you have just taken on a project and the logic of submit is complicated and can involve many things. It’s risky to intrude into the source code to extend functionality, where the decorator pattern comes in handy.
modular
Only two commonly used modules are recorded here: CommonJS module and ES6 module.
CommonJS module
Node.js adopts the CommonJS module specification, which is loaded synchronously when it is run on the server and can be run only after compilation when it is used on the client.
The characteristics of
- Modules can be loaded multiple times. However, the first time the module is loaded, the result is cached, and the module is loaded again to fetch the cached result directly
- The order in which modules are loaded, in the order they appear in the code
grammar
- Exposure module:
module.exports = value
或exports.xxx = value
- Introduction module:
require('xxx')
If the module is a third-party module, XXX is the module name. For a custom module, XXX is the module file path - Clear module cache:
delete require.cache[moduleName];
, the cache is saved inrequire.cache
, you can delete the property
Module loading mechanism
- Loading a module is actually loading the module
module.exports
attribute exports
Is pointing tomodule.exports
A reference to themodule.exports
Is an empty object,exports
Is also an empty object,module.exports
When the object is not emptyexports
The object is ignored- A module loads a copy of a value, and once the value is printed, changes within the module do not affect the value, except for reference types
Module. exports is not empty:
// nums.js
exports.a = 1
module.exports = {
b: 2
}
exports.c = 3
Copy the code
let nums = require('./nums.js') // { b: 2 }
Copy the code
The module. Exports is empty:
// nums.js
exports.a = 1
exports.c = 3
Copy the code
let nums = require('./nums.js') // { a: 1, c: 3 }
Copy the code
Value copy embodiment:
// nums.js
let obj = {
count: 10
}
let count = 20
function addCount() {
count++
}
function getCount() {
return count
}
function addObjCount() {
obj.count++
}
module.exports = { count, obj, addCount, getCount, addObjCount }
Copy the code
let { count, obj, addCount, getCount, addObjCount } = require('./nums.js')
// Primitive types are not affected
console.log(count) / / 20
addCount()
console.log(count) / / 20
// If you want to get the changed value, you can use the function return
console.log(getCount()) / / 21
// The reference type will be changed
console.log(obj) // { count: 10 }
addObjCount()
console.log(obj) // { count: 11 }
Copy the code
ES6 module
The ES6 module is designed to be as static as possible, so that the module dependencies, as well as the input and output variables, can be determined at compile time.
The characteristics of
- For static analysis reasons, ES6 module loading can only be used at the top level of code
- Modules cannot load the same variable more than once
grammar
- Exposure module:
export
或export default
- Introduction module:
import
Module loading mechanism
- Modules load copies of references, and changes within modules affect values
// nums.js
export let count = 20
export function addCount() {
count++
}
export default {
other: 30
}
Copy the code
// Import both export default and export variables
import other, { count, addCount } from './async.js'
console.log(other) // { other: 30 }
console.log(count) / / 20
addCount()
console.log(count) / / 21
Copy the code
Differences between ES6 modules and CommonJS modules
- The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value.
- The CommonJS module is run time loaded, and the ES6 module is compile time output interface.
The resources
- JavaScript Advanced Programming (Version 3)
- JavaScript You Don’t Know (Volume 1)
- JavaScript Design Patterns and Development Practices
- ES6 Tutorial
- Front-end Modular Details (Full version)
Recommended reading
- Gold nine silver ten, preliminary intermediate front-end interview review summary “Vue chapter”
- Summary of “Browser, HTTP, Front-end security”