20230509----重返学习-对象拷贝-ES6的兼容-对象的合并-前端开发中的同步异步编程

91 阅读14分钟

day-066-sixty-six-20230509-对象拷贝-ES6的兼容-对象的合并-前端开发中的同步异步编程

对象拷贝

对象浅拷贝与对象深拷贝

  • 拷贝的定义是开辟一块对象堆内存。

    • let obj2 = obj这个操作不是拷贝(克隆),仅仅是把obj的地址赋值给了obj2,两个对象共有相同的空间。

    • 拷贝一定是不同的空间,只不过空间中的内容是相同的。

      • 浅拷贝:只拷贝对象的第一级内容,对于更深层级的内容,新对象和原有对象,用的还是相同的堆内存地址。

      • 深拷贝:所有级都是重新创建新的对象,并且把对应的内容拷贝一份过来!

        • 深拷贝后,新对象和原有对象,彻底没有了关联。

对象浅拷贝

  • 对象浅拷贝方式

    1. 扩展运算符

      let obj = {
        x: 10,
        y: {
          x: 20,
        },
        bool:true,
        time:new Date()
      };
      let obj2 = {...obj}
      console.log(`obj-->`, obj);
      console.log(`obj2-->`, obj2);
      console.log(`obj2===obj-->`, obj2===obj);//false
      console.log(`obj2.y===obj.y-->`, obj2.y===obj.y);//true
      console.log(`obj2.fn===obj.fn-->`, obj2.fn===obj.fn);//true
      
    2. Object.assign()对象合并

      • Object.assign(obj1,obj2)两个对象合并

        • 浅合并两个对象,用 obj2 中的每个成员,去替换 obj1 中的每个成员

          • 两个对象都具备的成员以 obj2 为主
          • obj1有的但obj2没有:依然存在
          • obj1没有但obj2有:给obj1新增这个成员
        • 修改的是obj1这个对象,obj2对象不变,最后返回的值也是obj1这个对象!

          let obj1 = {
            x: 10,
            y: 20,
          };
          let obj2 = {
            y: 30,
            z: 40,
          };
          let res = Object.assign(obj1, obj2);
          console.log(res === obj1); //true
          console.log(obj1, obj2);
          
      • Object.assign(obj1,obj2,obj3,obj4)多个对象合并

        • 多个对象合并,返回和修改的都是obj1

          1. obj2替换obj1
          2. obj3替换obj1
      let obj = {
        x: 10,
        y: {
          z: 20,
        },
        bool: true,
        time: new Date(),
        fn() {},
        [Symbol("AA")]: "AAA",
      };
      let obj2 = Object.assign({}, obj);
      console.log(`obj-->`, obj);
      console.log(`obj2-->`, obj2);
      console.log(`obj2===obj-->`, obj2===obj);//false
      console.log(`obj2.y===obj.y-->`, obj2.y===obj.y);//true
      console.log(`obj2.fn===obj.fn-->`, obj2.fn===obj.fn);//true
      
    3. 笨办法:迭代源对象,把每一项分别值给目标对象。

      • 也是最通用和灵活的。
      let obj = {
        x: 10,
        y: {
          z: 20,
        },
        bool: true,
        time: new Date(),
        fn() {},
        [Symbol("AA")]: "AAA",
      };
      let obj2 = {};
      //_.each是之前写过的each函数,用来对一个对象进行forEach循环的。
      _.each(obj, (value, key) => {
        obj2[key] = value;
      });
      
      console.log(`obj-->`, obj);
      console.log(`obj2-->`, obj2);
      console.log(`obj2===obj-->`, obj2 === obj); //false
      console.log(`obj2.y===obj.y-->`, obj2.y === obj.y); //true
      console.log(`obj2.fn===obj.fn-->`, obj2.fn === obj.fn); //true
      
      • 相当于:

        let obj = {
          x: 10,
          y: {
            z: 20,
          },
          bool: true,
          time: new Date(),
          fn() {},
          [Symbol("AA")]: "AAA",
        };
        let obj2 = {};
        let keys = Object.getOwnPropertyNames(obj);
        if (typeof Symbol !== "undefined") {
          keys = keys.concat(Object.getOwnPropertySymbols(obj));
        }
        keys.forEach((value, key) => {
          obj2[key] = value;
        });
        
        console.log(`obj-->`, obj);
        console.log(`obj2-->`, obj2);
        console.log(`obj2===obj-->`, obj2 === obj); //false
        console.log(`obj2.y===obj.y-->`, obj2.y === obj.y); //true
        console.log(`obj2.fn===obj.fn-->`, obj2.fn === obj.fn); //true
        
  • 数组的浅拷贝

    1. [...arr]
    2. Object.assign([], arr)
    3. [].concat(arr)
    4. arr.slice(0)
    5. arr.map(item => item)
    6. Array.from(arr)
    let arr = [10, 20, 30, [40, 50]];
    let arr2 = [...arr];
    // let arr2 = Object.assign([], arr)
    // let arr2 = [].concat(arr)
    // let arr2 = arr.slice(0)
    // let arr2 = arr.map(item => item)
    console.log(arr, arr2);
    console.log(arr === arr2); //false
    console.log(arr[3] === arr2[3]); //true
    

对象深拷贝

  • 对象深拷贝的方法

    1. JSON.parse(JSON.stringify(变量))

      • 对于正常的一些属性值,而且属性名都是字符串类型的,可以直接基于JSON.stringify()/JSON.parse()实现深拷贝。

        • 例如:数字、字符串、布尔值、普通对象、普通数组…
      • 原理:先把对象变为JSON字符串,再把字符串转换为对象。

        • 此过程会把所有级别内容的内存空间,都重新创建一份新的。

          // 对于正常的一些属性值,例如:数字/字符串/布尔/普通对象/数组...,而且属性名都是字符串类型的,我们可以直接基于 JSON.stringify/parse 实现深拷贝!!
          // 原理:先把对象变为JSON字符串,再把字符串转换为对象「此过程会把所有级别的内容空间,都重新创建一份新的」
          let obj = {
            x: 10,
            y: {
              z: 20,
              arr: [10, 20, { b: 300 }],
            },
            bool: true,
            str: "珠峰培训",
          };
          let obj2 = JSON.parse(JSON.stringify(obj));
          console.log(obj2);
          console.log(obj2 === obj); //false
          console.log(obj2.y === obj.y); //false
          console.log(obj2.y.arr === obj.y.arr); //false
          console.log(obj2.y.arr[2] === obj.y.arr[2]); //false
          
      • JSON.stringify()具备很强的局限性,因为它是ES3的,当时还没ES6相关的规则。

        1. 无法处理BigInt类型的值,会报错Do not know how to serialize a BigInt

        2. 成员值是undefinedSymbol函数的,会直接把此成员删除掉。

        3. 正则对象错误对象直接变为{},这样等到基于parse转换为对象的时候,正则值会被处理为空对象。

        4. 把日期对象变为日期字符串

          • 有点另类,是调用日期对象.toJSON()方法来进行处理的,如'2023-05-09T03:14:12.344Z'

            • 这个字符串基于JSON.parse()转换为对象的时候,是无法再转换为日期对象的。
        5. 属性名是Symbol类型的,也会直接把此成员删除掉。

        6. 一旦对象出现套娃循环操作,则直接报错Converting circular structure to JSON

        • let obj = {
            name: "珠峰",
            age: 13,
            bool: true,
            n: null,
            u: undefined,
            sym: Symbol("sym"),
            //big: 10n,//报错`Do not know how to serialize a BigInt`;
            list: [10, 20, { a: 100, b: 200 }],
            reg: /\d+/,
            time: new Date(),
            err: new Error("xxx"),
            ke: { js: "基础课", web: "高级课", arr: [1000, 2000] },
            [Symbol("KEY")]: 100,
            fn: function () {},
          };
          //obj.obj = obj;//报错`Converting circular structure to JSON`
          console.log(JSON.parse(JSON.stringify(obj)))
          
    2. 迭代源对象,把每一项分别值给目标对象

      • 1.数组对象的深浅拷贝.js-简易版

        //===================================
        /* 深拷贝的处理 */
        
        let obj = {
          name: "珠峰",
          age: 13,
          bool: true,
          n: null,
          u: undefined,
          sym: Symbol("sym"),
          big: 10n,
          list: [10, 20, { a: 100, b: 200 }],
          reg: /\d+/,
          time: new Date(),
          err: new Error("xxx"),
          ke: { js: "基础课", web: "高级课", arr: [1000, 2000] },
          [Symbol("KEY")]: 100,
          fn: function () {},
        };
        obj.obj = obj;
        
        // 实现数组和对象的深拷贝
        //里面的_就是自己写的utils.js中的函数
        const clone = function clone(obj, exist = []) {
          if (!_.isObject(obj)) return obj; //原始值类型不进行处理
          let type = _.isType(obj),
            Ctor = obj.constructor;
          if (type === "regexp" || type === "date") return new Ctor(obj);
          if (type === "error") return new Ctor(obj.message);
          if (!_.isArray(obj) && !_.isPlainObject(obj)) return obj;
          // 防止套娃操作
          if (exist.indexOf(obj) >= 0) return obj;
          exist.push(obj);
          // 操作的是数组和纯粹对象
          let result = new Ctor();
          _.each(obj, (value, key) => {
            result[key] = clone(value, exist); //基于递归的方式,对每一个成员值,再次进行拷贝
          });
          return result;
        };
        
        let obj2 = clone(obj);
        console.log(obj2);
        console.log(obj2 === obj); //false
        console.log(obj2.list === obj.list); //false
        console.log(obj2.list[2] === obj.list[2]); //false
        console.log(obj2.reg === obj.reg); //false
        console.log(obj2.fn === obj.fn); //true
        console.log(obj2.obj === obj); //true
        console.log(obj2.obj === obj2); //false
        
      • 1.数组对象的深浅拷贝.js-个人优化版

        //===================================
        /* 深拷贝的处理 */
        
        let obj = {
          name: "珠峰",
          age: 13,
          bool: true,
          n: null,
          u: undefined,
          sym: Symbol("sym"),
          big: 10n,
          list: [10, 20, { a: 100, b: 200 }],
          reg: /\d+/,
          time: new Date(),
          err: new Error("xxx"),
          ke: { js: "基础课", web: "高级课", arr: [1000, 2000] },
          [Symbol("KEY")]: 100,
          fn: function () {},
        };
        obj.obj = obj;
        
        // 实现数组和对象的深拷贝
        //里面的_就是自己写的utils.js中的函数
        const clone = function clone(obj, exist = []) {
          // 原始值类型不进行处理。
          if (!_.isObject(obj)) {
            return obj;
          }
        
          // 对特定类型进行创建。
          let type = _.isType(obj);
          let Ctor = obj.constructor;
        
          // 正则或时间对象。
          if (type === "regexp" || type === "date") {
            return new Ctor(obj);
          }
        
          // 错误对象;
          if (type === "error") {
            return new Ctor(obj.message);
          }
        
          // 非上方特定类型并且非数组或纯粹对象,直接返回-如函数。
          if (!_.isArray(obj) && !_.isPlainObject(obj)) {
            return obj;
          }
        
          // 操作的是数组和纯粹对象
          let result = new Ctor();
        
          // // 防止套娃循环操作-这个个人觉得可以做一个循环,push([obj,result]),找到obj后,返回出result
          // if (exist.indexOf(obj) >= 0) {
          //   return obj;
          // }
          // exist.push(obj);
        
          // 防止套娃操作
          let index = exist.findIndex((item) => item[0] === obj);
          if (index >= 0) {
            return exist[index][1];
          }
          exist.push([obj, result]);
        
          // 操作的是数组和纯粹对象
          _.each(obj, (value, key) => {
            result[key] = clone(value, exist); //基于递归的方式,对每一个成员值,再次进行拷贝。
          });
          return result;
        };
        
        let obj2 = clone(obj);
        console.log(obj2);
        console.log(obj2 === obj); //false
        console.log(obj2.list === obj.list); //false
        console.log(obj2.list[2] === obj.list[2]); //false
        console.log(obj2.reg === obj.reg); //false
        console.log(obj2.fn === obj.fn); //true
        console.log(obj2.obj === obj); //false
        console.log(obj2.obj === obj2); //true
        
      • utils.js

        (function (global, factory) {
          "use strict";
          if (typeof module === "object" && typeof module.exports === "object") {
            module.exports = factory(global, true);
            return;
          }
          factory(global);
        })(
          typeof window !== "undefined" ? window : this,
          function factory(window, noGlobal) {
            /* 检测数据类型 */
            const toString = Object.prototype.toString,
              isArray = Array.isArray,
              typeReg = /^(object|function)$/,
              fnToString = Function.prototype.toString;
        
            // 万能检测数据类型的方法
            const isType = function isType(obj) {
              if (obj == null) return obj + "";
              let type = typeof obj,
                reg = /^\[object (\w+)\]$/;
              return !typeReg.test(type)
                ? type
                : reg.exec(toString.call(obj))[1].toLowerCase();
            };
        
            // 检测是否为对象
            const isObject = function isObject(obj) {
              return obj !== null && typeReg.test(typeof obj);
            };
        
            // 检测是否是window对象
            const isWindow = function isWindow(obj) {
              return obj != null && obj === obj.window;
            };
        
            // 检测是否为函数
            const isFunction = function isFunction(obj) {
              return typeof obj === "function";
            };
        
            // 检测是否为数组或者伪数组
            const isArrayLike = function isArrayLike(obj) {
              if (isArray(obj)) return true;
              let length = !!obj && "length" in obj && obj.length;
              if (isFunction(obj) || isWindow(obj)) return false;
              return (
                length === 0 ||
                (typeof length === "number" && length > 0 && length - 1 in obj)
              );
            };
        
            // 检测是否为一个纯粹的对象(标准普通对象)
            const isPlainObject = function isPlainObject(obj) {
              if (isType(obj) !== "object") return false;
              let proto, Ctor;
              proto = Object.getPrototypeOf(obj);
              if (!proto) return true;
              Ctor = proto.hasOwnProperty("constructor") && proto.constructor;
              return (
                isFunction(Ctor) && fnToString.call(Ctor) === fnToString.call(Object)
              );
            };
        
            // 检测是否为空对象
            const isEmptyObject = function isEmptyObject(obj) {
              if (!isObject(obj)) throw new TypeError(`obj is not an object`);
              // let keys = Reflect.ownKeys(obj)
              let keys = Object.getOwnPropertyNames(obj);
              if (typeof Symbol !== "undefined")
                keys = keys.concat(Object.getOwnPropertySymbols(obj));
              return keys.length === 0;
            };
        
            // 检测是否为有效数字
            const isNumeric = function isNumeric(obj) {
              let type = isType(obj);
              return (type === "number" || type === "string") && !isNaN(+obj);
            };
        
            /* 其它基础方法 */
        
            // 迭代数组/伪数组/对象「支持中途结束循环」
            const each = function each(obj, callback) {
              if (typeof callback !== "function") callback = () => {};
              if (typeof obj === "number" && !isNaN(obj) && obj > 0)
                obj = new Array(obj);
              if (typeof obj === "string") obj = Object(obj);
              if (!isObject(obj)) return obj;
              if (isArrayLike(obj)) {
                for (let i = 0; i < obj.length; i++) {
                  let item = obj[i];
                  let res = callback.call(obj, item, i);
                  if (res === false) break;
                }
                return obj;
              }
              // let keys = Reflect.ownKeys(obj)
              let keys = Object.getOwnPropertyNames(obj);
              if (typeof Symbol !== "undefined")
                keys = keys.concat(Object.getOwnPropertySymbols(obj));
              for (let i = 0; i < keys.length; i++) {
                let key = keys[i],
                  value = obj[key];
                let res = callback.call(obj, value, key);
                if (res === false) break;
              }
              return obj;
            };
        
            // 具备有效期的LocalStorage存储
            const storage = {
              set(key, value) {
                localStorage.setItem(
                  key,
                  JSON.stringify({
                    time: +new Date(),
                    value,
                  })
                );
              },
              get(key, cycle = 2592000000) {
                cycle = +cycle;
                if (isNaN(cycle)) cycle = 2592000000;
                let data = localStorage.getItem(key);
                if (!data) return null;
                let { time, value } = JSON.parse(data);
                if (+new Date() - time > cycle) {
                  storage.remove(key);
                  return null;
                }
                return value;
              },
              remove(key) {
                localStorage.removeItem(key);
              },
            };
        
            // 万能的日期格式化工具
            const formatTime = function formatTime(time, template) {
              try {
                if (time == null)
                  time = new Date().toLocaleString("zh-CN", { hour12: false });
                if (typeof template !== "string") template = "{0}/{1}/{2} {3}:{4}:{5}";
                let arr = [];
                if (/^\d{8}$/.test(time)) {
                  let [, $1, $2, $3] = /^(\d{4})(\d{2})(\d{2})$/.exec(time);
                  arr.push($1, $2, $3);
                } else {
                  arr = time.match(/\d+/g);
                }
                return template.replace(/\{(\d+)\}/g, (_, $1) => {
                  let item = arr[$1] || "00";
                  if (item.length < 2) item = "0" + item;
                  return item;
                });
              } catch (_) {
                return "";
              }
            };
        
            // 转移“_”的使用权
            let origin = null;
            const noConflict = function noConflict() {
              if (window._ === utils) {
                window._ = origin;
              }
              return utils;
            };
        
            /* 暴露API */
            const utils = {
              isType,
              isObject,
              isArray,
              isArrayLike,
              isWindow,
              isFunction,
              isPlainObject,
              isEmptyObject,
              isNumeric,
              noConflict,
              each,
              storage,
              formatTime,
            };
            if (typeof noGlobal === "undefined") {
              origin = window._;
              window.utils = window._ = utils;
            }
            return utils;
          }
        );
        
      • index.html

        <!DOCTYPE html>
        <html>
          <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>珠峰零基础高薪就业班</title>
            <!-- IMPORT CSS -->
          </head>
        
          <body>
            <!-- IMPORT JS -->
            <script src="utils.js"></script>
            <!-- <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script> -->
            <script src="1.数组对象的深浅拷贝.js"></script>
          </body>
        </html>
        

