js手写

117 阅读4分钟

juejin.cn/post/687515…

1.bind,call,apply

//filter需要注意的点,你可以传第二个参数指定调用对象。
Array.prototype.myFilter = function (callback,thisArg){
    let res = [];
    for(let i=0;i<this.length;i++){
      if(callback.apply(thisArg,[this[i],i,this]))
      res.push(this[i]);
    }
    return res;
}
    
Function.prototype.myApply = function(obj,argArr = []){
  if (obj === undefined || obj === null) {
    return this();
  }

  obj._tempFunc = this; //添加一个临时对象,否则你就修改了obj的属性
  const result = obj._tempFunc(...argArr);
  delete obj._tempFunc;

  return result;
 }

 Function.prototype.myCall = function(obj, ...argArr) {
  if (obj === undefined || obj === null) {
    return this();
  }

  obj._tempFunc = this; //添加一个临时对象,否则你就修改了obj的属性
  const result = obj._tempFunc(...argArr);
  delete obj._tempFunc;

  return result;
}

Function.prototype.myBind = function(obj,...argsBefore) {

  const tempFunction = this;
  return function(...args){
    //console.log("this指向的是谁",this); //这里的this指向的是window,而非函数本身,因此用闭包保存下函数本身
    return tempFunction.apply(obj,[...argsBefore,...args]);//记得这里有return
  }
}

2.展开数组

const arr = [1, [2, [3, [4, 5]]], 6];


//方法一:Infinity是层数
arr.flat(Infinity); 

//方法二:递归
const myFlat = (arr)=>{
  let ans = [];
  for(let i=0;i<arr.length;i++){
    if(typeof arr[i] === 'number'){
      ans.push(arr[i]);
    } else {
      ans.push(...myFlat(arr[i]));
    }
  }
  return ans;
}

//方法三:转字符串
let str = arr.toString();
console.log(str.split(',').map(number => {
  return number-'0';
}))

3.数组去重

请重点看filter方法

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]


//1.使用set去重
const res1 = Array.from(new Set(arr));

//2.使用indexOf和include,原理相同
const unique2 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
  }
  return res;
}

const unique3 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) res.push(arr[i]);
  }
  return res;
}

//3.使用filter
const unique4 = arr => {
  return arr.filter((item, index) => {
    return arr.indexOf(item) === index;
  });
}

4.将类数组转化为数组

4.1什么是类数组

像数组,但不是数组。他有length属性,但没有数组原型对象上的方法map,reduce,filter等等

4.2 如何获取一个类数组


//leishu没有map方法
  let leishu = document.querySelectorAll('div');
  leishu.map(item => {
    console.log("item是什么",item);
  })
  
  function Fun(){
      console.log(arguments)//arguments是类数组;
  }

4.3如何将类数组转化为普通数组

Array.from(document.querySelectorAll('div'))

[...document.querySelectorAll('div')]

//concat和slice在类数组上不存在,因此通过call或者apply来调用

Array.prototype.slice.call(document.querySelectorAll('div'))
Array.prototype.concat.apply([], document.querySelectorAll('div'));

5.防抖和截流

5.1截流函数

使用场景:点击按钮,发送网络请求。

function throttle(originalFunction, time) {
  let timer = null;
  return function(...args){
    if(!timer){
      originalFunction.apply(this, args); 
      timer = setTimeout(() => {
        timer = null;
      }, time);
    }
  }
}

5.2防抖函数

debounce的本质:首次不触发,在规定的时间达到后才触发。

使用场景:输入框联想、窗口resize触发新手引导。

      function debounce(originalFunction, time) {
        let timer = null;

        return function (...args) {
          clearTimeout(timer);
          timer = setTimeout(() => {
            originalFunction.apply(this, args);
            timer = null;
          }, time);
        };
      }

6.渲染10万条数据不卡顿

6.1原生js实现

通过requestAnimationFrame实现每次重绘的时候,只渲染你规定数量的li。createDocumentFragment不是一个真实的html元素,插入到dom树之后,它就消失了。

setTimeout(() => {
  // 插入十万条数据
  const total = 100000;
  // 一次插入的数据
  const once = 20;
  // 插入数据需要的次数
  const loopCount = Math.ceil(total / once);
  let countOfRender = 0;
  const ul = document.querySelector('ul');
  // 添加数据的方法
  function add() {
    const fragment = document.createDocumentFragment();
    for(let i = 0; i < once; i++) {
      const li = document.createElement('li');
      li.innerText = Math.floor(Math.random() * total);
      fragment.appendChild(li);
    }
    ul.appendChild(fragment);
    countOfRender += 1;
    loop();
  }
  function loop() {
    if(countOfRender < loopCount) {
      window.requestAnimationFrame(add);
    }
  }
  loop();
}, 0)

6.2实现虚拟列表

import React, { useState, useEffect } from 'react';
import './VirtualList.css';

const ITEM_HEIGHT = 30;
const VISIBLE_ITEMS = 20;

const VirtualList = ({ items }) => {
  const [scrollTop, setScrollTop] = useState(0);

  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
  };

  const startIndex = Math.floor(scrollTop / ITEM_HEIGHT);
  const endIndex = Math.min(startIndex + VISIBLE_ITEMS, items.length);

  const visibleItems = items.slice(startIndex, endIndex).map((item, index) => (
    <li key={startIndex + index} style={{ height: `${ITEM_HEIGHT}px` }}>
      {item}
    </li>
  ));

  return (
    <div className="virtual-list-container" onScroll={handleScroll}>
      <div className="virtual-list-spacer" style={{ height: `${scrollTop}px` }} />
      <ul>{visibleItems}</ul>
      <div
        className="virtual-list-spacer"
        style={{ height: `${(items.length - endIndex) * ITEM_HEIGHT}px` }}
      />
    </div>
  );
};

