手写JavaScript常用的函数

3,398 阅读10分钟

一、bind、call、apply函数的实现

改变函数的执行上下文中的this指向,但不执行该函数(位于Function构造函数的原型对象上的方法)

Function.prototype.myBind = function (target) {
    if (typeof this !== 'function') {
        throw Error('myBind is not a function')
    }
    var that = this
    var args1 = [...arguments].slice(1)
    var func = function () {
        var args2 = [..arguments].slice(1)
        return that.apply(target || window, args1.concat(args2))    }
    return func
}

Function.prototype.myCall = function (context=window) {
    if (typeof this !== 'function') {
        throw Error('myBind is not a function')    
    }     
    context.fn = this
    var args = [...arguments].slice(1)
    var result = context.fn(..args)    
    delete context.fn
    return result                                                                                                                                                                                                                                                                                                                                                                                                         
}

Function.prototype.myApply = function (context=window) {
    if (typeof this !== 'function') {
        throw Error('myApply is not a function')
    }
    context.fn = this
    var result
    if (argument[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn
    return result
}

二、深拷贝方法的实现

function cloneDeep (target, map = new WeakMap()) {
    const mapTag = '[object Map]';const setTag = '[object Set]';const arrayTag = '[object Array]';const objectTag = '[object Object]';const argsTag = '[object Arguments]';const boolTag = '[object Boolean]';const dateTag = '[object Date]';const numberTag = '[object Number]';const stringTag = '[object String]';const symbolTag = '[object Symbol]';const errorTag = '[object Error]';const regexpTag = '[object RegExp]';const funcTag = '[object Function]';const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
    function isObject (target) {
        return typeof target === 'function' || typeof target === 'obejct';
    }

    function getType (target) {
        return Object.prototype.toString.call(target);
    }
    if (!isObejct(target)) {
        return target;
    } 
    function getInit (target) {
        return new (target.constructor)
    }

    function cloneOtherType (target, type) {
    
    }
    
    const type = getType(target);
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target);
    }  else {
        retrun cloneOtherType(target, type);
    }
    if (map.get(target)) {
        return map.get(target);
    }
    map.set(target, cloneTarget);
    if (type === setTag) {
        target.forEach(item => {
            cloneTarget.add(deepClone(item, map));
        })
        return cloneTarget;
    } 
    if (type === mapTag) {
        target.forEach((key, value)=> {
            cloneTarget.set(key, deepClone(value, map));
        })
        return cloneTarget;
    }
    for (let key in target) {
        cloneTarget[key] = deepClone(target[key], map);
    }
    return cloneTarget;
}

思路:

  • 输入需要深拷贝的目标target输出深拷贝后的结果
  • 通过Object.prototype.toString准确判断传入的目标target的数据类型,当target的数据类型为对象或者数组时,会对target进行递归遍历直至当遍历的数组或者对象中的数据全部为基本数据类型为止

三、数组flat函数的实现

Array.prototype.flat = function () {
    var temp = []
    function recurision (arr) {
        for (var i = 0; i < arr.length; i++) {
            let item = arr[i]
            if (Array.isArray(item)) {
                recurision(item)
            } else {
                temp.push(item)
            }
        }
    }
    recurision(this)
    return temp
}

四、实现n的阶乘

分析:首先找规律,举例如3的阶乘等于3*2*1,也就是等于n*n-1*n-2的阶乘,也就是等于3*2*1的阶乘,计算到1的阶乘之后,整个计算过程才结束。分析到很容易想到通过递归来实现这个数的阶乘,因为第一,这个计算过程有规律可循,第二它有最终停止计算的出口,也就是当计算到1的时候就停止运算,以下通过递归来实现

function factorial (num) {
    if (num < 0) {
        throw new Error('负数没有阶乘')
    }
    if (num === 1 || num === 0) {
        return 1
    }
    return num * factorial(num-1)
}

factorial(3)  //6

五、实现斐波拉契数列

分析:按照上述阶乘的分析过程分析,这里不赘述

function fibonacci (n) {
  //此方法应使用尾递归法进行优化,这里不作优化,简单实现
  if ( n <= 1 ) {return 1};
  return fibonacci(n - 1) + fibonacci(n - 2);}

