手写实现

410 阅读11分钟

手写实现

Promise.all

let PromiseAll = function (promiseArr) {
    let resultArr = [];
    let promiseArrLength = promiseArr.length;
    let resolveCount = 0;
    return new Promise((resolve, reject) => {
        promiseArr.forEach((item, index) => {
            item.then(res => {
                resolveCount++;
                resultArr[index] = res;
                if (resolveCount === promiseArrLength) {
                    resolve(resultArr)
                }
            }).catch(err => {
                reject(err)
            });
        })
    })
}

const p1 = new Promise(res => res(1))
const p2 = new Promise(res => res(1))
const p3 = new Promise(res => res(1))
const su = PromiseAll([p1, p2, p3])
su.then(res => {
    console.log(res)
}).catch(res => {
    console.log(res)
})

限制请求并发数量

let limitTask2 = function (arrParams, maxTask, cb) {
    let resultArr = [];
    let arrLength = arrParams.length;
    if (!arrLength) return Promise.resolve();
    let resolveCount = 0;
    let fistTaskList = arrParams.splice(0, maxTask);
    return new Promise((resolve, reject) => {
        requestAction(fistTaskList)
        function requestAction(requestItem){
            if(requestItem){
                requestItem.forEach((item, index) => {
                    cb(item).then(res => {
                        successFun(res, index)
                    }).catch(err => {
                        reject(err)
                    });
                })
            }else{
                let nextTast = arrParams.shift();
                if(!nextTast){
                    return;
                }
                let otherLength = arrParams.length;
                cb(nextTast).then(res => {
                    successFun(res, arrLength - otherLength - 1);
                }).catch(err => {
                    reject(err)
                })
            }
        }
        function successFun(res, index) {
            resolveCount++;
            resultArr[index] = res;
            if (resolveCount === arrLength) {
                resolve(resultArr);
                return;
            }
            requestAction();
        }
    })
}

const cb = (timeout) =>  new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(timeout);
    }, timeout)
})
console.time();
const su = limitTask2([1000, 2000, 3000], 2, cb)
su.then(res => {
    console.log(res)
    console.timeEnd();
}).catch(res => {
    console.log(res)
})

防抖

function debounce(func, wait) {
  let timeout;
  return function() {
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(this, arguments);
    }, wait);
  };
}
// 使用
window.onscroll = debounce(function() {
  console.log('debounce');
}, 1000);

节流

function throttle(fn, delay) {
  var prevTime = Date.now();
  return function() {
    var curTime = Date.now();
    if (curTime - prevTime > delay) {
      fn.apply(this, arguments);
      prevTime = curTime;
    }
  };
}
// 使用
var throtteScroll = throttle(function() {
  console.log('throtte');
}, 1000);
window.onscroll = throtteScroll;

Bind Call Apply

Function.prototype.myBind = function(context, ...args){
    if(typeof context!== "function"){
        throw "err"
    }
    const fn = this;
    return function(){
        return fn.call(context, ...args, ...arguments)
    }
}
Function.prototype.myBind = function (context) {
    if(typeof this !== 'function'){
    	return new Error("not function");
    }
    const _this = this;
    let args = [...arguments].slice(1);
    return function F(){
        args = args.concat(...arguments);
    	if(this instanceof F){
    		return new _this(...args);
        }else{
    		_this.apply(context, args);
        }
    }
}

Function.prototype.myCall = function(context, ...args){
    context._fn = this;
    const result = context.fn(...args);
    delete context._fn;
    return result;
}

Function.prototype.myApply = function(context, args){
    context._fn = this;
    const result = context.fn(...args);
    delete context._fn;
    return result;
}

深克隆

function deepClone(data) {
    function judgeType(obj) {
        return Object.prototype.toString.call(obj).slice(8,-1);
    }
    const type = judgeType(data);
    let obj;
    if (type === 'Array') {
        obj = [];
        for (let i = 0, len = data.length; i < len; i++ ) {
            obj.push(deepClone(data[i]));
        }
    } else if (type === 'Object') {
        obj = new data.constructor ();//可保持继承链,解决该问题:如果obj中的对象是由构造函数生成的,则使用深拷贝后,会丢弃对象的constructor;
        for (let key in data) {
            obj[key] = deepClone(data[key]);//实现深克隆的关键
        }
    } else {
        return data;
    }
    return obj;
}

instanceOf

// L 表示左表达式,R 表示右表达式
function instance_of(L, R) {
    var O = R.prototype;
    L = L.__proto__;
    while (true) {
        if (L === null) return false;
        // 这里重点:当 O 严格等于 L 时,返回 true
        if (O === L) return true;
        L = L.__proto__;
    }
}

New

function myNew() {
    // 创建一个实例对象
    var obj = new Object();
    // 取得外部传入的构造器
    var Constructor = Array.prototype.shift.call(arguments);
    // 实现继承,实例可以访问构造器的属性
    obj.__proto__ = Constructor.prototype;
    // 调用构造器,并改变其 this 指向到实例
    var ret = Constructor.apply(obj, arguments);
    // 如果构造函数返回值是对象则返回这个对象,如果不是对象则返回新的实例对象
    return typeof ret === 'object' && ret !== null ? ret : obj;
}

Object.create

Object.create = Object.create || function(obj){
  var F = function(){};
  F.prototype = obj;
  return new F();
}

柯里化

function createCurry(func, args) {
  var argity = func.length;
  var args = args || [];
  
  return function () {
    var _args = [].slice.apply(arguments);
    args.push(..._args);
    
    if (args.length < argity) {
      return createCurry.call(this, func, args);
    }
    
    return func.apply(this, args);
  }
}

手写Promise

class MyPromise {
  constructor(fn) {
    this.resolvedCallbacks = [];
    this.rejectedCallbacks = [];
    
    this.state = 'PENDING';
    this.value = '';
    
    fn(this.resolve.bind(this), this.reject.bind(this));
    
  }
  
  resolve(value) {
    if (this.state === 'PENDING') {
      this.state = 'RESOLVED';
      this.value = value;
      
      this.resolvedCallbacks.map(cb => cb(value));   
    }
  }
  
  reject(value) {
    if (this.state === 'PENDING') {
      this.state = 'REJECTED';
      this.value = value;
      
      this.rejectedCallbacks.map(cb => cb(value));
    }
  }
  
  then(onFulfilled, onRejected) {
    if (this.state === 'PENDING') {
      this.resolvedCallbacks.push(onFulfilled);
      this.rejectedCallbacks.push(onRejected);
      
    }
    
    if (this.state === 'RESOLVED') {
      onFulfilled(this.value);
    }
    
    if (this.state === 'REJECTED') {
      onRejected(this.value);
    }
  }
}

手写jsonp

function jsonp ({url, query}) {
    let script = document.createElement("script");
    let cb = `jsonpCallBack${new Date().getTime()}${Math.floor(Math.random(5)*100000)}`
    let params = {...query, cb}

    let arr = []
    for (let key in params) {
      arr.push(`${key}=${params[key]}`)
    }

    script.src = `${url}?${arr.join("&")}`
    document.body.appendChild(script)

    return new Promise((resolve, rej) => {
        window[cb] = function (res) {
              resolve(res)
              document.body.removeChild(script)
              delete window[cb]
          }
      })
}

jsonp({
    url:'/getList',
    query: {name: 'ys',age: 19}
}).then((res) => {
  	console.log(res)
})

柯里化

const sum = (a, b, c, d) => a + b + c + d;
function curry2(fn){
    return function judge(...args){
        console.warn(args,fn);
        if(args.length >= fn.length){
            return fn(...args)
        }else{
            return function (...arg){
                return judge(...args, ...arg)
            }
        }
    }
}
const currySum = curry2(sum);
console.warn(currySum(1)(2)(3)(4));

数据结构和算法

常用数据结构

  • 数组 适合查找 不适合插入
  • 链表 适合插入 不适合查找 需要从头找任何元素
  • 集和 就是Set 由一组无序且唯一的项组成
  • 字典 就是对象 以键和值的方式存储
    • 根节点 内部节点 外部节点(叶子节点)
    • 二叉树 每个节点最多有两个子节点
    • 二叉搜索树 BST 只允许左侧节点比父小 右侧节点比父大或等于父节点
    • 二叉搜索树
  • 中序遍历 从最小向最大遍历
  • 先序遍历
  • 后序遍历

排序

  • 冒泡
  • 快排序
  • 归并排序
    • 分而治之的思想 都拆分成最后为一个的数组
    • 拆分成左右两个数组 同时两个数组需要按照顺序排列 并且按照顺序返回
  • 选择排序 找出最高放第一个 再剩余中找出最高放第二个
  • 插入排序 分为已排好 和未排好 每次从未排好拿出一个放在已经排好里

冒泡

// 先实现一个交换数据的方案
function swap(myArray, p1, p2){
    var temp = myArray[p1];
    myArray[p1] = myArray[p2];
    myArray[p2] = temp;
}
function bubbleSort(myArray){
    var len = myArray.length;
    var i;
    var j;
    var stop;
    for (i = 0; i < len - 1; i++){
        for (j = 0, stop = len - 1 - i; j < stop; j++){
            if (myArray[j] > myArray[j + 1]){
              	swap(myArray, j, j + 1);
            }
        }
    }
    return myArray;
}

快排

它的基本思想很简单:先确定一个“支点”(pivot),将所有小于“支点”的值都放在该点的左侧,大于“支点”的值都放在该点的右侧,然后对左右两侧不断重复这个过程,直到所有排序完成。

var quickSort = function(arr) {
  if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++){
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
};

选择排序

每次找到最小的放入空数组

选择排序(Selection Sort)与冒泡排序类似,也是依次对相邻的数进行两两比较。不同之处在于,它不是每比较一次就调换位置,而是一轮比较完毕,找到最大值(或最小值)之后,将其放在正确的位置,其他数的位置不变。

function selectionSort(myArray){
    var len = myArray.length,
        min;
    for (i=0; i < len; i++){
        // 将当前位置设为最小值
        min = i;
        // 检查数组其余部分是否更小
        for (j=i+1; j < len; j++){
            if (myArray[j] < myArray[min]){
                min = j;
            }
        }
        // 如果当前位置不是最小值,将其换为最小值
        if (i != min){
            swap(myArray, i, min);
        }
    }
    return myArray;
}

插入排序

一次排序一个

插入排序(insertion sort)比前面两种排序方法都更有效率。它将数组分成“已排序”和“未排序”两部分,一开始的时候,“已排序”的部分只有一个元素,然后将它后面一个元素从“未排序”部分插入“已排序”部分,从而“已排序”部分增加一个元素,“未排序”部分减少一个元素。以此类推,完成全部排序。

function insertionSort(myArray) {

    var len     = myArray.length,     // 数组的长度
        value,                      // 当前比较的值
        i,                          // 未排序部分的当前位置
        j;                          // 已排序部分的当前位置
    for (i=0; i < len; i++) {
        // 储存当前位置的值
        value = myArray[i];
        /*
         * 当已排序部分的当前元素大于value,
         * 就将当前元素向后移一位,再将前一位与value比较
         */
        for (j=i-1; j > -1 && myArray[j] > value; j--) {
            myArray[j+1] = myArray[j];
        }
        myArray[j+1] = value;
    }
    return myArray;
}

合并排序

它的基本思想是,将两个已经排序的数组合并,要比从头开始排序所有元素来得快。因此,可以将数组拆开,分成n个只有一个元素的数组,然后不断地两两合并,直到全部排序完成。

function merge(left, right){
    var result  = [],
        il      = 0,
        ir      = 0;
    while (il < left.length && ir < right.length){
        if (left[il] < right[ir]){
            result.push(left[il++]);
        } else {
            result.push(right[ir++]);
        }
    }
    return result.concat(left.slice(il)).concat(right.slice(ir));
}
function mergeSort(myArray){
    if (myArray.length < 2) {
        return myArray;
    }
    var middle = Math.floor(myArray.length / 2),
        left    = myArray.slice(0, middle),
        right   = myArray.slice(middle);

    return merge(mergeSort(left), mergeSort(right));
}

