前端面试相关js算法(深拷贝,防抖节流,函数柯里化,虚拟dom转换等)

391 阅读6分钟
  • Vue数据双向绑定
class Observer {
  constructor(obj) {
    this.walk(obj)
  }
  walk(obj) {
    for (let i in obj) {
      this.defineReactive(obj, i, obj[i])
    }
  }
  array() {
    const arrayProto = Array.prototype
    // 创建一个对象作为拦截器
    const arrayMethods = Object.create(arrayProto)

    // 改变数组自身内容的7个方法
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    /**
     * Intercept mutating methods and emit events
     */
    const that = this
    methodsToPatch.forEach(function (method) {
      const original = arrayProto[method]      // 缓存原生方法
      Object.defineProperty(arrayMethods, method, {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function mutator(...args) {
          const result = original.apply(this, args)
          console.log(method, '方法操作了数组', args);
          let inserted
          switch (method) {
            case 'push':
            case 'shift':
              inserted = args
              break;
            case 'splice':
              inserted = args.slice(2)
              break;
            default:
              break;
          }
          if (inserted) that.walk(inserted)

          return result
        }
      })
    })
    return arrayMethods
  }
  defineReactive(obj, key, value) {
    if (Array.isArray(value)) {
      value.__proto__ = this.array()
    }
    if (value && typeof value === 'object') {
      this.walk(value)
    }
    Object.defineProperty(obj, key, {
      get() {
        console.log(key, '被读取了');
        return value
      },
      set(newVal) {
        console.log(key, '被设置了');
        if (newVal !== value) {
          value = newVal
          return value
        }
      }
    })
  }
}
new Observer(obj)
  • 深拷贝

  • 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

//代码实现深拷贝
function deepClone(obj, weakmap = new WeakMap()) {
  //界限判断
  if (obj instanceof RegExp) {
    return new RegExp(obj)
  }
  if (obj instanceof Date) {
    return new Date(obj)
  }
  if (typeof obj !== 'object') {
    return obj
  }
  if (weakmap.has(obj)) {
    return weakmap.get(obj)
  }
  //优先选择
  // let target = new obj.__proto__.constructor()
  //持续优化
  let target = Reflect.construct(obj.__proto__.constructor, [])
  weakmap.set(obj, target)
  // 优于 let target = Object.prototype.toString.call(obj)==='[object Array]'?[]:{}
  for (let i in obj) {
    if (obj.hasOwnProperty(i)) {
      if (typeof obj[i] === 'object') {
        target[i] = deepClone(obj[i], weakmap)
      } else {
        target[i] = obj[i]
      }
    }
  }
  return target
}
//扁平数组
let a = '哈哈哈'

let arr = [1, '2', '3', a, [4, 5, [6, [7, [8]]]]]
function flat(arr) {
  let res = []
  for (let i of arr) {
    Array.isArray(i) ? res.push(...flat(i)) : res.push(i)
  }
  return res
}
function flat(arr) {
  while (arr.some(i => Array.isArray(i))) {
    arr = [].concat(...arr)
  }
  return arr
}
function flat(arr) {
  return arr.reduce((i, j) => {
    Array.isArray(j) ? i.push(...flat(j)) : i.push(j)
    return i
  }, [])
}
console.log(flat(arr));
//发布订阅模式 
let eventEmitter = {
  list: {},
  on: function (events, fn) {
    let _this = this;
    (_this.list[events] || (_this.list[events] = [])).push(fn);
    return _this;
  },
  emit: function (...args) {
    let _this = this;
    let event = [].shift.call(args),
      fns = [..._this.list[event]];
    if (!fns || fns.length === 0) {
      return false;
    }
    fns.forEach(fn => {
      fn.apply(_this, args)
    })
    return _this;
  }
}

function user1(content) {
  console.log('用户1订阅了:', content);
};
function user2(content) {
  console.log('用户2订阅了:', content);
};
// 订阅
eventEmitter.on('article', user1);
eventEmitter.on('article', user2);

eventEmitter.emit('article', 'Javascript 发布-订阅模式');
//js并发请求
class Scheduler {
            constructor(limit) {
                this.limit = limit
                this.number = 0
                this.queue = []
            }
            addTask(timeout, str) {
                this.queue.push([timeout, str])
            }
            start() {
                if (this.number < this.limit&&this.queue.length) {
                    var [timeout, str] = this.queue.shift()
                    this.number++
                    setTimeout(() => {
                        console.log(str)
                        this.number--
                        this.start()
                    }, timeout * 1000);
                    this.start()
                }
            }
 }
 
 const scheduler = new Scheduler(2)
 scheduler.addTask(1, '1'); // 1s后输出'1'
 scheduler.addTask(2, '2'); // 2s后输出'2'
 scheduler.addTask(1, '3'); // 2s后输出'3'
 scheduler.addTask(1, '4'); // 3s后输出'4' 
 scheduler.start();

//promise手写
let PENDING = 'pending'
let FULFILLED = 'fulfilled'
let REJECTED = 'rejected'
class myPromise {
  constructor(excutor) {
    this.value = '';
    this.reason = '';
    this.status = PENDING;
    this.queueRes = [];
    this.queueRej = [];
    excutor(this.reslove, this.reject)
  } 
  reslove = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
      while (this.queueRes.length) {
        this.queueRes.shift()(this.value)
      }
    }
  }
  reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      while (this.queueRej.length) {
        this.queueRej.shift()(this.reason)
      }
    }
  }
  then(onFulfilled, onRejected) {
    // 实现then的不传参
    typeof onFulfilled === 'function' ? onFulfilled : value => value
    typeof onRejected === 'function' ? onRejected : reason => reason
    const p2 = new myPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        queueMicrotask(() => {
          const x = onFulfilled(this.value);
          //处理return自己的情况
          if (p2 === x) {
            return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
          }
          if (x instanceof myPromise) {
            x.then(resolve, reject)
          } else {
            resolve(x)
          }
        })
      } else if (this.status === REJECTED) {
        onRejected(this.reason)
      } else if (this.status === PENDING) {
        this.queueRes.push(onFulfilled);
        this.queueRej.push(onRejected);
      }
    })
    return p2
  }
}


//关于promise的一些考点:

1//用promise限制请求数
function concurrentRequest(requestFns, limit) {
  // 递归函数
  let i = 0
  function recursion(requestFn) {
    requestFn().finally(() => {
      if (requestFns.length > 0) {
        recursion(requestFns.pop());
      }
    });
  }
  // 限制最大并发量
  while (i < limit && requestFns.length > 0) {
    i++
    recursion(requestFns.pop());
  }
}
// ----------下面是测试用例------------
// 模拟一个异步请求函数
function createRequest(delay) {
  return () =>
    new Promise((resolve) => {
      setTimeout(() => {
        resolve("done");
      }, delay);
    }).then((res) => {
      console.log(res);
    });
}
const requestFns = [];
for (let i = 0; i < 10; i++) {
  requestFns.push(createRequest(1000));
}
concurrentRequest(requestFns, 3);


2//用promise实现取消请求(请求多次,取消前面所有的,只执行最后一次)
function CancelablePromise() {
  this.pendingPromise = null;
}
// 包装一个请求并取消重复请求
CancelablePromise.prototype.request = function (requestFn) {
  if (this.pendingPromise) {
    this.cancel("取消重复请求");
  }
  const _promise = new Promise((resolve, reject) => (this.reject = reject));
  this.pendingPromise = Promise.race([requestFn(), _promise]);
  // console.log(1111111, this.pendingPromise, this.reject);
  return this.pendingPromise;
};
// 取消当前请求
CancelablePromise.prototype.cancel = function (reason) {
  this.reject(new Error(reason));
  // this.pendingPromise = null;
};
// ----------下面是测试用例------------
// 模拟一个异步请求函数
function createRequest(delay) {
  return () =>
    new Promise((resolve) => {
      setTimeout(() => {
        resolve("done");
      }, delay);
    });
}
const cancelPromise = new CancelablePromise();
// 前四个请求将被自动取消
for (let i = 0; i < 6; i++) {
  cancelPromise
    .request(createRequest(1000))
    .then((res) => console.log(res)) // 最后一个 done
    .catch((err) => console.error(err)); // 前四个 error: 取消重复请求
}
// 设置一个定时器等3s,让前面的请求都处理完再继续测试
setTimeout(() => {
  // 手动取消最后一个请求
  cancelPromise
    .request(createRequest(1000))
    .then((res) => console.log(res))
    .catch((err) => console.error(err)); // error:手动取消
  cancelPromise.cancel("手动取消");
}, 3000);
// 设置一个定时器等4s,让前面的请求都处理完再继续测试
setTimeout(() => {
  cancelPromise
    .request(createRequest(1000))
    .then((res) => console.log(res)) // done
    .catch((err) => console.error(err));
}, 4000);


