手撕JS源码(new,curry,Promise,双向绑定)

·  阅读 2527

1.手撕new

function new (Func, ...arg){
	let obj = {}  //定义了一个对象。
	obj.__proto__ = Func.prototype  //将Func.prototype赋值为对象的__proto__属性,即原型链的概念
	let res = Func.call(obj, ...arg) //更改Func的this指向
	return res instanceof Object ? res : obj 
}
复制代码

通过这段代码我们可以看得出来,当我们new一个对象的时候,系统都做了些什么事情。我们可以测试一下,因为new是关键字,我们先改个函数名。

function Feng (Func, ...arg){
	let obj = {}  //定义了一个对象。
	obj.__proto__ = Func.prototype  //将Func.prototype赋值为对象的__proto__属性,即原型链的概念
	let res = Func.call(obj, ...arg) //更改Func的this指向
	return res instanceof Object ? res : obj 
}
new Feng(Array,[1,2,3])
复制代码

2.手撕函数柯里化

const curry = ( fn, arr = []) => (...args) => ( a => a.length === fn.length? fn(...a) : curry(fn, a))([...arr, ...args])
let curryPlus = curry((a,b,c,d)=>a+b+c+d)

curryPlus(1,2,3)(4) //返回10
curryPlus(1,2)(4)(3) //返回10
curryPlus(1,2)(3,4) //返回10
复制代码

3.手撕Promise

面试的时候经常会遇到让你手写promise的面试题,大多人都手足无措,其实手撕promise没有那么难。

首先,先看一下promise的用法:

new Promise((resolve, reject) => {
    resolve('hello'); // or reject('hello')
})
 .then(res => {})
 .catch(err => {})

也可以写为:

let executor = (resolve, reject) => {
    resolve('hello'); // or reject('hello')
}
new Promise(executor)
 .then(res => {})
 .catch(err => {})
复制代码

分析一下promise的逻辑:

  1. Promise 是一个类   exectuor 是一个立即执行函数。
  2. 有三个状态 默认是【PENDING】 FULFILED (成功态)、REJECTED(失败态),状态一经改变不能再修改。
  3. resolve和reject的结果传入到then中的回调函数中。
  4. 发布订阅模式实现异步执行。
  5. 如果promise返回一个普通值(不论是 resolve 还是reject中返回的)都传递到下一个then的成功中。
  6. 如果返回一个错误 一定走到下一次的失败。
  7. 如果返回的是一个promise 会采用promise的状态决定下一次的成功还是失败,如果离自己最近的then没有错误处理,会向下找。
  8. 每次执行promise.then 都会返回一个'全新的promise'。
复制代码

3.1 Promise 基本结构

class Promise {
    constructor(executor) {
        // 定义 resolve
        let resolve = res => {}
        // 定义 reject
        let reject = err => {}

        // 自动执行
        executor(resolve, reject);
    }
}
复制代码

3.2 Promise 三种状态实现

Promise有三种状态:

  • pending [待定] 初始状态

  • fulfilled [实现] 操作成功

  • rejected [被否决] 操作失败

promise 状态有如下特点:

1.promise 对象初始化状态为 pending。

2.当调用resolve(成功),会由pending => fulfilled。

3.当调用reject(失败),会由pending => rejected。

class Promise {
    constructor(executor) {
        this.status = "pending"; // 默认状态
        this.value;  // resolve 成功时的值
        this.error;  // reject 失败时的值

        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);
    }
}
复制代码

3.3 Promise 对象方法 then 实现

then接受两个回调:onFulfilled(成功的回调),onRejected(失败的回调)

promise.then(onFulfilled, onRejected);


1.onFulfilled(onResolved):可选参数,如果不是函数则必须忽略。
2.onRejected:可选参数,如果不是函数则必须忽略。
3.当promise成功执行,所有onFulfilled按注册顺序执行,如果promise被拒绝,所有onRejected按注册顺序执行。
4.onFulfilled 和 onRejected必须作为纯函数调用。
5.promise的executor执行完毕并调用resolve或reject方可调用then参数onFulfilled 和 onRejected。
6.无论promise状态是resolved还是rejected,只要还有未执行onFulfilled,onRejected或catch(只处理reject状态)存在且调用,返回的promise均为resolved状态。

