zoukankan      html  css  js  c++  java
  • 566 手写37个 原生JavaScript 系列汇总(含promise A+)

    1、promise

    // 考虑到兼容性问题,不使用ES6+
    ; (function () {
      // 构造函数constructor
      function MyPromise(executor) {
        // 参数合法校验
        if (typeof executor !== "function") {
          throw new TypeError('MyPromise resolver ' + executor + ' is not a function');
        }
    
        // 设置实例的私有属性
        var self = this;
        this.PromiseStatus = 'pending';
        this.PromiseValue = undefined;
        // 【1、实例的resolveFunc函数、rejectFunc函数,为了拿到then中的2个函数,把这2个函数挂载到实例上; 2、值设置为空函数的好处,就是支持resolve方法中不写then。】
        this.resolveFunc = function () { };
        this.rejectFunc = function () { };
    
        // 修改实例的状态和value:只有当前状态为pending才能修改状态
        function change(status, value) {
          // 下面的self.PromiseStatus不能用传递进来的status
          if (self.PromiseStatus !== "pending") return;
          self.PromiseStatus = status;
          self.PromiseValue = value;
          // 通知基于.then注入的某个方法执行(执行resolve、reject都是异步的) 
          // 【用等待时间为0的setTimeout模拟微任务】
          var delayTimer = setTimeout(function () {
            clearTimeout(delayTimer);
            delayTimer = null;
            // 不用重新定义变量,直接用上面的self.PromiseStatus、self.PromiseValue即可
            var status = self.PromiseStatus;
            var value = self.PromiseValue;
            // 把.then注入的某个方法执行拿出来执行
            status === "fulfilled" ?
              self.resolveFunc.call(self, value) :
              self.rejectFunc.call(self, value);
          }, 0);
        }
    
        // new MyPromise的时候会给 resolve函数传参value
        function resolve(value) {
          change('fulfilled', value);
        }
    
        // new MyPromise的时候会给 reject函数传参reason
        function reject(reason) {
          change('rejected', reason);
        }
    
        // new MyPromise的时候会立即把executor函数执行,executor函数中的第一、二个参数分别为resolve、reject函数
        // executor函数执行出现错误,也会把实例的状态改为失败,且value是失败的原因
        try {
          // 【执行executor,就会根据情况执行resolve、reject中的一个,然后去执行change,再然后决定执行resolveFunc、rejectFunc中的一个】
          executor(resolve, reject);
        } catch (err) {
          change('rejected', err.message);
        }
      }
    
      // 把MyPromise当作对象
      MyPromise.resolve = function (value) {
        // 创建一个状态为成功的实例,通知这个实例的then中的某个方法执行。如果没有写then,定时器到达一定时间之后,就会去执行成功或失败的方法,但是实例没有成功和失败的方法。
        return new MyPromise(function (resolve) {
          resolve(value);
        });
      };
    
      MyPromise.reject = function (reason) {
        // function的形参可以下划线占位,不能用null
        return new MyPromise(function (_, reject) {
          reject(reason);
        });
      };
    
      // MyPromise.prototype
      // 不仅要把resolveFunc、rejectFunc挂载到 实例上,还要知道resolveFunc、rejectFunc执行的时候,是否报错,以及返回值是什么;最后还要返回一个新的promise实例
      MyPromise.prototype.then = function (resolveFunc, rejectFunc) {
        // 参数不传默认值的处理:目的是实现状态的顺延 【不传,或者传的不是函数】
        if (typeof resolveFunc !== "function") {
          // (1) 形参value从哪来?promise实例中执行resolve()传递的数据;加一个函数,这个函数可以接收到value; (2) 不是this.resolveFunc
          resolveFunc = function (value) {
            // 【不传,或者传的不是函数时】怎么往下顺延呢?返回一个resolve即可
            return MyPromise.resolve(value);
          };
        }
    
        if (typeof rejectFunc !== "function") {
          rejectFunc = function (reason) {
            return MyPromise.reject(reason);
          };
        }
    
        var self = this;
        // resolveFunc、rejectFunc执行的成功、失败直接影响了新返回的MyPromise的成功、失败
        // 返回的新实例的成功和失败由resolveFunc、rejectFunc执行是否报错来决定,或者由返回值是否为新的MyPromise实例来决定
        return new MyPromise(function (resolve, reject) {
          // 最终目的是执行resolveFunc,外面包一层匿名函数,是想知道resolveFunc执行时,是否报错,返回值是什么
          // document.body.onclick = fn,如果想 改变fn的值、this、预先传参等等,就先绑定一个匿名函数,document.body.onclick = function() { fn() }
          // value 要写在外层的匿名函数中
          self.resolveFunc = function (value) {
            // 这里面的this就是实例了,因为执行change方法,传过来的就是实例,也可以用this写
            try {
              // 1、用不用call改变this都可以;2、then的2个回调函数参数有返回值; 3、value是resolveFunc接收【实例】的返回结果
              var x = resolveFunc.call(self, value);
              // 1、不是promise实例,一定是成功的,执行resolve;2、如果x是promise实例,then会通知resolve或reject执行,通知resolve执行,返回的new MyPromise就是成功的,通知reject执行,返回的new MyPromise就是失败的。如果返回的新实例x是失败的,就执行reject,new MyPromise就是失败的,反之是成功的。
              x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
            } catch (err) {
              reject(err.message);
            }
          };
    
          self.rejectFunc = function (reason) {
            try {
              var x = rejectFunc.call(self, reason);
              x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
            } catch (err) {
              reject(err.message);
            }
          };
        });
      };
    
      MyPromise.prototype.catch = function (rejectFunc) {
        return this.then(null, rejectFunc);
      };
    
      MyPromise.all = function (promiseArr) {
        return new MyPromise(function (resolve, reject) {
          var index = 0; // 成功的个数
          var values = [];
    
          for (var i = 0; i < promiseArr.length; i++) {
            // 利用闭包的方式保存循环的每一项索引
            (function (i) {
              var item = promiseArr[i];
              // 如果当前项不是Promise,直接算作当前项成功
              !(item instanceof MyPromise) ? item = MyPromise.resolve(item) : null;
              // 回调函数的参数 value、reason来自于 race中的参数 promise实例的promiseValue
              item.then(function (value) {
                index++;
                // 不是values[i] = item,item是promise实例,value才是实例的值
                values[i] = value;
                if (index >= promiseArr.length) {
                  resolve(values); // 所有的实例都是成功的
                }
              }).catch(function (reason) {
                reject(reason); // 只要有一个失败,整体就是失败的
              });
            })(i);
          }
        });
      };
    
      // 补充race
      MyPromise.race = function (promises) {
        return new MyPromise(function (resolve, reject) {
          promises.forEach(function (p) {
            !(p instanceof MyPromise) ? p = MyPromise.resolve(p) : null
            // 回调函数的参数 value、reason来自于 race中的参数 promise实例的promiseValue
            p.then(function (value) {
              resolve(value)
            }).catch(function (reason) {
              reject(reason)
            })
          })
        })
      }
    
      // 补充finally
      Promise.prototype.finally = function (callback) {
        let P = this.constructor;
        // 不管成功、失败,都会执行callback,放到this.then中,成功执行P.resolve,并在P.resolve中执行callback;失败同理。
        return this.then(
          value => P.resolve(callback()).then(() => value),
          reason => P.resolve(callback()).then(() => { throw reason })
        );
      };
    
      window.MyPromise = MyPromise;
    })();
    
    
    // ------------------------------------
    
    function fn1() {
      return MyPromise.resolve(1);
    }
    
    function fn2() {
      return new MyPromise((resolve, reject) => {
        setTimeout(() => {
          resolve(2);
        }, 2000);
      });
    }
    
    function fn3() {
      return new MyPromise((resolve, reject) => {
        setTimeout(() => {
          reject(3);
        }, 1000);
      });
    }
    
    MyPromise.all([fn1(), fn2(), fn3(), 10])
      .then(function (values) {
        console.log('OK', values);
      })
      .catch(function (reason) {
        console.log('NO', reason);
      });
    
    
    new MyPromise(function (resolve, reject) {
      // resolve(10);
      reject(20);
    }).then(function (value) {
      console.log('OK', value);
      return MyPromise.reject(200);
    },
      /* function (reason) {
      console.log('NO', reason);
      return 100;
    } */
    ).then(function (value) {
      console.log('OK', value);
    }, function (reason) {
      console.log('NO', reason);
    });
    

    2、防抖

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    
    <body>
      <button id="btn">按钮</button>
    </body>
    
    </html>
    <script>
      function debounce(func, wait = 500, immediate = false) {
        let timer = null;
        return function anonymous(...params) {
          // 第一次触发 或 立即执行的条件:immediate为true,timer为null
          let now = immediate && !timer;
          clearTimeout(timer);
          timer = setTimeout(() => {
            timer = null;
            !immediate ? func.call(this, ...params) : null;
          }, wait);
    
          // 用于第一次触发 或 立即执行 
          now ? func.call(this, ...params) : null;
        };
      }
    
      function func() {
        console.log('OK');
      }
    
      let btn = document.querySelector('#btn')
      btn.onclick = debounce(func, 1000, false)
    </script>
    

    3、节流

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    
    <body>
      <button id="btn">按钮</button>
    </body>
    
    </html>
    <script>
      /*
       * 假设我们频率设置为500ms,我们频繁触发了10000ms,对于防抖,则只触发一次;对于节流,则触发20次 */
      // 【第一次触发时,remaining是负数,previous被赋值为当前时间。】
      // 【第二次触发,假设是在间隔20ms,则remaining = 500 - (新的当前时间 - 上一次触发时间) = 500 - 20 = 480,也就是定时器的等待时间remaining。】
      function throttle(func, wait = 500) {
        let timer = null;
        let previous = 0; // 记录上一次操作时间
    
        return function anonymous(...params) {
          let now = new Date(); // 当前操作的时间
          let remaining = wait - (now - previous); // 剩余时间
          // remaining <= 0 与 remaining > 0 的区别:后者多了个定时器,previous不一样
          if (remaining <= 0) {
            // 两次间隔时间超过频率:把方法执行即可
            // clearTimeout是从系统中清楚定时器,timer值不会变为null 【银行系统清理排队号】
            clearTimeout(timer);
            // 这是给变量timer赋值为null,就可以通过timer是否为null,判断有木有定时器 【自己把小纸条丢垃圾篓,也可以不扔,拿手里,但是就不能通过timer是否为null,判断有木有定时器了。】
            timer = null;
            previous = now; // 【把上一次操作时间修改为当前时间】
            func.call(this, ...params);
          } else if (!timer) {
            // 两次间隔时间小于频率,如果没有定时器,设置定时器;有定时器了,就不用重新设置定时器,而是以上一次的计时为准
            // 两次间隔时间没有超过频率,说明还没有达到触发标准,设置定时器等待即可(还差多久,就等多久) 【假设时间间隔是500ms,第20ms点击,剩余480ms,就等待480ms。】
            timer = setTimeout(() => {
              // 这两行代码要写到定时器里
              clearTimeout(timer);
              timer = null;
              // 过了remaining时间后,才去执行func,所以previous不能等于now
              previous = new Date(); // 【把上一次操作时间修改为当前时间】
              func.call(this, ...params);
            }, remaining);
          }
        };
      }
    
      function func() {
        console.log('OK');
      }
    
      let btn = document.querySelector('#btn')
      btn.onclick = throttle(func, 1000);
    
    
      // -------------------------------------------
    
    
      //  防止频繁点击触发:设置标识进行判断 
      // let isClick = false;
      // btn.onclick = function () {
      //   // isClick为false,则不继续往下执行
      //   if (isClick) return;
      //   isClick = true;
      //   setTimeout(() => {
      //     console.log('OK');
      //     isClick = false;
      //   }, 500);
      // };
    </script>
    

    4、手写实现call

    /* call的作用:改变函数中this指向的 */
    Function.prototype.call = function call(context, ...params) {
      // this:就是调用call的函数, context:就是第一个参数, params:就是[形参集合]
    
      // (1)undefined == null 是true;(2)条件成立,context就是window;条件不成立,context就是传进来的值;(3)这里就是处理形参context的值为ndefined、null的情况。
      context == null ? context = window : null;
      // 只有引用数据类型值才能设置对应的属性
      let contextType = typeof context;
      if (!/^(object|function)$/.test(contextType)) {
        // 不是引用类型我们需要把其变为引用类型
        // 下面几行代码,直接一行即可:context = Object(context)
        if (/^(symbol|bigint)$/.test(contextType)) {
          // symbol、bigint:基于Object创建对象值
          context = Object(context);
        } else {
          // 其余的可以基于new它的构造函数创建 
          // 【数值、字符串、布尔也可以用Object()转为对象:Object(11):Object(11); Object('aa'):String {"aa"};Object(true):Boolean {true}】
          context = new context.constructor(context);
        }
      }
      // 设置一个唯一的属性名 
      let key = Symbol('key'),
        result;
      // 给当前设置属性, 属性值是要执行的函数
      context[key] = this;
      // 让函数执行, 此时函数中的this => context 【context[key]:成员访问,this指向context。】
      result = context[key](...params);
      delete context[key]; // 用完移除
      return result;
    };
    
    let obj = {
      name: '哈哈'
    };
    
    function func(x, y) {
      console.log(this, x + y);
    }
    
    
    // obj.func(); // => Uncaught TypeError: obj.func is not a function
    // 自己处理:obj.xxx=func  只要让obj.xxx执行,也就相当于把func执行,但是此时方法中的this一定是obj了
    
    //  => func基于原型链查找机制,找到Function.prototype.call方法,把call方法执行
    //  => 在call方法内部执行中,才是把func执行,并且让里面的this变为obj,并且把10、20传递给func
    func.call('xxx', 10, 20);
    
    Function.prototype.myCall = function (context) {
      if (typeof this !== 'function') throw new TypeError('Error')
      // 完善部分,如果传入context是个基础类型是无法绑定fn函数的,所以
    
      if (typeof context === 'object') {
        context = context || window;
      } else {
        context = Object.create(null)
      }
    
      context = context || window
      // 如果context中有fn则会被覆盖并清除
      // newContext.fn = this
      // 使用Symbol()独一无二数据类型避免fn冲突
      let fn = Symbol('fn')
      context[fn] = this
      let args
      let result
      if ([...arguments][1]) {
        args = [...arguments].slice(1)
        result = newContext.fn(args)
      } else {
        result = newContext.fn()
      }
      delete context[fn]
      return result
    }
    
    function fn() {
      console.log(this.a, this)
    }
    
    const obj = {
      a: 21
    }
    
    fn.myCall(obj)
    

    更简便的手写call

    Function.prototype.myCall = function (context, ...args) {
      context = context || window
      const symbol = Symbol()
      context[symbol] = this
      const result = context[symbol](...args)
      delete context[symbol]
      return result
    }
    

    5、手写实现一个new方法

    // 有些简单函数就不用再画开辟堆保存函数的图
    // Object.create处理兼容
    Object.create = function create(prototype) {
      function Fn() { }; // 【创建一个空对象】
      Fn.prototype = prototype; // 【让空对象的prototype指向prototype】
      Fn.prototype.constructor = Fn // 【加的】
      return new Fn; // 【返回一个实例对象】
    
      // 也可以下面这样写,但是不推荐用__proto__
      // Fn.__proto__ = prototype
      // return Fn
    };
    
    function _new(Func, ...args) {
      // 【把Func.prototype作为新对象obj的原型,即obj.__proto__ = Func.prototype】
      let obj = Object.create(Func.prototype);
      // 【执行Func,让Func中的this指向obj。】
      let result = Func.call(obj, ...args);
      // 【不是引用类型值,返回obj。undefined == null。】
      if (result == null || !/^(object|function)$/.test(typeof result)) return obj;
      return result;
    }
    
    function Dog(name) {
      this.name = name;
    }
    
    Dog.prototype.bark = function () {
      console.log('wangwang');
    };
    
    Dog.prototype.sayName = function () {
      console.log('my name is ' + this.name);
    };
    
    let sanmao = _new(Dog, '三毛');
    sanmao.bark(); // "wangwang"
    sanmao.sayName(); // "my name is 三毛"
    console.log(sanmao instanceof Dog); // => true
    
    function _new(fn, ...arg) {
      let obj = {}
      let con = [].slice.call(arguments)
      obj.__proto__ = con.prototype //链接原型
      const ret = fn.call(obj, ...arg); //改变this的指向
      return ret instanceof Object ? ret : obj;
    }
    

    6、instanceof

    // example:要检测的实例 
    // classFunc:要检测的类
    function instance_of(example, classFunc) {
      // TZH: 判断是否是基本类型要反向判断,即判断是否object(null特殊处理)和function。
      // 因为纵观es发展历史,基本类型还可能会继续增加,但是引用类型应该会一直只有刚才说那两个,因为es进化时会考虑网络兼容性。
      // 判断是否function、object可以保证随着es进化继续向后兼容。
      // 下面2行是我增加的代码
      if (example === null) return false
      if (!/^object|function$/.test(typeof example)) return false
    
      // 找当前实例的原型,相当于example.__proto
      let proto = Object.getPrototypeOf(example);
      let classPrototype = classFunc.prototype;
      while (true) {
        // 到了Object.prototype.__proto__
        if (proto === null) return false;
        // 在当前实例的原型链上找到了当前类
        if (proto === classPrototype) return true;
        // 继续找上一级的原型 【不是找classPrototype的上一级原型链。】
        proto = Object.getPrototypeOf(proto);
      }
    }
    
    // Function.prototype : Symbol.hasInstance
    console.log([] instanceof Array);
    // 浏览器内部其实是基于Symbol.hasInstance检测的
    console.log(Array[Symbol.hasInstance]([]));
    
    let res = instance_of([12, 23], Array);
    console.log(res); // true
    
    let res2 = instance_of({}, Object)
    console.log(res2) // true
    
    let res3 = instance_of(function () { }, Object)
    console.log(res3) // true
    
    let res4 = instance_of({}, Array)
    console.log(res4) // false
    
    let res5 = instance_of(function () { }, Array)
    console.log(res5) // false
    
    let res6 = instance_of(11, Number)
    console.log(res6) // false
    
    

    7、数组去重

    方法一:利用Set

    const res1 = Array.from(new Set(arr));
    const res2 = [...new Set(arr)];
    
    

    方法二:两层for循环+splice

    const unique1 = arr => {
      let len = arr.length;
      for (let i = 0; i < len; i++) {
        for (let j = i + 1; j < len; j++) {
          if (arr[i] === arr[j]) {
            arr.splice(j, 1);
            // 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能
            len--;
            j--;
          }
        }
      }
      return arr;
    }
    
    

    方法三:利用indexOf

    const unique2 = arr => {
      const res = [];
      for (let i = 0; i < arr.length; i++) {
        if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
      }
      return res;
    }
    
    

    当然也可以用include、filter,思路大同小异。


    方法四:利用includes

    const unique3 = arr => {
      const res = [];
      for (let i = 0; i < arr.length; i++) {
        if (!res.includes(arr[i])) res.push(arr[i]);
      }
      return res;
    }
    
    

    方法五:利用filter

    const unique4 = arr => {
      return arr.filter((item, index) => {
        return arr.indexOf(item) === index;
      });
    }
    
    

    方法六:利用Map

    const unique5 = arr => {
      const map = new Map();
      const res = [];
      for (let i = 0; i < arr.length; i++) {
        if (!map.has(arr[i])) {
          map.set(arr[i], true)
          res.push(arr[i]);
        }
      }
      return res;
    }
    
    

    老周版

    let obj = { aa: 11, bb: 22 }
    console.log(obj.cc) // undefined
    
    ~ function () {
      /*
       * myUnique : 实现数组去重
       *   @params
       *   @return
       *      [Array] 去重后的数组
       * by 666 on 20190805
       */
      function myUnique() {
        // 此时没有传递要操作的ARY进来,但是方法中的THIS是当前要操作的数组,因为是该数组调用该方法:ARY.MYUNIQUE()
        let obj = {};
        for (let i = 0; i < this.length; i++) {
          let item = this[i];
          // 如果obj中没有item这一项,就是undefined;不等于undefined,说明有了
          if (typeof obj[item] !== 'undefined') {
            // (1)删除重复项,会把这一项后面的所有项都往前提一位,性能差;
            // (2)下一轮循环,i++,就会空出一位,防止出现塌陷问题,i--;
            // (3)最后一项拿过来,替换当前项,当前项就不能用了,然后把最后一项删除
            this[i] = this[this.length - 1];
            this.length--;
            i--;
            continue; // 存在了,就不往里存了
          }
          obj[item] = item;
        }
        obj = null;
        // 保证当前方法执行完返回的结果依然是ARRAY类的一个实例
        return this;
      }
      // => 扩展到内置类的原型上
      Array.prototype.myUnique = myUnique;
    }();
    
    let ary = [12, 23, 13, 12, 23, 24, 34, 13, 23];
    // ary.myUnique(); 返回去重后的数组(也是ARRAY类的实例)
    // ary.sort((a, b)  =>  a - b); 返回排序后的数组
    // => 链式写法(保证返回值依然是当前类的实例 一般都会RETURN THIS)
    // ary.myUnique().sort((a, b)  =>  a - b).reverse().slice(2).push('珠峰').concat(12);// => Uncaught TypeError: ary.myUnique(...).sort(...).reverse(...).slice(...).push(...).concat is not a function  执行完push返回的是一个数字(新增后数组的长度),不是数组了,不能再继续使用数组的方法
    ary.myUnique().sort((a, b) => a - b).reverse();
    console.log(ary);
    
    
    /* Array.prototype.push = function () {
      console.log("哈哈哈");
    }
    let ary = [1, 2, 3];
    ary.push(100); // => "哈哈哈"
    console.log(ary); // => 数组没变*/
    
    

    8、用ES5实现数组的map方法

    • 特点:
    1. 循环遍历数组,并返回一个新数组
    2. 回调函数一共接收3个参数,分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」
    • 用法:
    let array = [1, 2, 3].map((item) => {
      return item * 2;
    });
    
    console.log(array);  // [2, 4, 6]
    
    
    • 实现:
    Array.prototype.map = function(fn) {
      let arr = [];
      for(let i = 0; i < this.length; i++) {
        arr.push(fn(this[i], i, this));
      }
      return arr;
    };
    
    

    ~

    方法2

    Array.prototype.map = function (callback, thisArg) {
      if (this == undefined) {
        throw new TypeError('this is null or not defined');
      }
    
      if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
      }
    
      const res = [];
      const O = Object(this);
      const len = O.length >>> 0;
    
      for (let i = 0; i < len; i++) {
        if (i in O) {
          // 调用回调函数并传入新数组
          res[i] = callback.call(thisArg, O[i], i, this);
        }
      }
      return res;
    }
    
    

    9、用ES5实现数组的filter方法

    • 特点:
    1. 该方法返回一个由通过测试的元素组成的新数组,如果没有通过测试的元素,则返回一个空数组
    2. 回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」
    • 用法:
    let array = [1, 2, 3].filter((item) => {
      return item > 2;
    });
    
    console.log(array); // [3]
    
    
    
    • 实现:
    Array.prototype.filter = function(fn) {
      let arr = [];
      for(let i = 0; i < this.length; i++) {
        // 执行fn函数,传递this[i],如果fn(this[i])的返回结果是true,就添加到arr
        fn(this[i]) && arr.push(this[i]);
      }
      return arr;
    };
    
    

    10、用ES5实现数组的some方法

    • 特点:
    1. 在数组中查找元素,如果找到一个符合条件的元素就返回true,如果所有元素都不符合条件就返回 false;
    2. 回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」。
    • 用法:
    let flag = [1, 2, 3].some((item) => {
      return item > 1;
    });
    
    console.log(flag); // true
    
    
    
    • 实现:
    Array.prototype.some = function(fn) {
      for(let i = 0; i < this.length; i++) {
        // 执行fn函数,传递this[i],如果fn(this[i])的返回结果是true,就返回true
        if (fn(this[i])) {
          return true;
        }
      }
      // for 循环结束了,都没有找到就返回false
      return false;
    };
    
    

    11、用ES5实现数组的every方法

    • 特点:
    1. 检测一个数组中的元素是否都能符合条件,都符合条件返回true,有一个不符合则返回 false
    2. 如果收到一个空数组,此方法在任何情况下都会返回 true
    3. 回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」
    • 用法:
    let flag = [1, 2, 3].every((item) => {
      return item > 1;
    });
    
    console.log(flag); // false
    
    
    
    • 实现:
    Array.prototype.every = function(fn) {
      for(let i = 0; i < this.length; i++) {
        // 执行fn函数,传递this[i],如果所有的fn(this[i])的返回结果是false,就返回false
        if(!fn(this[i])) {
          return false
        }
      }
      return true;
    };
    
    
    

    12、用ES5实现数组的find方法

    • 特点:
    1. 在数组中查找元素,如果找到符合条件的元素就返回这个元素,如果没有符合条件的元素就返回 undefined,且找到后不会继续查找
    2. 回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」
    • 用法:
    let item = [1, 2, 3].find((item) => {
      return item > 1;
    });
    
    console.log(item); // 2
    
    
    
    • 实现:
    Array.prototype.find = function(fn) {
      for(let i = 0; i < this.length; i++) {
        // 执行fn函数,传递this[i],如果fn(this[i])的返回结果是true,就返回this[i]
        if (fn(this[i])) return this[i];
      }
      // return undefined // 这句代码可加可不加
    };
    
    

    13、用ES5实现数组的forEach方法

    • 特点:
    1. 循环遍历数组,该方法没有返回值
    2. 回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」
    • 用法:
    [1, 2, 3].forEach((item, index, array) => {
      // 1 0 [1, 2, 3]
      // 2 1 [1, 2, 3]
      // 3 2 [1, 2, 3]
      console.log(item, index, array)  
    });
    
    
    • 实现:
    Array.prototype.forEach = function(fn) {
      for(let i = 0; i < this.length; i++) {
        fn(this[i], i, this);
      }
    };
    
    

    14、用ES5实现数组的reduce方法

    • 特点:
    1. 初始值不传时的特殊处理:会默认用数组中的第一个元素

    2. 函数的返回结果会作为下一次循环的 prev

    3. 回调函数一共接收4个参数,分别是「上一次调用回调时返回的值、正在处理的元素、正在处理的元素的索引、正在遍历的集合对象」

      arr.reduce(prev, next, currentIndex, array)
      
      - prev:上一次调用回调时返回的值
      - 正在处理的元素
      - 正在处理的元素的索引
      - 正在遍历的集合对象
      
      
    • 用法:
    let total = [1, 2, 3].reduce((prev, next, currentIndex, array) => {
      return prev + next;
    }, 0);
    console.log(total); // 6
    
    
    
    • 实现:
    Array.prototype.reduce = function (fn, prev) {
      for (let i = 0; i < this.length; i++) {
        // 初始值不传时的处理
        if (typeof prev === 'undefined') {
          // 明确回调函数的参数都有哪些
          // 【这里的i + 1,不能写成 ++i 和 i++,】
          prev = fn(this[i], this[i + 1], i + 1, this);
          // 经测试,这句代码注释掉,prev有初始值时,结果没有影响;没有初始值,结果会多加一次数组的第二项
          // 第一次是处理第一、二项,第二次是处理第一次的结果 和 第三项,要 ++i
          ++i;
        } else {
          prev = fn(prev, this[i], i, this)
        }
      }
    
      return prev; // 函数的返回结果会作为下一次循环的 prev
    };
    
    let total = [1, 6, 3].reduce((prev, next, currentIndex, array) => {
      return prev + next;
    });
    console.log(total); // 6
    
    

    15、柯理化函数

    柯理化函数含义:是给函数分步传递参数,每次传递部分参数,并返回一个更具体的函数接收剩下的参数,这中间可嵌套多层这样的接收部分参数的函数,直至返回最后结果。

    // add的参数不固定,看有几个数字累计相加
    function add(a, b, c, d) {
      return a + b + c + d
    }
    
    function currying(fn, ...args) {
      // fn.length 回调函数的参数的总和
      // args.length currying函数 后面的参数总和 
      // 如:add (a,b,c,d)  currying(add,1,2,3,4)
      if (fn.length === args.length) {
        return fn(...args)
      } else {
        // 继续分步传递参数 newArgs 新一次传递的参数
        return function anonymous(...newArgs) {
          // 将先传递的参数和后传递的参数 结合在一起
          let allArgs = [...args, ...newArgs]
          return currying(fn, ...allArgs)
        }
      }
    }
    
    let fn1 = currying(add, 1, 2) 
    let fn2 = fn1(3)  
    let fn3 = fn2(4)  // 10
    console.log(fn1, fn2, fn3) // [Function: anonymous] [Function: anonymous] 10
    
    let res = currying(add)(1, 2, 3, 4) 
    console.log(res) // 10
    
    

    16、实现一个反柯理化函数

    • 特点:

    使用callapply可以让非数组借用一些其他类型的函数,比如,Array.prototype.push.call, Array.prototype.slice.calluncrrying把这些方法泛化出来,不在只单单的用于数组,更好的语义化。

    • 用法:
    // 利用反柯里化创建检测数据类型的函数
    // Object.prototype.toString也是Function的实例,所以可以访问Function.prototype上的方法
    let checkType = Object.prototype.toString.unCurring()
    
    console.log(checkType(1)); // [object Number]
    console.log(checkType("hello")); // [object String]
    console.log(checkType(true)); // [object Boolean]
    
    
    • 实现:
    Function.prototype.unCurring = function () {
      var self = this;
      return function () {
        // 返回Function.prototype.call方法
        return Function.prototype.call.apply(self, arguments);
      }
    };
    
    

    17、组合compose函数

    • 特点:
    • 将需要嵌套执行的函数平铺,嵌套执行就是一个函数的返回值将作为另一个函数的参数。该函数调用的方向是从右至左的(先执行 sum,再执行 toUpper,再执行 add)。
    // funcs: 存储的是最后需要按照顺序依次执行的函数集合
    function compose(...funcs) {
      // 要返回一个函数,外面才能传参调用
      return function anonymous(...args) {
        // args: 存储的是给第一个函数执行传递的实参集合
        if (funcs.length === 0) return args.length <= 1 ? args[0] : args;
        if (funcs.length === 1) return funcs[0](...args);
        // funcs = [add1, add1, mul3, div2]
        // args = [0]
        
        return funcs.reduce((result, item) => {
          // 通过判断是否是函数类型
          return typeof result === "function" ?
            item(result(...args)) :
            item(result);
        }); 
       
        // 不用判断是否是函数类型
        /* let n = 0;
        return funcs.reduce((result, item) => {
          n++;
          return n === 1 ? item(result(...args)) : item(result);
        }); */
      };
    }
    
    const add1 = (x) => x + 1;
    const mul3 = (x) => x * 3;
    const div2 = (x) => x / 2;
    
    // => 0 如果不指定任何函数, 直接把最后传递的结果返回,传递一个返回一个值,传递多个返回一个数组
    let result = compose()(0);
    console.log(result);
    
    // => add1(0) 只指定一个函数,就是把最后的结果传递这个函数,执行函数获取其返回值即可
    result = compose(add1)(0);
    console.log(result);
    
    result = compose(add1, add1, mul3, div2)(0);
    console.log(result);
    
    

    方法2

    • 用法
    function sum(a, b) {
      return a + b;
    }
    
    function toUpper(str) {
      return str.toUpperCase();
    }
    
    function add(str) {
      return '===' + str + '==='
    }
    
    // 使用 compose 之前:
    console.log(add(toUpper(sum('cherry', '27')))); // ===CHERRY27===
    // 使用 compose 之后:
    console.log(compose(add, toUpper, sum)('cherry', '27')); // ===CHERRY27===
    
    
    // 使用 ES6 - reduce 一行代码实现
    const compose = (...fns) => fns.reduce((a, b) => (...args) => a(b(...args)));
    
    // 使用 ES5- reduceRight 实现
    function compose(...fns) {
      return function (...args) {
        let lastFn = fns.pop();
        return fns.reduceRight((a, b) => {
          return b(a);
        }, lastFn(...args));
      };
    }
    
    // 使用 ES6 - reduceRight 实现
    const compose = (...fns) => (...args) => {
      let lastFn = fns.pop();
      return fns.reduceRight((a, b) => b(a), lastFn(...args));
    };
    
    

    18、实现一个 Pipe (管道)

    • 特点:

    pipe函数跟compose函数的作用是一样的,也是将参数平铺,只不过他的顺序是从左往右。(先执行 splitString,再执行 count)

    • 用法:
    function splitString(str) {
      return str.split(' ');
    }
    
    function count(array) {
      return array.length;
    }
    
    // 使用 pipe 之前:
    console.log(count(splitString('hello cherry'))); // 2
    // 使用 pipe 之后:
    console.log(pipe(splitString, count)('hello cherry')); // 2
    
    
    
    • 实现:
    // 使用 ES6 - reduce 一行代码实现:(redux源码)
    const pipe = (...fns) => (...args) => fns.reduce((a, b) => b(a), ...args);
    
    const pipe = function () {
      const args = [].slice.apply(arguments);
      return function (x) {
        return args.reduce((res, cb) => cb(res), x);
      }
    }
    
    // 使用 ES5- reduceRight 实现
    function pipe(...fns) {
      return function (...args) {
        let lastFn = fns.shift();
        return fns.reduceRight((a, b) => {
          return b(a);
        }, lastFn(...args));
      };
    }
    
    // 使用 ES6 - reduceRight 实现
    const pipe = (...fns) => (...args) => {
      let lastFn = fns.shift();
      return fns.reduceRight((a, b) => b(a), lastFn(...args));
    };
    
    

    19、数组扁平化

    数组扁平化是指将一个多维数组变为一个一维数组

    const arr = [1, [2, [3, [4, 5]]], 6];
    // => [1, 2, 3, 4, 5, 6]
    
    

    方法一:使用flat()

    const res1 = arr.flat(Infinity);
    
    

    方法二:利用正则

    const res2 = JSON.stringify(arr).replace(/[|]/g, '').split(',');
    
    

    但数据类型都会变为字符串


    方法三:正则改良版本

    const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/[|]/g, '') + ']');
    
    

    方法四:使用reduce

    const flatten = arr => {
      return arr.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
      }, [])
    }
    const res4 = flatten(arr);
    
    

    方法五:函数递归

    const arr = [1, [2, [3, [4, 5]]], 6];
    const res5 = [];
    const fn = arr => {
      for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
          fn(arr[i]);
        } else {
          res5.push(arr[i]);
        }
      }
    }
    fn(arr);
    console.log(res5) //  [1, 2, 3, 4, 5, 6]
    
    
    // 我写的
    const arr = [1, [2, [3, [4, 5]]], 6];
    const res5 = [];
    
    const fn = arr => {
      for (let i = 0; i < arr.length; i++) {
        let item = arr[i]
        if (item instanceof Array) {
          fn(item)
        } else {
          res5.push(item)
        }
      }
    }
    
    fn(arr)
    console.log(res5)
    
    

    // 辉森面试题:请将该 data 数组铺平
    const data = [
      {
        id: 1,
        title: "课程 1",
        children: [
          { id: 4, title: "课程 1-1" },
          {
            id: 5,
            title: "课程 1-2",
            children: [
              { id: 6, title: "课程 1-2-1" },
              { id: 7, title: "课程 1-2-2" },
            ],
          },
        ],
      },
      { id: 2, title: "课程 2" },
      { id: 3, title: "课程 3" },
    ];
    
    // 输出结果:
    const formatData = [
      { id: 1, title: "课程 1" },
      { id: 4, title: "课程 1-1" },
      { id: 5, title: "课程 1-2" },
      { id: 6, title: "课程 1-2-1" },
      { id: 7, title: "课程 1-2-2" },
      { id: 2, title: "课程 2" },
      { id: 3, title: "课程 3" },
    ];
    
    function getFormData(data) {
      let res = []
      function handleData(arr) {
        arr.forEach(item => {
          res.push({ id: item.id, title: item.title })
          const children = item.children
          if (children && Array.isArray(children)) {
            handleData(children)
          }
        })
      }
    
      handleData(data)
      return res
    }
    
    let r = getFormData(data)
    console.log(r)
    
    // 加的
    const arr = []
    function fn(data) {
      if (!(data instanceof Array)) return
    
      data.forEach(item => {
        arr.push({ id: item.id, title: item.title })
        item.children instanceof Array && fn(item.children)
      })
      return arr
    }
    
    let res = fn(data)
    console.log(res)
    
    

    20、浅克隆/浅拷贝

    浅克隆:只拷贝对象或数组的第一层内容

    const shallClone = (target) => {
      if (typeof target === 'object' && target !== null) {
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {
          // 遍历对象自身可枚举属性(不考虑继承属性和原型对象)
          if (target.hasOwnProperty(prop)) { 
            cloneTarget[prop] = target[prop];
        }
        return cloneTarget;
      } else {
        return target;
      }
    }
    
    

    21、深克隆(考虑日期、正则、循环引用等)

    const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;
    
    function deepClone(target, map = new Map()) {
      // 先判断该引用类型是否被 拷贝过
      if (map.get(target)) {
        return target;
      }
      // 获取当前值的构造函数:获取它的类型
      let constructor = target.constructor;
    
      // 检测当前对象target是否与 正则、日期格式对象匹配
      if (/^(RegExp|Date)$/i.test(constructor.name)) {
        return new constructor(target); // 创建一个新的特殊对象(正则类/日期类)的实例
      }
    
      if (isObject(target)) {
        map.set(target, true); // 为循环引用的对象做标记
        const cloneTarget = Array.isArray(target) ? [] : {};
    
        for (let prop in target) {
          if (target.hasOwnProperty(prop)) {
            cloneTarget[prop] = deepClone(target[prop], map);
          }
        }
        return cloneTarget;
      } else {
        return target;
      }
    }
    
    

    22、实现一个可以拖拽的DIV

    <!DOCTYPE html>
    <html>
    
    <head lang="en">
      <meta charset="UTF-8">
      <title></title>
      <style>
        .login-header {
           100%;
          text-align: center;
          height: 30px;
          font-size: 24px;
          line-height: 30px;
        }
    
        ul,
        li,
        ol,
        dl,
        dt,
        dd,
        div,
        p,
        span,
        h1,
        h2,
        h3,
        h4,
        h5,
        h6,
        a {
          padding: 0px;
          margin: 0px;
        }
    
        .login {
          display: none;
           512px;
          height: 280px;
          position: fixed;
          border: #ebebeb solid 1px;
          left: 50%;
          top: 50%;
          background: #ffffff;
          box-shadow: 0px 0px 20px #ddd;
          z-index: 9999;
          transform: translate(-50%, -50%);
        }
    
        .login-title {
           100%;
          margin: 10px 0px 0px 0px;
          text-align: center;
          line-height: 40px;
          height: 40px;
          font-size: 18px;
          position: relative;
          cursor: move;
        }
    
        .login-input-content {
          margin-top: 20px;
        }
    
        .login-button {
           50%;
          margin: 30px auto 0px auto;
          line-height: 40px;
          font-size: 14px;
          border: #ebebeb 1px solid;
          text-align: center;
        }
    
        .login-bg {
          display: none;
           100%;
          height: 100%;
          position: fixed;
          top: 0px;
          left: 0px;
          background: rgba(0, 0, 0, .1);
        }
    
        a {
          text-decoration: none;
          color: #000000;
        }
    
        .login-button a {
          display: block;
        }
    
        .login-input input.list-input {
          float: left;
          line-height: 35px;
          height: 35px;
           350px;
          border: #ebebeb 1px solid;
          text-indent: 5px;
        }
    
        .login-input {
          overflow: hidden;
          margin: 0px 0px 20px 0px;
        }
    
        .login-input label {
          float: left;
           90px;
          padding-right: 10px;
          text-align: right;
          line-height: 35px;
          height: 35px;
          font-size: 14px;
        }
    
        .login-title span {
          position: absolute;
          font-size: 12px;
          right: -20px;
          top: -30px;
          background: #ffffff;
          border: #ebebeb solid 1px;
           40px;
          height: 40px;
          border-radius: 20px;
        }
      </style>
    </head>
    
    <body>
      <div class="login-header">
        <a id="link" href="javascript:;">点击,弹出登录框</a>
      </div>
    
      <div id="login" class="login">
        <div id="title" class="login-title">
          登录会员
          <span>
            <a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a>
          </span>
        </div>
    
        <div class="login-input-content">
          <div class="login-input">
            <label>用户名:</label>
            <input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="list-input">
          </div>
    
          <div class="login-input">
            <label>登录密码:</label>
            <input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="list-input">
          </div>
        </div>
    
        <div id="loginBtn" class="login-button">
          <a href="javascript:void(0);" id="login-button-submit">登录会员</a>
        </div>
      </div>
      <!-- 遮盖层 -->
      <div id="bg" class="login-bg"></div>
    
      <script>
        // 1. 获取元素
        var login = document.querySelector('.login');
        var mask = document.querySelector('.login-bg');
        var link = document.querySelector('#link');
        var closeBtn = document.querySelector('#closeBtn');
        var title = document.querySelector('#title');
    
        // 2. 点击弹出层这个链接 link  让mask 和login 显示出来
        link.addEventListener('click', function () {
          mask.style.display = 'block';
          login.style.display = 'block';
        })
    
        // 3. 点击 closeBtn 就隐藏 mask 和 login 
        closeBtn.addEventListener('click', function () {
          mask.style.display = 'none';
          login.style.display = 'none';
        })
    
        // 4. 开始拖拽
        // (1) 当我们鼠标按下, 就获得鼠标在盒子内的坐标
        title.addEventListener('mousedown', function (e) {
          var x = e.pageX - login.offsetLeft;
          var y = e.pageY - login.offsetTop;
    
          // (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
          document.addEventListener('mousemove', move)
    
          function move(e) {
            login.style.left = e.pageX - x + 'px';
            login.style.top = e.pageY - y + 'px';
          }
    
          // (3) 鼠标弹起,就让鼠标移动事件移除
          document.addEventListener('mouseup', function () {
            document.removeEventListener('mousemove', move);
          })
        })
      </script>
    </body>
    
    </html>
    
    

    23、实现数组的取交集,并集,差集

    1. 取交集

    Array.prototype.includes

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    let intersection = a.filter(v => b.includes(v));
    console.log(intersection); // [ 2 ]
    
    

    Array.from

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    let aSet = new Set(a);
    let bSet = new Set(b);
    let intersection = Array.from(new Set(a.filter(v => bSet.has(v))));
    
    console.log(intersection); // [ 2 ]
    
    

    Array.prototype.indexOf

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    let intersection = a.filter((v) => b.indexOf(v) > -1);
    
    console.log(intersection); // [ 2 ]
    
    

    2. 取并集

    Array.prototype.includes

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    
    let union = a.concat(b.filter(v => !a.includes(v)));
    
    console.log(union); // [ 1, 2, 3, 4, 5 ]
    
    

    Array.from

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    let aSet = new Set(a);
    let bSet = new Set(b);
    let union = Array.from(new Set(a.concat(b)));
    
    console.log(union); // [ 1, 2, 3, 4, 5 ]
    
    

    Array.prototype.indexOf

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    let union = a.concat(b.filter((v) => a.indexOf(v) === -1));
    
    console.log(union); // [ 1, 2, 3, 4, 5 ]
    
    
    

    3. 取差集

    Array.prototype.includes

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    
    let difference = a.concat(b).filter(v => !a.includes(v) || !b.includes(v));
    
    console.log(difference); // [ 1, 3, 4, 5 ]
    
    

    Array.from

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    let aSet = new Set(a);
    let bSet = new Set(b);
    let difference = Array.from(new Set(a.concat(b).filter(v => !aSet.has(v) || !bSet.has(v))));
    
    console.log(difference); // [ 1, 3, 4, 5 ]
    
    

    Array.prototype.indexOf

    let a = [1, 2, 3];
    let b = [2, 4, 5];
    let difference = a.filter((v) => b.indexOf(v) === -1).concat(b.filter((v) => a.indexOf(v) === -1));
    
    console.log(difference); // [ 1, 3, 4, 5 ]
    
    

    24、实现Object.create方法(经常考)

    • 特点:

    创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

    • 用法:
    let demo = {
      c : '123'
    };
    let cc = Object.create(demo);
    console.log(cc);
    
    
    
    • 实现:
    function create(proto) {
      // 排除传入的对象是 null 和 非object的情况
      if (proto === null || typeof proto !== 'object') {
        throw new TypeError(`Object prototype may only be an Object: ${proto}`);
      }
      
      function Fn() { };
      // 将Fn的原型指向传入的 proto
      Fn.prototype = proto;
      Fn.prototype.constructor = Fn;
      return new Fn();
    };
    
    

    方法2

    function create(prototype) {
      // 排除传入的对象是 null 和 非object的情况
      if (prototype === null || typeof prototype !== 'object') {
        throw new TypeError(`Object prototype may only be an Object: ${prototype}`);
      }
      // 让空对象的 __proto__指向 传进来的 对象(prototype)
      // 目标 {}.__proto__ = prototype
      function Temp() { };
      Temp.prototype = prototype;
      return new Temp;
    }
    
    

    25、寄生式组合继承

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    
    <body>
      <script>
        // 借用父构造函数继承属性
        // 1. 父构造函数
        function Father(uname, age) {
          // this 指向父构造函数的对象实例
          this.uname = uname;
          this.age = age;
        }
    
        Father.prototype.money = function () {
          console.log(100000);
        };
    
        // 2.子构造函数 
        function Son(uname, age, score) {
          // (1)this 指向子构造函数的对象实例;(2)不能写成new Father.call(),Father.call is not a constructor,Father.call不是一个构造函数
          Father.call(this, uname, age);
          this.score = score;
        }
    
        // 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
        // Son.prototype = Father.prototype;
    
        // Son.prototype = new Father();
        Son.prototype = Object.create(Father.prototype);
        // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
        Son.prototype.constructor = Son;
    
        // 这个是子构造函数专门的方法 【子构造函数专门的方法 要写在上面两行代码的后面。】
        Son.prototype.exam = function () {
          console.log('孩子要考试');
        }
    
        var son = new Son('刘德华', 18, 100);
        console.log(son); // (1)有money、exam方法; (2)Son {uname: "刘德华", age: 18, score: 100}
        console.log(Father.prototype); // (1)有money方法,没有exam方法; (2){money: ƒ, constructor: ƒ}
        console.log(Son.prototype.constructor); //
      </script>
    </body>
    
    </html>
    
    

    26、实现一个 sleep 函数

    思路:比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现。

    const sleep = (time) => {
      return new Promise(resolve => setTimeout(resolve, time))
    }
    
    sleep(1000).then(() => {
        // 这里写你的骚操作
    })
    
    // --------------------
    
    const sleep = (time) => {
      return new Promise(resolve => setTimeout(resolve, time))
    }
    
    async function sleepAsync() {
      console.log('fuck the code')
      await sleep(1000)
      console.log('fuck the code again')
    }
    
    sleepAsync()
    
    // --------------------
    
    // 我的改版
    // 如果定时器的回调函数是简写,那么resolve函数不能传参,否则不能实现定时器效果
    const fn2 = (t) => new Promise(resolve => setTimeout(resolve(666), t))
    
    // 定时器的回调函数不简写,resolve函数传参,才能实现定时器效果
    const fn2 = (t) => {
      return new Promise(resolve => setTimeout(function () {
        resolve(666)
      }, t))
    }
    
    async function fn3(t) {
      let res = await fn2(t)
      console.log(res) // 666
      console.log(555) // 555
    }
    
    fn3(1000)
    
    

    ~

    // Promise
    const sleep = time => {
      return new Promise(resolve => setTimeout(resolve, time))
    }
    sleep(1000).then(() => {
      console.log(1)
    })
    
    // -----------------------------
    
    // Generator
    function* sleepGenerator(time) {
      yield new Promise(function (resolve, reject) {
        setTimeout(resolve, time);
      })
    }
    sleepGenerator(1000).next().value.then(() => { console.log(1) })
    
    // -----------------------------
    
    // async
    function sleep(time) {
      return new Promise(resolve => setTimeout(resolve, time))
    }
    
    async function output() {
      let out = await sleep(1000);
      console.log(1);
      return out;
    }
    output();
    
    // ES5
    function sleep(callback, time) {
      if (typeof callback === 'function')
        setTimeout(callback, time)
    }
    
    function output() {
      console.log(1);
    }
    sleep(output, 1000);
    
    

    27、类数组转化为数组

    类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。


    方法一:Array.from

    Array.from(document.querySelectorAll('div'))
    
    

    方法二:扩展运算符

    [...document.querySelectorAll('div')]
    
    

    方法三:Array.prototype.slice.call()

    Array.prototype.slice.call(document.querySelectorAll('div'))
    
    

    方法四:利用concat

    Array.prototype.concat.apply([], document.querySelectorAll('div'));
    
    

    29、Object.is

    Object.is解决的主要是这两个问题:

    +0 === -0  // true
    NaN === NaN // false
    console.log(Object.is(+0, -0)) // false
    console.log(Object.is(NaN, NaN)) // true
    
    
    const is= (x, y) => {
      if (x === y) {
        // +0 和 -0应该不相等
        return x !== 0 || y !== 0 || 1/x === 1/y;
      } else {
        // 取反操作
        return x !== x && y !== y;
      }
    }
    
    

    29、Object.assign

    Object.defineProperty(Object, 'assign', {
      value: function (target, ...args) {
        if (target == null) {
          return new TypeError('Cannot convert undefined or null to object');
        }
    
        // 目标对象需要统一是引用数据类型,若不是会自动转换
        const to = Object(target);
    	    
        for (let i = 0; i < args.length; i++) {
          // 每一个源对象
          const nextSource = args[i];
          // args的每一项都得是对象,不能是null
          if (nextSource !== null) {
            // 使用for...in和hasOwnProperty双重判断,确保只拿到本身的属性、方法(不包含继承的)
            for (const nextKey in nextSource) {
              if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)){
                to[nextKey] = nextSource[nextKey];
              }
            }
          }
        }
        return to;
      },
      // 不可枚举
      enumerable: false,
      writable: true,
      configurable: true,
    })
    
    

    30、jsonp

    script标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好,但仅限于GET请求

    const jsonp = ({ url, params, callbackName }) => {
      const generateUrl = () => {
        // 判断是否含有参数,用queryString代替下面的dataSrc
        // let queryString = url.indexOf("?") === "-1" ? "?" : "&";
    
        let dataSrc = '';
    
        for (let key in params) {
          if (Object.prototype.hasOwnProperty.call(params, key)) {
            dataSrc += `${key}=${params[key]}&`;
          }
        }
    
        dataSrc += `callback=${callbackName}`;
        return `${url}?${dataSrc}`;
      }
    
      return new Promise((resolve, reject) => {
        const scriptEle = document.createElement('script');
        scriptEle.src = generateUrl();
        document.body.appendChild(scriptEle);
    
        window[callbackName] = data => {
          resolve(data);
          document.removeChild(scriptEle);
        }
      })
    }
    
    
    function jsonp(url, params, callback) {
      // 判断是否含有参数
      let queryString = url.indexOf("?") === "-1" ? "?" : "&";
    
      // 添加参数 【要去掉最后一个&,substr()】
      for (var k in params) {
        if (params.hasOwnProperty(k)) {
          queryString += k + "=" + params[k] + "&";
        }
      }
    
      // 处理回调函数名 【时间戳更好】
      let random = Math.random().toString().replace(".", "");
      let callbackName = "myJsonp" + random;
    
      // 添加回调函数
      queryString += "callback=" + callbackName;
    
      // 构建请求
      let scriptNode = document.createElement("script");
      scriptNode.src = url + queryString;
    
      window[callbackName] = function () {
        // 调用回调函数
        callback(...arguments);
        // 删除这个引入的脚本
        document.getElementsByTagName("head")[0].removeChild(scriptNode);
      };
    
      // 发起请求 【应该写到上面】
      document.getElementsByTagName("head")[0].appendChild(scriptNode);
    }
    
    
    import originJSONP from 'jsonp'
    
    export default function jsonp(url, data, option) {
      // 判断url后面是否有问号
      url += (url.indexOf('?') < 0 ? '?' : '&') + param(data)
    
      // 【这里需要向后套发送请求,是异步的,所以用Promise】
      return new Promise((resolve, reject) => {
        originJSONP(url, option, (err, data) => {
          if (!err) {
            resolve(data) // 成功
          } else {
            reject(err) // 失败
          }
        })
      })
    }
    
    // 拼接data
    function param(data) {
      let url = ''
      for (var k in data) {
        let value = data[k] !== undefined ? data[k] : ''
        url += `&${k}=${encodeURIComponent(value)}`
      }
      // 删除第一个&,这里的url开始是空字符串
      return url ? url.substring(1) : ''
    }
    
    

    31、ajax

    const getJSON = function (url) {
      return new Promise((resolve, reject) => {
        const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Accept', 'application/json');
        
        xhr.onreadystatechange = function () {
          if (xhr.readyState !== 4) return;
          if (xhr.status === 200 || xhr.status === 304) {
            resolve(xhr.responseText);
          } else {
            reject(new Error(xhr.responseText));
          }
        }
        xhr.send();
      })
    }
    
    

    32、图片懒加载

    可以给img标签统一自定义属性data-src='default.png',当检测到图片出现在窗口之后再补充src属性,此时才会进行图片资源加载。

    function lazyload() {
      const imgs = document.getElementsByTagName('img');
      const len = imgs.length;
      // 视口的高度
      const viewHeight = document.documentElement.clientHeight;
      // 滚动条高度
      const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
    
      for (let i = 0; i < len; i++) {
        const offsetHeight = imgs[i].offsetTop;
        if (offsetHeight < viewHeight + scrollHeight) {
          const src = imgs[i].dataset.src;
          imgs[i].src = src;
        }
      }
    }
    
    // 可以使用节流优化一下
    window.addEventListener('scroll', lazyload);
    
    
    // 补充
    获得页面滚动过的高度:
    document.body.scrollTop || document.documentElement.scrollTop
    【document.documentElement.scrollTop,这个貌似比document.body.scrollTop管用。】
    
    
    offsetWidth:  width + padding + border (披着羊皮的狼) 
    clientWidth: width  +  padding, 不包含border。      
    scrollWidth:  如果内容没有超出盒子,就是width + padding。如果超过盒子宽度,就是内容的宽度。
    高度同理。
    
    
    offset 概述
    offset 翻译过来就是偏移量, 我们使用 offset系列相关属性可以动态的得到该元素的位置(偏移)、大小等。
    1. 获得元素距离带有定位父元素的位置
    2. 获得元素自身的大小(宽度高度)
    3. 注意:返回的数值都不带单位
    4. offsetLeft:它以带有定位的父亲为准,如果没有父亲或者父亲没有定位,则以 body 为准
    
    

    img


    33.滚动加载

    原理就是监听页面滚动事件,分析clientHeightscrollTopscrollHeight三者的属性关系。

    window.addEventListener('scroll', function() {
      const clientHeight = document.documentElement.clientHeight;
      const scrollTop = document.documentElement.scrollTop;
      const scrollHeight = document.documentElement.scrollHeight;
      if (clientHeight + scrollTop >= scrollHeight) {
        // 检测到滚动至页面底部,进行后续操作
        // ...
      }
    }, false);
    
    

    一个Demo:页面滚动加载的Demo


    34.渲染几万条数据不卡住页面

    渲染大数据时,合理使用createDocumentFragmentrequestAnimationFrame,将操作切分为一小段一小段执行。

    setTimeout(() => {
      const total = 100000; // 插入十万条数据
      const once = 20; // 一次插入的数据
      const loopCount = Math.ceil(total / once); // 插入数据需要的次数
      let countOfRender = 0;
      let index = 0; // 我加的
      const ul = document.querySelector('ul');
    
      // 添加数据的方法
      function add() {
        const fragment = document.createDocumentFragment();
    
        for (let i = 0; i < once; i++) {
          const li = document.createElement('li');
          // li.innerText = Math.floor(Math.random() * total);
          li.innerText = ++index; // 我改的,用递增的数字更直观
          fragment.appendChild(li);
        }
    
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }
    
      function loop() {
        if (countOfRender < loopCount) {
          // window.requestAnimationFrame() 告诉浏览器 —— 你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
          window.requestAnimationFrame(add);
        }
      }
      
      loop();
    }, 0)
    
    

    35.打印出当前网页使用了多少种HTML元素

    一行代码可以解决:

    const fn = () => {
      return [...new Set([...document.querySelectorAll('*')].map(el => el.tagName))].length;
    }
    
    

    值得注意的是:DOM操作返回的是类数组,需要转换为数组之后才可以调用数组的方法。


    36、Object.freeze

    Object.freeze:冻结一个对象,让其不能再添加/删除属性,也不能修改该对象已有属性的可枚举性、可配置可写性,也不能修改已有属性的值和它的原型属性,最后返回一个和传入参数相同的对象。

    function myFreeze(obj) {
      // 判断参数是否为Object类型,如果是就封闭对象,循环遍历对象。去掉原型属性,将其writable特性设置为false
      if (obj instanceof Object) {
        // Object.seal():封闭一个对象,阻止添加新属性,并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。
        Object.seal(obj);
        for (let key in obj) {
          if (obj.hasOwnProperty(key)) {
            Object.defineProperty(obj, key, {
              writable: false   // 设置只读
            })
            // 如果属性值依然为对象,要通过递归来进行进一步的冻结
            myFreeze(obj[key]);
          }
        }
      }
    }
    
    

    37、JS实现String.trim()方法

    • trim本质就是在字符串的前后去掉空格
    String.prototype.emuTrim = function() {
      return this.replace(/(^s*)|(s*$)/g, '')
    }
    
    
  • 相关阅读:
    Spring4
    Mysql索引优化、JMM内存模型
    深入浅出JVM
    ZGC垃圾搜集器
    Hibernate4基础
    红楼梦 各版本及资料
    JavaME环境配置
    5.1 Intent
    4.3 异步任务
    4.2.2 网络编程之Socket
  • 原文地址:https://www.cnblogs.com/jianjie/p/13902421.html
Copyright © 2011-2022 走看看