export default VirtualList;

如何使用上面的虚拟列表

import React from 'react';
import VirtualList from './VirtualList';

const App = () => {
  const items = Array.from({ length: 100000 }, (_, i) => `Item ${i + 1}`);

  return (
    <div>
      <h1>Virtual List Example</h1>
      <VirtualList items={items} />
    </div>
  );
};

export default App;

7. 实现模板字符串

const fn1 = (str, obj) => {
	let res = '';
    // 标志位,标志前面是否有{
	let flag = false;
	let start;
	for (let i = 0; i < str.length; i++) {
		if (str[i] === '{') {
			flag = true;
			start = i + 1;
			continue;
		}
		if (!flag) res += str[i];
		else {
			if (str[i] === '}') {
				flag = false;
				res += match(str.slice(start, i), obj);
			}
		}
	}
	return res;
}
// 对象匹配操作
 const match = (str, obj) => {
        const keys = str.split('.').slice(1);
        let res = obj;
        for(let i = 0;i<keys.length;i++){
          res =  res[keys[i]];
        }
        return res;
    }


8.promise的All和Race区别

在all方法中,会等到最慢的执行完,才会走到then。如果有一个错误,那就走catch(返回最快的resolve)。

const promise1 = new Promise((resolve) => {
    setTimeout(() => resolve("One"), 1000);
});

const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => reject("Two"), 2000);
});

Promise.all([promise1, promise2])
.then((values) => {
  console.log(values);
})
.catch((res) => {
  console.log("走到了catch", res);
});

在race方法中,会直接获取最快的promise的结果。如果最快的是resolve那就走then,如果最快的reject,那就走catch。

const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => reject("One"), 1000);
  });

  const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => reject("Two"), 2000);
  });

  Promise.race([promise1, promise2])
    .then((values) => {
      console.log(values);
    })
    .catch((res) => {
      console.log("走到了catch", res);
    });

手写race和all

重点:先把用法列出来,例如下面的用法,一眼看出promiseRace需要返回一个Promise。才会有then方法,所以第一行直接return new Promise

      promiseRace([myPro, myPro1])
        .then((res) => {
          console.log("所有的都成功了", res);
        })
        .catch((err) => {
          console.log("又失败的", err);
        });
  function PromiseAll(promiseArr) {
    return new Promise((resolve, reject) => {
      let count = 0;
      let result = [];
      for (let i = 0; i < promiseArr.length; i++) {
        Promise.resolve(promiseArr[i])
          .then((res) => {
            result[i] = res;
            count++;
            if (count === promiseArr.length) {
              resolve(result);
            }
          })
          .catch((err) => {
            reject(err);
          });
      }
    });
  }

  function promiseRace(promiseArr) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < promiseArr.length; i++) {
        Promise.resolve(promiseArr[i])
          .then((res) => {
            resolve(res);
          })
          .catch((err) => {
            reject(err);
          });
      }
    });
  }

9.图片的预加载和懒加载

预加载

原理:new Image()对象,给其设置src,即开始发送网络请求。等到页面真正滚动到图片位置,图片已经加载完成了。

      window.onload = function () {
        const image = new Image();
        image.src = "https://pic.cnblogs.com/avatar/1862411/20200610010655.png";
      };

      // 两种方法二选一即可
      // document.addEventListener("DOMContentLoaded", function () {
      //   imageUrl = "https://example.com/path/to/your/large-image.jpg";
      //   preloadImage(imageUrl);
      // });

      const myButton = document.querySelector("button");
      myButton.addEventListener("click", () => {
        let img = document.querySelector("img");
        img.src = "https://pic.cnblogs.com/avatar/1862411/20200610010655.png";
      });

如何检测文档加载完成?

  1. DOMContentLoaded:只有HTML加载、解析完成。样式表、图片还未加载完成
  2. window.onload 事件:window.onload 事件在页面上的所有资源加载完成。

懒加载

监听元素滚入视口。

document.documentElement.clientheight(window.innerHeight) > imgDOM.getBoundingClient().top

还可以使用新API。entry是一个数组,你observe了几个,数组里就有几个。

     // 获取目标元素
      const targetElement = document.getElementById("target-element");
      const targetElement2 = document.getElementById("target-element2");

      // 定义回调函数,当目标元素进入或离开视口时触发
      function handleIntersection(entries, observer) {
        console.log(" entries是什么", entries);
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            console.log("Element is visible in the viewport");
          } else {
            console.log("Element is not visible in the viewport");
          }
        });
      }

      // 创建 Intersection Observer 实例
      const observer = new IntersectionObserver(handleIntersection);

      // 观察目标元素
      observer.observe(targetElement);
      observer.observe(targetElement2);

11.柯里化

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn(...args);
    } else {
      return function (...nextArgs) {
        return curried(...args, ...nextArgs);
      };
    }
  };
}

function sum(a, b, c) {
  console.log(a + b + c);
}

const fn = curry(sum);

fn(1, 2, 3); // 6
fn(1, 2)(3); // 6
fn(1)(2, 3); // 6
fn(1)(2)(3); // 6