JS 手写经典题

发表于:2023-03-15
字数统计:10.3k 字
阅读时长:26 分钟
阅读量:396

call

  • 语法:fn.call(obj,...args)
  • 功能:执行 fn,使 this 为 obj,并将后面的 n 个参数传给 fn
Function.prototype.myCall = function (obj, ...args) {
  if (obj == undefined || obj == null) {
    obj = globalThis;
  }
  obj.fn = this;
  let res = obj.fn(...args);
  delete obj.fn;
  return res;
};
value = 2;

let foo = {
  value: 1,
};

let bar = function (name, age) {
  console.log(name, age, this.value);
};

bar.myCall(foo, "HearLing", 18); //HearLing 18 1
bar.myCall(null, "HearLing", 18); //HearLing 18 2

apply

  • 语法:fn.apply(obj,arr)
  • 功能:执行 fn,使 this 为 obj,并 arr 数组中元素传给 fn
Function.prototype.myAplly = function (obj, arr) {
  if (obj == undefined || obj == null) {
    obj = globalThis;
  }
  obj.fn = this;
  let res = obj.fn(...arr);
  delete obj.fn;
  return res;
};
value = 2;

let foo = {
  value: 1,
};

let bar = function (name, age) {
  console.log(name, age, this.value);
};

bar.myAplly(foo, ["HearLing", 18]); //HearLing 18 1
bar.myAplly(null, ["HearLing", 18]); //HearLing 18 2

bind

  • 语法:fn.bind(obj,...args)
  • 功能:返回一个新函数,给 fn 绑定 this 为 obj,并制定参数为后面的 n 个参数
Function.prototype.myBind = function (obj, ...args) {
  let that = this;
  let fn = function () {
    if (this instanceof fn) {
      return new that(...args);
    } else {
      return that.call(obj, ...args);
    }
  };
  return fn;
};

value = 2;

let foo = {
  value: 1,
};

let bar = function (name, age) {
  console.log(name, age, this.value);
};
let fn = bar.myBind(foo, "HearLing", 18);
//fn() //HearLing 18 1
let a = new fn(); //HearLing 18 undefined
console.log(a.__proto__); //bar {}

防抖和节流

防抖和节流都是用来控制事件触发频率的技术,适用于需要限制函数执行频率的场景。

区别:

防抖是将多次执行变为最后一次执行。

节流是将多次执行变成每隔一段时间执行。

应用场景:

防抖:

如输入框输入、滚动页面等,可以防止函数多次执行,以达到减少函数执行次数,降低服务器压力和提高页面性能的效果。例如:

  • 输入框搜索:当用户输入时,不立即搜索,而是等用户停止输入一段时间后才执行搜索操作,避免多次请求服务器;
  • 按钮点击:当用户连续点击某个按钮时,只执行一次点击事件,避免重复提交表单或其他操作;
  • 窗口调整:当用户调整窗口大小时,不会触发多次调整事件,而是等用户停止调整一段时间后才执行窗口调整事件,避免频繁重排和重绘页面。

节流:

节流的应用场景主要是在需要限制函数执行频率的场合,如滚动事件、鼠标移动事件等。

  • 滚动事件:限制滚动事件的触发频率,避免在滚动时过多地执行处理函数。
  • 鼠标移动事件:限制鼠标移动事件的触发频率,避免在鼠标移动时过多地执行处理函数。
  • 窗口大小改变事件:限制窗口大小改变事件的触发频率,避免在窗口大小改变时过多地执行处理函数。

防抖

  • 理解:在函数频繁触发是,在规定之间以内,只让最后一次生效
  • 场景:搜索框实时联想(keyup/input)、按钮点击太快,多次请求(登录、发短信)、窗口调整(resize)
  • 功能:防抖函数在被调用后,延迟 delay 毫秒后调用,没到 delay 时间,你又点了,清空计时器重新计时,直到真的等了 delay 这么多秒。
function debounce(fn, delay) {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

合并(立即执行/非立即执行)

function debounce(fn, delay = 300, immediate = false) {
  var timer = null;
  return function () {
    var params = [this, arguments];
    clearTimeout(timer);
    if (immediate) {
      !timer && fn.apply(...params);
      timer = setTimeout(() => {
        timer = null;
      }, delay)
    } else {
      timer = setTimeout(() => {
        fn.apply(...params);
      }, delay)
    }
  }
}

节流

  • 理解:在函数多次频繁触发时,函数执行一次后,只有大于设定的执行周期后才会执行第二次
  • 场景:页面滚动(scroll)、DOM 元素的拖拽(mousemove)、抢购点击(click)、播放事件算进度信息
  • 功能:节流函数在设置的 delay 毫秒内最多执行一次(简单点说就是,我上个锁,不管你点了多少下,时间到了我才解锁)
function throttle(fn, delay) {
  let flag = true;
  return (...args) => {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, args);
      flag = true;
    }, delay);
  };
}

合并版(立即执行/非立即执行)

function throttle(fn, delay = 300, immediate = true) {
  var flag = true;
  return function() {
    if(flag) {
      var func = () => { return fn.apply(this, arguments) }
      immediate && func();
      flag = false;
      setTimeout(() => {
        !immediate && func();
        flag = true;
      }, delay);
    }
  }
}

用滚动事件来做一个测试

function handleScroll(e) {
  console.log(e)
}
window.addEventListener('scroll', throttle(handleScroll, 200, true), false)

函数柯里化 curry

函数柯里化(Currying)是一种将函数转换为接收一部分参数并返回另一个函数的技术。被转换后的函数能够继续接收剩余的参数并返回结果。这个过程可以被多次重复,直到所有参数都被传递完毕并返回最终结果。

function mycurry(fn, beforeRoundArg = []) {
  return function () {
    let args = [...beforeRoundArg, ...arguments];
    if (args.length < fn.length) {
      return mycurry.call(this, fn, args);
    } else {
      return fn.apply(this, args);
    }
  };
}

function sum(a, b, c) {
  return a + b + c;
}

let sumFn = mycurry(sum);
console.log(sumFn(1)(2)(3)); //6

new

1、创建一个新对象,并将新对象的原型连接到构造函数的原型

2、执行构造函数,将构造函数的 this 指向新对象

3、返回新对象,如果构造函数返回非空对象,则返回该对象,否则返回新对象

function myNew(constructor, ...args) {
  let obj = Object.create(constructor.prototype)
  let res = constructor.apply(obj, args)
  return (typeof res === 'object' && res !== null) ? res : obj
}

测试:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const person = myNew(Person, 'John', 30);
console.log(person.name); // 输出:John
console.log(person.age); // 输出:30

instanceof

function instance_of(left, right) {
  let prototype = right.prototype;
  while (true) {
    if (left === null) {
      return false;
    } else if (left.__proto__ === prototype) {
      return true;
    }
    left = left.__proto__;
  }
}
let a = {};
console.log(instance_of(a, Object)); //true

深拷贝

// 浅拷贝的方法
//JSON.parse(JSON.stringify(obj))
function deepClone(target, hashMap = new WeakMap()) {
  if (typeof target !== "object" || target == null) {
    if (target instanceof Function) return target.call(this, ...arguments);
    return target;
  }
  if (target instanceof Date) return new Date(target);
  if (target instanceof RegExp) return new RegExp(target);
  let res = new target.constructor();
  if (hashMap.get(target)) return hashMap.get(target);
  hashMap.set(res, target);
  for (let key in target) {
    res[key] = deepClone(deepClone(target[key], hashMap));
  }
  return res;
}

const a = {
  i: Infinity,
  s: "",
  bool: false,
  n: null,
  u: undefined,
  sym: Symbol(),
  obj: {
    i: Infinity,
    s: "",
    bool: false,
    n: null,
    u: undefined,
    sym: Symbol(),
  },
  array: [
    {
      nan: NaN,
      i: Infinity,
      s: "",
      bool: false,
      n: null,
      u: undefined,
      sym: Symbol(),
    },
    123,
  ],
  fn: function () {
    return "fn";
  },
  date: new Date(),
  re: /hi\d/gi,
};
let a2 = deepClone(a);
console.log(a2 !== a);
console.log(a2.i === a.i);
console.log(a2.s === a.s);
console.log(a2.bool === a.bool);
console.log(a2.n === a.n);
console.log(a2.u === a.u);
console.log(a2.sym === a.sym);
console.log(a2.obj !== a.obj);
console.log(a2.array !== a.array);
console.log(a2.array[0] !== a.array[0]);
console.log(a2.array[0].i === a.array[0].i);
console.log(a2.array[0].s === a.array[0].s);
console.log(a2.array[0].bool === a.array[0].bool);
console.log(a2.array[0].n === a.array[0].n);
console.log(a2.array[0].u === a.array[0].u);
console.log(a2.array[0].sym === a.array[0].sym);
console.log(a2.array[1] === a.array[1]);
console.log(a2.fn !== a.fn);
console.log(a2.date !== a.date);
console.log(a2.re !== a.re);
// 都要为 true

数组扁平化

// 递归展开
function flattern1(arr) {
  let res = [];
  arr.foreach((item) => {
    if (Array.isArray(item)) {
      res.push(...flattern1(item));
    } else {
      res.push(item);
    }
  });
  return res;
}

数组去重

function unique(arr) {
  const res = [];
  const obj = {};
  arr.forEach((item) => {
    if (obj[item] === undefined) {
      obj[item] = true;
      res.push(item);
    }
  });
  return res;
}
//其他方法
//Array.from(new Set(array))
//[...new Set(array)]

手写 reduce

// 语法 array.reduce(function(prev, currentValue, currentIndex, arr), initialValue)
Array.prototype.MyReduce = function (fn, initialValue) {
  // 浅拷贝数组const arr = Array.prototype.slice.call(this);
  // 注意: reduce() 对于空数组是不会执行回调函数的。if (!arr.length) return;

  let res; // res(是上面的prev)// 默认初始值
  res = initialValue || arr[0];
  // 遍历数组的每一个值for (let i = initialValue ? 0 : 1; i < arr.length; i++) {
    // 每一个值都会在该方法中被(加工处理),
    res = fn.call(null, res, arr[i], i, this);
  }
  // 最后的返回值return res;
};

快排(算法)

function quickSort(arr) {
  if (arr.length < 2) return arr;

  const cur = arr[arr.length - 1];
  const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);
  const right = arr.filter((v) => v > cur);
  return [...quickSort(left), cur, ...quickSort(right)];
}
// console.log(quickSort([3, 6, 2, 4, 1]));

大数相加(算法)

function add(a, b) {
  // 取两个数字的最大长度const maxLength = Math.max(a.length, b.length);
  // 用0去补齐长度
  a = a.padStart(maxLength, 0); // "0009007199254740991"
  b = b.padStart(maxLength, 0); // "1234567899999999999"// 定义加法过程中需要用到的变量let t = 0;
  let f = 0; // "进位"let sum = "";
  for (let i = maxLength - 1; i >= 0; i--) {
    t = parseInt(a[i]) + parseInt(b[i]) + f;
    f = Math.floor(t / 10);
    sum = (t % 10) + sum;
  }
  if (f !== 0) sum = `${f}${sum}`;

  return sum;
}

手写简易 Ajax 请求

function ajax({method = 'GET', url, params = {}}) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open(method, url, true)
    xhr.onreadystatechange = function () {
      if(xhr.readyState === 4) {
        const status = xhr.status
        if(status === 200) {
          resolve(JSON.parse(xhr.responseText))
        }else if(status === 404) {
          reject(new Error('404 not found'))
        }
      }
    }
    xhr.send(JSON.stringify(params))
  })
}

ajax({
  method: 'POST',
  url: '/login',
  params: {
    username: '张三',
    password: '123456'
  }
}).then(data => {
  console.log(data)
}).catch(err => {
  console.log(err)
})

手写简易 jQuery 考虑插件和扩展性

html

<body>
  <p>第一段文字 1</p>
  <p>第二段文字 2</p>
  <p>第三段文字 3</p>

  <script src="./jquery.js"></script>
  <script>
    const p = new jQuery('p');
    console.log(p)
    console.log(p.get(1))
    p.each(el => {
      console.log(el.nodeName)
    })
    p.on('click', () => {
      alert('clicked')
    })
    p.log('插件')
  </script>
</body>

js

class jQuery {
  constructor(selector) {
    const result = document.querySelectorAll(selector);
    const length = result.length;

    for (let i = 0; i < length; i++) {
      this[i] = result[i];
    }

    this.length = length;
    this.selector = selector;
  }

  get(index) {
    return this[index];
  }

  each(fn) {
    for (let i = 0; i < this.length; i++) {
      const el = this[i];
      fn(el)
    }
  }

  on(type, fn) {
    return this.each(el => {
      el.addEventListener(type, fn, false);
    })
  }
}

// 插件
jQuery.prototype.log = function(info) {
  console.log(info)
}

// 造轮子
class myjQuery extends jQuery {
  constructor(selector) {
    super(selector)
  }

  // 扩展自己的方法
  addClass(className) {}

  style(data) {}
}



1/0