六、实现一个计算字符串字节长度的函数

分析:首先我们要知道英文的字节长度是1,而中文的字节长度是2,但是如何判断当前字符位是汉字还是英文呢,通过charCodeAt来判断当前字符位的unicode编码是否大于255,如何大于255则是汉字,那就给字符串的字节长度加2,如果小于255则是英文,就给字符串的字节长度加1,以下按照这个思路实现

function countBytesLength(str){
    var length = 0
    //首先遍历传入的字符串
    for(var i = 0; i < str.length; i++) {
        if (str[i].charCodeAt(i) > 255) {
            length += 2
        } else {
            length++
        }
    }
     return length
}

var str = 'DBCDouble陈'
countBytesLength(str) //11

七、实现isNaN函数

分析:要判断传入的值是否是"is not a number"(isNaN全拼),首先进行一个数字的隐式类型转换,通过Number包装类来实现Number(x),再判断Numberz(x)的返回值是否是NaN,如果是的话再与NaN进行比对,但是由于NaN虽然是number类型的,但是是不能进行比较的,所以我们先将Number(x)返回的结果变成字符串形式,再去判断,实现如下

function isNaN(num) {
    var ret = Number(num)
    ret += ''
    if (ret === 'NaN') {
        return true
    }
    return false
} 
isNaN('123abc') // true

八、实现数组的push函数

分析:首先push函数是位于Array构造函数的原型对象上的方法,所以要在Array.prototype上去定义,然后再分析push函数的作用是往数组的末尾添加元素,可以添加任意个数的元素,并且最终返回数组的长度,实现代码如下

Array.prototype.push = function () {
    for (var i = 0; i< arguments.length; i++) {
        this[this.length] = arguments[i]
    }
    return this.length
}

七、实现能够识别所有数据类型的typeof

分析:首先typeof是位于window对象上的全局方法,所以我们定义完成之后要将其挂载到window上,其次要实现识别所有数据类型包括:基本数据类型和复杂数据类型(引用数据类型),我们需要通过Object.prototype.toString方法去做才唯一能够最准确判断当前值为什么数据类型,实现代码如下

window.typeof = function (value) {
  return Object.prototype.toString.call(val).slice(8, -1)
}

八、实现数组的去重方法

分析:首先因为是给所有数组实例实现一个去重方法,所以同样是在原型链上进行编程

Array.prototype.unique = function () {
    //这里是利用对象键hash值的唯一性来去重
    var obj = {}
    var result = []
    for (var i = 0; i < this.length; i++) {
        if (!obj[this[i]]) {
            obj[this[i]] = true
            result.push(this[i])
        }
    }
    return result
}

var arr = [1,2,2,3,3]
arr.unique() //[1,2,3]

Array.prototype.unique = function () {
    //利用ES6的Array.prototype.includes
    var result = []
    for (var i = 0; i < this.length; i++) {
        if (!result.includes(this[i])) {
            result.push(this[i])
        }
    }
    return result
}

Array.prototype.unique = function () {
    //利用ES6的Set
    var result = new Set(this)   //生成一个类数组
    return Array.from(result)    //通过Array.from将类数组转换成真正的数组
}

Array.prototype.unique = function () {
    //利用Array.prototype.filter返回符合条件的元素
    //利用Array.prototype.indexOf返回数组中第一次出现当前元素的索引值
    //该方法写法最为优雅,一行代码搞定,函数式编程
    return this.filter((item, index) => this.indexOf(item) === index)
}

九、实现函数的防抖、节流

function debounce (fn, wait=300) {
    var timer
    return function () {
        if (timer) {
            clearTimeOut(timer)
        }
        timer = setTimeout({
            fn.apply(this, arguments) 
        }, wait)
    }
}

function throttle (fn, wait=300) {
    var prev = +new Date()
    return function () {
       var now = +new Date()
       if (prev - now > 300) {
          fn.apply(this, arguments)
          prev = now
       }
    }
}

十、封装ajax

