1. Implement the new method
During the process of new, the following four processes occur
-
- Create a new Object (or a new Object if the function is not built into JS)
-
- Point the __proto__ attribute of this empty object to the constructor’s prototype object (link to the prototype implementation inheritance)
-
- Bind this to execute the code in the constructor (add attributes to the new object)
-
- Returns a new object (this new object is automatically returned if the function returns no other object; If a function returns a non-object, the new object is automatically returned, overwriting the non-object.)
function _new() {
// create a new object
const obj = {}
// 2. Bind the prototype
let [constructor.args] = [...arguments]
obj.__proto__ = constructor.prototype/ / 3. Bindingthis
const result = constructor.apply(obj, args) // 4. Return a new objectreturn Object.prototype.toString.call(result) = = '[object Object]' ? result : obj;
}
/ / use
function People(name,age) {
this.name = name
this.age = age
}
let peo = createNew(People,'Bob'.22)
console.log(peo.name)
console.log(peo.age)
Copy the code
Implement a call function
Bind this to an object by passing it a primitive type that would be converted to an object by the wrapper function – boxing.
// The last call is used first
function add(c,d) {
return this.a + this.b + c + d
}
const obj = { a: 1.b: 2}
add.call(obj, 3.4) / / 10
add.apply(obj, [3.4]) / / 10
Copy the code
Call changes this to… What actually happens is something like this
-
- Add an add attribute to obj, and this points to obj
-
- Obj.add (3,4) gives the same result as add.call(obj, 3,4)
-
- Finally, delete the additional add attribute
Implement Call based on ES3
Function.prototype.es3Call = function(context) {
// There is only one context parameter. If the parameter is empty, context is assigned the window value, otherwise the value is passed in
context = context || window
// Whoever calls this is this
context.fn = this
var args = []
Below the argunments input parameter pseudo-array is the filter out of the first object, push into the other parameters
for (var i= 1; len = argunments.lenght; i < len; i++) {
args.push('arguments['+i+'] ')}// the eval() method parses the string as js code
var result = eval('context.fn('+args+') ')
// args will automatically call args.toString() because 'context.fn(' + args +')' is essentially a string concatenation and will automatically call toString()
delete context.fn
return result
}
Copy the code
Based on ES6 implementation, more convenient
Function.prototype.es6Call = function(context) {
context = context || window
context.fn = this
let args = [...arguments].slice(1);
// Return the result of the execution
constresult = context.fn(... args)delete context.fn
return result
}
Copy the code
Implement the Apply method
Apply does the same thing as Call, except that the extra arguments accepted by Apply must be arrays
Function.prototype.apply = function(context,arr) {
context = context ? Object(context) : window;
context.fn = this
var result;
if(! arr) { result = context.fn(); }else {
/ / <! -- ES3 -->
result = eval('context.fn('+arr+') ')
/ / <! -- ES6 -->result = context.fn(... arr) }delete context.fn
return result
}
Copy the code
Implement a bind function
Bind differs from call and apply in that call and apply return the result of an execution, whereas bind returns a function
Function.prototype.bind = function(context) {
if (typeof this! = ='function') {
throw new TypeError('Error')}let fn = this
let arg = [...arguments].slice(1)
return function F() {
// Handle the case where the function uses new
if (this instanceof F) {
return newfn(... arg, ... arguments) }else {
returnfn.apply(context, arg.concat(... Arguments))}}} copy codeCopy the code
6. The difference and application of call, apply and bind
The basic grammar
fun.call(thisArg, param1, param2, ...) fun.apply(thisArg, [param1, param2, ...] ) fun.bind(thisArg, param1, param2, ...)Copy the code
The return value
Call () and apply() return the value that should be returned by the function, and bind() returns a new function that has been hardbound by apply or call(showing the binding)
perform
Call () and apply() execute immediately upon being called, while bind() simply completes the binding of this. Since the function does not execute immediately, it is appropriate to use bind() in the event binding function, both to complete the binding and to ensure that it will only execute when the event fires
Application scenarios
Call (), apply(), and bind() can all change this. When do I need to change this? Most of the time it’s actually about borrowing methods, that is, calling methods on objects that don’t have them themselves. Here’s an example
1. Determine the data type
Object. The prototype. ToString. Call () can accurately determine the data type
var a = "abc";
var b = [1.2.3];
Object.prototype.toString.call(a) == "[object String]" //true
Object.prototype.toString.call(b) == "[object Array]" //true
Copy the code
The principle is that calling Object’s native toString() method on any value will generate a string of the form [Object NativeconstructorName], from which you can accurately determine the data type of any value. Since both Array and Function inherit this method from Object, why not call them directly? This is because toString() has been overwritten and is not a native method, so instead we call the method of Object and bind this to the corresponding value.
2. Simulate shallow copy
To simulate a shallow copy, we need to remove attributes from the prototype chain. Considering that the source Object may be created based on object.create () and such an Object does not have the hasOwnPrototype() method, we do not call this method directly on the source Object. But by the Object. The prototype. HasOwnPrototype. Call () method to call, because the Object must have this method, so we can borrow
if (Object.prototype.hasOwnPrototype.call(nextSource,nextKey)) {
to[nextKey] = nextSource[nextKey]
}
Copy the code
3. The inheritance
One of the ways that JavaScript can inherit is by borrowing constructors and assuming that there are child constructors Son and parent constructors Paren. In the case of Son, the internal this refers to the object that will be instantiated later. To take advantage of this, we can augment the subclass instance by calling parent inside Son via call or apply and passing this
4. Class array borrow array method
For example, arguments is an array of classes and does not have the array’s forEach() method. We can call the array’s forEach() method and bind this to arguments
Array.prototype.forEach.call(arguments.item= > {
console.log(item)
})
Copy the code
5. Maximize the array
The core is that apply can be used to expand an array, as we said earlier, to convert an array of parameters to a list of parameters. For example, we can find the maximum value of an array. Even though the Math object has a Max () method, it only accepts a list of parameters. Math.max.call(NULL, arr)
7. Shallow copy and deep copy implementation
Shallow copy
/ / method
let copy1 = { ... {x: 1}}
/ / method 2
let copy2 = Object.assign({}, {x:1})
Copy the code
Deep copy
// Method 1: disadvantages L copy objects that contain regular expressions, functions or undefined equivalents will fail
JSON.parse(JSON.strigify(obj))
// Method 2: recursive copy
function deepClone(obj) {
let copy = Obj instanceif Array ? [] : {}
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
copy[i] = typeof Obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
}
}
return copy
}
Copy the code
8. Throttling and shaking prevention
Fat podcast
preface
In the following scenarios, events are frequently triggered and heavy actions such as DOM operations and resource loading are frequently performed, resulting in UI pauses or even browser crashes
-
- Resize, Scroll events for the window object
-
- Mousemove event while dragging
-
- Mousedown, keyDown events in shooters
-
- Text such as, auto-complete keyup event
In fact, for window resize events, most of the actual requirements are to stop resize n milliseconds before performing subsequent processing; Most of the other events require that subsequent processing be performed at a certain frequency.
What is a debounce
Definition 1.
If you hold a spring down, it will not spring until you release it, that is, the action will not be performed until n milliseconds after the call. If the action is called within n milliseconds, the execution time will be recalculated (if it is not triggered a second time within the specified time, it will be executed) interface definition
/** ** Idle control, return function continuous calls, idle time must be "idle", action will be executed *@param Delay Idle time *@param Fn is the function that needs to be called in the actual application@return Returns the customer service call function */
Copy the code
Simple implementation
const debounce = (fn, delay) {
// Use closures to save timers
let timer
return function() {
// If triggered again within the specified time, the timer will be cleared and reset again
clearTimeout(timer)
timer = setTimeout(() = > {
fn.apply(this.arguments)
}, delay)
}
}
function fn() {
console.log('if you')
}
addEventListener('scroll', debounce(fn,1000))
Copy the code
What is throttling
Built-in trigger once at a specified time (and tighten the tap water, only drop a drop at a certain time)
function throttle(fn,delay) {
// Use closures to save time
let prev = Date.now()
return function(){
const now = Date.now()
if (now - prev >= delay) {
fn.apply(this.arguments)
prev = Date.now()
}
}
}
function fn () { console.log('the throttling') }
addEventListener('scroll', throttle(fn, 1000))
Copy the code
9. How Instanceof works
The prototype of the right-hand variable is on the prototype chain of the left-hand variable
function instanceOf(left, right) {
let leftValue = left.__proto__ // take implicit archetypes
let rightValue = right.prototype // Take an explicit prototype
white(true) {
if (leftValue === null) {
return false
}
// Returns true when the right explicit stereotype is strictly equal to the left implicit stereotype
if (leftValue ==== rightValue) {
return true
}
leftValue = leftValue.__proto__
}
}
Copy the code
10. Implementation of Currization functions
Definition: Converting a multi-parameter function to a single-parameter form Principle: Using the closure principle, you can form a non-destructible scope during execution, then store the pre-processed content in this non-destructible scope, and return a function with the fewest parameters.
The first is to fix the passed parameters and execute them when enough parameters are passed
/** * Implementation note: When a function receives enough arguments, it executes the original function, so how do we determine when enough arguments are reached? * The curry function remembers the arguments you gave it, and defaults to an empty array if it didn't. * Each subsequent call checks to see if it was given enough arguments, executes fn if it was given enough, and returns a new curry function that stuffs the existing arguments into it */
// The function to be currified
let sum = (a, b, c, d) = > {
return a + b + c + d
}
// The currified function returns a processed function
let curry = (fn, ... arr) = > {// arR records existing parameters
console.log(arr)
const fnParamsLen = fn.length
return (. args) = > { // args receives new parameters
if (fnParamsLen <= [...arr, ...args].length) { // If the parameters are sufficient, execution is triggered
returnfn(... arr, ... args) }else { // Continue adding parameters
return curry(fn, [...arr, ...args])
}
}
}
var sumPlus = curry(sum)
sumPlus(1) (2) (3) (4)
sumPlus(1.2) (3) (4)
sumPlus(1.2.3) (4)
Copy the code
The second way: do not fix the incoming parameter, execute at any time
/** * The main effect of curritization is to delay execution. The trigger condition of execution is not necessarily equal to the number of arguments, but also other conditions, such as the number of arguments accepted at the end of 0, so we need to modify the above function slightly */
// The function to be currified
let sum = arr= > {
return arr.reduce((a, b) = > { return a + b }, [])
}
let curry = (fn, ... arr) = > {
return (. args) = > {
if (args.length === 0) {
returnfn(... arr, ... args) }else {
returncurry(fn,... arr, ... args) } } }Copy the code
11. Basic implementation principle of Object.cteate
** Method description: **
- The object.create () method creates a new Object and takes the first argument of the method as the value of the __proto__ property of the new Object (the prototype Object that takes the first argument as the constructor of the new Object);
- Object. The create () method and the second optional parameter that is an Object, the Object of every attribute itself as the new Object attribute, Object attribute value to descriptor (Object. GetOwnPropertyDescriptor (obj, ‘key’)) in the form of Enumenrable is false by default
Define an empty constructor, then specify the prototype object of the constructor, create an empty object with the new operator, and if a second argument is passed, DefineProperties to set the key and value of the created Object and return the created Object. Source: original link
Object.myCreate = function(proto, propertyObject = undefined) {
if (propertyObject === null) {
// There is no check on whether the propertyObject is the original wrapper object
throw 'TypeError'
return
}
// Define an empty constructor
function Fn() {}
// Specify the original object of the constructor
Fn.prototype = proto
// Create an empty object with the new operator
const obj = new Fn()
// If the second argument is not null
if(propertyObject ! = =undefined) {
Object.defineProperties(obj, propertyObject)
}
if(proto = = =null) {// Create an Object with no prototype Object, object.create (null)
obj.__proto__ = null
}
return obj
}
/ / sample
// When the second argument is null, TypeError is raised
// const throwErr = Object.myCreate({a: 'aa'}, null) // Uncaught TypeError
// Build a
const obj1 = Object.myCreate({a: 'aa'})
console.log(obj1) // {}, the prototype object of obj1's constructor is {a: 'aa'}
const obj2 = Object.myCreate({a: 'aa'}, {
b: {
value: 'bb'.enumerable: true}})console.log(obj2) // {b: 'bb'}, obj2's constructor prototype object is {a: 'aa'}
Copy the code
12. Implement a basic Event Bus
The purpose of EventBus is to act as middleware, a bridge between the two components
- The sender passes the data and eventName to EventBus via EventBusName.$emit(‘eventName’, data)
- The receiver processes the data via EventBusName.$ON (‘eventName’, methods)
Simple implementation
class EventBus {
constructor() {
// Store events
this.events = this.events || new Map()}// Listen on events
addListener(type, fn) {
if (!this.events.get(type)) {
this.events.set(type, fn)
}
}
// Trigger the event
emit(type) {
let handle = this.events.get(type)
handle.apply(this,[...arguments].slice(1))}}/ / test
let emitter = new EventBus()
// Listen on events
emitter.addListener('add'.age= > {console.log(age)})
// Trigger the event
emitter.emit('add'.18)
Copy the code
upgrade
What if you listen for the same event name repeatedly? After the first binding holds, there is no way to register the binding for subsequent listeners, because we need to put subsequent listeners into an array
Class EventBus {
constructor() {
// Store events
this.events = this.events || new Map()}// Listen on events
addListener(type, fn) {
const handler = this.events.get(type)
if(! handler) {this.events.set(type, fn)
} else if(handler &&typeof handler ==== 'function') {// If handler is a function, there is only one listener before
this._events.set(type, [handler, fn]); // We need to store multiple listeners in arrays
} else {
handler.push(fn); // There are already multiple listeners, so just add push to the array}}// Trigger the event
emit(type,... args) {
const handler = this.events.get(type)
// If it is an array, it means that there are multiple listeners
if (Array.isArray(handler)) {
for (let i = 0; i < handler.lenght; i++) {
handler[i].apply(this, args)
}
} else {
// We can trigger a single function directly
handler.apply(this, args); }}// Remove the listener using removeListener
removeListener(type, fn) {
const handler = this._events.get(type); // Get the function list corresponding to the event name
// If it is a function, it is only listened once
if (handler && typeof handler === 'function') {
this._events.delete(type, fn);
} else {
let postion;
// If handler is an array, it is necessary to find the corresponding function
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
postion = i;
} else {
postion = -1; }}// If a matching function is found, it is cleared from the array
if(postion ! = = -1) {
// Find the location of the array and clear the callback directly
handler.splice(postion, 1);
// If only one function is cleared, cancel the array and save it as a function
if (handler.length === 1) {
this._events.set(type, handler[0]); }}else {
return this;
}
}
}
}
}
Copy the code
13. Implement a two-way data binding
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// Data hijacking
Object.defineProperty(obj, 'text', {
configurable: true.// can be operated
enumerable: true.// enumerable
get() {
console.log('Got the data.')},set(newVal) {
console.log('Data updated')
input.value = newVal
span.innerHTML = newVal
}
})
// Input listener
input.addEventListener('keyup'.function(e) {
obj.text = e.target.value
})
Copy the code
See this in more detail and this is probably the most detailed explanation of a responsive system
Implement a simple route
How it works: Using the hash form (which can also be handled using the History API) as an example, when the HASH of the URL changes, the callback registered with hashchange is triggered to perform different operations and display different contents in the callback.
class Route {
constructor() {
// Route storage object
this.routes = {}
/ / the current hash
this.currentHash = ' '
FreshRoute = this.freshroute.bind (this)
window.addEventListener('load'.this.refresh.bind(this), false)
window.addEventListener('hashchange'.this.freshRoute.bind(this), false)}/ / update
freshRoute () {
this.currentHash = location.hash.slice(1) | |'/'
this.routes[this.currentHash]()
}
// Store or add
storeRoute (path, cb) {
this.routes[path] = cb || function () {}}}Copy the code
15. Implement lazy loading
<ul>
<li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li>
</ul>
Copy the code
The initial image SRC load will display the default image. The actual image is placed on the data attribute of the IMG tag. Principle: Monitor the screen scrolling, calculate the position of the image, and replace the SRC of the image with the actual resource to be displayed when the image is in the visible area of the screen. So the key is to know if the image is in the viewable area (traverse the image, figure out the critical point, just in and just out)
// Internet Explorer 8, 7, 6, 5:
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
// Get the nodes of all images
const imgs = document.querySelectorAll('img')
// Perform lazy load calculation while listening to screen scroll
window.addEventListener('scroll', lazyLoad)
/ / lazy loading
function lazyLoad () {
// Roll out the height
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
// Traverse the image node to determine the assignment SRC where the image appears in the visible area
for (let i = 0; i < imgs.length; i++) {
// Find the distance between the top corner of the current image and the outer element
const curImgOffsetTop = imgs[i].offsetTop
// Find the critical point, just to roll into, and just roll out completely (all the outer element vertices are reference points)
// Just the point to scroll into
const readyInOffestTop = scrollTop + clientHeight
// Just out of the point (just out of the picture, the top left corner of the distance from the top)
const justOutOffestTop = scrollTop - imgs[i].height
// appear in viewable area
if(curImgOffsetTop > readyInOffestTop && curImgOffsetTop < justOutOffestTop) {imgs[I].src = imgs[I].getAttribute('data')}}}Copy the code
Optimize to find the index of the first image under the screen, less than the update SRC of the index
16. Mobile ADAPTS rem
To understand:
- Em is a unit of size relative to the font size of the parent element. When the parent element is 30px and the child element is 0.8em, the child element is 30 * 0.8 = 24px.
- Rem is a unit of size relative to the HTML gen element, when
<html lang="zh-cmn-Hans" style="font-size: 50px;">
<body style="font-size: 0">
<div class="box1">This is the div box</div>
<body>
</html>Box1 {font-size: 6rem} ' 'will render the actual box size will be' 50px * 6 = 300px '; So rem is 50px so the key point for mobile adaptation is to determine how much PX REM is; When we get the design drawing development, we need to pay attention to two, one is' different size of the phone ', the second is' the design will only give a standard design draft ', then we also hope that we can easily write REM by looking at the design draft. Now pretend that the fontSize of our root element is set to X (px), and I want to set the width of Box1 to the full screen width, say 750 and 1334; I hope I'll just focus on the design for the rest of my writing. Then I want the following to be true: HTML<div :style="width: `${designWidth}rem`;">This is the div box</div>
Copy the code
const designWidth = 750
// The width of the device is required
const clientWidth = document.documentElement.clientWidth || document.body.clientWidth || window.innerWidth || window.screen.width
designWidth + rem = designWidth * x = clientWidth
// Solve the equation
x = clientWidth / designWidth
Copy the code
So the correct position to set the fontSize of the HTML root element is:
document.documentElement.style.fontSize = ( clientWidth / designWidth) + 'px';
Copy the code
Then in the actual work, we will encounter landscape or portrait, and the designWidth will change; If the screen size can be changed on the PC side, listen for resize to change the fontSize of the root element
addEventListener("resize", setHtmlRootFontSize)
Copy the code