class Promise {
        constructor(executor) {
            this.status = "pending"; // 默认promise状态
            this.value;  // resolve成功时的值
            this.error;  // reject失败时的值

            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)
        }

        // 声明 then
        then(onFullfilled, onRejected) {
            if(this.status === "resolved") {
                onFullfilled(this.value)
            }
            if(this.status === "rejected") {
                onRejected(this.error)
            }
        }
    }
复制代码

我们增加了onFullfilled和onRejected,用来储存我们在then中回调。then中可传递一个成功和一个失败的回调,当pending的状态变为resolve时执行成功回调,当pending的状态变为reject或者出错时则执行失败的回调。

3.4 Promise异步实现

当 resolve 在 setTimeout 内执行,then函数执行 时 state 还是 pending 等待状态。我们就需要在 then 调用的时候,将成功和失败存到各自的数组,一旦 reject 或者 resolve,就调用它们。

类似于分布订阅,先将 then 内的两个函数存储,由于 promise 可以有多个 then,所以存在同一个数组内。当成功或失败的时候用 forEach 调用他们。

class Promise {
        constructor(executor) {
            this.status = "pending"; // 默认promise状态
            this.value;  // resolve成功时的值
            this.error;  // reject失败时的值
            this.resolveQueue = []; // 成功存放的数组
            this.rejectQueue = []; // 失败存放法数组

            let resolve = value => {
                if(this.status === "pending") {
                    this.value = value;
                    this.status = "resolved";
                    // 一旦resolve执行,调用成功数组的函数
                   this.resolveQueue.forEach(fn => fn());
                }
            }

            let reject = value => {
                if(this.status === "pending") {
                    this.error = value;
                    this.status = "rejected";
                }
                // 一旦reject执行,调用失败数组的函数
                this.rejectQueue.forEach(fn => fn());
            }

            executor(resolve, reject)
        }
        
        // 执行到then的时候
        then(onFullfilled, onRejected) {
            if(this.status === "resolved") {
                this.resolveQueue.push(() => {
                    onFullfilled(this.value);
                })
            }
            if(this.status === "rejected") {
                this.rejectQueue.push(() => {
                    onRejected(this.error);
                })
            }
            // 当状态state为pending时
            if(this.status === "pending") {
                // onFulfilled传入到成功数组
                this.resolveQueue.push(() => {
                    onFullfilled(this.value);
               })
                // onRejected传入到失败数组
                this.rejectQueue.push(() => {
                    onRejected(this.error);
                })
            }
        }
    }
复制代码

3.5 then 的链式调用

链式调用,也就是我们经常看到的new Promise().then().then()这样的写法,可以解决地狱回调。

class Promise {
    constructor(executor) {
        this.status = "pending"; // 默认promise状态
        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函数,处理自己return的promise和默认的promise2的关系
                resolvePromise(promise2, 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);
                })
            }
        });
        
        // 返回 promise,达成链式效果
        return promise2;
    }
}

/**
 * 处理promise递归的函数
 *
 * promise2 {Promise} 默认返回的promise
 * x {*} 我们自己 return 的对象
 * resolve
 * reject
 */
 function resolvePromise(promise2, x, resolve, reject){
  
  // 循环引用报错
  if(x === promise2){
    // reject 报错抛出
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  
  // 锁,防止多次调用
  let called;
  
  // x 不是 null 且 x 是对象或者函数
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+ 规定,声明then = x的then方法
      let then = x.then;
      
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') { 
        // then 执行 第一个参数是 this 后面是成功的回调 和 失败的回调
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // 核心点2:resolve 的结果依旧是 promise 那就继续递归执行
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(err);// 失败了就失败了
        })
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) { // 走到 catch 也属于失败
      if (called) return;
      called = true;
      // 取then出错了那就不要在继续执行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
复制代码

3.6 值穿透调用

什么是值穿透,就是当执行多个 then,我们期望最后那个 then 打印出 'Feng'。

new Promise((resolve, reject)=>{
    resolve('Feng');
}).then().then().then().then().then().then().then((res)=>{ 
    console.log(res);
})
复制代码

实现很简单:onFulfilled 如果不是函数,就忽略 onFulfilled,直接返回 value。

相应的,我们也要处理下没有 onRejected 的情况:onRejected 如果不是函数,就忽略 onRejected,直接扔出错误。

class Promise {
    constructor(executor) {
        this.status = "pending"; // 默认promise状态
        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函数,处理自己return的promise和默认的promise2的关系
                resolvePromise(promise2, 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);
                })
            }
        });
        
        // 返回 promise,达成链式效果
        return promise2;
    }
}