//封装ajax方法
function ajax (options) {  
    options = options || {};  
    options.method = options.method || 'GET';  
    options.url = options.url || '';  
    options.async = options.async || true;  
    options.data = options.data || null;  
    options.success = options.success || function () {};  
    var xhr = null;  
    if (XMLHttpRequest) {    
        xhr = new XMLHttpRequest();  
    } else {    
        xhr = new ActiveXObject('Microsoft.XMLHTTP');
    }  
    xhr.open(options.method, options.url, options.async)  
    if (method === 'GET') {    
        xhr.send(null);  
    } else if (method === 'POST') {   
        xhr.send(options.data);  
    }  
    xhr.onreadystatechange = function () {    
        if (( (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 ) && xhr.readyState === 4) {      
            options.success(xhr.responseText)    
        }  
    }}

十一、实现new操作符

//接受一个函数
//最后返回一个对象
function new (fn) {
    return function () {
      var obj = { 
         '__proto__': fn.prototype
      }
      fn.apply(obj, arguments)      
      return obj
    }
}

十二、常用六种继承方式

1、原型链继承:子类型的原型对象为父类型的实例对象

function Person (name, age) {
    this.name = name 
    this.age = age
}

Person.prototype.setName = function () {
    console.log(this.name)
}

function Student (height) {
    this.height = height
}

Student.prototype = new Person()
var stu = new Student('175')
console.log(stu)

2、借用构造函数实现继承:在子类的构造函数中通过call调用父类的构造函数实现继承

function Person (name, age) {
    this.name = name 
    this.age = age
}

Person.prototype.setName = function () {
    console.log(this.name)
}

function Student (height, age, name) {
    Person.call(this, age, name)
    this.height = height
}

var stu = new Student(175, 'cs', 24)
console.log(stu)

3、原型链+借用构造函数的组合继承方式:通过在子类构造函数中通过call调用父类构造函数,继承父类的属性并保留传参的优点,再通过将父类的实例作为子类的原型对象,实现继承

function Person (name, age) {
    this.name = name 
    this.age = age
}


function Son (sex, name, age) {
    Person.call(this, name, age)
    this.sex = sex
}
Son.prototype = new Person()
var son = new Son('男', 'DBCDouble', 25)
console.log(son)

4、寄生组合式继承(圣杯继承)

function Person (name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.setAge = function () {}

function Son (sex) {
    this.sex = sex;
}

function F () {}
F.prototype = Person.prototype
Son.prototype = new F()
Son.prototype.constructor = Son
var s1 = new Son()
console.log(s1)

十三、instanceof和typeof实现

window.instanceof = instance
window.typeof = typeof

//instanceof的原理,实际就是沿着L的__proto__属性查找是否存在R.prototype,
//直到L.__proto__指向null
function instanceof (L, R) {
  var O = R.prototype
  L = L.__proto__
  while (true) {
    if (L === null) {
        return false
    }
    if (L === O) {
        return true
    }
    L = L.__proto__
  }
}

function typeof () {
    //使用Object.prototype.toString实现
}

十四、JS面向对象笔试题 《实现Cash》

1、实现Cash类、LazyMan

class Cash {
    constructor (value) {
        this.value = value
    }

    static add (...cashes) {
        return new Cash(cashes.reduce((result, item) => result += item, 0))
    }

    add (...cashes) {
        return Cash.add(this, ...cashes)
    }

    valueOf () {
        return this.value
    }

    toString () {
        const tempArr = String(this.value).split('')
        const len = tempArr.length
        return tempArr.map((item, index) => item * Math.pow(10, len - index - 1)).join('-')
    }
}

// 实现一个lazyMan,可以按照以下方式调用
// LazyMan('Hank');输出:
// Hi! This is Hank!
// LazyMan('Hank').sleep(10).eat('dinner');输出:
// Hi! This is Hank!
// //等待10秒..
// Wake up after 10s!
// Eat dinner~
// LazyMan('Hank').eat('dinner').eat('supper');输出:
// Hi This is Hank!
// Eat dinner~
// Eat supper~
// LazyMan('Hank').sleepFirst(5).eat('supper');输出:
// //等待5秒
// Wake up after 5
// Hi This is Hank!
// Eat supper~
// 以此类推。

function lazyMan (name) {
  class _lazyMan (name) {
    constructor (name) {
      this.task = [];
      const fn = () => {
         console.log(`Hi This is ${ name }`)
      }
      this.task.push(fn);
      settimeout(() => {
        this.next();
      }, 0)
    }
    next () {
      const fn = this.task.shift();
      fn === 'function' && fn();
    }
    eat (something) {
      const fn = () => {
        console.log(`Eat ${ something }`);
        this.next();
      }
      this.task.push(fn);
      return this;
    }
    sleep (timeout) {
      const fn = () => {
        settmeout(() => {
          console.log(`Wake up after ${ timeout }`)
          this.next();
        }, timeout * 1000)
      }
      this.task.push(fn);
      return this;
    }
    sleepFirst (timeout) {
      const fn = () => {
        settimeout(() => {
          console.log(`Wake up after ${ timeout }`);
          this.next();
        }, timeout * 1000)
      }
      this.task.unshift(fn);
      return this;
    }
  }
  return new _lazyMan(name);
}

十五、手写观察者模式、发布订阅模式

//观察者模式:被观察者和观察者是直接关联的,当被观察者状态发生变化时,会通知所有依赖它的观察者调
//用自己的更新方法
//发布-订阅模式:发布者和订阅者是不直接关联的,它们之间多了一个Event channel事件通道将它们俩联系
//起来,当发布者状态改变时,只会通知特定订阅者,而非所有的订阅者

//观察者模式
const Subject = (() => {
    const observers = [];
    const addOb = (...ob) => {
        observers.push(...ob)
    }
    const notify = () => {
        for (const ob of observers) {
            if (typeof fn === 'function') {
                ob.update();
            }
        }
    }
    return { addOb, notify }
})()

const ob1 = {
    update: () => console.log('通知ob1')
}

const ob2 = {
    update: () => console.log('通知ob2')
}

Subject.addOb(ob1, ob2);
Subject.notify(); // 通知ob1, 通知ob2

//发布-订阅者模式
const PubSub = (() => {
    const topics = {}
    const publish = (type, ...args) => {
        for (const fn of topics[type]) {
            fn(...args);
        }
    }
    const subscribe = (type, fn) => {
        if (!topics[type]) {
            topics[type] = []
        }
        topics[type].push(fn)
    }
    return { publish, subscribe }
})();

PubSub.subscribe('SubA', () => console.log('Trigger subscribe SubA function'))
PubSub,subscribe('SubB', () => console.log('Trigger subscribe SubB function'))
PubSub.publish('SubA')

十六、按照PromiseA+规范实现Promise

const resolvePromise = (promise2, x, resolve, reject) => {
    if (promise2 === x) {
        return reject(new TypeError('循环引用'));
    }
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;
            then.call(x, y => {
                resolvePromise(promise2, y, resolve, reject);
            }, err => {
                reject(err);
            })
        } catch (err) {
            reject(err);
        }    
    } else {
        resolve(x);
    }
}

