前端常见手写题

70 阅读3分钟

1: 手写create

function create(obj) {
  // 构造函数
  function Func () {

  }
  // 构造函数的原型指向传入的对象
  Func.prototype = obj;
  // 返回该对象
  return new Func();
}
let newObj = create(obj);

2:js常见的金额处理

2.1 金额转千分位

function moneyToThousands(money) {
  // 处理异常
let isMoneny = (money+ '').match(/\d+.?\d{0,2}/);
console.log(isMoneny);
if(!isMoneny) {
  console.log('金额不合法');
  return
}
  // 整数部分
  let integersPart = (money+ '').split('.')[0];
  // 小数部分
  let decimalPart = (money + '').split('.')[1] || '';
    let arr = integersPart.split('').reverse(); // 千分位是从小到大分割的
    let r = ''
    arr.forEach((item, i) => {
       if(i !== 0 && i % 3 === 0) {
         r = item + ',' + r;
       } else {
         r = item + r;
       }
    })
  return r + (decimalPart ? '.' + decimalPart : '');
}

2.2 千分位转金额

function ThousandsToMoney(thousands) {
   while(thousands.indexOf(',') !== -1) {
     thousands = thousands.replace(',', '');
   }
   console.log(thousands);
   return thousands
}

2.3 金额转大写

3 手写instanceOf

function  myInstanceof(obj, fun) {
  let pro = Object.getPrototypeOf(obj); // 对象的原型
  let prototype = fun.prototype;
  while(true) {
    if(!pro) {
      return false;
    }
    if(pro === prototype) {
      return true;
    }
    pro = Object.getPrototypeOf(pro)
  }
}

手写New

// 实现new
function myNew(construct, ...args) {
  let obj = {};
  construct.prototype = Object.create(obj);
  construct.apply(obj, args);
  return obj
}
function Person (name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function () {
  console.log(this.name)
}
let test = myNew(Person, 'LIUHUAN', 21);
console.log(test);

防抖

function debounceFun(fn, delay=500) {
  let timer = null;
  return function () {
    if(timer) {
      clearTimeout(timer);
    }
    setTimeout(() => {
      fn.apply(this, args);
    }, delay)
  }
}

节流

throttle(fun, delay) {
  let timer = null;
  return function () {
    if(!timer) {
      timer = setTimeout(() => {
        fun();
        timer = null;
      }, delay)
    }
  }
},

判断类型

function getType(value) {
  if(value === null) {
    return value + ''
  }
  if(typeof value === "object") {
    return Object.prototype.toString.call(value).slice(8,1).toLowerCase()
  } else {
    return typeof value;
  }
}

手写call,apply,bind

Function.prototype.myCall = function (ctx, ...args) {
  if(ctx === null || ctx === undefined) ? globalThis : Object(ctx);
  var key = Symbol('temp'); // 防止和对象cxt有重复的属性
  Object.defineProperty(ctx, key, {
    enumrable: false,
    value: this
  })
  let result = ctx[key](...args); // 调用this()
  delete ctx[key];
  return result;
}

手写promise

class Mypromse {
  static PENDING = '等待';
  static FULFILLED='成功';
  static REJECTTED = '失败';
  constructor(func) {
    this.status = Mypromse.PENDING;
    this.result = null;
    this.resolveCallbacks = [];
    this.rejectCallbacks = [];
    try{
      func(this.resolve.bind(this), this.reject.bind(this))
    }catch(error) {
      this.reject(error);
    } ;
  }
  resolve(result) {
    setTimeout(() => {
      this.status = Mypromse.FULFILLED;
      this.result = result;
      this.resolveCallbacks.forEach(callback => {
        callback(result)
      })
    })
  }
  reject(result) {
    setTimeout(() => {
      this.status = Mypromse.REJECTTED;
      this.result = result;
      this.rejectCallbacks.forEach(callback => {
        callback(result)
      })
    })
  }
  then(onFullfield, onRejected) {
    onFullfield = typeof onFullfield === 'function' ? onFullfield : () => {};
    onRejected = typeof onRejected === 'function' ? onRejected : () => {};
    if(this.status === Mypromse.PENDING) {
      this.resolveCallbacks.push(onFullfield)
      this.rejectCallbacks.push(onRejected)
    }
    if(this.status === Mypromse.FULFILLED) {
     setTimeout(() => {onFullfield(this.result)})
    }
    if(this.status === Mypromse.REJECTTED) {
      setTimeout(() => {onRejected(this.result)})
    }
  }
}
console.log('第一步')
let p1 = new Mypromse((resolve, reject) => {
  console.log('第二走')
  setTimeout(() => {
    resolve('这次一定');
    console.log('第三步')
  })
})
p1.then(res => {console.log(res)}, res => {console.log(res.message)});
console.log('第四步');

发布订阅者模式

class Observer{
  constructor() {
    this.message = {} // 调度者对象
  }
  // 订阅事件
  $on(type, callback) {
    if(!this.message[type]) {
      this.message[type] = []
    }
    this.message[type].push(callback);
  }
  // 促发事件
  $emit(type) {
   if(!this.message[type]) {return}
   this.message[type].forEach(item => {
     item();
   })
  }
  // 解绑事件
  $off(type, callback) {
    if(!this.message[type]) {
      return
    }
    if(!callback) {return}
    this.message[type] = this.message[type].filters(item => {
    item !== callback
  })
}

树形数据拍扁

1.直接拍扁,但是不用找parentId的 测试数据:

let treeData = [
  {
    id: 1,
    name: '一级部门1',
    children: [
      {
        id: 11,
        name: '二级部门1',
        children: [
          { id: 111, name: '三级部门1' },
          { id: 112, name: '三级部门2' },
        ],
      },
      {
        id: 12,
        name: '二级部门2',
        children: [
          {
            id: 121,
            name: '三级部门3',
            children: [
              { id: 1211, name: '四级部门1' },
              { id: 1212, name: '四级部门2' },
            ],
          },
        ],
      },
    ],
  },
  {
    id: 2,
    name: '一级部门2',
    children: [
      { id: 21, name: '二级部门3' },
      { id: 22, name: '二级部门4' },
    ],
  },
];

代码实现:

function flatData(data) {
  let arr = [];
  data.forEach(item => {
    arr.push({id: item.id, name: item.name});
    item.children && arr.push(...flatData(item.children))
  })
  return arr
}
console.log(flatData(treeData));

2.直接拍扁,但是需要找parentId的,这种只需要再每次循环的时候记住parentId就行;

function flatDataWithParent(data, parentId=null) {
  let arr = [];
  console.log(parentId);
  data.forEach(item => {
    arr.push({id: item.id, name: item.name, parentId: parentId})
    item.children && arr.push(...flatDataWithParent(item.children, item.id))
  })
  return arr
}
console.log(flatDataWithParent(treeData));

3.拍扁成路径的多维数组的形式,例如返回成这样的[[1,11,111],[2,22,222]]形式 这种在elemnt-ui级联选择器组件里面,选中某个节点,组件返回的就是这种多维数组的形式,于是自己写了一个简单的小实现。 `function transformTreeData(treeData) { const result = [];

function traverse(node, path) { // 将当前节点的 id 添加到路径中 const newPath = [...path, node.id];

// 如果当前节点没有子节点,将路径推入结果数组
if (!node.children || node.children.length === 0) {
  result.push(newPath);
  return;
}

// 递归遍历子节点
for (const child of node.children) {
  traverse(child, newPath);
}

}

// 遍历根节点 for (const node of treeData) { traverse(node, []); }

return result; }`

扁平数据转树形

上一节讲了如何将树形数据拍扁,这节讲一下如何将拍扁的数据还原成树形数据;这种场景还是很常见的,也是通过递归的方式实现的 测试数据: