JS数据转化

一、扁平数据和树形数据的相互转化
1、扁平数据
const flatData = [
    { id: 1, name: "节点1", parentId: null },
    { id: 2, name: "节点2", parentId: 1 },
    { id: 3, name: "节点3", parentId: 1 },
    { id: 4, name: "节点4", parentId: 2 },
    { id: 5, name: "节点5", parentId: 2 },
    { id: 6, name: "节点6", parentId: 3 },
  ];
2、树形数据
const treeData = [
    {
      id: 1,
      name: "节点1",
      children: [
        {
          id: 2,
          name: "节点2",
          children: [
            { id: 4, name: "节点4" },
            { id: 5, name: "节点5" },
          ],
        },
        {
          id: 3,
          name: "节点3",
          children: [{ id: 6, name: "节点6" }],
        },
      ],
    },
  ];
3、相互转化
// 转树
function flatToTree(data, parentId = null) {
  let tree = [];
  data.forEach((item) => {
    if (parentId === item.parentId) {
      const children = flatToTree(data, item.id);
      if (children.length > 0) {
        item.children = children;
      }
      tree.push(item);
    }
  });
  return tree;
}
// 转平
function treeToFlat(data, parentId = null) {
  let flat = [];
  data.forEach((item) => {
    flat.push({ id: item.id, name: item.name, parentId: parentId });
    if (item.children && item.children.length > 0) {
      let Ft = treeToFlat(item.children, item.id);
      flat.push(...Ft);
    }
  });
  return flat;
}
二、JS去重方法
function deduplicate(data) {
  方法一
  return Array.from(new Set(data));
  return [...new Set(data)];
  方法二
  return data.filter((item, index) => data.indexOf(item) === index);
  方法三
    for (let i = 0; i < data.length; i++) {
      for (let j = i + 1; j < data.length; j++) {
        if (data[i] === data[j]) {
          data.splice(j, 1);
          j--; // 因为splice方法会改变数组的长度,所以需要将j减一,否则会出错
        }
      }
    }
    return data;

  方法四
    let arr = [];
    data.forEach((item) => {
      if (arr.indexOf(item) === -1) {
        // arr = [...arr, item];
        arr.push(item);
      }
    });
    return arr;

  方法五
  利用对象属性名不能重复这一特点
  如果对象中不存在,就可以给 push 进去
   let newArr = [];
   let obj = {};
   for (let i = 0; i < arr.length; i++) {
     if (!obj[arr[i]]) {
       newArr.push(arr[i]);
       obj[arr[i]] = 1;
     } else {
       obj[arr[i]]++;
     }
   }
  return newArr;
  方法六
  let newArr = [];
  let map = new Map();
  for (let i = 0; i < data.length; i++) {
    // 如果map里不包含就设置进去
    if (!map.has(data[i])) {
      map.set(data[i], 1);
      newArr.push(data[i]);
    }
  }
  return newArr;
  // 方法七
  let newArr = [];
  return data.reduce((prev, current) => {
    return newArr.includes(current) ? newArr : (newArr = [...newArr, current]);
  }, []);

    let newArr = [];
    return data.reduce((prev, current, index, data) => {
      // 如果包含,就返回原数据,不包含,就把新数据追加进去
      return newArr.includes(current) ? newArr : newArr.push(current);
    }, []);

  方法八
  利用includes 检查新数组是否包含原数组的每一项
  如果不包含,就push进去
  let newArr = [];
  for (let i = 0; i < arr.length; i++) {
    newArr.includes(arr[i]) ? newArr : newArr.push(arr[i]);
  }
  return newArr;
}
三、apply,call,bind

apply与call基本相同,区别是apply的第二个参数是传数组,而call是传一个或多个参数。bind的区别是调用时要加(),它返回的是一个函数。三者的作用都是为方法指定this的值。

// apply
this.x = 9;
const module01 = {
  x: 81,
  getX: function (y) {
    return this.x + ":" + y;
  },
};
var module1 = {
  x: 91,
};
console.log(module01.getX(10)); // 81:10
var module2 = module01.getX;

console.log(module2(10)); // 9:10

var module3 = module2.apply(module01, [10]);
console.log(module3); // 81:10
var module4 = module2.apply(module1, [10]);
console.log(module4); // 91:10

// bind
this.x = 9;
const module02 = {
  x: 81,
  getX: function () {
    return this.x;
  },
};
var module1 = {
  x: 91,
};
console.log(module02.getX()); // 81

var module2 = module02.getX;
console.log(module2()); // 9

var module3 = module2.bind(module02);
console.log(module3()); // 81

var module4 = module2.bind(module1);
console.log(module4()); // 91

// call
this.x = 9;
const module = {
  x: 81,
  getX: function (y) {
    return this.x + ":" + y;
  },
};
var module1 = {
  x: 91,
};

console.log(module.getX(10)); // 81:10

var module2 = module.getX;
console.log(module2(10)); // 9:10

var module3 = module2.call(module, 10, 20);
console.log(module3); // 81:10

var module4 = module2.call(module1, 10, 20);
console.log(module4); // 91:10
四、arguments

arguments是一个类数组对象,第一个参数的属性名是“0”,依次类推,它还有length属性;_proto_是指向Object的,这也说明了他是个类数组对象,而不是一个数组。callee这个属性是存放函数代码的