class MyPromise {
    constructor (excutor) {
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.resolvedCallbacks = [];
        this.rejectedCallbacks = [];
    }
    const resolve = value => {
        this.status = 'fullfilled';
        this.value = value;
        this.resolvedCallbacks.forEach(fn => fn());
    }
    const reject = reason => {
        this.status = 'rejected';
        this.reason = reason;
        this.rejectedCallbacks.forEach(fn => fn());
    }

    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
    
    then (onFullFilled, onRejected) {
       onFullFilled = typeof onFullFilled === 'function' ? onFullFilled : v => v;
       onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
       let promise2;
       if (this.status === 'fullfilled') {
         promise2 = new MyPromise((resolve, reject) => {
           settimeout(() => {
             try {
               const x = onFullFilled(this.value);
               resolvePromise(promise2, x, resolve, reject);
             } catch (err) {
               reject(err);
             }
           })
         })
       } else if (this.status === 'rejected') {
          promise2 = new MyPromise((resolve, reject) => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          })
       } else if (this.status === 'pending') {
          promise2 = new MyPromise((resolve, reject) => {
            this.onResolvedCallback.push(() => {
              settimeout(() => {
                try {
                    const x = onFullFilled(this.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (err) {
                    reject(err);
                }
              }, 0)
            })
            this.onRejectedCallback.push(() => {
                settimeout(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                }, 0)
            })
          })
       }
       return promise2;
    }
    catch (onRejected) {
        return this.then(null, onRejected);
    }

