美文网首页
夯实JavaScript功底,前端要会的手写方法

夯实JavaScript功底,前端要会的手写方法

作者: 飞跃疯人院_a | 来源:发表于2019-10-10 19:23 被阅读0次

    call: 简单来说就是改变执行方法当前的this,可以传入不定参数

    Function.prototype.myCall = function(context, ...args) {
      context.fn = this;
      const result = context.fn(...args);
      delete context.fn;
      return result;
    }
    
    getMessage.myCall(obj, 'name'); 
    
    立即执行getMessage方法,不过是以obj.getMessage的方式,
    所以这个时候getMessage内的this是obj,传入参数'name'。
    (obj可能压根就没有getMessage方法)
    
    

    apply: 和call作用一致,传递参数格式不同,需是数组

    Function.prototype.myApply = function(context, args) {
      context.fn = this;
      const result = context.fn(args);
      delete context.fn;
      return result;
    }
    
    getMessage.myApply(obj, ['name']); 
    
    

    bind: 改变指定方法的this后执行,以函数的形式返回执行结果

    Function.prototype.myBind = function(context, ...args) {
      return () => {
        return this.myApply(context, args);
      }
    }
    
    const result = getMessage.myBind(obj) 
    
    result是一个函数,再执行一次才是getMessage方法的执行结果
    
    

    new:将构造函数实例化,将参数创建为对象以及赋值原型方法

    function createNew(Ctor, ...args) {
      const obj = Object.create(Ctor.prototype);
      const ret = Ctur.apply(obj, args);
      return ret instanceof Object ? ret : obj;
    }
    
    1. 将构造函数的原型赋值给新建的obj的隐式原型__proto__。
    2. 在obj下执行构造函数,并传入参数,
       这个时候构造函数内的this就是obj。
    3. 如果这个'构造函数'没有return对象格式的结果,
       返回新创建的obj。
    
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.getName = function() {
      console.log(this.name);
    }
    
    const xm = createNew(Person, 'xiaoming', 22);
    
    

    instanceof: 判断一个变量是否是某个类型

    function myInstanceOf(left, right) {
      while(1) {
        if(left.__proto__ === null) {
          return false;
        }
        if(left.__proto__ === right.prototype) {
          return true;
        }
        left = left.__proto__;
      }
    }
    
    instanceof的原理就是通过原型链查找,
    所以一直向上查找左侧的隐式原型__ptoto__是否等于右侧显式原型,
    原型链的尽头是null,没找到就返回false。
    
    myInstanceOf([1,2], Array);  // true
    
    

    forEach: 遍历数组,这个大家经常用,想必不说都懂

    Array.prototype.myForEach = function(fn) {
      const arr = this;
      for(let i = 0; i < arr.length; i++) {
        fn(arr[i], i, arr);
      }
    }
    
    接受一个fn回调函数,传递给回调函数三个参数:
    每项的值,下标,自身。第二个参数有人用么?
    
    const arr = ['a','b','c'];
    arr.myForEach(item => {
      console.log(item);  // a   b   c
    })
    
    

    map: 返回经过处理的数组

    Array.prototype.myMap = function(fn) {
      const arr = this;
      const ret = [];
      for(let i = 0; i < arr.length; i++) {
        ret.push(fn(arr[i], i, arr));
      }
      return ret;
    }
    
    和forEach类似也是接受一个fn回调,
    不过会将回调处理的结果放入一个新的数组,
    所以map回调内的每一项需要return,
    因为要组成新的数组结果。
    
    const arr = ['a', 'b', 'c'];
    const newArr = arr.myMap(item => {  // a1   b1   c1
      return item + 1; // 要return结果
    })
    
    

    filter: 返回回调处理结果为true的新数组

    Array.prototype.myFilter = function(fn) {
      const arr = this;
      const ret = [];
      for(let i = 0; i < arr.length; i++) {
        if(fn(arr[i], i, arr)) {
          ret.push(arr[i]);
        }
      }
      return ret;
    }
    
    大同小异,过滤出处理条件为true的值。
    
    返回数组中不重复的值:
    function repeat(arr) {
      return arr.myFilter((v, i, a) => {
        return a.indexOf(v) === a.lastIndexOf(v);
      })
    }
    
    const arr = [1,2,3,4,1,2,3,5,6,8,3];
    repeat(arr); // [4,5,6,8]
    
    

    find:返回处理条件第一个为true的数组项

    Array.prototype.myFind = function(fn) {
      const arr =this;
      for(let i = 0; i < arr.length; i++) {
        if(fn(arr[i], i, arr)) {
          return arr[i];
        }
      }
    }
    
    否则返回undefined
    
    

    findIndex: 返回处理条件第一个为true的数组下标

    大家自己写下咯~
    
    

    every:如果数组每一项都符合处理条件。返回true,否则返回false

    Array.prototype.myEvery = function(fn) {
      const arr = this;
      for(let i = 0; i < arr.length; i++) {
        if(!fn(arr[i], i, arr)) {
          return false;
        }
      }
      return true;
    }
    
    

    some:只要数组有一项符合处理条件。返回true,都不满足返回false。

    这个相信大家都知道怎么写了~
    
    

    reduce: 一般为数组做累计结果使用。

    Array.prototype.myReduce = function(fn, second) {
      const arr = this;
      let index = 0;
      if(typeof second === undefined) { // 没传第二个参数
        index = 1;
        second = arr[0];
      }
      for(let i = index; i < arr.length; i++) {
        const invoked = fn(second, arr[i], i, arr);
        second = invoked;
      }
      return second;
    }
    
    一般会传入第二个参数作为初始值,如果没有传入,
    初始值就是数组的第一项,将处理的结果进行累计,
    最后返回累计的结果。
    
    返回数组中指定参数重复的次数:
    function count(arr, value) {
      return arr.myReduce((f, s) => {
        return Object.is(s, value) ? f + 1 : f + 0;
      }, 0)
    }
    
    const arr = [1,2,3,4,1,2,3,2,1];
    count(arr, 2); // 3
    
    

    debounce: 函数防抖

    function debounce(fn, delay = 1000) {
      let timer;
      return () => {
        if (timer) {
          clearTimeout(timer);
        }
        timer = setTimeout(() => {
          fn.apply(this, arguments);
        }, delay)
      }
    }
    
    函数防抖指的是一定时间内没有再次触发函数,就执行该函数,否则重新计时。
    wow为例:
    2.5s施法的寒冰箭,再读条的过程中,
    你身子抖动打断了施法,想再次触发技能时麻烦您重新读条。
    

    throttle:函数节流

    function throttle(fn, delay = 100) {
      let timer;
      return () => {
        if (!timer) {
          timer = setTimeout(() => {
            fn.apply(this, arguments);
            timer = null;
          }, delay)
        }
      }
    }
    
    函数节流指的是规定某个时间内只能执行一次函数。
    wow为例:
    火冲为瞬发技能,不过你规定cd为8s,
    所以即使8s内按了10次,也只能来1发,节省点体力吧。
    

    deepClone:深拷贝

    一般够用型
    function deepClone(source) {
      if (typeof source !== 'object' || source == null) {
        return source;
      }
      const target = Array.isArray(source) ? [] : {};
      for (const key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          if (typeof source[key] === 'object' && source[key] !== null) {
            target[key] = deepClone(source[key]);
          } else {
            target[key] = source[key];
          }
        }
      }
      return target;
    }
    
    解决循环引用和symblo类型
    function cloneDeep(source, hash = new WeakMap()) {
      if (typeof source !== 'object' || source === null) {
        return source;
      }
      if (hash.has(source)) {
        return hash.get(source);
      }
      const ret = Array.isArray(source) ? [] : {};
      Reflect.ownKeys(source).forEach(key => {
        const val = source[key];
        if (typeof val === 'object' && val != null) {
          ret[key] = cloneDeep(val, hash);
        } else {
          ret[key] = val;
        }
      })
      return ret;
    }
    

    Promise:手写简易版

    class MyPromise {
      constructor(fn) {
        this.state = 'PENDING';
        this.value = null
        this.resolvedCallbacks = []
        this.rejectedCallbacks = []
        const resolve = value => {
          if (this.state === 'PENDING') {
            this.state = 'RESOLVED'
            this.value = value
            this.resolvedCallbacks.map(cb => cb())
          }
        }
        const reject = value => {
          if (this.state === 'PENDING') {
            this.state = 'REJECTED'
            this.value = value
            this.rejectedCallbacks.map(cb => cb())
          }
        }
        try {
          fn(resolve, reject)
        } catch (e) {
          reject(e)
        }
      }
      
      then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
        onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }
        if (this.state === 'PENDING') {
          this.resolvedCallbacks.push(onFulfilled)
          this.rejectedCallbacks.push(onRejected)
        }
        if (this.state === 'RESOLVED') {
          onFulfilled(this.value)
        }
        if (this.state === 'REJECTED') {
          onRejected(this.value)
        }
      }
      
      catch(fn) {
        return this.then(null, fn);
      }
    }
    
    const promise = new MyPromise((resolve, reject) => {
      resolve('hello world~');
    })
    promise.then(res => {
      console.log(res);
    })
    

    iterator:不使用Generator函数创建迭代器。

    function myIterator(items) {
      let i = 0;
      return {
        next() {
          const done = i >= items.length;
          const value = !done ? items[i++] : undefined;
          return {
            done,  // 是否全部迭代完成
            value  // 返回迭代的值
          }
        }
      }
    }
    
    const interator = myIterator([1, 2, 3]);
    interator.next();
    

    JSON.stringify: 将对象转为json字符串

    function jsonStringify(obj) {
      const type = typeof obj;
      if (type !== 'object') {
        if (type === 'string') {
          obj = '"' + obj + '"';
        }
        return String(obj);
      } else {
        const json = [];
        const arr = Array.isArray(obj);
        for (const k in obj) {
          let v = obj[k];
          const type = typeof v;
          if (type === 'string') {
            v = '"' + v + '"';
          } else if (v === null) { // 处理null情况
            v = null
          } else if (/function|undefined/.test(type)) { 
            // 原生方法会移除function和undefined,其实我们可以不移除
            delete obj[k];
          } else {
            v = jsonStringify(v); // 递归
          }
          json.push((arr ? "" : '"' + k + '":') + String(v));
        }
        return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
      }
    }
    
    const obj = {
      a: 'a1',
      b: [1, 2, 3],
      c: 22,
      d: function () {},
      e: Date.now(),
      f: null,
      g: /str/ig,
      h: undefined
    }
    
    const str = jsonStringify(obj); 
    // {"a":"a1","b":[1,2,3],"c":22,"e":1562815128952,"f":null,"g":{}}
    
    

    JSON.parse: 将字符串格式的对象转为对象。

    function jsonParse(str) {
      return new Function('return ' + str)(); // return后有一个空格
    }
    
    很神奇有木有,直接在字符串前面加上'return '关键字就可以转为对象。
    在组件精讲小册里有一个实例,在线vue单文件编辑器。
    原理就是将编辑器内的vue单文件字符串使用正则分割,
    js部分将‘export default’替换为'return '。
    通过new Function转为js对象使用。
    
    const sum = new Function('a','b','return a + b');
    sum(1, 2); // 3
    
    const str = '{"a":"a1","b":[1,2,3],"c":22,"e":1562815128952,"f":null,"g":{}}';
    jsonParse(str); //
    a: "a1",
    b: [1, 2, 3],
    c: 22,
    e: 1562815128952,
    f: null,
    g: {}
    
    

    Events:事件中心管理

    class Events {
      constructor() {
        this._evnets = Object.create(null);
      }
      
      on(event, fn) {  // 往事件中心添加事件
        if (Array.isArray(event)) {
          for (let i = 0; i < event.length; i++) {
            this.on(evnet[i], fn);
          }
        } else {
          (this._evnets[event] || (this._evnets[event] = [])).push(fn);
        }
      }
      
      emit(event, ...args) {  // 触发事件中心对应事件
        const cbs = this._evnets[event];
        if (cbs) {
          for (let i = 0; i < cbs.length; i++) {
            cbs[i].apply(this, args);
          }
        }
      }
      
      off(event, fn) {  // 移除事件
        if (!arguments) {
          this._evnets = Object.create(null);
          return this;
        }
        if (Array.isArray(event)) {
          for (let i = 0; i < event.length; i++) {
            this.off(event[i], fn);
          }
          return this;
        }
        if (!fn) {
          this._evnets[event] = null;
          return this;
        }
        const cbs = this._evnets[event];
        let i = cbs.length;
        while (i--) {
          const cb = cbs[i];
          if (cb === fn || cb.fn === fn) {
            cbs.splice(i, 1);
            break;
          }
        }
        return this;
      }
      
      once(evnet, fn) {  // 只执行一次
        function on() {
          this.off(evnet, on);
          fn.apply(this, arguments);
        }
        on.fn = fn;
        this.on(evnet, on);
        return this;
      }
    }
    
    const event = new Event();
    event.on('test', (name, sex) => { // 添加事件
      console.log(`${name}:${sex}`);
    })
    event.emit('test', 'cc', 'man');  // 传参并触发事件
    evnet.off();  // 清空所有事件
    

    setInterval: 使用setTimeout模拟,并可以取消

    function mySetInterval(fn, delay) {
      let timer;
      const loop = (fn, delay) => {
        timer = setTimeout(() => {
          loop(fn, delay);
          fn.call(this, timer);
        }, delay);
      };
      loop(fn, delay);
    }
    
    mySetInterval(timer => {
      console.log('test');
      // clearTimeout(timer);  取消定时器
    }, 200);
    

    setInterval: 使用requestAnimationFrame模拟

    function mySetInterval(fn, interval) {
      const now = Date.now;
      let startTime = now();
      const loop = () => {
        const timer = requestAnimationFrame(loop);
        if (now() - startTime >= interval) {
          startTime = now();
          fn.call(this, timer);
        }
      }
      loop();
    }
    
    一般来说是不建议使用setInterval的,
    如内部函数复杂就不能保证一定在规定时间内自动执行。
    一般是通过setTimeout模仿setInterval。
    那为什么要实现setInterval?
    因为它内部的实现是使用requestAnimationFrame实现的,
    该方法自带函数节流。
    如有持续的动画需要执行,
    基本会保证在16.6毫秒内执行一次,
    提高动画性能并延时也是精确的。
    
    mySetInterval(timer => {
      console.log('a');
      // cancelAnimationFram(timer); 取消当前定时器
    })
    
    

    setTimeout: 使用requestAnimationFrame模拟

    定时执行一次回调就取消掉,自己实现下吧~
    
    

    分享一个笔者自己写的组件库,哪天可能会用的上了 ~ ↓

    你可能会用的上的一个vue功能组件库,持续完善中...

    相关文章

      网友评论

          本文标题:夯实JavaScript功底,前端要会的手写方法

          本文链接:https://www.haomeiwen.com/subject/vcgapctx.html