最大连续子数组

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

var maxSubArray = function(array) {
    if (array.length == 0)
        return 0
    var sum = array[0] //保存每组的和
    var maxSum = array[0] //连续子数组最大和
    for (var i = 1; i < array.length; i++) {
        sum = Math.max(sum + array[i], array[i]);
        maxSum = Math.max(sum, maxSum)
    }
	return maxSum
};

手写Promise

// 判断变量否为function
const isFunction = variable => typeof variable === 'function'
// 定义Promise的三种状态常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
    constructor(handle) {
        if (!isFunction(handle)) {
            throw new Error('MyPromise must accept a function as a parameter')
        }
        // 添加状态
        this._status = PENDING
        // 添加状态
        this._value = undefined
        // 添加成功回调函数队列
        this._fulfilledQueues = []
        // 添加失败回调函数队列
        this._rejectedQueues = []
        // 执行handle
        try {
            handle(this._resolve.bind(this), this._reject.bind(this))
        } catch (err) {
            this._reject(err)
        }
    }

    // 添加resovle时执行的函数
    _resolve(val) {
        const run = () => {
            if (this._status !== PENDING) return
            this._status = FULFILLED
            // 依次执行成功队列中的函数,并清空队列
            const runFulfilled = (value) => {
                let cb;
                while (cb = this._fulfilledQueues.shift()) {
                    cb(value)
                }
            }
            // 依次执行失败队列中的函数,并清空队列
            const runRejected = (error) => {
                let cb;
                while (cb = this._rejectedQueues.shift()) {
                    cb(error)
                }
            }
            /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
              当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
            */
            if (val instanceof MyPromise) {
                val.then(value => {
                    this._value = value
                    runFulfilled(value)
                }, err => {
                    this._value = err
                    runRejected(err)
                })
            } else {
                this._value = val
                runFulfilled(val)
            }
        }
        // 为了支持同步的Promise,这里采用异步调用
        setTimeout(run, 0)
    }

    // 添加reject时执行的函数
    _reject(err) {
        if (this._status !== PENDING) return
        // 依次执行失败队列中的函数,并清空队列
        const run = () => {
            this._status = REJECTED
            this._value = err
            let cb;
            while (cb = this._rejectedQueues.shift()) {
                cb(err)
            }
        }
        // 为了支持同步的Promise,这里采用异步调用
        setTimeout(run, 0)
    }

    // 添加then方法
    then(onFulfilled, onRejected) {
        const {_value, _status} = this
        // 返回一个新的Promise对象
        return new MyPromise((onFulfilledNext, onRejectedNext) => {
            // 封装一个成功时执行的函数
            let fulfilled = value => {
                try {
                    if (!isFunction(onFulfilled)) {
                        onFulfilledNext(value)
                    } else {
                        let res = onFulfilled(value);
                        if (res instanceof MyPromise) {
                            // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    // 如果函数执行出错,新的Promise对象的状态为失败
                    onRejectedNext(err)
                }
            }
            // 封装一个失败时执行的函数
            let rejected = error => {
                try {
                    if (!isFunction(onRejected)) {
                        onRejectedNext(error)
                    } else {
                        let res = onRejected(error);
                        if (res instanceof MyPromise) {
                            // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    // 如果函数执行出错,新的Promise对象的状态为失败
                    onRejectedNext(err)
                }
            }
            switch (_status) {
                // 当状态为pending时,将then方法回调函数加入执行队列等待执行
                case PENDING:
                    this._fulfilledQueues.push(fulfilled)
                    this._rejectedQueues.push(rejected)
                    break
                // 当状态已经改变时,立即执行对应的回调函数
                case FULFILLED:
                    fulfilled(_value)
                    break
                case REJECTED:
                    rejected(_value)
                    break
            }
        })
    }

    // 添加catch方法
    catch(onRejected) {
        return this.then(undefined, onRejected)
    }

    // 添加静态resolve方法
    static resolve(value) {
        // 如果参数是MyPromise实例,直接返回这个实例
        if (value instanceof MyPromise) return value
        return new MyPromise(resolve => resolve(value))
    }

    // 添加静态reject方法
    static reject(value) {
        return new MyPromise((resolve, reject) => reject(value))
    }

    // 添加静态all方法
    static all(list) {
        return new MyPromise((resolve, reject) => {
            /**
             * 返回值的集合
             */
            let values = []
            let count = 0
            for (let [i, p] of list.entries()) {
                // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
                this.resolve(p).then(res => {
                    values[i] = res
                    count++
                    // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
                    if (count === list.length) resolve(values)
                }, err => {
                    // 有一个被rejected时返回的MyPromise状态就变成rejected
                    reject(err)
                })
            }
        })
    }

    // 添加静态race方法
    static race(list) {
        return new MyPromise((resolve, reject) => {
            for (let p of list) {
                // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
                this.resolve(p).then(res => {
                    resolve(res)
                }, err => {
                    reject(err)
                })
            }
        })
    }

    finally(cb) {
        return this.then(
            value => MyPromise.resolve(cb()).then(() => value),
            reason => MyPromise.resolve(cb()).then(() => {
                throw reason
            })
        );
    }
}

Vue

Vue和React和Angular有什么区别

Vue的设计之初借鉴了Angular的模板语法和数据绑定,同时又参考了React的组件化思想和虚拟Dom技术。

MVVM

Modal-View-ViewModal,Modal标识模型层,View标识试图层,ViewModal是View和Modal的桥梁,数据更新到viewModal层并自动渲染到页面中,试图的变化又会更新数据。

Vue如何实现响应式

Vue2: Object.defineProperty 重新定义 data 中所有的属性, Object.defineProperty 可以使数据的获取与设置增加一个拦截的功能,拦截属性的获取,进行依赖收集。拦截属性的更新操作,进行通知。 具体的过程:首先Vue使用 initData 初始化用户传入的参数,然后使用 new Observer 对数据进行观测,如果数据是一个对象类型就会调用 this.walk(value) 对对象进行处理,内部使用 defineeReactive 循环对象属性定义响应式变化,核心就是使用 Object.defineProperty 重新定义数据。

Vue中如何检测数组的变化

  • 是用来函数劫持的方式,重写了数组方法,具体呢就是更改了数组的原型,更改成自己的,用户调数组的一些方法的时候,走的就是自己的方法,然后通知视图去更新。
  • 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象才能进行观测,观测过的也不会进行观测
  • vue3:改用 proxy ,可直接监听对象数组的变化。

事件绑定原理

原生 DOM 的绑定:Vue在创建真实DOM时会调用 createElment ,默认会调用 invokeCreateHooks 。会遍历当前平台下相对的属性处理代码,其中就有 updateDOMListeners 方法,内部会传入 add() 方法

组件绑定事件,原生事件,自定义事件;组件绑定之间是通过Vue中自定义的 $on 方法实现的。

如何自定义v-modal

v-model 可以看成是 value+input 方法的语法糖(组件)。原生的 v-model ,会根据标签的不同生成不同的事件与属性。解析一个指令来。

自定义:自己写 model 属性,里面放上 prop 和 event

为什么采用异步渲染

Vue 是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图。核心思想 nextTick 。

dep.notify() 通知 watcher进行更新, subs[i].update 依次调用 watcher 的 update , queueWatcher 将watcher 去重放入队列, nextTick( flushSchedulerQueue )在下一tick中刷新watcher队列(异步)

nextTick

异步方法,异步渲染最后一步,与JS事件循环联系紧密。主要使用了宏任务微任务(setTimeout、promise那些),定义了一个异步方法,多次调用nextTick会将方法存入队列,通过异步方法清空当前队列。

父子组件生命周期调用顺序

类似于洋葱模型

Vue组件通信

  • 父子间通信:父亲提供数据通过属性 props传给儿子;儿子通过 on绑父亲的事件,再通过on 绑父亲的事件,再通过 emit 触发自己的事件(发布订阅)
  • 利用父子关系 parentparent 、 children
  • ref 获取组件实例,调用组件的属性、方法
  • 跨组件通信 Event Bus
  • vuex 状态管理实现通信

Vuex原理

如何从真实DOM到虚拟DOM

  • 将模板转换成 ast 树, ast 用对象来描述真实的JS语法(将真实DOM转换成虚拟DOM)
  • 优化树
  • 将 ast 树生成代码

用Vnode描述一个DOM

虚拟节点就是用一个对象来描述一个真实的DOM元素。首先将 template (真实DOM)先转成 ast , ast 树通过 codegen 生成 render 函数, render 函数里的 _c 方法将它转为虚拟dom

diff算法

  • 最小量更新, key 很重要。这个可以是这个节点的唯一标识,告诉 diff 算法,在更改前后它们是同一个DOM节点
  • 只有是同一个虚拟节点才会进行精细化比较,否则就是暴力删除旧的,插入新的。
  • 只进行同层比较,不会进行跨层比较。

data为什么是一个函数

避免组件中的数据互相影响。同一个组件被复用多次会创建多个实例,如果 data 是一个对象的话,这些实例用的是同一个构造函数。为了保证组件的数据独立,要求每个组件都必须通过 data 函数返回一个对象作为组件的状态。

为什么要使用异步组件

  • 节省打包出的结果,异步组件分开打包,采用jsonp的方式进行加载,有效解决文件过大的问题。
  • 核心就是包组件定义变成一个函数,依赖 import() 语法,可以实现文件的分割加载。

action 与 mutation 的区别

  • mutation 是同步更新, $watch 严格模式下会报错
  • action 是异步操作,可以获取数据后调用 mutation 提交最终数据

插槽与作用域插槽的区别

创建组件虚拟节点时,会将组件儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿子进行分类 {a:[vnode],b[vnode]}

渲染组件时会拿对应的 slot 属性的节点进行替换操作。(插槽的作用域为父组件)

作用域插槽 作用域插槽在解析的时候不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。

普通插槽渲染的作用域是父组件,作用域插槽的渲染作用域是当前子组件。

vue中相同逻辑如何抽离

其实就是考察 vue.mixin 用法,给组件每个生命周期,函数都混入一些公共逻辑。

谈谈对keep-alive的了解

keep-alive 可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。

常用的2个属性 include/exclude ,2个生命周期 activated , deactivated

React

setStatey异步

调用setState后发生了什么

  • React将传入的参数和当前的状态合并
  • 走生命周期 按照流程触发render
  • 生成新的虚拟DOM,与旧的虚拟DOM进行diff
  • 重新渲染UI

React中事件处理逻辑

为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React 并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样 React 在更新 DOM 的时候就不需要考虑如何去处理附着在 DOM 上的事件监听器,最终达到优化性能的目的。

为什么虚拟DOM会提高性能

虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。

Babel

抽象语法树,AST;

词法分析 -> 语法分析 -> 语法树

@babel/core、@babel/preset-env

ES6、7代码输入 -> babylon进行解析 -> 得到AST(抽象语法树)-> plugin用babel-traverse对AST树进行遍历转译 ->得到新的AST树->用babel-generator通过AST树生成ES5代码

React16生命周期的变更

  • 弃用的生命周期:componentWillMount、componentWillReceivePorps,componentWillUpdate
  • 新增的生命周期:componentDidCatch getDerivedStateFromProps getSnapshotBeforeUpdate

React diff算法

  • tree diff
  • 两棵树只对同一层次节点进行比较,如果不存在则直接删除,不会进一步比较
  • component diff
  • element diff 插入 删除 移动

Webpack

loader的作用:

  1. 实现对不同格式的文件的处理,比如说将scss转换为css,或者typescript转化为js
  2. 转换这些文件,从而使其能够被添加到依赖图中

Plugins

而plugins并不是直接操作单个文件,它直接对整个构建过程起作用

module,chunk 和 bundle的区别

module,chunk 和 bundle 其实就是同一份逻辑代码在不同转换场景下的取了三个名字

我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle。