一、我的学习过程

193 阅读5分钟

diff算法

传送门

虚拟dom

虚拟 DOM (Virtual DOM,简称 VDOM) vue提供的渲染函数render相比于模板template更加灵活,因为你可以完全地使用 JavaScript 来构造你想要的 vnode 虚拟dom是一个js对象,因为真实dom对象,占用内存比较大内置有很多我们不常用的属性。所以用虚拟dom提高渲染性能。 如图所示,虚拟dom中有标签名字,props属性名字,子节点对象等等;

运行时渲染器将会遍历整个虚拟 DOM 树,并据此构建真实的 DOM 树。这个过程被称为挂载 (mount)。 如果我们有两份虚拟 DOM 树,渲染器将会有比较地遍历它们,找出它们之间的区别,并应用这其中的变化到真实的 DOM 上。这个过程被称为更新 (patch),又被称为“比对”(diffing) 或“协调”(reconciliation) 虚拟Dom.png

diff算法

查出新旧虚拟dom的差异(比对两个js对象的差异),最小化的更新视图;

当数据方式改变后,触发setter->触发Dep.notify->通知订阅者(数据使用位置)->使用patch方法接收新旧虚拟dom

  1. 如果不是同类标签直接全部替换;
  2. 如果是同类标签的话直接调用patchVnode方法比对:
    1. 判断新旧节点是否相同,如果相等就不需要比对

    2. 如果不相等(比对原则以新虚拟节点为准)

      • 比对文本节点
      • 旧的没有子节点,新的有子节点,直接添加新的子节点
      • 旧的有子节点,新的没有,直接删除旧子节点
      • 如果都有子节点的情况执行updateChildren方法

原型1.png updateChildren方法内部

比对原则:最小化更新视图,同级比对,减少比对次数,最大化提升性能;

比对原则.png 采用首位指针法

注意:同一个父元素下的子元素必须具有唯一的 key。重复的 key 将会导致渲染异常。 updataChildren过程.png

updata结果.png