//jsonp跨域
function jsonp(url, callback) {
  const script = document.createElement('script')
  script.src = url
  document.body.appendChild(script)
  return new Promise((reslove, reject) => {
    window[callback] = function (data) {
      try {
        reslove(data)
      } catch (error) {
        reject(error)
      } finally {
        document.body.removeChild(script)
        delete window[callback]
      }
    }
  })
}
jsonp('./json.json', 'fn').then((res) => {
  console.log(res)
})
//手写apply
Function.prototype.myApply = function (context, args) {
    context = context || window
    args = args ? args : []
    //给context新增一个独一无二的属性以免覆盖原有属性
    const key = Symbol()
    context[key] = this
    //通过隐式绑定的方式调用函数
    const result = context[key](...args)
    //删除添加的属性
    delete context[key]
}

//手写bind
Function.prototype.myBind = function (context, ...args) {
    const fn = this
    args = args ? args : []
    return function newFn(...newFnArgs) {
        if (this instanceof newFn) {
            return new fn(...args, ...newFnArgs)
        }
        return fn.apply(context, [...args,...newFnArgs])
    }
}

//实现防抖
function debounce(fn, delay) {
  let timer = null
  return function (...arg) {
    if (timer) {
      clearTimeout(timer)
    }
    let context = this
    timer = setTimeout(() => {
      fn.apply(context, arg)
    }, delay)
  }
}
//实现节流
function throttle(fn, delay) {
  let timer = null
  return function (...arg) {
    let context = this
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(context, arg)
        timer = null
      }, delay)
    }
  }
}
//实现函数柯里化1
function add(...args) {
  return args.reduce((a, b) => {
    return a + b
  })
}
function currying(fn) {
  let args = []
  return function temp(...newVal) {
    if (newVal.length) {
      args = [...args, ...newVal]
      return temp
    } else {
      let val = fn.apply(null, args)
      return val
    }
  }
}
let addCurry = currying(add)
console.log(addCurry(1, 2, 3)(2, 3, 6)())
//实现函数柯里化2(有点难理解)
function add() {
   // 第一次执行时,定义一个数组专门用来存储所有的参数
   var _args = Array.prototype.slice.call(arguments);
   // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
   var _adder = function() {
       _args.push(...arguments);
       return _adder;
   };
   // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
   _adder.toString = function () {
       return _args.reduce(function (a, b) {
           return a + b;
       });
    }
    console.log(_adder);
   return _adder;
}
let a = add(1,2,3)(2)(3)
console.log(a);
// 数组转成树
let input = [
  { id: 1, val: '学校', parentId: null },
  { id: 2, val: '班级1', parentId: 1 },
  { id: 3, val: '班级2', parentId: 1 },
  { id: 4, val: '学生1', parentId: 2 },
  { id: 5, val: '学生2', parentId: 3 },
  { id: 6, val: '学生3', parentId: 7 },
  { id: 7, val: '班级3', parentId: 1 },
]
//1
function arrToTree(arr) {
  let data = JSON.parse(JSON.stringify(arr))
  let c = data.filter(i => {
    let arr = data.filter(j => j['pid'] === i['id'])
    arr.length && (i['children'] = arr)
    return i.pid === 0
  })
  return c[0]
}
//2
function arrToTree(arr) {
  let dataArr = JSON.parse(JSON.stringify(arr))
  let data = new Map()
  for (let i of dataArr) {
    data.set(i['id'], i)
  }
  for (let i of dataArr) {
    if (i.pid === 0) {
      continue
    }
    let y = data.get(i['pid'])
    y['children'] ? y['children'].push(i) : y['children'] = [i]
  }
  return data.get(1)
}
// 虚拟dom
//一气呵成,太有成就感啦!
//示例
let virtualDom = {
  tag: 'div',
  props: {
    value: '父级div',
    color: 'red'
  },
  children: [
    {
      tag: 'span',
      props: {
        value: '儿级span',
        color: 'blue'
      },
      children: [
        {
          tag: 'div',
          children: 'niubi',
        },
      ],
    },
    { tag: 'span', children: 'world' },
  ],
}
//咱们专业一点:结构化
class Element {
  constructor(tag, props, children) {
    this.tag = tag;
    this.props = props;
    this.children = children;
  }
}
let createElement = (type, props, children) => {
  return new Element(type, props, children)
}
//将示例转换
function toCdom(Vdom) {
  if (typeof Vdom.children !== 'object') {
    return createElement(Vdom['tag'], Vdom['props'] || {}, Vdom['children'])
  } else {
    let arr = []
    let _c = createElement(Vdom['tag'], Vdom['props'] || {}, arr)
    for (let i of Vdom['children']) {
      arr.push(toCdom(i))
    }
    return _c
  }
}
let _c = toCdom(virtualDom)
//属性判断
function setAtr(node, key, value) {
  switch (key) {
    case 'value':
      if (node.tagName.toLowerCase() === 'input' ||
        node.tagName.toLowerCase() === 'textarea'
      ) {
        node.setAttribute(key, value)
      } else {
        node.setAttribute(key, value)
      }
      break;
    case 'style':
      node.style.cssText = value
      break;
    default:
      node.setAttribute(key, value)
      break;
  }
}
//render函数
function render(_c) {
  let ele = document.createElement(_c['tag'])
  if (typeof _c['children'] !== 'object') {
    ele.innerText = _c['children']
  } else {
    for (let i of _c['children']) {
      ele.appendChild(render(i))
    }
  }
  for (let i in _c['props']) {
    setAtr(ele, i, _c['props'][i])
  }
  return ele
}
console.log(render(_c));

