1. Hand new

function new (Func, ... Arg){let obj = {} // defines an object. Prototype // Assign func. prototype to the __proto__ property of the object. Let res = func. call(obj,... Arg) // Change this of Func to return res instanceof Object? res : obj }Copy the code

From this code we can see what the system is doing when we create a new object. We can test that out, because new is the keyword, so let’s change the name of the function.

function Feng (Func, ... Arg){let obj = {} // defines an object. Prototype // Assign func. prototype to the __proto__ property of the object. Let res = func. call(obj,... Arg) // Change this of Func to return res instanceof Object? Res: obj} new Feng(Array,[1,2,3])Copy the code

2. Coriolization of hand tearing function

const curry = ( fn, arr = []) => (... args) => ( a => a.length === fn.length? fn(... a) : curry(fn, a))([...arr, ... the args) let curryPlus = curry ((a, b, c, d) = > a + b + c + d) curryPlus (1, 2, 3) (4) / / return 10 curryPlus (1, 2) and (4) and (3) / / return 10 CurryPlus (1,2)(3,4Copy the code

3. Hand Promise

It’s not that hard to tear a promise by hand.

First, let’s look at how promise is used:

new Promise((resolve, reject) => { resolve('hello'); / / or reject (' hello ')}). Then (res = > {}). The catch (err = > {}) can also be written as:  let executor = (resolve, reject) => { resolve('hello'); // or reject('hello') } new Promise(executor) .then(res => {}) .catch(err => {})Copy the code

Break down the logic of promises:

1. Promise is a class exectuor is a function that executes immediately. 2. By default, there are three states: [PENDING] FULFILED and REJECTED. Once the states are changed, they cannot be changed. 3. The results of resolve and reject are passed into the callback functions in THEN. 4. Publish and subscribe mode to achieve asynchronous execution. 5. If a promise returns a normal value (either resolve or Reject), it is passed to the success of the next THEN. 6. If an error is returned, it must go to the next failure. 7. If a promise is returned, the state of the promise will be used to determine the success or failure of the next time. If the nearest THEN has no error processing, the search will be downward. 8. Each execution of promise.then returns a 'brand new Promise'.Copy the code

3.1 Basic structure of Promise

Class Promise {constructor(executor) {// define resolve let resolve => {} // define reject let reject => {} // Automatically execute executor(resolve, reject); }}Copy the code

3.2 Three state realization of Promise

Promise has three states:

  • Pending Indicates the initial state

  • This is a big pity

  • The operation failed

The promise state has the following characteristics:

1. The promise object is initialized to pending.

2. When you call resolve(success), there will be pending => depressing.

3. Reject => Rejected when you call reject.

class Promise { constructor(executor) { this.status = "pending"; // Default state this.value; // resolve = this.error; Let resolve = res => {if(this.status === "pending") {this.value = res; this.status = "resolved"; } } let reject = err => { if(this.status === "pending") { this.error = err; this.status = "rejected"; } } executor(resolve, reject); }}Copy the code

3.3 Promise object method then implementation

This is a big pity, which is onFulfilled (fulfilled) and onRejected (failed).

promise.then(onFulfilled, onRejected); 1. OnFulfilled (onResolved) : This parameter is optional. If it is not a function, it must be ignored. 2. OnRejected: This parameter is optional. If it is not a function, it must be ignored. 3. When the promise is successfully implemented, all onFulfilled will be implemented in the registration order. If the promise is rejected, all onRejected will be implemented in the registration order. 4. OnFulfilled and onRejected must be called as pure functions. 5. The Promise executor can invoke the then parameters onFulfilled and onRejected after it completes and calls resolve or Reject. 6. No matter the promise state is resolved or Rejected, as long as there is still onFulfilled and onRejected or catch (only reject) exists and is called, the returned promise will be in the resolved state. class Promise { constructor(executor) { this.status = "pending"; // Default promise state this.value; // resolve = this.error; Let resolve = res => {if(this.status === "pending") {this.value = res; this.status = "resolved"; } } let reject = err => { if(this.status === "pending") { this.error = err; this.status = "rejected"; }} execute (resolve, reject)} then(onFullfilled, onRejected) { if(this.status === "resolved") { onFullfilled(this.value) } if(this.status === "rejected") { onRejected(this.error) } } }Copy the code

We added onFullfilled and onRejected to store our callback in then. A successful and a failed callback can be passed in THEN, with a successful callback executed when the pending state changes to resolve and a failed callback executed when the pending state changes to Reject or fails.

3.4 Asynchronous implementation of Promise

When resolve is executed within setTimeout, then is executed with state as pending. We need to store success and failure in each array when we call then, and call either Reject or resolve as soon as we call them.

Similar to distributed subscription, the two functions in THEN are stored first. Since promises can have multiple THEN, they are stored in the same array. Call them with forEach when they succeed or fail.

class Promise { constructor(executor) { this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject this. ResolveQueue = []; This.rejectqueue = []; this.rejectQueue = []; Resolve = value => {if(this.status === "pending") {this.value = value; this.status = "resolved"; / / once resolve execution, success of the array function called enclosing resolveQueue. ForEach (fn = > fn ()); } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; } // Once reject executes, call this.rejectQueue.foreach (fn => fn()); } execute (resolve, reject)} then(onFullfilled, onRejected) { if(this.status === "resolved") { this.resolveQueue.push(() => { onFullfilled(this.value); }) } if(this.status === "rejected") { this.rejectQueue.push(() => { onRejected(this.error); If (this.status === "pending") {// this.status === "pending") {// This.resolvequeue.push (() => { onFullfilled(this.value); }) // OnRejectQueue.push (() => {onRejectQueue.push () => {onRejectQueue.push (); })}}}Copy the code

3.5 chain calls to then

Chain calls, often written like New Promise().then().then(), solve hell callbacks.

class Promise { constructor(executor) { this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject this. ResolveQueue = []; This. rejectQueue = []; Let resolve = value => {if(this.status === "pending") {this.value = value; this.status = "resolved"; this.resolveQueue.forEach(fn => fn()) } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; this.rejectQueue.forEach(fn => fn()) } } executor(resolve, reject) } then(onFullfilled, onRejected) { let promise2; promise2 = new Promise((resolve, reject) => { if(this.status === "resolved") { let x = onFullfilled(this.value); // resolvePromise(x, resolve, reject); // resolvePromise(x, resolve, reject); } if(this.status === "rejected") { let x = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); } if(this.status === "pending") { this.resolveQueue.push(() => { let x = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.rejectQueue.push(() => { let x = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); }}})); // return promise2; }} /** * Function that handles promise recursion ** promise2 {promise} default return promise * x {*} our own return object * resolve * reject */ function resolvePromise(promise2, x, resolve, Reject){// Loop reference error if(x === promise2){// reject error thrown return Reject (New TypeError('Chaining cycle detected for promise')); } // lock to prevent multiple calls to let called; // x is not null and x is an object or function if (x! = = = = null && (typeof x 'object' | | typeof x = = = 'function')) {try {/ / A + rules, method statement then = x then let then = x.t hen; // If then is a function, If (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') { Y => {// Only one if (called) return can be called; called = true; ResolvePromise (promise2, y, resolve, reject); }, err => {// Success and failure can only call one if (called) return; called = true; reject(err); })} else {resolve(x); }} catch (e) {// If (called) return; called = true; Reject (e); reject(e); } } else { resolve(x); }}Copy the code

3.6 Value penetration calls

What value penetration is, is that when executing multiple THEN, we expect the last THEN to print ‘Feng’.

new Promise((resolve, reject)=>{
    resolve('Feng');
}).then().then().then().then().then().then().then((res)=>{ 
    console.log(res);
})
Copy the code

The implementation is simple: if onFulfilled is not a function, ignore onFulfilled and return value directly.

If onRejected is not a function, we should ignore onRejected and throw an error.

class Promise { constructor(executor) { this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject this. ResolveQueue = []; This. rejectQueue = []; Let resolve = value => {if(this.status === "pending") {this.value = value; this.status = "resolved"; this.resolveQueue.forEach(fn => fn()) } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; this.rejectQueue.forEach(fn => fn()) } } executor(resolve, reject) } then(onFullfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; } let promise2; promise2 = new Promise((resolve, reject) => { if(this.status === "resolved") { let x = onFullfilled(this.value); // resolvePromise(x, resolve, reject); // resolvePromise(x, resolve, reject); } if(this.status === "rejected") { let x = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); } if(this.status === "pending") { this.resolveQueue.push(() => { let x = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.rejectQueue.push(() => { let x = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); }}})); // return promise2; }} /** * Function that handles promise recursion ** promise2 {promise} default return promise * x {*} our own return object * resolve * reject */ function resolvePromise(promise2, x, resolve, Reject){// Loop reference error if(x === promise2){// reject error thrown return Reject (New TypeError('Chaining cycle detected for promise')); } // lock to prevent multiple calls to let called; // x is not null and x is an object or function if (x! = = = = null && (typeof x 'object' | | typeof x = = = 'function')) {try {/ / A + rules, method statement then = x then let then = x.t hen; // If then is a function, If (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') { Y => {// Only one if (called) return can be called; called = true; ResolvePromise (promise2, y, resolve, reject); }, err => {// Success and failure can only call one if (called) return; called = true; reject(err); })} else {resolve(x); }} catch (e) {// If (called) return; called = true; Reject (e); reject(e); } } else { resolve(x); }}Copy the code

3.7 Promise Object method Catch

A catch is a failed callback, equivalent to executing this.then(null,fn).

class Promise { constructor(executor) { this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject this. ResolveQueue = []; This. rejectQueue = []; Let resolve = value => {if(this.status === "pending") {this.value = value; this.status = "resolved"; this.resolveQueue.forEach(fn => fn()) } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; this.rejectQueue.forEach(fn => fn()) } } executor(resolve, reject) } then(onFullfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; } let promise2; promise2 = new Promise((resolve, reject) => { if(this.status === "resolved") { let x = onFullfilled(this.value); // resolvePromise(x, resolve, reject); // resolvePromise(x, resolve, reject); } if(this.status === "rejected") { let x = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); } if(this.status === "pending") { this.resolveQueue.push(() => { let x = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.rejectQueue.push(() => { let x = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); }}})); // return promise2; } catch(onRejected) { return this.then(null, OnRejected)}} /** * A function that handles promise recursion ** promise2 {promise} default return promise * x {*} our own return object * resolve * reject */ function resolvePromise(promise2, x, resolve, Reject){// Loop reference error if(x === promise2){// reject error thrown return Reject (New TypeError('Chaining cycle detected for promise')); } // lock to prevent multiple calls to let called; // x is not null and x is an object or function if (x! = = = = null && (typeof x 'object' | | typeof x = = = 'function')) {try {/ / A + rules, method statement then = x then let then = x.t hen; // If then is a function, If (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') { Y => {// Only one if (called) return can be called; called = true; ResolvePromise (promise2, y, resolve, reject); }, err => {// Success and failure can only call one if (called) return; called = true; reject(err); })} else {resolve(x); }} catch (e) {// If (called) return; called = true; Reject (e); reject(e); } } else { resolve(x); }}Copy the code

4. Manually tear the Vue3 bidirectional binding

In VUe2, bidirectional binding is achieved by using the Object.defineProperty() method to set the get and set methods on attributes. Vue3’s bidirectional binding uses proxy objects in ES6.

What is a Proxy? Create a traget virtual object with new, and then the proxy can intercept the underlying object operations inside the JavaScript engine. These underlying operations, when intercepted, trigger trap functions that respond to specific operations.

Basic usage:

const p = new Proxy(target, handler)
Copy the code

target

The target object (which can be any type of object, including a native array, function, or even another Proxy) that you want to wrap with a Proxy.

handler

An object that typically has functions as attributes, and the functions in each attribute define the behavior of agent P when performing various operations.

Let’s see how Proxy is used:

// define an empty object let data = {}; Let Proxy = new Proxy(data, {}); // Change the name attribute of the Proxy object proxy.name = 'maple '; console.log(proxy); // {name: 'maple'} console.log(data); // {name: 'maple'}Copy the code

The Handler object is a placeholder object that holds a set of specific properties. It contains the various traps of the Proxy.

parameter

role

handler.get()

The catcher for the property read operation.

handler.set()

Property set the catcher for the operation.

handler.set

The handler.set() method is used to intercept operations that set property values.

// define an object let data = {name: "maple ", age: '23'}; Let p = new Proxy(data, {set(target, prop, Console. log(target, prop, value) // {name: 'maple ', age: '23' } 'age' 18 return Reflect.set(... arguments) } }); P.age = 18; Console. log(data) //{name: "", age: "23"}Copy the code

When we modify p, we invoke the set method to get the modified property, and then we need to assign the modified value to data.

Add a line of code: return reflect.set (… The arguments).

Reflect objects, like Proxy objects, are a new API provided by ES6 for manipulating objects. We need to return a reflect.set (…) in handler.set() Arguments) to assign to the target object.

Reflect.set

The reflect. set method sets the name property of the target object to value.

// define an object let data = {name: "maple ", age: '23'}; Let p = new Proxy(data, {set(target, prop, Console. log(target, prop, value) // {name: 'maple ', age: '23' } 'age' 18 return Reflect.set(... arguments) } }); P.age = 18; Console. log(data) //{name: "", age: "18"}Copy the code

Two-way binding

Handwritten Vue’s bidirectional binding doesn’t seem too arcane, getting parameters, proxy, data rendering.

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="./src/index.js"></script> </head> <body> <div id="app">{{name}} <h2>{{age}}</h2> <input type="text" v-model="name"> {{name}} </div> <script> let vm = new Reactive({// mount element el: "#app", data: {name: "# ", age: 23,}}); </script> </body> </html>Copy the code

index.js

[6] Class Reactive extends EventTarget {constructor(options) {super(); this.options = options; $data = this.options.data; // Mount the element this.el = document.querySelector(this.options.el); // call compile function this.compile(this.el); // Call the two-way bind this.observe(this.$data); // This let _this = this; // This let _this = this; $data = new Proxy(data, {set(target, prop,); Value) {// create a CustomEvent CustomEvent [5] // create a CustomEvent using prop let event = new CustomEvent(prop, {// pass in new value detail: Value}) // dispatchEvent _this.dispatchEvent(event); return Reflect.set(... arguments); }})} compile(el) {let child = el.childnodes; ForEach (node => {// If node type is TEXT_NODE if (node.nodetype === 3) {// Get the text content let TXT = node.textContent; // Let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if (reg.test(txt)) { let $1 = RegExp.$1; $data[$1] && (Node.textContent = txt.replace(reg, this.$data[$1]))) E => {// replace the detail node.textContent = txt.replace(reg, Else if (node.nodeType === 1) {// Get attr let attr = node.attributes; If (attr.hasOwnProperty('v-model')) {let keyName = attr['v-model'].nodeValue; Value = this.$data[keyName] // Bind event Node.addeventListener ('input', This. $data[keyName] = node.value})} this.compile(node)}}Copy the code