ES6的兼容

  • ES6兼容

    • 直接基于babel和对应的语法包@babel/preset-env就可以处理了

      • 有一些特殊的语法,需要依赖一些babel的插件

        • 例如:装饰器
  • 内置API的兼容:Promise、Object.is()…

    • 上面的语法包是无法解决它的兼容的,它兼容的解决方法只有一种

      • 原理:按照官方的说法,就是对内置API进行重写,重新添加一些对象

      • 解决方式

        • corejs

        • @babel/polyfill

        • 它们内部包含了很多ES6常用内置API的重写

          • fetch()
          • [].fill()
          • 这一类可能没有polyfill。
  • 项目中使用caniuse.com查看浏览器兼容性。

    • 使用babeljs.io/repl可以查看各种设置条件下的使用babel进行处理后得到的代码。

      • 这些代码需要转化,还要对webpack/.browserslistrc/babel.config.js进行配置。
    • 也可以使用lodash去修改一些原生的方法。

      • lodash中的方法。

        _.cloneDeep 深拷贝
        _.assign 浅合并
        _.merge 深合并
        

修改默认方法

  • 用Object.defineProperty()修改快有对象的属性。

    /* //merge: 实现数组和对象的合并
      @params
        deep: 是否为深合并,布尔值:false浅合并,true深合并
        target: 要被合并/替换的对象
        objs: 替换target的对象集合
      @return
        target: 被替换后的对象
    */
    const merge = function merge(deep = false, target, ...objs) {};
    
    // 修改默认方法。
    // 实现浅合并
    Object.defineProperty(Object, "assign", {
      configurable: true,
      writable: true,
      enumerable: false,
      value: function shallowMerge(...parsms) {
        merge(deep, ...parsms);
      },
    });
    // 实现深合并
    Object.defineProperty(Object, "assignDeep", {
      configurable: true,
      writable: true,
      enumerable: false,
      value: function deepMerge(...parsms) {
        merge(deep, ...parsms);
      },
    });
    
    // 供外界调用的深浅合并方法
    const def = function def(obj, key, value) {
      Object.defineProperty(obj, key, {
        configurable: true,
        writable: true,
        enumerable: false,
        value,
      });
    };
    def(Object, "assign", (...params) => merge([], false, ...params));
    def(Object, "assignDeep", (...params) => merge([], true, ...params));
    
  • 自然也可以修改全局变量,里面有一些方法。

对象的合并

对象的浅合并

  • Object.assign()处理的是浅合并

    • 只合并第一级,或者不论属性值是什么类型,直接用后面对象中的值,替换前面对象中的值

      let obj1 = {
        name: "张三",
        age: 25,
        hobby: {
          music: 100,
          jump: 80,
        },
      };
      obj1.AAA = obj1;
      
      let obj2 = {
        name: "李四",
        age: 22,
        sex: 0,
        hobby: {
          read: 100,
          music: 90,
        },
      };
      obj2.AAA = obj2;
      
      let obj3 = {
        name: "王五",
        age: 20,
        height: "158cm",
        score: {
          math: 100,
          chinese: 90,
        },
      };
      
      // Object.assign:处理的浅合并「只合并第一级,或者不论属性值是啥类型,直接用obj2中的值,替换obj1中的值」
      let obj = Object.assign({}, obj1, obj2);
      console.log(obj);
      
  • 通过循环,直接把第一层对象的成员赋值给新对象

对象的深合并

  • 原理是通过循环,一个属性一个属性地合并

    • index.html

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="UTF-8" />
          <meta http-equiv="X-UA-Compatible" content="IE=edge" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>珠峰零基础高薪就业班</title>
          <!-- IMPORT CSS -->
        </head>
      
        <body>
          <!-- IMPORT JS -->
          <script src="utils.js"></script>
          <!-- <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script> -->
          <script src="2.数组对象的深浅合并.js"></script>
        </body>
      </html>
      
    • 2.数组对象的深浅合并.js

      let obj1 = {
        name: "张三",
        age: 25,
        hobby: {
          music: 100,
          jump: 80,
        },
      };
      obj1.AAA = obj1;
      
      let obj2 = {
        name: "李四",
        age: 22,
        sex: 0,
        hobby: {
          read: 100,
          music: 90,
        },
      };
      obj2.AAA = obj2;
      
      let obj3 = {
        name: "王五",
        age: 20,
        height: "158cm",
        score: {
          math: 100,
          chinese: 90,
        },
      };
      
      
      /* 
      merge:实现数组和对象的合并
        @params
          deep:布尔 false浅合并 true深合并
          target:要被合并/替换的对象
          objs:替换target的“对象集合”
        @return 
          target 被替换后的对象
      */
      //里面的_就是自己写的utils.js中的函数
      const isArrayOrObject = (obj) => {
        return _.isArray(obj) || _.isPlainObject(obj);
      };
      const merge = function merge(exist, deep, target, ...objs) {
        if (!isArrayOrObject(target))
          throw new TypeError(`被替换的目标必须是数组或者纯粹对象`);
        if (objs.length === 0) return target; //如果不具备替换的对象,则直接返回目标对象
        // 迭代 objs 对象集合中的每一项
        objs.forEach((obj) => {
          // obj:每一个替换的对象,我们需要拿它去替换目标对象target「但要保证它也得是数组/对象」
          if (!isArrayOrObject(obj)) return;
          // 防止套娃
          if (exist.indexOf(obj) >= 0) return;
          exist.push(obj);
          // 依次迭代这个对象中的每个成员,去替换target目标对象中,同名的这个成员
          _.each(obj, (value, key) => {
            let targetValue = target[key];
            if (deep && isArrayOrObject(targetValue) && isArrayOrObject(value)) {
              // 把这两个对象,基于递归的方式,再次合并一下
              target[key] = merge(exist, deep, targetValue, value);
              return;
            }
            // 浅合并「或者直接让 替换对象值 替换 目标对象值」
            target[key] = value;
          });
        });
        return target;
      };
      
      // 供外界调用的深浅合并方法
      const def = function def(obj, key, value) {
        Object.defineProperty(obj, key, {
          configurable: true,
          writable: true,
          enumerable: false,
          value,
        });
      };
      def(Object, "assign", (...params) => merge([], false, ...params));
      def(Object, "assignDeep", (...params) => merge([], true, ...params));
      
      // 测试
      console.log(Object.assign({}, obj1, obj2, obj3)); //->merge(false, {}, obj1, obj2, obj3)
      console.log(Object.assignDeep({}, obj1, obj2, obj3)); //->merge(true, {}, obj1, obj2, obj3)
      
    • utils.js

      (function (global, factory) {
        "use strict";
        if (typeof module === "object" && typeof module.exports === "object") {
          module.exports = factory(global, true);
          return;
        }
        factory(global);
      })(
        typeof window !== "undefined" ? window : this,
        function factory(window, noGlobal) {
          /* 检测数据类型 */
          const toString = Object.prototype.toString,
            isArray = Array.isArray,
            typeReg = /^(object|function)$/,
            fnToString = Function.prototype.toString;
      
          // 万能检测数据类型的方法
          const isType = function isType(obj) {
            if (obj == null) return obj + "";
            let type = typeof obj,
              reg = /^\[object (\w+)\]$/;
            return !typeReg.test(type)
              ? type
              : reg.exec(toString.call(obj))[1].toLowerCase();
          };
      
          // 检测是否为对象
          const isObject = function isObject(obj) {
            return obj !== null && typeReg.test(typeof obj);
          };
      
          // 检测是否是window对象
          const isWindow = function isWindow(obj) {
            return obj != null && obj === obj.window;
          };
      
          // 检测是否为函数
          const isFunction = function isFunction(obj) {
            return typeof obj === "function";
          };
      
          // 检测是否为数组或者伪数组
          const isArrayLike = function isArrayLike(obj) {
            if (isArray(obj)) return true;
            let length = !!obj && "length" in obj && obj.length;
            if (isFunction(obj) || isWindow(obj)) return false;
            return (
              length === 0 ||
              (typeof length === "number" && length > 0 && length - 1 in obj)
            );
          };
      
          // 检测是否为一个纯粹的对象(标准普通对象)
          const isPlainObject = function isPlainObject(obj) {
            if (isType(obj) !== "object") return false;
            let proto, Ctor;
            proto = Object.getPrototypeOf(obj);
            if (!proto) return true;
            Ctor = proto.hasOwnProperty("constructor") && proto.constructor;
            return (
              isFunction(Ctor) && fnToString.call(Ctor) === fnToString.call(Object)
            );
          };
      
          // 检测是否为空对象
          const isEmptyObject = function isEmptyObject(obj) {
            if (!isObject(obj)) throw new TypeError(`obj is not an object`);
            // let keys = Reflect.ownKeys(obj)
            let keys = Object.getOwnPropertyNames(obj);
            if (typeof Symbol !== "undefined")
              keys = keys.concat(Object.getOwnPropertySymbols(obj));
            return keys.length === 0;
          };
      
          // 检测是否为有效数字
          const isNumeric = function isNumeric(obj) {
            let type = isType(obj);
            return (type === "number" || type === "string") && !isNaN(+obj);
          };
      
          /* 其它基础方法 */
      
          // 迭代数组/伪数组/对象「支持中途结束循环」
          const each = function each(obj, callback) {
            if (typeof callback !== "function") callback = () => {};
            if (typeof obj === "number" && !isNaN(obj) && obj > 0)
              obj = new Array(obj);
            if (typeof obj === "string") obj = Object(obj);
            if (!isObject(obj)) return obj;
            if (isArrayLike(obj)) {
              for (let i = 0; i < obj.length; i++) {
                let item = obj[i];
                let res = callback.call(obj, item, i);
                if (res === false) break;
              }
              return obj;
            }
            // let keys = Reflect.ownKeys(obj)
            let keys = Object.getOwnPropertyNames(obj);
            if (typeof Symbol !== "undefined")
              keys = keys.concat(Object.getOwnPropertySymbols(obj));
            for (let i = 0; i < keys.length; i++) {
              let key = keys[i],
                value = obj[key];
              let res = callback.call(obj, value, key);
              if (res === false) break;
            }
            return obj;
          };
      
          // 具备有效期的LocalStorage存储
          const storage = {
            set(key, value) {
              localStorage.setItem(
                key,
                JSON.stringify({
                  time: +new Date(),
                  value,
                })
              );
            },
            get(key, cycle = 2592000000) {
              cycle = +cycle;
              if (isNaN(cycle)) cycle = 2592000000;
              let data = localStorage.getItem(key);
              if (!data) return null;
              let { time, value } = JSON.parse(data);
              if (+new Date() - time > cycle) {
                storage.remove(key);
                return null;
              }
              return value;
            },
            remove(key) {
              localStorage.removeItem(key);
            },
          };
      
          // 万能的日期格式化工具
          const formatTime = function formatTime(time, template) {
            try {
              if (time == null)
                time = new Date().toLocaleString("zh-CN", { hour12: false });
              if (typeof template !== "string") template = "{0}/{1}/{2} {3}:{4}:{5}";
              let arr = [];
              if (/^\d{8}$/.test(time)) {
                let [, $1, $2, $3] = /^(\d{4})(\d{2})(\d{2})$/.exec(time);
                arr.push($1, $2, $3);
              } else {
                arr = time.match(/\d+/g);
              }
              return template.replace(/\{(\d+)\}/g, (_, $1) => {
                let item = arr[$1] || "00";
                if (item.length < 2) item = "0" + item;
                return item;
              });
            } catch (_) {
              return "";
            }
          };
      
          // 转移“_”的使用权
          let origin = null;
          const noConflict = function noConflict() {
            if (window._ === utils) {
              window._ = origin;
            }
            return utils;
          };
      
          /* 暴露API */
          const utils = {
            isType,
            isObject,
            isArray,
            isArrayLike,
            isWindow,
            isFunction,
            isPlainObject,
            isEmptyObject,
            isNumeric,
            noConflict,
            each,
            storage,
            formatTime,
          };
          if (typeof noGlobal === "undefined") {
            origin = window._;
            window.utils = window._ = utils;
          }
          return utils;
        }
      );
      