    static all (promises) {
        return new MyPromise(resolve => {
            const arr = [];
            let i = 0;
            const processData = (index, data) => {
                arr[index] = data;
                if (++i === promises.length) {
                    resolve(arr);
                }
            }
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(data => {
                    processData(i, data);
                })
            }
        })
    }

    static race (promises) {
        return new MyPromise((resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(resolve, reject);
            }
        })
    }

    static resolve (value) {
        return new myPromise(resolve => resolve(value));
    }

    static reject (reason) {
        return new myPromise((resolve, reject) => reject(reason));
    }
}

十六、实现Express中间件原理

//index.js node入口文件
const express = require('express');
const app = express();

const middleware1 = (req, res, next) => {
    console.log('middleware1 start');
    next();
    console.log('middleware1 end');
}
const middleware2 = (req, res, next) => {
    console.log('middleware2 start');
    next();
    console.log('middleware2 end');
}
const middleware3 = (req, res, next) => {
    console.log('middleware3 start');
    next();
    console.log('middleware3 end')
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);

// express.js实现
//从index.js入口文件分析可知,express返回的就是一个函数,返回的app是个引用类型数据,且存在
//一个use方法,并且在需要创建http服务
const http = require('http');
function express () {
    let middlewares = [];
    let i = 0;
    const app = (req, res) => {
        function next () {
            let task = middlewares[i++];
            if (!task) return;
            task(req, res, next); 
        }
        next();
    }
    app.use = function (...newMiddlewares) {
        middlewares = [...middlewares, ...newMiddlewares];
    }
    http.createServer(app).listen(3000, () => console.log('http server is running at 3000'))
    return app;
}

//总结:将中间件按顺序存在一个数组中,每次当前执行中间件的时候,给当前中间件提供一个调用下个中间件的
//参数来让调用方定制化何时调用下一个中间件

十七、React16和React17架构react-dom实现

react17和react16对比最大的不同是16是对虚拟dom对象进行栈递归来实现深度优先遍历,栈递归的特点是无法打断且容易内存溢出,也就意味着每次发生状态更新,都要从根节点开始做一次性无法打断的栈递归做深度优先遍历,一旦当cpu内存不足时,可能发会发生预期之外的更新或者更新失败,因为一般来说浏览器的刷新率是60Hz,也就是一秒钟刷新60次,也就是说每次刷新被分配的时间为1000ms/60次 = 16.6ms来完成js脚本执行 -> 样式布局 -> 样式绘制。还有在产生状态更新时发现内存不足,而此时用户触发点击事件或输入框输入(在react中被称为离散事件)时,会发现点击事件无法触发或输入框输入无法聚焦,会让用户感到明显很卡顿的感觉;在react17中,对这种不可打断的更新做了优化,将单次庞大的不可中断的更新任务拆分成了时间分片,说白了就是讲一个大的更新任务拆分成一个个小的任务,每完成一次小的任务后,就判断当前浏览器是否还剩可剩余空闲时间,再决定是否继续下一个任务,且在react17架构中新增了调度器Scheduler模块,来实现根据优先级来更新的策略,这样的话就能对每一种更新任务打上对应优先级的tag来做更新

// react16的react-dom.js

function updateProps (node, props) {
    Obect.keys(props).filter(p => p !== 'children').forEach(key => {
        if (key === 'className') {
            node.setAttribute('class', props[key]);
        } else {
            node.setAttribute(key, props[key]);
        }
    })
}

function reconcileChildren (node, childVnode) {
    const newChildVnode = Array.isArray(childVnode) ? childVnode : [childVnode];
    newChildVnode.forEach(child => render(child, node));
}