function arg() {
  var a = "这里是代码";
  var b = "这是另一段代码";
  var c = a + b;

  console.log(arguments.callee);

  return c;
}
arg();
1、利用arguments实现方法的重载
function add() {
  var len = arguments.length,
    sum = 0;
  for (; len--; ) {
    sum += arguments[len];
  }
  return sum;
}
console.log(add(1, 2, 3)); //6
console.log(add(1, 3)); //4
console.log(add(1, 2, 3, 5, 6, 2, 7)); //26
2、利用arguments.callee实现递归
function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * arguments.callee(num - 1);
  }
}
console.log(factorial(10));
五、深浅拷贝

浅拷贝:创建新的数据,这个数据有着原始数据属性值的一份精确拷贝

特征:如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址,即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

1.手写浅拷贝
function shallowClone(obj) {
  let newObj = {};
  for (let i in obj) {
    if (obj.hasOwnProperty(i)) {
      newObj[i] = obj[i];
    }
  }
  return newObj;
}

const o = {
  name: "zs",
  hobby: ["排球", "网球", "乒乓球"],
};
const Obj = shallowClone(o);
Obj.name = "lisi"; //不是修改原数据
Obj.hobby[0] = "篮球"; // 会修改原数据
// Obj的第一层是深拷贝,hobby里面的是浅拷贝
console.log(o);
console.log(Obj);
2.es6的展开语法{...obj}
3.Object.assign({}, obj)进行对象的合并
4.数组和字符串的方法slice
5.数组和字符串的方法concat
const arr = [1, 2, { name: "nordon" }];
  const newArr = arr.slice();
  console.log(arr);
  console.log(newArr);

  const arr1 = [1, 2, { name: "nordon" }];
  const newArr1 = arr.concat();
  console.log(arr1);
  console.log(newArr1);

深拷贝:开辟一个新的栈,两个对象属性完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

1.递归实现深拷贝
function deepClone(obj) {
    let newObj = obj instanceof Array ? [] : {};
    for (let i in obj) {
      if (obj[i] && typeof obj[i] == "object") {
        // 若对象属性还是引用类型,进行递归
        newObj[i] = deepClone(obj[i]);
      } else {
        // 对象属性是基础引用类型,直接赋值
        newObj[i] = obj[i];
      }
    }
    return newObj;
  }
2.巧妙运用Object.entries(obj)遍历对象的属性和值
function deepClone(obj) {
    let newObj = obj instanceof Array ? [] : {};
    for (const [k, v] of Object.entries(obj)) {
      newObj[k] = typeof v == "object" ? deepClone(v) : v;
    }
    return newObj;
  }
3.JSON.parse(JSON.stringify(待拷贝对象)
const bj = {
    name: "zs",
    hobby: ["排球", "网球", "乒乓球"],
  };
  const ne = JSON.parse(JSON.stringify(bj));
  ne.name = "lisi";
  ne.hobby[0] = "篮球";
  console.log(bj);
  console.log(ne);
4.引入loadsh,提供 cloneDeep 实现
vue中使用loash库实现深拷贝的步骤
  安装loadsh    npm i --save lodash
  引入loadsh    import _ from 'lodash'
  直接调用loadsh库的方法 const newObj = _.cloneDeep(this.obj)
六、JS中的任务

任务分为同步任务和异步任务,异步任务又分为宏任务和微任务

宏任务有:<script>整体代码、setTimeout、setInterval、setImmediate、Ajax、DOM事件

微任务有:process.nextTick、MutationObserver、Promise.then catch finally

setTimeout(() => {
    new Promise((resolve) => {
      resolve();
    }).then(() => {
      console.log("test");
    });
    console.log(4);
  }, 3000);
  new Promise((resolve) => {
    resolve();
    console.log(1);
  }).then(() => {
    console.log(3);
    Promise.resolve()
      .then(() => {
        console.log("before timeout");
      })
      .then(() => {
        Promise.resolve().then(() => {
          console.log("also before timeout");
        });
      });
  });
  console.log(2);
  // 1, 2, 3, before timeout, also before timeout, 4, test
1. 任务队列

任务队列就是一个事件队列,其中最重要的是异步任务事件和定时事件。

异步任务事件:一般我们绑定一个事件,比如点击事件等等,都是在某一个时刻才触发执行的,这个时候就得放到任务队列里面,等待执行,而在某个DOM节点上绑定了事件,就要有相应的回调函数,它们是相辅相成的。
所谓回调函数,就是那些被挂载起来,等待执行的代码,主线程执行任务队列里面的异步任务,其实就是执行这些回调函数。
定时事件:setInterval 和 setTimeout

2. 同步任务和异步任务

同步任务(synchronous):在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务(asynchronous):不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。 (异步任务事件,定时事件,页面渲染请求图片获取异步资源等)
异步执行机制如下:
所有同步任务都在主线程上执行,形成一个执行栈。
主线程之外,还有一个“任务队列”,只要异步任务有了运行结果,就在“任务队列”之中放置一个事件。
一旦“执行栈”中的所有同步任务执行完毕了,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
主线程不断重复上面的三步。(事件循环)

3. 宏任务和微任务

不同类型的任务会进入不同的Event Queue,有宏任务的队列和微任务的队列。
这里需要注意的是new Promise是会进入到主线程中立刻执行,而promise.then则属于微任务。
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后——开始第一轮循环执行完所有微任务——下一轮宏任务…

宏任务(macro-task):整体代码script、setTimeOut、setInterval
微任务(mincro-task):promise.then、process.nextTick(node)

4. Event Loop

1、整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;
2、同步任务会直接进入主线程依次执行;
3、异步任务会再分为宏任务和微任务;
4、宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
5、微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
6、当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务;
7、上述过程会不断重复,这就是Event Loop事件循环;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值