前端开发中的同步异步编程

前端异步任务

  • 微任务 microtask
  • 宏任务 macrotask

进程与线程

  • 进程和线程

    • 一个进程中,包含一到多个线程

      • 进程:指的是一个程序(或者是浏览器打开一个页卡,就是开辟一个进程)

      • 线程:干活的,一个程序中可能需要同时做多件事情,此时就需要开辟多个线程

        • 一个线程同时只能干一件事件
  • 同步编程

    • 只有一个线程,所以同时只能干一件事件,只有当上一件事件处理完毕,下一事件才能处理。

      • 线程有运行和空闲两个状态
  • 异步编程

    • 当前进程(程序)具备多个线程,可以同时处理多件事件,上一件事件即便没有处理完毕,也可以安排一个其它线程去处理,主线程可以继续向下执行!
  • 现代浏览器是一个多进程多线程的软件。

    • 浏览器是多线程的

      • 每一次加载页面,浏览器都会分配很多线程,去同时做一些事件,此时来提高页面的渲染速度。

        • GUI渲染线程:渲染和解析HTML与css的,以及最后绘制出相应的页面。

          • HTML与CSS规范是w3c的。
        • JS引擎线程:渲染和解析JavaScript代码的。

          • JavaScript中ES是ECMA委员会的。
          • 这个就是主线程。
        • 定时器监听线程:监听定时器的。

        • 事件监听线程:监听DOM事件的。

        • HTTP网络请求线程:发送网络请求,从服务器获取资源的。

  • JavaScript是单线程的:因为浏览器只分配了一个JS引擎线程去渲染解析JavaScript代码。

    • 在JavaScript中大部分代码都是同步代码。

      • 例如:循环…
    • 但是JavaScript中也有异步代码。

      • 通过事件循环EventLoop来做。

进阶参考

  1. caniuse.com查看浏览器兼容性
  2. babel查看重写后的写法
  3. leetcode.cn - 刷算法-leetcode官方中文网
  4. lodash - lodash-cdn地址发布
  5. lodash.com - lodash英文官网
  6. lodash.com - lodash中文文档