/**
 * 处理promise递归的函数
 *
 * promise2 {Promise} 默认返回的promise
 * x {*} 我们自己 return 的对象
 * resolve
 * reject
 */
 function resolvePromise(promise2, x, resolve, reject){
  
  // 循环引用报错
  if(x === promise2){
    // reject 报错抛出
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  
  // 锁,防止多次调用
  let called;
  
  // x 不是 null 且 x 是对象或者函数
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+ 规定,声明then = x的then方法
      let then = x.then;
      
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') { 
        // then 执行 第一个参数是 this 后面是成功的回调 和 失败的回调
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // 核心点2:resolve 的结果依旧是 promise 那就继续递归执行
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(err);// 失败了就失败了
        })
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) { // 走到 catch 也属于失败
      if (called) return;
      called = true;
      // 取then出错了那就不要在继续执行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
复制代码

3.7 Promise 对象方法 catch

catch 是失败的回调,相当于执行 this.then(null,fn)。

class Promise {
    constructor(executor) {
        this.status = "pending"; // 默认promise状态
        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函数,处理自己return的promise和默认的promise2的关系
                resolvePromise(promise2, 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);
                })
            }
        });
        
        // 返回 promise,达成链式效果
        return promise2;
    }

        catch(onRejected) {
            return this.then(null, onRejected)
        }
}

