面试手写JS必会

Git github 更多完整手写JS,之后也会以文章的形式稍后更新
防抖,节流

防抖为事件触发后N秒后执行回调,如果在N秒内再次触发,则重新计数,类似于百度的搜索效果

debounce

// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
  // 缓存一个定时器id
  let timer = 0
  // 这里返回的函数是每次用户实际调用的防抖函数
  // 如果已经设定过定时器了就清空上一次的定时器
  // 开始一个新的定时器,延迟执行用户传入的方法
  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

防抖,是多次执行变为最后一次的执行。而节流则为多次执行变为周期性执行

节流函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效

throttle

// func是用户传入需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 50) => {
  // 上一次执行该函数的时间
  let lastTime = 0
  return function(...args) {
    // 当前时间
    let now = +new Date()
    // 将当前时间和上一次执行函数时间对比
    // 如果差值大于设置的等待时间就执行函数
    if (now - lastTime > wait) {
      lastTime = now
      func.apply(this, args)
    }
  }
}

setInterval(
  throttle(() => {
    console.log(1)
  }, 500),
  1
)
手撕instanceof
  • 利用的是原型链的原理
  • 首先获取当前类的原型,当前实例对象的原型链
  • 而后利用原型链的原理,一直循环
    • 取得当前对象得原型链得原型链
    • 如果等于 对象得prototype 则返回True
    • 否则如果等于null (Object基类得原型为null) 则返回false
function myinstanceof(example,classFunc){
  let proto = Object.getPrototypeOf(example)
  while(true){
    if (proto === null){
      return false
    }
    if (proto === classFunc.prototype){
      return true
    }
    proto = Object.getPrototypeOf(proto)
  }
}
new
  • 首先明确new操作符所做的事情
    • 创建了一个新的对象
    • 而后使得新对象的__proto__ 指向构造函数的原型 (prototype)
    • 调用构造函数,使用call,apply 改变this的指向
    • 返回值为Object类型的对象,若不是,则返回全新的对象
function mynew(e,...args){
  const demo = Object.create(e.prototype)
  let fn = e.call(demo,...args)
  return typeof fn === 'object' ? fn:demo
}
function A(){

}
const a = mynew(A)
console.dir(a)
console.log(a.__proto__ === A.prototype)
实现一个call

call做了什么

  • 将函数设为对象的属性
  • 执行或删这个函数
  • 指定this到函数,并传入给定参数执行函数
  • 如果不传入参数,则默认指定的是window
// 模拟 call bar.mycall(null);
//实现一个call方法:
// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()
Function.prototype.myCall = function(context = window, ...args) {
  // this-->func  context--> obj  args--> 传递过来的参数

  // 在context上加一个唯一值不影响context上的属性
  let key = Symbol('key')
  context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的方法
  // let args = [...arguments].slice(1)   //第一个参数为obj所以删除,伪数组转为数组
  
  let result = context[key](...args);
  delete context[key]; // 不删除会导致context属性越来越多
  return result;
};

使用

//用法:f.call(obj,arg1)
function f(a,b){
 console.log(a+b)
 console.log(this.name)
}
let obj={
 name:1
}
f.myCall(obj,1,2) //否则this指向window
模拟Object.create
// function mycreate(proto){
//   function F(){}
//   F.prototype = proto
//   return new F()
// }
function mycreate(proto){
  function F(){

  }
  F.prototype = proto
  return new F();
}
function f(){}
fobj = mycreate(f)
console.log(fobj.__proto__ === f.prototype)
手写Promise 代码太长了。。。
解析URL
url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'
function parseParem(url){
  let arr = url.split('?')[1].split('&')
  let res = {}
  for(let fn of arr){
    let [key,value] = fn.split('=')
    if (value === undefined){
      res[key] = true
    }
    else{
      if (key in res){
        Array.isArray(res[key]) ? res[key].push(value) : res[key] = [res[key]].concat(value)
      }
      else{
        res[key] = decodeURI(value)
      }
    }
  }
  return res
}
console.log(parseParem(url))
模板编译器的实现

正则匹配 test exec(顺序匹配,其结果挺有意思的) replace

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
console.log(render(template, data)); 

function render(template,data){
  const reg = /\{\{(\w+)\}\}/;
  if(reg.test(template)){
    const name = reg.exec(template)[1]
    console.log(reg.exec(template))
    template = template.replace(reg,data[name])
    return render(template,data)
  }
  return template
}
驼峰命名法的转换

正则呀,重点

var s1 = "get-element-by-id"

function change(fn){
  return fn.replace(/-\w/g,function(x){
    console.log(x)
    return x.slice(1).toUpperCase()
  })
}

console.log(change(s1))
-e
-b
-i
getElementById
查找字符串中出现最多的字符和个数
let str = "abcabcabcbbccccc";
let num = 0;
let char = '';

str = str.split('').sort().join('');
let res = new Map()
for (let s of str){
  if (s in res){
    res[s]+=1
  }
  else{
    res[s] = 1
  }
  if (res[s] > num){
    num = res[s]
    char = s
  }
}

// let re = /(\w)\1+/g;
// str.replace(re,($0,$1) => {
//   console.log($0,$1)
//   if (num < $0.length){
//     num = $0.length
//     char = $1;
//   }
// });

console.log(`字符最多的是${num},出现了${char}`)

字符串查找

若存在,则返回首字母的下标

  • js循环的下标为字符串

int -> str value.toString()

str -> int parseInt(str) parseFloat(str)

~~ 也能转

// a='34';b='1234567'; // 返回 2
// a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
console.log(isContain(a,b));


function isContain(a,b){
  for (let i in b){
    if(a[0] === b[i]){
      let tmp = true
      for (let j in a){
        console.log(typeof i)
        if (a[j] !== b[~~i + ~~j]){
          console.log(a[j],b[i+j])
          tmp = false
        }
      }
      if (tmp){
        return i
      }
    }
  }
  return -1
}

插播知识点 JSON.parse() 从字符串中解析JSON对象 JSON.stringify 将对象解析为字符串

正则匹配 ,永远的晕头转向
  • 邮箱
  • 身份证
  • 电话
  • 数字的千分位转换

代码稍后会在Github,或者是新文章内更新

实现数组的flat
  • 多维数组转一维
let arr = [1, [2, [3, [4, 5]]], 6];
// let str = JSON.stringify(ary);
// console.log(str)
arr_flat = arr.flat(Infinity);
console.log(arr_flat)

let res = []
const fn = function(arr){
  for(let i = 0; i<arr.length; i++){
    let tmp = arr[i]
    if (Array.isArray(tmp)){
      fn(tmp)
    }
    else{
      res.push(tmp)
    }
  }
}
fn(arr)
console.log(res)

function flatten(arr){
  return arr.reduce((pre,cur) => {
    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
  },[])
}
console.log(flatten(arr))
  • 对象的扁平化与反扁平化

扁平化

function objectFlat(obj = ''){
  const res = {}
  function flat(item , preKey = ''){
    Object.entries(item).forEach(([key,value]) => {
      let newKey = key
      // console.log(value,typeof value,Array.isArray(value))
      if (Array.isArray(item)){
        // console.log('是数组')
        newKey = preKey ? `${preKey}[${key}]` : key
      }else{
        newKey = preKey ? `${preKey}.${key}` : key
      }
      if (value && typeof value === 'object'){
        flat(value , newKey)
      }else{
        res[newKey] = value
      }
    })
  }
  flat(obj)
  return res
}

const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source));
const obj = {
  a: 1,
  b: [1, 2, { c: true }],
  c: { e: 2, f: 3 },
  g: null,
};
console.log(objectFlat(obj));

反扁平化


function testPropTypes(value, type, dev) {
  const sEnums = ['number', 'string', 'boolean', 'undefined', 'function']; // NaN
  const oEnums = ['Null', 'Object', 'Array', 'Date', 'RegExp', 'Error'];
  const nEnums = [
    '[object Number]',
    '[object String]',
    '[object Boolean]',
    '[object Undefined]',
    '[object Function]',
    '[object Null]',
    '[object Object]',
    '[object Array]',
    '[object Date]',
    '[object RegExp]',
    '[object Error]',
  ];
  const reg = new RegExp('\\[object (.*?)\\]');

  // 完全匹配模式,type应该传递类似格式[object Window] [object HTMLDocument] ...
  if (reg.test(type)) {
    // 排除nEnums的12种
    if (~nEnums.indexOf(type)) {
      if (dev === true) {
        console.warn(value, 'The parameter type belongs to one of 12 types:number string boolean undefined Null Object Array Date RegExp function Error NaN');
      }
    }

    if (Object.prototype.toString.call(value) === type) {
      return true;
    }

    return false;
  }
}
function getValue(obj, key, defaultValue) {
  // 结果变量
  const defaultResult = defaultValue === undefined ? undefined : defaultValue;

  if (testPropTypes(obj, 'Object') === false && testPropTypes(obj, 'Array') === false) {
    return defaultResult;
  }

  // 结果变量,暂时指向obj持有的引用,后续将可能被不断的修改
  let result = obj;

  // 得到知道值
  try {
    // 解析属性层次序列
    const keyArr = key.split('.');
    // console.log(keyArr[0])
    var res = []
    for (let i = 0; i < keyArr.length; i++) {
      let k0 = keyArr[i]
      // console.log(k0.split('['))
      k0 = k0.split('[')
      for (let i = 0; i < k0.length; i++) {
        if (k0[i] !== '') {
          res.push(k0[i][0])
        }
      }
    }
    console.log(res)
    // 迭代obj对象属性
    for (let i = 0; i < res.length; i++) {
      // 如果第 i 层属性存在对应的值则迭代该属性值
      if (result[res[i]] !== undefined) {
        result = result[res[i]];

        // 如果不存在则返回未定义
      } else {
        return defaultResult;
      }
    }
  } catch (e) {
    return defaultResult;
  }

  // 返回获取的结果
  return result;
}

// 示例
var object = { a: [{ b: { c: 3 } }] }; // path: 'a[0].b.c'
var array = [{ a: { b: [1] } }]; // path: '[0].a.b[0]'r
// res = 'a'

// function getValue(target, valuePath, defaultValue) {}

console.log(getValue(object, "a[0].b.c", 0)); // 输出3
console.log(getValue(array, "[0].a.b[0]", 12)); // 输出 1
console.log(getValue(array, "[0].a.b[0].c", 12)); // 输出 12
什么是柯里化

将接受多个参数的函数,变为接受部分参数的函数。而后返回一个函数接受余下参数。足够后,执行函数

function curry(fn,args){
  var length = fn.length
  var args = args || []
  return function(){
    // Array.prototype.slice.call(arguments) 非数组对象转为数组
    newArgs = args.concat(Array.prototype.slice.call(arguments))
    if(newArgs.length < length){
      return curry.call(this,fn,newArgs)
    }else{
      return fn.apply(this,newArgs)
    }
  }
}


function multiFn(a, b, c) {
  return a * b * c;
}

var multi = curry(multiFn);

multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
console.log(multi(2,3)(4))
利用闭包实现结果缓存(备忘模式)
function memorize(fn){
  var cache = {}
  return function(){
    // 将类数组转化为数组
    // arguments 为ES5写法的参数的总和
    // 而现在使用...args 解构赋值
    console.log(arguments)
    const args = Array.prototype.slice.call(arguments)
    const key = JSON.stringify(args)
    return cache[key] || (cache[key] = fn.apply(fn,args))
  }
}
function add(a) {
  return a + 1
}

var adder = memorize(add)

adder(1)            // 输出: 2    当前: cache: { '[1]': 2 }
adder(1)            // 输出: 2    当前: cache: { '[1]': 2 }
adder(2)    
function memorize(fn){
  var cache = {}
  return function(...args){
    // 将类数组转化为数组
    // arguments 为ES5写法的参数的总和
    // 而现在使用...args 解构赋值
    console.log(args)
    console.log(arguments)
    const key = JSON.stringify(args)
    return cache[key] || (cache[key] = fn.apply(fn,args))
  }
}
function add(a) {
  return a + 1
}

var adder = memorize(add)

adder(1)            // 输出: 2    当前: cache: { '[1]': 2 }
adder(1)            // 输出: 2    当前: cache: { '[1]': 2 }
adder(2)   

[Arguments] { '0': 1 }
[ 1 ]
[Arguments] { '0': 1 }
[ 2 ]
[Arguments] { '0': 2 }

arguments 的用法以及数据结构,以及解构赋值

更多代码稍后会在Github,或者是新文章内更新

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值