Write promises that conform to the PromiseA+ specification
const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected';
// Fulfill our Promise
class MyPromise {
constructor(excutor) {
this.state = PENDING
this.value = undefined
this.reason = undefined
this.resolveList = []
this.rejectList = []
// resolve changes the Promise state to success and saves the success value
const resolve = value= > {
if (this.state === PENDING) {
this.state = FULFILLED
this.value = value
this.resolveList.forEach(fn= > fn())
}
}
// reject changes the Promise state to a failure, saving the failure value
const reject = reason= > {
if (this.state === PENDING) {
this.state = REJECTED
this.reason = reason
this.rejectList.forEach(fn= > fn())
}
}
try {
excutor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(resolveFn, rejectFn) {
resolveFn = typeof resolveFn === 'function' ? resolveFn : value= > value
rejectFn = typeof rejectFn === 'function' ? rejectFn : err= > { throw err }
let bridgePromise
return bridgePromise = new MyPromise((resolve, reject) = > {
if (this.state === FULFILLED) {
setTimeout(() = > {
try {
const x = resolveFn(this.value)
resolvePromise(bridgePromise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
}
if (this.state === REJECTED) {
setTimeout(() = > {
try {
const x = rejectFn(this.reason)
resolvePromise(bridgePromise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
}
if (this.state === PENDING) {
this.resolveList.push(_= > {
setTimeout(() = > {
try {
const x = resolveFn(this.value)
resolvePromise(bridgePromise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
this.rejectList.push(_= > {
setTimeout(() = > {
try {
const x = rejectFn(this.reason)
resolvePromise(bridgePromise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0); })})}}If x is a Promise/thenable object needs to recursively get the final value and state, give bridePromise
function resolvePromise(bridgePromise, x, resolve, reject) {
if (bridgePromise === x) return reject(new TypeError('circle error')); // Prevent x and bridgePromise from referencing each other
if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) {
let called = false
try {
const then = x.then
if (typeof then === 'function') {
then.call(x,
y= > {
if (called) return
called = true
resolvePromise(bridgePromise, y, resolve, reject)
},
err= > {
if (called) return
called = true
reject(err)
}
)
} else {
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
resolve(x)
}
}
Promise-aplus-tests demo10.js // Promise-aplus-tests demo10.js // Promise-aplus-tests demo10.js // Promise-aplus-tests demo10.js // Promise-aplus-tests demo10.js // Promise-aplus-tests demo10.js // Promise-aplus-tests demo10.js // Promise-aplus-tests demo10.js Demo10. Js)
MyPromise.deferred = function () {
let defer = {};
defer.promise = new MyPromise((resolve, reject) = > {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
try {
module.exports = MyPromise
} catch (e) { }
Copy the code
JS type conversion
-
Explicit type conversion:
Number() String() Boolean() undefined NaN Put it in quotes 'undefined'
false
null 0 Put it in quotes 'null'
false
number The same +0 and -0 are converted '0'
, and the others are in quotation marksBoth +0 and -0 are converted to NaN false
, the other fortrue
string 1, the string ” is converted 0
If the middle string consists entirely of digits, the string is returned regardless of whether there are Spaces at the beginning or the end.'123' = > 123; '123' = > 123
), all others returnNaN
(1 1=>NaN
;1i1=>NaN
)The same ‘ ‘to false
, everything elsetrue
boolean True to 1
, false to0
Put it in quotes 'false','true'
The same Object (Reference type) Convert to TopramParticiple (object,’number’) return value Convert to TopramParticiple (Object,’string’) return value For all true
- ToPramitive:As the name suggests, this is a function that converts a reference type to the original value. The first argument is the data to be converted, and the second argument is bias, i.e. bias towards ‘number’ or ‘string’. Below is a pseudo-code implementation of this function
function toPrimitive(object, direction) { // 1, when bias is 'number' if (direction === 'number') { Object.valueof () is called first. If the value is a primitive type, it is returned directly if (object.valueOf() === 'Basic type') return object.valueOf() If object.valueof () returns a non-primitive type, call Object.tostring () if (object.toString() === 'Basic type') return object.toString() // 1.3, Object.tostring () and Object.valueof () return values are not primitive types, new TypeError('Cannot convert object to primitive value') is raised throw new TypeError('Cannot convert object to primitive value')}// 2, when bias is 'string' if (direction === 'string') { Object.tostring () is called first. If the value is a primitive type, it is returned directly if (object.toString() === 'Basic type') return object.toString() If object.tostring () returns a non-primitive type, call Object.valueof (), which returns a primitive type if (object.valueOf() === 'Basic type') return object.valueOf() // 1.3, Object.tostring () and Object.valueof () return values are not primitive types, new TypeError('Cannot convert object to primitive value') is raised throw new TypeError('Cannot convert object to primitive value')}// 3, object. ToString and object. ValueOf are generally returned without overwriting / / Object. ToString: return to the original Object of type string, is equal to the return Object. The prototype. ToString. Call (Object) // object. valueOf: Returns the original Object // 4, note that array toString returns array element strings, e.g // [].toString=>'' / / [1, 2, 3]. The toString () = > '1, 2, 3' } Copy the code
- ToPramitive:As the name suggests, this is a function that converts a reference type to the original value. The first argument is the data to be converted, and the second argument is bias, i.e. bias towards ‘number’ or ‘string’. Below is a pseudo-code implementation of this function
-
Implicit type conversion: Implicit conversions occur only when a conflict occurs between two types of data and between data and operators. When a conflict occurs, the data may be converted by calling Number(), String(), Boolean(), and toPrimitive() in the display cast. So we need to know when the data calls which display conversion, and the bias determination below helps us do that.
-
Bias: When an implicit type conversion occurs, we have a bias determination, which is to determine what data type the current expression is expected to evaluate to, such as’ I’m ${18} years old. ‘, where we clearly expect the output to be a string, the bias is string, so excluding the reference type, The other primitive types use string to get the return value, whereas reference types expect a string value, so we call toPrimitive and pass string as the second argument. That is, we call toString first to get the original value.
-
Bias determination under different scenarios: Implicit type conversion is personally referred to as bias determination. Bias to the data type will be implicitly converted to the current data type. The following table shows the bias under different scenarios.
Implicit type conversion scenarios occur bias To deepen the memory Template string ‘ ‘ string For template strings we obviously prefer to get strings An operation number For bitwise operations, we would definitely prefer to be numeric Logical operation! boolean The logical operation is obviously to determine whether or not, so we would rather have a Boolean type Four operations (excluding +) number The four operations are of course expected to be numeric = = = and! = = Value reference address Exact is equal to and exact is not equal to, for more accurate judgment we of course according to the reference address to determine whether equal or unequal + Favors string whenever a character type is present on either side of +, favors number otherwise If there are character types on both sides of the +, then we expect a concatenated string, so the bias of the + operation is a string, which makes sense. Otherwise, the bias is number, because we expect a numeric type except for concatenated strings. Comparison operations (excluding == and! = =) If both sides of the comparison are character types, it favors string, otherwise it favors number When both sides of the comparison operator are character types, we can easily compare the size to the ASCII encoding, so string is sufficient to compare the size, except for the other cases where both sides are character types, we would certainly prefer to convert them to numeric types to compare the size more conveniently = = and! = =) (1) : (null | undefined) = = (null | undefined); (2) : (null | undefined)! == any other type); If both sides of the current symbol are of the same type, then the bias bit references the address; The bias is number if both sides of the symbol are not of the same type or null/undefined The bias bit values refer to addresses. The bias bit values of different types are not undefined/null. The bias bit values of different types refer to addresses -
Note that if there is a reference type in the expression, we need to calculate the reference type according to the bias to get the basic type, and then continue to calculate the final bias according to the current two basic types, for example, 1+{}, our bias is number, 1+{}=>1+toPrimitive({},’number’)=>1+'[object object]’ So 1+'[object object]’ => ‘1’ +'[object object]’ = ‘1[object object]’
-
With tables of bias and bias determination, we can determine how to implicitly transform the current expression based on the table bias. Take a look at the following example and parse it.
expression bias Implicit conversion process according to bias The results of `I’m ${ {toString(){ return 12 }} } years old.` string toPrimitive({toString(){ return 12 }},’string’ ) I'm 12 yars old.
! {} boolean ! Boolean({})=>! true=>false false ’12’-‘a’ number 12-NaN=>NaN NaN null === {} Value reference address 0000 = = = 0011 false ‘1’ + {} string ‘1’+{}.toString()=>’1’+'[object Object]’=>’1[object Object]’ ‘1[object Object]’ ‘1’ + [1, 2] string ‘1’ + [1, 2]. The toString () = > ‘1’ + ‘1, 2′ = > ’11, 2’ ’11’ false + 0 number 0 + 0 0 false + true number 0 + 1 1 false + [] number string false + toPrimitive([],’number’)=>false+”=>’false’+”=>’false’ ‘false’ ‘123’ = = ‘123’ string ‘123’ = = ‘123’ true = = ‘123’, 123 number 123 = = 123 true ‘123’ = = {} number ‘123’== toPrimitive({},’number’)=>’123’== ‘[object Object]’ false false == true number 0 ==1 => false false null == undefined equal equal true null == 0 Not equal to the Not equal to the false undefined == [] Not equal to the Not equal to the false NaN == 0 number NaN returns false when compared to any numeric type, including itself false ‘a’ > ‘c’ String, compare both ASCII false false ‘a’ > 1 number NaN > 1 false ’12’ > ‘1’ number 12 > 1 true
-
3. Hash mode and History mode in the front-end route
-
Single page Application (SPA) :A traditional page usually has a URL corresponding to a server page resource. When the URL changes, the browser requests the server for the resource page of the current URL. Therefore, traditional pages are generally multi-page applications.The single-page application has only one page from beginning to end. When the URL changes, it does not request new page resources to the server, but uses JS to display the corresponding content according to the current URL.The following two figures give a good idea of the difference between traditional pages and SPA.
-
Front-end routing: Front-end routing is divided into hash routing and history routing. Front-end routing mainly solves the problems faced by single-page applications. That is, when the URL changes, it does not send new resource requests to the server and only uses JS to display the corresponding content according to the current URL.
-
In fact, we only need to solve the following two problems to achieve front-end routing:
-
1, when the URL changes, the browser does not make a new page resource request to the server.
-
2. It can monitor the changes of each URL and display the content to be displayed for the corresponding URL after the url changes. So let’s count several scenarios that can cause URL changes as follows:
-
1. Manually type the URL
-
2. Click the browser’s forward and back buttons
-
3. Change the URL with JS
-
4. The href attribute of the A tag changes the URL
-
-
-
Hash mode implementation principle: The hash mode is www.abc.com#page1/page2 and the string #page1/page2. And the hash change in the URL does not cause the browser to reload a new page resource. Second, the browser provides hashchange, a hashchange event that can listen for all four URL change scenarios. So these two features of Hash are ideal for implementing front-end routing. Here are a few simple hash-related methods that will be used to implement hash routing.
-
Get hash: var hash = window.location.hash To get the hash of the current page
-
Hash: window.location.hash = ‘# XXX ‘to set the hash of the page
-
Listen for hash changes: window.addeventListener (‘hashChange’,callBack) listens for hash changes
-
-
Simple implementation of the Hash pattern
<! DOCTYPEhtml> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="Width = device - width, initial - scale = 1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id='root'> </div> <script> class HashRouter { constructor() { // 1, save all hashes and what js needs to do for the current hash this.hashCallback = {} // 2, listen for hash changes, perform the corresponding hash callback, change the page content window.addEventListener('hashchange'.this.hashCallbackRun.bind(this))}// 3, listen for hash changes, perform the corresponding hash callback, change the page content hashCallbackRun() { // 3.1, get the hash value of the current URL with # removed const hash = window.location.hash.slice(1) If this.hashCallback has the hash, the corresponding callback will be filled with the corresponding content, otherwise the home page will be filled by default if (this.hashCallback[hash]) { this.hashCallback[hash]() } else { return this.hashCallback['index'] ()}}// 4, register hash route, callback: the function that needs to change the page content when the current hash is performed regist(hash, callback) { this.hashCallback[hash] = callback } } // 5, the home page, contains an A tag to link to page1 and a button to jump to Page2 const Index = () = > { document.getElementById('root').innerHTML = ' ' let a = document.createElement("a") a.href = "#page1" a.innerHTML = "link to page1" let button = document.createElement("button") button.onclick = () = > window.location.hash = '#page2' button.innerHTML = "jump to page2" document.getElementById('root').appendChild(a) document.getElementById('root').appendChild(button) } // 6, page page1 const Page1 = () = > { document.getElementById('root').innerHTML = 'page1' } // 7, page2 const Page2 = () = > { document.getElementById('root').innerHTML = 'page2' } // create a hashRouter const router = new HashRouter() // 9, register hash and its corresponding page content function router.regist('index', Index) router.regist('page1', Page1) router.regist('page2', Page2) // 10, the page is initialized to index router.hashCallbackRun() </script> </body> </html> Copy the code
-
The history mode: The H5 browser history object provides history.pushState (which preserves the session history while adding the new URL to the history) and history.replaceState (which replaces the current page history with the specified URL), which can change the URL without refreshing the page. Therefore, we can change the URL by using history.pushState during the page jump, and then execute the corresponding function to update the content of the SPA page.
-
In history mode, you can perform the following operations to redirect a page:
-
Click window.addEventListener(‘ popState ‘,callback) and then perform the corresponding function to update the content of the SPA page.
-
3. If it is a JS behavior jump, then manually update the history, and then execute the corresponding update SPA page content function.
-
4. For manually changing the browser navigation bar path to enter the page, the history mode will obtain new resources, so in this case, you need to intercept on the server side, and return to the current SPA page in other paths
-
Ps: history mode needs to be deployed on the server using the relative path shown in the code below. Direct access to local files requires the absolute path, so the following code is recommended to be accessed in the server static resources to ensure that it runs smoothly
<! DOCTYPEhtml> <body> <div id="nav"> <a href="/page1">page1</a> <a href="/page2">page2</a> <button id="btn3">page3</button> <button id="btn4">page4</button> </div> <div id="container"></div> <script> class HistoryRouter { // 1, history manages the front-end route constructor constructor() { // 1.1, the history path corresponds to the dictionary of js changes SPA page content functions this.historyRouterList = {} // 1.2, use the popState event to listen for history changes caused by the browser's forward and back buttons, and execute the corresponding callback function to change the content of the SPA page window.addEventListener('popstate'.e= > { // Get the changed pathname when the history changes, and then execute the corresponding callback to change the SPA page content const state = e.state || {}, pathname = state.path || ' ' this.historyCallbackExcute(pathname) }) // 1.3, popState event can't listen for the click of a tag to cause the change of history, so we hijack all a tags, listen for the click of A tag, and then execute the corresponding callback function to change the content of SPA page window.addEventListener('click'.e= > { // 1.3.1, get a label and ref on a label const dom = e.target, href = dom.getAttribute('href') if (dom.tagName.toUpperCase() === 'A' && href) { // 1.3.2, prevent a tag from default behavior e.preventDefault() // 1.3.3, replace with the history.pushState operation history.pushState({ path: href }, null, href) // 1.3.4, perform the corresponding callback to change the SPA page content historyRouter.historyCallbackExcute(href) } }) } // 1.2, which provides a path to inject into historyRouterList and its corresponding callback function to change the SPA page content historyRouterRegist(pathname, callback) { this.historyRouterList[pathname] = callback } // 1.3, when the history changes, execute the corresponding callback to change the content of the SPA page historyCallbackExcute(pathname) { if (pathname) { this.historyRouterList[pathname]() } else { // pathName ===" this.historyRouterList['/'] ()}}}// 2, register the path in history with HistoryRouter corresponding to the change page content callback const historyRouter = new HistoryRouter() historyRouter.historyRouterRegist('/page1'._= > container.innerHTML = 'This is page1.') historyRouter.historyRouterRegist('/page2'._= > container.innerHTML = 'this is page2') historyRouter.historyRouterRegist('/page3'._= > container.innerHTML = 'this is page3') historyRouter.historyRouterRegist('/page4'._= > container.innerHTML = 'this is page4') historyRouter.historyRouterRegist('/'._= > container.innerHTML = 'This is the home page') // 3, click the button to change the page history btn3.onclick = function () { // 3.1 Since popState does not listen for click events, we manually change history history.pushState({ path: '/page3' }, null.'/page3') // 3.2 and execute the corresponding callback to change the content of the SPA page historyRouter.historyCallbackExcute('/page3') } btn4.onclick = function () { history.pushState({ path: '/page4' }, null.'/page4') historyRouter.historyCallbackExcute('/page4')}// 5, obtain the path before entering the page and confirm which page to enter history.pushState({ path: location.pathname }, null, location.pathname) historyRouter.historyCallbackExcute(location.pathname) // 6, server interception processing const express = require('express') // const app = express() // app.use(express.static('public')) // // intercepts other page resource requests and returns to the SPA page // app.use((req, res, next) => { // res.sendFile(__dirname + '/public/index.html') // }) // app.listen(3000, _ => console.log('demo1 is running! ')) </script> </body> </html> Copy the code
-
-
Summary of hash mode and History mode
- 1. Hash mode: In hash mode, window.loaction.hash is used to change the hash value to ensure that the URL does not change and the page does not change.
- 2. History mode: History is made by the mode of the H5 provide history. The pushState/history replaceState change the url to ensure the url change page does not change, then listen to the url change, execution of the current url corresponding change the callback function to achieve SPA page content in the SPA front routing.
-
Advantages and disadvantages of both:
- Advantages of hash mode (disadvantages of history mode) : Hash mode is compatible and does not require the server to process urls that are not on the current SPA page
- Disadvantages of Hash mode (History mode advantage) : In hash mode, there is # in the URL, which is strange, and hash mode will invalidate some anchor functions (because anchors probably do not have any SPA page content update function), and in hash mode, the same hash value will not be pushed into the URL history stack, while history mode can, and hash mode is not good for SEO. Since spiders don’t think hash is a new URL, they won’t include it, so we usually use historyRouter for launch.
4. Data attributes and accessor attributes of attributes
-
Data attributes and accessor attributes: There are some attributes on the object attributes that describe the characteristics of the current object attributes. These attributes are divided into two types: data attributes and accessor attributes. The two do not exist at the same time.
-
Data attributes: Data attributes are classified into the following four types
true false [[Configurable]] â‘ You can use delete to delete the current object property, â‘¡ you can modify the four data properties of the current object property, and â‘¢ you can change the current object property to accessor property The Writable can only be changed from true to false, but cannot be changed from false to true. The property of the current object cannot be modified into an accessor property, and the delete mode and Enumerable controller are different [[Enumerable]] You can use a for in traversal to get the current object properties You cannot use a for in traversal to get current object properties [[Writable]] You can modify the current object property value You cannot modify the current object property value [[Value]] Value is the current object attribute value, which can be any value, unlike the preceding three attributes that are limited to true and false -
Accessor properties: Accessor properties fall into the following four categories
true false [[Configurable]] â‘ You can use delete to delete the current object property, â‘¡ you can modify the four accessor properties of the current object property, and â‘¢ you can change the current object property to data property â‘ You cannot use delete to delete the current object attributes, â‘¡ you cannot modify the four accessor attributes of the current object attributes, and â‘¢ you cannot change the current object attributes to data attributes [[Enumerable]] You can use a for in traversal to get the current object properties You cannot use a for in traversal to get current object properties [[Get]] Value is a function that returns the current object property value by default [[Set]] Value is a function that, by default, sets the received value to the current object property value -
Relationship between data attributes and accessor attributes:
- Both are a way of describing the properties of the current object, but only one can be used,
- Enumerable is a different information system, different from any other information system
- The data attribute and the accessor attribute can be exchanged only if the 64X and 64X are configured with true
- Data properties to accessor properties: Use Object.defineProperty to add any of the get/set methods in the accessor property
- Accessor properties to data properties: Use Object.defineProperty to add either of the writable/ Value properties of the data properties
- Note: When setting feature properties, data properties (writable/value) and accessor properties (GET /set) cannot be set at the same time. Otherwise, an error will be reported.
-
Methods for obtaining object characteristic attributes (data attributes and accessor attributes) :
- 1, Object getOwnPropertyDescriptor (obj, ‘a’) : obtaining the Object obj on a characteristic of the attribute
- 2, and the Object. GetOwnPropertyDescriptors (obj) : get all the characteristics of the attribute Object obj
-
How to set characteristic properties (data properties and accessor properties) :
- Non-display setting feature properties: use
{a:1}
orconst obj = {}; obj.a = 1
The default attribute is data attribute. The three Boolean value attribute is true. Value is the value of the set object attribute. - Display Settings feature attributes: use Object. DefineProperty/Object defineProperties respectively used to set up one or more attributes of the Object, attribute.
-
Object.defineproperty: The first argument is the set Object, the second argument is the set Object property, and the third argument is the specific attribute property
const obj1 = {} Object.defineProperty(obj1, 'a', { configurable: true.enumerable: false.writable: true.value: 1.// get(){}, // set(){}, }) Copy the code
-
Object.defineproperties: The first parameter is the set Object and the second parameter is the multiple properties of the current Object and their corresponding specific attribute properties to be set
const obj1 = {} Object.defineProperties(obj1, { 'a': { value: 1 }, 'b': { configurable: true.enumerable: false.writable: true.value: 2 }, / /... other property }) Copy the code
-
Note: if the property is the first time to use the Object. The defineProperty/ies method display Settings feature attribute (that is, not be displayed or the display Settings before attributes), if there is not in the six characteristics of the property set of properties, these properties will take the default value, then, is as follows:
Configurable Enumerable Writable Value Get Set The default value false false false undefined undefined undefined
-
- Non-display setting feature properties: use
-
Two-way data binding using accessor properties:
<! DOCTYPEhtml> <body> <input id='input' /> <script> const state = {} // Hijack accessor properties for bidirectional binding, modify state. Value to trigger input.value changes Object.defineProperty(state, 'value', { configurable: true.get() { return input.value }, set(value) { input.value = value } }) input.oninput = e= >state.value = e.target.value </script> </body> </html> Copy the code
5. Implement require in modular CommonJS
- Implement the idea: require the essence of the operation is: read the js file content wrapped in the function, inject the module object prepared, execute the function, the function will internal exported data into the module, so that we can obtain the JS file exported data from the module.
- Specific implementation steps:
const { resolve, extname } = require('path') const { accessSync, readFileSync } = require('fs') const { runInThisContext } = require('vm') // Module is used to create each Module object store data class Module { constructor() { this.exports = {} } } Module.extnames = ['.js'.'.json'] // require_ only works with JS /json files Module.cache = {} // cache location for exporting data after reuqire_ / / implementation require_ function require_(path) { // 1, the file is processed into an absolute path path = resolve(path) // 2, only js/json files, non-JS /json files throw error (path.extname: get file path extension) if(! Module.extnames.includes(extname(path)))throw new Error('Make sure the incoming is a JS/JSON file') // 3, check whether the current file is accessible. (fs.accesssync: Used to verify that the current file is accessible (exists). If not, an error will be thrown.) try { accessSync(path) } catch (error) { throw new Error('The file could not be found')}If there is a cache result corresponding to the current file path, the cache result is directly returned if (Module.cache[path]) return Module.cache[path].exports let module = new Module() // 5, if it is a JSON file, cache and return the file contents if (extname(path) === '.json') { ReadFileSync: read the contents of the file synchronously. the default value is Buffer. The second parameter is to convert the bytecode to the corresponding encoding format, go to UTF-8. const content = readFileSync(path, 'utf8') // 5.2, save exported JSON data module.exports = content // 5.3, cache exported JSON data Module.cache[path] = module // 5.4, return JSON data export result return module.exports } // 6, if it is a JS file, wrap the file with a function, bind its scope to a global scope (to prevent contamination of the local scope), inject module and require_ into the function, execute the function, export the function to module.exports, cache the result, and return the result else { If it is a js file, use (function () {}) to wrap the contents of the js file. () turns the script into an expression const script = `(function(module,require_){${readFileSync(path)}}) ` // 6.2, execute string js scripts in global scope to prevent contamination of current local scope. Vm.runinthiscontext: similar to eval, executes string code, but does not access the local scope, only the global scope const fn = runInThisContext(script) // 6.3, inject module to fn with require_ parameter. Fn's result, if exported (module.exports= XXX), will be saved in module fn.call(this.module, require_) // 6.4, cache and return fn export result (that is, current path export result) return Module.cache[path] = module.module.exports } } Copy the code
6, offset – | client – | scroll – | event – | getBoundingClientRect () – | coordinates properties window – explanation
-
Offset family: offsetParent | offsetWidth | offsetHeight | offsetTop | offsetLeft
-
offsetParent
-
OffsetParent role: offsetTop | offsetLeft are calculated current element relative to the offsetParent elements
-
The offsetParent determine: Htmlelement. offsetParent is a read-only property. If the parent has a location element, the offsetParent of the current element refers to the location parent element. This element refers to the nearest table/ TD /th/body parent element (table/ TD /th is rare).
-
The offsetParent note:
- 1. In WebKit, if the current element or its parent element
display:none
Or the current elementposition:fixed
, the offsetParent of the current element is null. - 2, if and only if the current element in IE9
position:fixed
, the offsetParent of the current element is null.
- 1. In WebKit, if the current element or its parent element
-
-
OffsetWidth: offsetWidth is a read-only property that returns the width of the layout element. It is calculated as: offsetWidth = the distance from the outside of the left border to the outside of the right border + the vertical scroll bar width (if present)
-
OffsetHeight: the same as offsetWidth, which is calculated as follows: offsetHeight = the distance from the outer side of the upper border to the outer side of the lower border + the horizontal scroll bar width (if any)
-
OffsetLeft: offsetLeft is a read-only property that returns the distance from the outside left border of the current element to the inside left border of offsetParent.
-
OffsetTop: offsetHeight is a read-only property that returns the distance from outside the border of the current element to inside the border of offsetParent.
-
-
Client: family clientWidth | clientHeight | clientLeft | clientTop
-
ClientWidth: The clientWidth of the inline element is 0, and the clientWidth of the non-inline element is the width from the inner left border to the inner right border of the previous element
-
ClientHeight: The clientHeight of the inline element is 0. The clientHeight of the non-inline element is the width from the inside of the upper border to the inside of the lower border of the current element
-
ClientTop: border-top-width of the element
-
ClientLeft: border-left-width of the element
-
-
Scroll family: scrollWidth | scrollHeight | scrollLeft | scrollTop
-
ScrollWidth: the same as clientWidth if the current element content does not overflow, or if overflow occurs, the current clientWidth+ overflow part width, as shown below
-
ScrollHeight: same as scrollWidth, the same as clientHeight if the current element content does not overflow, or the height of the current clientHeight+ overflow portion if overflow occurs.
-
ScrollLeft: in horizontal scrolling, the width of the scrolling content is scrollWidth. ScrollLeft can be regarded as the width of the hidden part caused by the left scrolling of the scrollWidth area, as shown in the following figure
-
ScrollTop: the same as scrollLeft. In vertical scrolling, the scrolling content height is scrollHeight, and scrollTop can be regarded as the width of the hidden part caused by upward scrolling of the scrollHeight area.
-
-
Event Specifies the coordinates on the event
- E. ffsetX, e. ffty: the coordinates of the event triggering position, which is the origin inside the upper left border of the element where the event is triggered
- E. clientx, e. Clienty: The origin is outside the upper left border of the body, and the coordinates of the event trigger position
- E. creenX, E. creenY: coordinates of the event trigger position, starting from the upper left corner of the electronic screen
- E. pagex, e. pagey: When the body scroll bar does not appear, the origin is outside the top left border of the body (the same as clientXY), and the coordinates of the position triggered by the event. However, if the body overflow occurs, the origin will be outside the top left border of the complete body.
-
Element. GetBoundingClientRect () family
- Element. GetBoundingClientRect (). Width: the current element left lateral border to the right border of the lateral distance
- Element. GetBoundingClientRect (). Height: the current element under lateral border to border on the lateral distance
- Element. GetBoundingClientRect (). The top: the current element outside the border on the top side mouth distance
- Element. GetBoundingClientRect (). Left: the current element left off the border side the most on the left side of the mouth
- Element. GetBoundingClientRect (). Right: the current element right off the border side the most on the left side of the mouth
- Element. GetBoundingClientRect (). Bottom: under the current element border side outside the top mouth distance
-
The family of the window
- Window. innerWidth: Width of the browser viewport
- Window. innerHeight: height of the browser viewport
- OuterWidth: indicates the width of the browser
- OuterHeight: Indicates the height of the browser
- Window.screen. width: electronic screen width
- Window.screen. height: the height of the electronic screen
7. Implement a base version of Axios
-
This version of AXIos contains the entire implementation process of the official AXIOS. It implements the interceptor, cancels the request, and browsers send get and POST requests. The HTML contains a javascript script, the first script is responsible for implementing AXIos (full code comments), the second script is responsible for testing AXIos. Json-server (Github) is recommended, the name should be called, 30s to set up a server (readme says how to use json-server), direct use.
<! DOCTYPEhtml> <html lang="en"> <head> </head> <body> <button class="btn" id='addPost'>post</button> <! -- This script implements the basic axios, including interceptor, cancel request, axios(), axios.request(), axios.get(), and axios.post(). <script> // Interceptor: Interceptor constructor, this.handles holds Interceptor success and failure callback function Interceptor() { this.handles = [] } / / Interceptor. Prototype. Use: put into this Interceptor success failure callback. The handles Interceptor.prototype.use = function (successCallback, errorCallback) { this.handles.push({ successCallback, errorCallback }) } // Axios: the Axios constructor // this. Defaults saves the configuration of the default AXIos network request (such as the default request mode get). / / this. Interceptors to save the request and response objects interceptor (add interceptor operations: axios. Interceptors. Request. Use (success, errCallback)) function Axios(defaultConfig) { this.defaults = defaultConfig this.interceptors = { request: new Interceptor, response: new Interceptor } } // axios.prototype. request: axios.request // The different ways axios initiates requests (axios(),axios.request(),axios.get()...)) In fact, it all ends up with a call to axios.prototype. request Axios.prototype.request = function (config) { // Merges the network request configuration object passed in by the consumer with the default configuration config = Object.assign(this.defaults, config) // Create a Promise object with value as the merged config let promise = Promise.resolve(config) Promise. then(dispatchRequest, undefined) let chain = [dispatchRequest, undefined] // Add the request response interceptor to the chain this.interceptors.request.handles.forEach(({ successCallback, errorCallback }) = > { chain.unshift(successCallback, errorCallback) }) this.interceptors.response.handles.forEach(({ successCallback, errorCallback }) = > { chain.push(successCallback, errorCallback) }) // promise.then(chain.shift(), chain.shift()) executes the request interceptor, sends the request, and responds to the interceptor while (chain.length) { promise = promise.then(chain.shift(), chain.shift()) } // Returns the final server data returned by the request interceptor, the request is sent, and the response interceptor obtains the final server data return promise } // axios.prototype. get: axios.get request implementation Axios.prototype.get = function (config) { return this.request(Object.assign(config, { method: 'get'}}))// axios.prototype. post: axios.post request implementation Axios.prototype.post = function (config) { return this.request(Object.assign(config, { method: 'post'}}))/ / Axios. Prototype. CancelToken: Axios request implementation // CancelToken basic usage: As a constructor, we pass in a function that takes an argument as a function and cancels the current network request by executing this function, new CancelToken(c=>window.cancel = c), window.cancel() cancels the current network request // New CancelToken creates a promise. The change in the promise state is passed in to the outside world. The outside world executes the function (window.cancel()) to change the state of the promise inside the constructor Promise.then (()=>{xhr.abort()})). Once the promise state is changed, promise.then is executed and the network request is cancelled. Promise. then is not executed and the network request is not cancelled // This allows the user to control when the network request is cancelled Axios.prototype.CancelToken = function (excutor) { let promiseResolve this.promise = new Promise(resolve= > promiseResolve = resolve) excutor(() = > promiseResolve()) } // dispatchRequest: Axios can send requests to the browser environment and node environment, because the browser uses XHR, node uses HTTP, so there is a distinction between dispatchRequest and network request // The node request is not implemented yet function dispatchRequest(config) { // Call xhrAdapter directly to send the network request return xhrAdapter(config).then( response= > response, err= > { throw (err) } ) } // xhrAdapter: Native browser network requests are wrapped in the form of promises function xhrAdapter(config) { return new Promise((resolve, reject) = > { const xhr = window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject // xhr. onreadyStatechange: listens for network request status, with actual resolve/reject applicable to the current Promise xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { // Process the result of the successful response into the data format returned by AXIos resolve({ config: config, data: JSON.parse(xhr.response), status: xhr.status, statusText: xhr.statusText, request: xhr, headers: xhr.getAllResponseHeaders() }) } else { reject(new Error('Request failed${xhr.status}`))}}}// Prepare an XHR request and wait to be sent xhr.open(config.method, config.url) // Set up the request header before the request is sent Object.entries(config.headers).forEach(([key, value]) = > { xhr.setRequestHeader(key, value) }) // The request is sent xhr.send(JSON.stringify(config.data)) // Implement axios cancel request config.cancelToken && config.cancelToken.promise.then(() = > xhr.abort()) }) } // createAxios: function to create an instance of axios, since axios needs to implement axios(), axios.request(), axios.get(), etc // Only the Axios instance returned by new Axios() does not support making requests in Axios(), so the purpose of this function is to create an Axios instance that can use multiple requests function createAxios(defaultConfig) { // Create an Axios instance let axiosInstance = new Axios(defaultConfig) // Create axios where the axios() request is implemented, but axios.request()/axios.get() is not implemented. let axios = Axios.prototype.request.bind(axiosInstance) // Implement axios.request()/axios.get() by attaching request and get directly to axios function objects. Object.keys(Axios.prototype).forEach(key= > axios[key] = Axios.prototype[key].bind(axiosInstance)) // Implement axios.defaults to get the default configuration object, and axios.interceptors to get the interceptor object. Similarly, copy the data from axios instance axiosInstance to axios function object Object.keys(axiosInstance).forEach(key= > axios[key] = axiosInstance[key]) // return the stitched axios return axios } // defaultConfig: the default network request configuration, only a simple configuration of the default request mode get, and two request header fields, source code default request configuration more let defaultConfig = { "method": "get"."headers": { "Accept": "application/json,text/plain,*/*"."Content-Type": "application/json; charset=utf-8"}}// Mount AXIos to the global object. Here we are only implementing axios for the browser, so mount it to the window so that we can import the file and use axios directly window.axios = createAxios(defaultConfig) </script> <! -- This script can test the implementation of axios, if you want to quickly build a test server (in 30s), recommend jSON-server, readme writes very clear how to use this guy --> <script> // 1, test request and response interceptor functionality axios.interceptors.request.use( config= > { console.log('Request interceptor 1'); return config }, err= > Promise.reject(err) ) axios.interceptors.request.use( config= > { console.log('Request interceptor 2'); return config }, err= > Promise.reject(err) ) axios.interceptors.response.use( res= > { console.log('Response interceptor 1'); return res }, err= > Promise.reject(err) ) axios.interceptors.response.use( res= > { console.log('Response interceptor 2'); return res }, err= > Promise.reject(err) ) // 2, test the cancel request function (click button continuously to cancel the last network request, of course, please make sure there is a delay of network request) let cancel = null // 3, test Axios to make network request (click button to send network request) addPost.addEventListener('click'.function () { cancel && cancel() // 4, you can also use axios.request or axios.post axios({ method: 'post'.url: 'http://localhost:3000/posts'.data: { title: "json-server-01".author: "axios" }, cancelToken: new axios.CancelToken(c= > cancel = c) }).then(res= > console.log(res)) }) </script> </body> </html> Copy the code
8. ParseInt
-
ParseInt (String,radix) : parseInt accepts two arguments string and radix.
-
String argument: The string argument is required, and if the argument is not a string, it will be converted to a string (calling its toString method) as its argument
-
Radix parameter: The parameter radix is not required and can be omitted, if not omitted, the legitimate Radix parameter should be a 2-36 number, and if the passed parameter is not a numeric type, it will be converted to a numeric type (calling its valueOf method) as its parameter
-
Return value: Converts the string argument to the specified radix base, and then converts the base result to a decimal number. Only two cases are decimal numbers or NaN.
-
-
About Radix Parameters:
-
When radix is these three cases: Undefined (i.e. ParseInt (string,undefined)),0 (i.e. ParseInt (string,0)) or unspecified (i.e. ParseInt (string,0)),radix will be based on the string parameter to determine its base.
-
If string arguments start with 0x or 0x, the radix is determined to be hexadecimal and parseInt will parse the rest of the string arguments in hexadecimal, for example parseInt(‘0xA7’) equals parseInt(‘A7’,16), The result is 10*16^1+7*16^0, so 167.
-
If the string argument starts with 0, the radix will be determined to be base 10 or base 8, where ES5 indicates that base 10 should be used, but not all browsers support it, so we should specify the radix value when using parseInt to prevent unexpected results. For example, parseInt(‘010′) is equivalent to parseInt(’10’,10). The calculation method of the analytical result is 1*10^1+0*10^0, so the result is 10.
-
If the string argument starts with some other value, the radix defaults to base 10.
-
-
If the RADIX parameter is specified, but the RADIX is no longer in the 2-36 range, then parseInt returns a NaN
-
-
About the String parameter
-
ParseInt (’12 3′,10) is the same as parseInt(’12 3′,10). ParseInt (’12 3′,10) is the same as parseInt(’12 3′,10)
-
ParseInt will parse the string argument bit by bit, stop parsing from that bit if it does not conform to the radix radix system or other illegal characters, and return the value from the previous valid bit. It may not be easy to understand, but it doesn’t matter. Here are two examples:
-
Do not conform to the radix system: ParseInt (‘1024′,2) parseInt(’10’,2) parseInt(‘1024’,2) So it’s 1 times 2 to the 1 plus 0 times 2 to the 0, which is 2
-
Other illegal characters: ParseInt (’10 24′,2) parseInt(’10 24′,2) parseInt(’10 24′,2) So it’s 1 times 2 to the 1 plus 0 times 2 to the 0, which is 2
-
-
ParseInt (‘-10’,2) returns -1*(1*2^1+0*2^0)
-
If the string argument was not parsed to A valid character in the first place, return NaN, like parseInt(‘A1’,10),parseInt (‘A1’,10),parseInt (‘A1’,10),parseInt (‘A1’,10), A is an invalid character, So from the first A to the last is not parsed, so NaN is returned.
-
-
[‘1′,’2′,’3’]. Map (parseInt) output why is [1,NaN,NaN]. Let’s examine how this line of code executes:
-
The first parameter of map should be a function. Here we pass parseInt as the first parameter of map, and map will inject three parameters to the function: the value of the current element, the index of the current element, the array of the current element, So [‘1′,’2′,’3’].map((v, I,arr)=>parseInt(v, I)), because parseInt only accepts two arguments, the third argument arr is ignored.
-
So we just need to compute parseInt(‘1′,’0’), parseInt(‘2′,’1’), parseInt(‘3′,’2’) to get the answer.
-
First parseInt(‘1′,’0’), because the radix is 0, so equivalent to parseInt(‘1’,10) converts 1 to base 10, so output 1
-
Second, parseInt(‘2′,’1’), the radix legal value is 2-36, there is no base 1 such, so return NaN.
-
Finally, parseInt(‘3′,’2’) converts 3 to base 2. In binary, the bits can only be at most 1. Carry all 1, 3 is illegal in base 2, so NaN is returned
-
So [‘1′,’2′,’3’].map(parseInt) finally prints [1,NaN,NaN]!
-
9. Deep copy
// getType: Get the exact data type
const getType = val= > Object.prototype.toString.call(val).slice(8).replace(/\]/g.' ').toLowerCase()
// wm: prevent circular references when reference types recurse
const wm = new WeakMap(a)/ / copy
function deepCopy(val) {
// Handle basic types: return the original value (! (Val instanceof Object)
if (['number'.'string'.'boolean'.'symbol'.'null'.'undefined'].includes(getType(val)) && ! (valinstanceof Object)) return val
// Handle reference types array/object/arguments: recursive processing
if (['array'.'object'.'arguments'].includes(getType(val))) {
console.log('1');
if (wm.has(val)) return val
const copyVal = new val.constructor()
wm.set(val, copyVal)
for (const key in val) val.hasOwnProperty(key) && (copyVal[key] = deepCopy(val[key]))
return copyVal
}
// handle the reference type set :(recursive)
if (['set'].includes(getType(val))) {
if (wm.has(val)) return val
const copyVal = new val.constructor()
wm.set(val, copyVal)
val.forEach(v= > copyVal.add(deepCopy(v)))
return copyVal
}
// handle the reference type map :(recursive)
if (['map'].includes(getType(val))) {
if (wm.has(val)) return val
const copyVal = new val.constructor()
wm.set(val, copyVal)
val.forEach((v, k) = > copyVal.set(k, deepCopy(v)))
return copyVal
}
// Process the re
if (['regexp'].includes(getType(val))) {
const source = val.source // Use val.source to fetch the regular body,
const modify = val.toString().match(/\w*$/) [0] // Use val.toString().match(/\w*$/)[0]
const copyVal = new val.constructor(source, modify) // Re-create the re
copyVal.lastIndex = val.lastIndex // Re-create the lastIndex of the original re (copy the lastIndex of the original re, which is used to indicate the current position of each match in /g)
return copyVal
}
// handle the function
if (['function'].includes(getType(val))) {
// If it is an arrow function, eval is the function string
if(! val.prototype)return eval(val.toString())
// If it is a normal Function, the re gets the parameters and body of the original Function, and uses new Function to instantiate the new Function
const param = val.toString().match(/ (? <=\()(.|\n)*(? =\)\s*\{)/) [0]
const body = val.toString().match(/ (? <=\)\s*\{)(.|\n)*(? = \}) /) [0]
return param ? new Function(... param.split(', '), body) : new val.constructor(body)
}
Number String Boolean Date Error... : uses its constructor to pass in the source value to return the copy value
return new val.constructor(val)
}
Copy the code
10. IntersectionObserver
-
IntersectionObserver: IntersectionObserver provides a method for asynchronously observing the intersecting status of target elements and their ancestor elements/top-level document viewports. Here is how to use cross viewer to determine if an element is in viewport (lazy loading, infinite scrolling etc.)
-
Create an instance of a cross observer where IntersectionObserver is the browser’s native constructor, callback is the callback function to be executed when element visibility changes, and option is the configuration option (optional).
const io = new IntersectionObserver(callback,option) Copy the code
-
Use the crossover observer to observe dom elements element1 and element2, unobserve, and close the crossover observer
// Start looking at element1 and element2 io.observe(element1) io.observe(element2) // Stop observing element1 and element2 io.unobserve(element1) io.unobserve(element2) // Close the cross viewer io.disconnect() Copy the code
-
The two important parameters of the second optional configuration object of the cross observer constructor, options, are threshold and root
-
The parameter threshold determines when the callback function is triggered. It is an array, and each member is a threshold. For example, threshold:[0,0.5,1] is the target element 0%, 50%, and 100% visible when the callback function is triggered. The callback is triggered when 0% of the target element is visible
const io = new IntersectionObserver(callback,{threshold: [0.0.5.1]}) Copy the code
-
The root parameter specifies the parent of the target element. Many times an element may need to be rolled not only relative to the viewport, but also relative to one of its ancestors, so we can specify which container element the target element is in, as in this case, the element whose container element id is: container.
const io = new IntersectionObserver(callback,{root:document.getElementById('container')}) Copy the code
-
-
The first argument to the cross-observer constructor, callback, is called when the visibility of the target element changes.
const io = new IntersectionObserver(entries= >{})Copy the code
-
The callback function parameter Entries are an array. Each element in the array corresponds to the visibility information of the registered element (IO. Observe (element1)). Two important data are Target and intersectionRadio (which can help us determine whether the element enters the viewport).
const io = new IntersectionObserver(entries= >console.log(entries[0])) // entries[0] output: { target:'Object element being observed' intersectionRadio:'The visible ratio of the target element, by which we can determine whether the element is visible (e.g. whether the image element is visible when the image is lazy), 1 when it is completely visible, 0 when it is completely invisible, 0.5 when it is half visible, etc.' time:'When the visibility changes' / /... Other information } Copy the code
-
-
-
Lazy loading of images using cross viewer:
<! DOCTYPEhtml> <head> <style> .container { height: 3000px; background-color: red; overflow: hidden; } .lazy-load { height: 200px; width: 200px; margin-top: 1000px; background-color: blue; } </style> </head> <body> <div class="container"> <img class="lazy-load" data-src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p1347785531.webp"> </div> <script> const io = new IntersectionObserver(entries= > { entries.forEach(el= > { // If the image element appears in the viewport, load the image resource if (el.intersectionRatio > 0) { // Set the SRC attribute of the image element el.target to load the image resource el.target.src = el.target.dataset.src // Unobserve the image element io.unobserve(el.target) } }) }) // Select all images that need lazy loading const lazyImgList = Array.from(document.querySelectorAll('.lazy-load')) // Add observations for all lazy images lazyImgList.forEach(el= > io.observe(el)) </script> </body> </html> Copy the code
-
The infinite scrolling principle is implemented using cross observers: a tail element is typically added to the end of the scroll container, which we observe, and once it appears, a new resource is loaded
-
The principle of traditional JS image lazy loading is to listen to the scroll event, and the scroll event callback function determines whether the element enters the viewport. If it enters the viewport, the image will be loaded.
-
Method to determine whether an element is in an viewport
function inViewport(el) { // 1, get the viewport height const viewHeight = window.innerHeight || document.documentElement.clientHeight // 2, get the height of the target element el from the top of the viewport using el.getBoundingClientRect().top const distance = el.getBoundingClientRect().top // 3, if the target element is less than the height of the viewport, the element enters the viewport return distance <= viewHeight } Copy the code
-
Thank you for the reference
- Mouse events and clientX, offsetX, screenX, pageX, x
- Silicon Valley 2021 latest version of AXIos entry and source code analysis
- Tutorial on using intersectionObserver API
- How to write a deep copy that will impress your interviewer