// 字节面试题   虚拟dom
let virtualDom = {
  tag: 'div',
  children: [
    {
      tag: 'span',
      children: [
        {
          tag: 'div',
          children: 'niubi',
        },
      ],
    },
    { tag: 'span', children: 'world' },
  ],
}
function renderTOHtml(vdom) {
  let el = document.createElement(vdom.tag)
  if (typeof vdom.children == 'string') {
    vdom.children = [vdom.children]
  }
  vdom.children.forEach((child) => {
    if (typeof child == 'object') {
      child = renderTOHtml(child)
    } else {
      child = document.createTextNode(child)
    }
    el.appendChild(child)
  })
  return el
}
let realDom = renderTOHtml(virtualDom)
console.log(realDom)
// 解析url
function parseQueryString(url) {
  if (typeof url !== 'string') throw new Error('invalid url')
  const search = decodeURIComponent(url).split('?')[1]
  if (!search) return {}
  return search.split('&').reduce((pre, cur) => {
    console.log(pre, cur)
    const [key, value] = cur.split('=')
    pre[key] = value
    return pre
  }, {})
}
parseQueryString('http://iauto360.cn/index.php?key0=0&key1=1&key2=2')
//数据代理(vue考点)
function proxy(vm, target, key) {
  Object.defineProperty(vm, key, {
    get() {
      return vm[target][key]
    },
    set(newVal) {
      vm[target][key] = newVal
    },
  })
}
let vm = {
  data() {
    return {
      title: '语文新课标',
      list: {
        name: 'liuhuang',
        age: '11',
      },
    }
  },
}
let data = vm.data
data = vm.data = typeof data === 'function' ? data.call(vm) : data || {}
for (let key in data) {
  proxy(vm, 'data', key)
}
console.log(vm.title)