function updateHostComponent (vnode) {
    const { type, props, children } = vnode;
    const node = document.createElement(type);
    updateProps(node, props);
    children && reconcileChildren(node, children);
    return node;
}

function updateFunctionComponent (vnode) {
    const { type, props } = vnode;
    const newVnode = type(props);
    const node = createNode(newVnode);
    return node;
}

function updateClassComponent (vnode) {
    const { type, props } = vnode;
    const newVnode = (new type(props)).render();
    const node = createNode(newVnode);
    return node;
}

function updateTextNode (vnode) {
    const node = document.createTextNode(vnode);
    return node;
}

function createNode (vnode) {
    const { type, children } = vnode;
    if (typeof type === 'string') {
        node = updateHostComponent(vnode);
    } else if (typeof type === 'function') {
        node = type.prototype.isReactComponent ? 
        updateClassComponent(vnode) : 
        updateFunctionComponent(vnode);
    } else if (typeof vnode === 'string') {
        node = updateTextNode(vnode);
    }
    return node;
}

function render (vnode, container) {
    const node = createNode(vnode);
    container.appendChild(node);
}

export default { 
    render 
}

// react17的react-dom.js

//给react的render阶段使用
let nextUnitOfWork = null;

//给react的commit阶段使用
let wipRoot = null;

function render (vnode, container) {
    wipRoot = {
        type: 'div',
        props: {
            children: { ...vnode }
        },
        child: null,
        sibling: null,
        return: null,
        stateNode: container
    }
    //任务从应用根节点开始
    nextUnitOfWork = wipRoot;
}

function updateProps (ndoe, props) {
    Object.keys(props).forEach(key => {
        if (key === 'children') {
            node.textContent = props[key];
        } else {
            if (key === 'className') {
                node.setAttribute('class', props[key]);
            } else {
                node.setAttribute(key, props[key]);
            }   
        }
    })
}

function createStateNode (workInProgress) {
    const { type, props } = workInProgress;
    const node = document.createElement(type);
    updateProps(node, props);
    return node;
}

function reconcileChildren (workInProgress, children) {
    if (typeof children === 'string' || typeof children === 'number') return;
    const newChildren = Array.isArray(children) ? children : [children];
    let preFiber = null;
    newChildren.forEach((child, index) => {
        let newFiber = {
            type: child.type,
            props: child.props,
            stateNode: null,
            child: null,
            sibling: null,
            return: workInProgress
        };
        if (index === 0) {
            workInProgress.child = newFiber;
        } else {
            preFiber.sibling = newFiber;
        }
        preFiber = newFiber;
    })
}

function updateHostComponent (workInProgress) {
    if (!workInProgress.stateNode) {
        workInProgress.stateNode = createStateNode(workInProgress);
    }
    reconcileChildren(workInProgress, workInProgress.props.children);
}

function updateFunctionComponent (workInProgress) {
    const { type, props } = workInProgress;
    const children = type(props);
    reconcileChildren(workInProgress, children);
}

function performUnitOfWork (workInProgress) {
    const { type } = workInProgress;
    if (typeof type === 'string') {
        updateHostComponent(workInProgress);
    } else if (typeof type === 'function') {
        updateFunctionComponent(workInProgress);
    }
    if (workInProgress.child) {
        return workInProgress.child;
    }
    let nextFiber = workInProgress;
    while (nextFiber) {
        if (nextFiber.sibling) {
            return next.sibling;
        } else {
            nextFiber = nextFiber.return;
        }
    }
}

function commitWork (workInProgress) {
    if (!workInProgress) {
        return;
    }

    let parentFiber = workInProgress.return;
    while (!parentFiber) {
        parentFiber = parentFiber.return;
    }
    let parentNode = parentFiber.stateNode
    workInProgress.stateNode && parentNode.appendChild(workInProgress.stateNode);
    commitWork(workInProgress.child);
    commitWork(workInProgress.sibling);
}

function commitRoot () {
    commitWork(wipRoot.child);
    wipRoot = null;
}

function workLoop (IdleDeadLine) {
    while (nextUnitOfWork && IdleDeadLine.timeRemaining() > 1) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
    if (!nextUnitOfWork && wipRoot) {
        commitRoot();
    }
}