propmise

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve('resolve')
    console.log('promise1 end')
}).then(function (res) {
    console.log('then',res)
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->then resolve ->promise2->settimeout

class类封装继承

关于构造函数与class原型链的区别

传送门

寄生组合式继承

class Animal {
    constructor(name){
        this.name = name;
        this.type = "动物";
    }
    
    says(say){
        console.info(this.type + "【" + this.name + "】" + "说 " + say);
    }
}

let dog = new Animal("狗狗");
dog.says("汪汪汪");//动物【狗狗】说 汪汪汪


class Bird extends Animal {
    constructor(name){
        super(name);
        this.type = "小鸟";
    }
}

let pigeon = new Bird("鸽子");
pigeon.says("我是一只小鸟");//小鸟【鸽子】说 我是一只小鸟

call、apply和bind的区别

传送门

相同点:

                作用相同,都是动态修改this指向;都不会修改原先函数的this指向。               

异同点:

(1)执行方式不同:

    call和apply是改变后页面加载之后就立即执行,是同步代码。

    bind改变后不会立即执行;而是返回一个新的函数,新函数的this会永久改变。

(2)传参方式不同:

    call和bind传参是一个一个逐一传入,不能使用剩余参数的方式传参。

    apply可以使用数组的形式传入的,只要是数组方式就可以使用剩余参数的方式传入。
  function fn (a, b) {
      console.log(this, a + b);
    };
    fn(1, 2); //window

    const obj = {
      name: '张三',
    };

    fn.call(obj, 3, 4); //obj
    fn.apply(obj, [3, 4]); //obj
    let newFn = fn.bind(obj); //obj
    newFn(2, 4)//6

递归转化 reduce

扁平转树状

    var menu_list = [
      {
        id: '1',
        menu_name: '设置',
        menu_url: 'setting',
        parent_id: 0
      }, {
        id: '1-1',
        menu_name: '权限设置',
        menu_url: 'setting.permission',
        parent_id: '1'
      }, {
        id: '1-1-1',
        menu_name: '用户管理列表',
        menu_url: 'setting.permission.user_list',
        parent_id: '1-1'
      },
      {
        id: '2',
        menu_name: '用户管理',
        menu_url: 'setting',
        parent_id: 0
      }, {
        id: '2-1',
        menu_name: '权限设置',
        menu_url: 'setting.permission',
        parent_id: '2'
      },
      {
        id: '3',
        menu_name: '用户管理',
        menu_url: 'setting',
        parent_id: 0
      }
    ]
    const arrData = JSON.parse(JSON.stringify(menu_list)) 
    const result = arrData.reduce((prev, curr, i, arr) => {
      curr.children = arr.filter((v => v.parent_id === curr.id))
      if(curr.children.length===0) delete curr.children
      if (curr.parent_id === 0) prev.push(curr)
      return prev
    }, [])
    console.log('转化前', menu_list)
    console.log('转化后', result)

传统的递归

/** *
 *
 *  将列表型的数据转化成树形数据 => 递归算法
 *  找二级的上级(其实就是一级)
 *  拿到一级的id和二级的pid来比较
 *  如果相等,说明该二级就是该一级的下一级
 *  再根据一级的id找二级
 * ***/
    function listToTree (list, id = '') {
      let arr = []
      list.forEach(item => {
        //注意数据pid和id的数据类型是否一致
        if (item.pid === id) {
          // 找到之后 就要去找 item 下面有没有二级
          item.children = listToTree(list, item.id)
          arr.push(item) // 将内容加入到数组中
        }
      })
      return arr
    }
    // 这种方法其实跟递归思路差不多
    function listToTree (arr) {
      //第一次过滤,先把一级过滤出来
      const data = arr.filter((value) => {
        //第二次过滤,把子级以及后代都过滤出来
        value.children = arr.filter((item) => value.id === item.pid)
        // 这里的''是父级pid
        return value.pid === ''//判断第一层
      })
      return data
    }
    //改进版 不用递归
    function listToTree (list) {
      // 第一种
      const arr = []
      list.forEach(item => {
        if (item.parent_id === 0) {
          arr.push(item)
        } else {
          const p = list.find(i => i.id === item.parent_id)
          if (!p.children) {
            p.children = []
          }
          p.children.push(item)
        }
      })
      return arr
    }

树状转扁平

   var menu_list = [
      {
        id: 1,
        pid: 0,
        name: '沃尔玛',
        childrens: [
          {
            id: 2,
            pid: 1,
            name: '生鲜区',
            childrens: [
              { id: 4, pid: 2, name: '鱼' },
              { id: 5, pid: 2, name: '牛肉' }
            ]
          },
          {
            id: 3,
            pid: 1,
            name: '日用品区',
            childrens: [
              { id: 6, pid: 3, name: '卫生纸' },
              { id: 7, pid: 3, name: '牙刷' }
            ]
          }
        ]
      }
    ]
    const arrData = JSON.parse(JSON.stringify(menu_list))
    const result = arrData.reduce(function (prev, curr, i, arr) {
      prev.push({
        id: curr.id,
        name: curr.name,
        pid: curr.pid
      })
      curr.childrens && curr.childrens.forEach(v => {
        v.pid = curr.id//如果字级没有pid 让字级id和父级id关联起来 
        arguments.callee(prev, v)
      });
      return prev
    }, [])
    console.log('转化前', menu_list)
    console.log('转化后', result)
    
    function treeToArray(tree) {
  return tree.reduce((res, item) => {
    const { children, ...i } = item
    return res.concat(i, children && children.length ? treeToArray(children) : [])
  }, [])
}

去重

new set去重

//去重引用数据类型数组
const list =[
    { name: "张三", age: 18, address: "北京" },
    { name: "李四", age: 20, address: "天津" },
    { name: "张三", age: 18, address: "北京" },
    { name: "张三", age: 18, address: "北京" },
]
const strings = list.map((item) => JSON.stringify(item))
const removeDupList = [...new Set(strings)]
const uniResult = removeDupList.map((item) => JSON.parse(item))
console.log('uniResult',uniResult)
//去重基本数据类型数组
const member = [1,1,1,1,1]
const uniArr = [...new Set(member)]
console.log('uniArr',uniArr)

使用filter和Map

function uniqueFunc(arr, uniId){
  const res = new Map();
  return arr.filter((item) => !res.has(item[uniId]) && res.set(item[uniId], 1));
}

const arr =[
    { name: "张三", age: 18, address: "北京" },
    { name: "李四", age: 20, address: "天津" },
    { name: "张三", age: 18, address: "北京" },
    { name: "张三", age: 18, address: "北京" },
]


let map = new Map();
let newArr = arr.filter((v) => !map.has(v.name) && map.set(v.name, 1));
console.log(newArr)

reduce去重

通过对象访问属性的方法

var arr = [{
   key: '01',
   value: '乐乐'
  }, {
   key: '02',
   value: '博博'
  }, {
   key: '03',
   value: '淘淘'
  },{
   key: '04',
   value: '哈哈'
  },{
   key: '01',
   value: '乐乐'
  }];
  // 方法1:利用对象访问属性的方法,判断对象中是否存在key
  var result = [];
  var obj = {};
  for(var i =0; i<arr.length; i++){
   if(!obj[arr[i].key]){
     result.push(arr[i]);
     obj[arr[i].key] = true;
   }
  }
  console.log(result); // [{key: "01", value: "乐乐"},{key: "02", value: "博博"},{key: "03", value: "淘淘"},{key: "04", value: "哈哈"}]
  // 方法2:利用reduce方法遍历数组,reduce第一个参数是遍历需要执行的函数,第二个参数是prev的初始值
  var obj = {};
  arr = arr.reduce(function(prev, curr) {
   obj[curr.key] ? '' : obj[curr.key] = true && prev.push(curr);
   return prev;
  }, []);
  console.log(arr); // [{key: "01", value: "乐乐"},{key: "02", value: "博博"},{key: "03", value: "淘淘"},{key: "04", value: "哈哈"}]

选项

      const  originArr =[
        {
          id:'1',
        },
        {
          id:'2',
        },
        {
          id:'3',
        },
        {
          id:'4',
        },
      ]
      const selectArr= [
        {
          id:'1',
        },
        {
          id:'4',
        },
        {
          id:'2',
        },
      ]
// 可选的项目
function options () {
  const arrData = selectArr.reduce((prev, curr) => {
   return prev.filter(element =>  element.id !== curr.id) //prev为上一次的处理结果 继续filter
  }, originArr)
  return arrData
}
options()