/**
 * 处理promise递归的函数
 *
 * promise2 {Promise} 默认返回的promise
 * x {*} 我们自己 return 的对象
 * resolve
 * reject
 */
 function resolvePromise(promise2, x, resolve, reject){
  
  // 循环引用报错
  if(x === promise2){
    // reject 报错抛出
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  
  // 锁,防止多次调用
  let called;
  
  // x 不是 null 且 x 是对象或者函数
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+ 规定,声明then = x的then方法
      let then = x.then;
      
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') { 
        // then 执行 第一个参数是 this 后面是成功的回调 和 失败的回调
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // 核心点2:resolve 的结果依旧是 promise 那就继续递归执行
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(err);// 失败了就失败了
        })
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) { // 走到 catch 也属于失败
      if (called) return;
      called = true;
      // 取then出错了那就不要在继续执行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
复制代码

4.手撕Vue3双向绑定

在vue2中,实现双向绑定,是使用Object.defineProperty()方法对属性设置get和set方法实现的。而Vue3的双向绑定则使用了es6中的proxy对象。

Proxy 翻译过来就是代理的意思,何为代理呢?就是 用 new 创建一个目标对象(traget)的虚拟化对象,然后代理之后就可以拦截JavaScript引擎内部的底层对象操作;这些底层操作被拦截后会触发响应特定操作的陷阱函数。

基本用法:

const p = new Proxy(target, handler)
复制代码

target

​ 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler

​ 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

我们看一下Proxy是如何使用的:

// 定义一个空对象
let data = {};
// 创建一个 Proxy , 将 data 作为目标对象
let proxy = new Proxy(data, {});
// 修改Proxy 代理对象的name属性
proxy.name = '枫';
console.log(proxy); // { name: '枫' }
console.log(data); // { name: '枫' }
复制代码

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。

参数

作用

handler.get()

属性读取操作的捕捉器。

handler.set()

属性设置操作的捕捉器。

handler.set

handler.set() 方法用于拦截设置属性值的操作。

// 定义一个对象
let data = {
  name: "枫",
  age: '23'
};
// 创建一个 Proxy , 将 data 作为目标对象
let p = new Proxy(data, {
  set(target, prop, value) {
    // target = 目标对象
    // prop = 设置的属性
    // value = 修改后的值
    console.log(target, prop, value)
    // { name: '枫', age: '23' } 'age' 18
    return Reflect.set(...arguments)
  }
});
// 直接修改p就可以了
p.age = 18;
console.log(data)
//{name: "枫", age: "23"}
复制代码

我们修改p的时候出发了set方法,获取到了修改后的属性,接下来我们需要把修改后的值赋给data。

加了一行代码: return Reflect.set(...arguments)。

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API 。我们需要在 handler.set()return 一个 Reflect.set(...arguments) 来进行赋值给目标对象。

Reflect.set

Reflect.set方法设置target对象的name属性等于value

// 定义一个对象
let data = {
  name: "枫",
  age: '23'
};
// 创建一个 Proxy , 将 data 作为目标对象
let p = new Proxy(data, {
  set(target, prop, value) {
    // target = 目标对象
    // prop = 设置的属性
    // value = 修改后的值
    console.log(target, prop, value)
    // { name: '枫', age: '23' } 'age' 18
    return Reflect.set(...arguments)
  }
});
// 直接修改p就可以了
p.age = 18;
console.log(data)
//{name: "枫", age: "18"}
复制代码

双向绑定

手写Vue的双向绑定貌似也没有很晦涩难懂,拿到参数,进行代理,数据渲染。

<!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({
            // 挂载元素
            el: "#app",
            data: {
                name: "枫",
                age: 23,
            }
        });
    </script>
</body>
</html>
复制代码

index.js

// EventTarget [6]
class Reactive extends EventTarget {
  // 接收参数
  constructor(options) {
    super();
    this.options = options;
    // data 赋值
    this.$data = this.options.data;
    // 挂载元素
    this.el = document.querySelector(this.options.el);
    // 调用 compile 函数
    this.compile(this.el);
    // 调用双向绑定
    this.observe(this.$data);
  }
  // 双向绑定
  observe(data) {
    // 备份this
    let _this = this;
    // 接收目标对象进行代理
    this.$data = new Proxy(data, {
      set(target, prop, value) {
        // 创建一个自定义事件 CustomEvent [5]
        // 事件名称使用的是 prop 
        let event = new CustomEvent(prop, {
          // 传入新的值
          detail: value
        })
        // 派发 event 事件
        _this.dispatchEvent(event);
        return Reflect.set(...arguments);
      }
    })
  }
  // 渲染数据
  compile(el) {
    // 获取el的子元素
    let child = el.childNodes;
    // 遍历判断是否存在文本
    [...child].forEach(node => {
      // 如果node的类型是TEXT_NODE
      if (node.nodeType === 3) {
        // 拿到文本内容
        let txt = node.textContent;
        // 正则匹配
        let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
        if (reg.test(txt)) {
          let $1 = RegExp.$1;
          this.$data[$1] && (node.textContent = txt.replace(reg, this.$data[$1]))
          // 绑定自定义事件
          this.addEventListener($1, e => {
            // 替换成传进来的 detail
            node.textContent = txt.replace(reg, e.detail)
          })
        }
        // 如果node的类型是ELEMENT_NODE
      } else if (node.nodeType === 1) {
        // 获取attr 
        let attr = node.attributes;
        // 判断是否存在v-model属性
        if (attr.hasOwnProperty('v-model')) {
          // 获取v-model中绑定的值
          let keyName = attr['v-model'].nodeValue;
          // 赋值给元素的value
          node.value = this.$data[keyName]
          // 绑定事件
          node.addEventListener('input', e => {
            // 当事件触发的时候我们进行赋值
            this.$data[keyName] = node.value
          })
        }
        // 递归执行
        this.compile(node)
      }
    })
  }
}
复制代码
分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改