//这里用requestIdleCallback来模拟React的调度器Schedule
window.requestIdleCallback(workLoop)

export default {
    render
}

十八、redux与react-redux

//关于redux相关api

funtion createStore (reducer, enhancer) {
    if (enhancer) {
        return enhancer(createStore)(reducer);
    }
    
    let state = {};
    const listeners = [];

    function subscribe (listener) {
        listeners.push(listener);
    }

    function dispatch (action) {
        state = reducer(state, action);
        listeners.forEach(listener => listener());
    }

    function dispatch () {
        return state;
    }

    dispatch({ type: 'INIT_STATE' })

    return {
        subscribe,
        getState,
        dispatch
    }
}

function combineReducers (reducers) {
    let finalReducers = {};
    for (let key in reducers) {
        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key];
        }
    }
    return (state, action) => {
       const result = {};
        for (var key in finalReducers) {
            const reducer = finalReducers[key];
            const reducerState = reducer(state[key], action);
            result[key] = reducerState;
        }
       return result;
    }
}

function bindActionCreators (actionCreators, dispatch) {
    let result = {};
    for (let key in actionCreators) {
        const actionCreator = actionCreators[key];
        result[key] = (...args) => dispatch(actionCreator(...args));
    }
    return result;
}

function applyMiddleware (...middlewares) {
    return createStore => (...args) => {
        const store = createStore(...args);
        const middlewareApi = {
            getState,
            dispatch: (...args) => dispatch(...args);
        } 
        const chains = middlewares.map(middleware => middleware(middlewareApi))
        dispatch = compose(...chains)(dispatch);
        return {
            ...store,
            dispatch
        }
    }
}

function compose (...funs) {
    if (funcs.length === 0) {
        return args => args;
    }

    if (funcs.length === 1) {
        return funs[0];
    }

    return funcs.recduce((ret, item) => (...args) => ret(item(...args)));
}


//react-redux的相关api

class Provider extends React.Component {
    constructor (props) {
        super(props);
        this.store = props.store;
    }

    static childContextType = {
        store: PropTypes.obejct
    }
    
    getChildContext () {
       return { store: this.store };
    }
    return this.props.children;
}

const connect = (mapStateToProps, mapDispatchStateToProps) => WrapperComponent => {
    return class Connect extends React.Component {
        constructor (props, context) {
            super(props, context);
            this.store = context.store;
            this.state = {
                props: {}
            }
        }

        static contextType = {
            store: propTypes.obejct
        }

        componentWillMount () {
            this.store.subscribe(this.update);
            this.update();
        }

        update () {
            const { getState, dispatch } = this.store;
            const stateToProps = mapStateToProps(getState());
            const dispatchStateToProps = bindActionCreators(mapDispatchStateToProps, dispatch);
            this.setState({
                props: {
                    ...this.state.props,
                    ...stateToProps,
                    ...dispatchStateToProps
                }
            })
        }
        render () {
            const { props } = this.state;
            return <WrapperComponent { ...props } />
        }
    }
}

十九、实现简单的webpack打包

//webpack.js
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');

function getModuleInfo (file) {
    const body = fs.readFileSync(file, 'utf-8');
    const ast = parser.parse(body, {
        sourceType: 'module'
    });
    const deps = {};
    traverse(ast, {
        ImportDeclaration ({ node }) {
            const dirname = path.dirname(file);
            const absPath = './' + path.join(dirname, node.source.value);
            deps[node.source.value] = absPath;
        }
    })
    const { code } = babel.transformFromAst(ast, null, {
        preset: ["@babel/preset-env"]
    });
    const moduleInfo = { file, deps, code }
    return moduleInfo;
}

function parseModules (file) {
    const entry = getModuleInfo(file);
    cont temp = [entry];
    const modules = {};
    for (let i = 0; i < temp.length; i++) {
        const { deps } = temp[i];
        if (deps) {
            for (let key in deps) {
                if (deps.hasOwnProperty(key)) {
                    temp.push(getModuleInfo(deps[key]));
                }
            }
        }
    }
    temp.forEach(item => {
        modules[file] = `((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
            eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ \"./src/utils.js\");\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_utils__WEBPACK_IMPORTED_MODULE_0__.a + _utils__WEBPACK_IMPORTED_MODULE_0__.default);\n\n// __webpack_require__.r(__webpack_exports__);\n// __webpack_require__.d(__webpack_exports__, {\n//   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n// });\n// var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/utils.js\");\n// const __WEBPACK_DEFAULT_EXPORT__ = (_utils__WEBPACK_IMPORTED_MODULE_0__.a + _utils__WEBPACK_IMPORTED_MODULE_0__.default);\n// console.log(_utils__WEBPACK_IMPORTED_MODULE_0__.default);\n// console.log(_utils__WEBPACK_IMPORTED_MODULE_0__.a);\n\n//# sourceURL=webpack://webpack5/./src/index.js?");            /***/ })        })`

    })
}

function bundle (file) {
    const modules = parseModules(files);
    return `(() => {
        "use strict"
        var __webapck_modules__ = modules;
        var __webpack_module_cache__ = {};

        function __webpack_require__ (moduleId) {
            var cacheModule = __webpack_module_cache__[moduleid];
            if (cacheModule !== undefined) {
                return cacheModule.exports;
            }
            var module = __webpack_module_cache[moduleId] = {
                exports: {}
            }
            __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
            return module.exports;
        }
    
        (() =>{
            __webpack_require__.o = function () {}
        })();
    
        (() => {
            __webpack_require__.d = function () {}
        })();

        (() => {
            __webpack_require__.r = function () {}
        })()
        __webpack_require__(file);
    })()`
}

const content = bundle('./src/index.js');
fs.mkdirSync('dist');
fs.writeFileSync('./dist/bundle.js', content);

十二、关于Promise的笔试题

// var arr = [1,2,3]
//使用promise实现每隔1s输出1,2,3

function countDown (arr) {
    arr.reduce((p,x) => {
        return p.then(() => {
            return new Promise(reslove => setTimeout(() => console.log(x)), 1000))
        })
    }, Promise.resolve());
}

//红灯3s亮一次,黄灯2s亮一次,绿灯1s亮一次;如何让三个灯不断交替亮灯?用Promise实现,两个亮灯函数已经存在
function red () {
    console.log('red');
}

function yellow () {
    console.log('yellow');
}

function green () {
    console.log('green')
}

function light (cb, timeout) {
    return new Promise(resolve => {
        setTimeout({
            cb();
            resolve();
        }, timeout);
    })
}

function step () {
    Promise.resolve().then(() => {
        return light(red, 3000);
    }).then(() => {
        return light(yellow, 2000);
    }).then(() => {
        return light(green, 1000)
    }).then(() => {
        step();
    })
}

step();

//我们知道Promise.all接收一个promise数组实现并发请求,但是是请求是无序的,现在需要你实现一个有序的
//mergePromise

function mergePromise (promises) {
    const result = []
    return promises.reduce((p, x) => {
        return p.then(res => {
            result.push(res);
            return result;
        })
    }, Promise.resolve());
}

//实现Promise.all
Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        cons result = [];
        let index = 0;

        for (let i = 0; i < promises.length; i++) {
            promises[i].then(res => {
                result[i] = res;
                if (++index === result.length) {
                    resolve(result);
                }
            })
            
        }
    })
}

//实现Promise.race
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i< promises.length; i++) {
            promises[i].then(resolve, reject);
        }
    })
}

//实现控制Promise并发数量

class Schedule {
    constructor (maxNum) {
        this.maxNum = maxNum;
        this.list = [];
        this.workingNum = 0;
    }

    addTask (...promises) {
        this.list.push(...promises);
    }

    start () {
        for (let i = 0; i < maxNum; i++) {
            this.doNext();
        }
    }

    
    
    doNext () {
        if (this.list.length && this.workingNum < this.maxNum) {
            this.workingNum++;
            const promise = this.length.shift();
            promise().then(() => {
                this.workingNum--;
                this.doNext();
            })
        }
    }
}