# 手写系列

# 手写instanceof

代码如下:

function myInstanceof(left, right) {
  let left_prototype = left.__proto__;
  let right_prototype = right.prototype;
  while (left_prototype !== null) {
    if (left_prototype === right_prototype) return true;
    left_prototype = left_prototype.__proto__;
  }
  return false;
}

// 开始测试 
let a = [];
let b = {};
function Foo(){};
let c = new Foo();
function child(){};
function father(){};
child.prototype = new father();
let d = new child();
console.log(myInstanceof(a, Array)); // true
console.log(myInstanceof(b, Object)); // true
console.log(myInstanceof(b, Array)); // false
console.log(myInstanceof(a, Object)); // true
console.log(myInstanceof(c, Foo)); // true
console.log(myInstanceof(d, child)); // true
console.log(myInstanceof(d, father)); // true

# 手写call

准备一个add函数:

function add(a, b) {
  console.log(this, a, b);
  return a + b;
}

第一版:

Function.prototype.myCall = function(obj, ...args) {
  obj.fn = this;
  obj.fn(...args);
  delete obj.fn;
}

add.myCall({}, 1, 2); // {fn: ƒ} 1 2

第二版:

Function.prototype.myCall = function(obj, ...args) {
  let key = Symbol('fn');
  obj[key] = this;
  obj[key](...args);
  delete obj[key];
}

add.myCall({}, 1, 2); // {Symbol(fn): ƒ} 1 2

第三版:

Function.prototype.myCall = function(obj, ...args) {
  let key = Symbol('fn');
  Object.defineProperty(obj, key, {
    enumerable: false,
    value: this
  })
  obj[key](...args);
  delete obj[key];
}

add.myCall({}, 1, 2); // {} 1 2

最终版:

Function.prototype.myCall = function(obj, ...args) {
  obj = (obj === null || obj === undefined) ? globalThis : Object(obj);
  let key = Symbol('fn');
  Object.defineProperty(obj, key, {
    enumerable: false,
    value: this
  })
  let result = obj[key](...args);
  delete obj[key];
  return result;
}

add.myCall({}, 1, 2); // {} 1 2
console.log(Object.prototype.toString.myCall(1)); // [object Number]
console.log(Object.prototype.toString.myCall([])); // [object Array]

# 手写数组转树

let input = [
  { id: 2, val: '班级1', parentId: 1 },
  { id: 6, val: '学生3', parentId: 3 },
  { id: 4, val: '学生1', parentId: 2 },
  { id: 3, val: '班级2', parentId: 1 },
  { id: 1, val: '学校', parentId: null },
  { id: 5, val: '学生2', parentId: 2 },
]

function arrayToTree(array) {
  let root = null, queue = [];
  array = array.sort((a, b) => a.parentId - b.parentId);
  for (let i = 0, len = array.length; i < len; ) {
    let item = array[i];
    if (i === 0) {
      root = {
        id: item.id,
        val: item.val,
        children: []
      };
      queue.push({ id: item.id, child: root.children });
      i++;
    } else {
      while (i < len && queue[0].id === array[i].parentId) {
        let obj = { id: array[i].id, val: array[i].val, children: [] };
        queue[0].child.push(obj);
        queue.push({ id: array[i].id, child: obj.children });
        i++;
      }
      queue.shift();
    }
  }
  return root;
}

console.log(arrayToTree(input));

# 防抖

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <input type="text">

  <script>
    let input = document.querySelector('input');
    function debounce(fn, delay) {
      let timer = null;
      return function() {
        let that = this, args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function() {
          fn.apply(that, args);
        }, delay);
      }
    }
    function test(a, b) {
      console.log("res: ", a + b);
    }
    input.addEventListener('input', debounce(test, 1000).bind(null, 1, 2)); // window
  </script>
</body>
</html>

# 节流

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <input type="text">

  <script>
    let input = document.querySelector('input');
    function throttle(fn, delay) {
      let previousTime = 0;
      return function() {
        let currentTime = +new Date();
        if(currentTime - previousTime > delay) {
          fn.apply(this, arguments);
          previousTime = currentTime;
        }
      }
    }

    function test(e, a, b) {
      console.log("res: ", a + b);
      console.log(e);
    }
    input.addEventListener('input', throttle(test, 1000));
  </script>
</body>
</html>

# 深拷贝

function deepClone(data) {
  const type = Object.prototype.toString.call(data).slice(8, -1);
  if (!['Object', 'Array'].includes(type)) return data;
  const newData = type === 'Object' ? {} : [];
  for (let key in data) {
    const temp = data[key];
    if (typeof temp === 'object' && temp !== null) {
      newData[key] = deepClone(temp);
    } else {
      newData[key] = temp;
    }
  }
  return newData;
}

let a = [1, 2, { a: 1, b: [1, 2, 3, 4] }, 4, null, undefined, function name() {}],
  b = deepClone(a);
a[2].a = 2;
console.log(a);
console.log(b);

# 判断是否为空对象

function isEmptyObject(data) {
  return data === null ||
    (typeof data === 'object' && !Reflect.ownKeys(data).length);
}

let obj = {};
Object.defineProperty(obj, 'haha', {
  enumerable: false,
  value: 1
})
console.log(isEmptyObject(null)); // true
console.log(isEmptyObject(undefined)); // false
console.log(isEmptyObject(0)); // false
console.log(isEmptyObject('')); // false
console.log(isEmptyObject({ [Symbol('symbol')]: 'haha' })); // false
console.log(isEmptyObject({ a: 1 })); // false
console.log(isEmptyObject({ })); // true
console.log(isEmptyObject(obj)); // false
console.log(isEmptyObject([])); // false
console.log(isEmptyObject([1, 2, 3])); // false

# 颜色值16进制转10进制rgb

function hexToRGB(str) {
  let len = str.length, result = [];
  if (!/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/.test(str)) throw new Error('invalid color hex');
  let step = (len - 1) / 3, num = step === 1 ? 2 : 1;
  for (let i = 1; i < len; i+=step) {
    result.push(Number.parseInt(str.slice(i, i + step).repeat(num), 16));
  }
  return `rgb(${result.join(",")})`;
}

console.log(hexToRGB('#f0ffff'));
console.log(hexToRGB('#f0f'));
console.log(hexToRGB('#f0ff'));

# 数组扁平化

使用内置函数:

let arr = [1, [2, [3, 4]]];
console.log(arr.flat(Infinity));

递归1:

function flatArray(array) {
  const result = [];
  recursion(array)
  return result;
  function recursion(array) {
    for (let item of array) {
      Array.isArray(item) ? recursion(item) : result.push(item);
    }
  }
}

console.log(flatArray([1, 2, [3, 4, [5, 6, [7, 8, 9], 10], 11, 12], 13, 14, 15]));

递归2:

function flatArray(array) {
  let result = [];
  for (let item of array) {
    Array.isArray(item) ?
      (result = [...result, ...flatArray(item)]) : result.push(item);
  }
  return result;
}

console.log(flatArray([1, 2, [3, 4, [5, 6, [7, 8, 9], 10], 11, 12], 13, 14, 15]));

# 生成长度是N,且在min、max内不重复的 整数随机数组

function rand(min, max, N) {
  N = max - min + 1 > N ? N : max - min + 1;
  let set = new Set();
  while (set.size < N) {
    set.add(Math.round(Math.random() * (max - min) + min));
  }
  return [...set];
}

console.log(rand(0, 5, 3));

# 字符串中的单词逆序输出

方法1:

function strReverse(str) {
  return str.split("").reverse().join("");
}

方法2:

function strReverse(str) {
  let stack = [], result = "";
  for (let char of str) stack.push(char);
  while (stack.length) result += stack.pop();
  return result;
}

# 分片渲染

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="box"></div>
  <script>
    let patch = 20, nowNum = 0, cnt = 0, timer = null;
    const time = 200, allNum = 1000000, box = document.querySelector("#box");
    // 直接渲染
    let frag = document.createDocumentFragment();
    for (let i = 0; i < allNum; i++) {
      let div = document.createElement('div');
      div.innerText = `这是第 ${i} 个内容`;
      frag.appendChild(div);
    }
    box.appendChild(frag);

    // 分片渲染
    timer = setInterval(() => {
      let frag = document.createDocumentFragment();
      for (let i = 0; i < patch; i++) {
        let div = document.createElement('div');
        div.innerText = `这是第 ${++cnt} 个内容`;
        frag.appendChild(div);
      }
      box.appendChild(frag);
      nowNum += patch;
      if (nowNum === allNum) clearInterval(timer);
    }, time);
  </script>
</body>

</html>

# 虚拟列表

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #box {
      width: 300px;
      border: 1px solid black;
      overflow: scroll;
      position: relative;
    }

    .empty,
    .container {
      width: 100%;
    }

    .container {
      position: absolute;
      left: 0;
    }

    .empty {
      position: absolute;
      top: 0;
      left: 0;
    }

    .container span {
      width: 100%;
      display: inline-block;
      text-align: center;
    }
  </style>
</head>

<body>
  <div id="box">
    <div class="empty"></div>
    <div class="container"></div>
  </div>

  <script>
    const allNum = 100000, itemHeight = 30, screenHeight = 300, maxHeight = allNum * itemHeight - screenHeight;
    const box = document.querySelector("#box"),
      empty = box.querySelector(".empty"),
      container = box.querySelector(".container");
    let start = 0, end = Math.ceil(screenHeight / itemHeight);
    box.style.height = `${screenHeight}px`;
    empty.style.height = `${itemHeight * allNum}px`;
    updateData();
    // 更新数据
    function updateData() {
      let frag = document.createDocumentFragment();
      for (let i = start;i <= allNum && i <= end; i++) {
        let span = document.createElement('span');
        span.style.height = `${itemHeight}px`;
        span.innerText = `这是第 ${i} 个数据`;
        frag.appendChild(span);
      }
      container.innerHTML = ``;
      container.appendChild(frag);
    }

    box.addEventListener('scroll', function (e) {
      let top = this.scrollTop;
      start = Math.floor(top / itemHeight);
      end = Math.floor((top + screenHeight) / itemHeight);
      updateData();
      container.style.transform = `translateY(${top}px)`;
    })

  </script>
</body>

</html>

# 实现Array.prototype.at方法

function rewriteArray(array) {
  if (!Array.isArray(array)) {
    throw new Error('The parameter passed in is not an array!');
  }
  return new Proxy(array, {
    get(target, property) {
      if (property.startsWith("-")) {
        if (/^-[1-9]\d{0,}$/.test(property)) {
          return target[target.length + +property];
        }
        throw new Error('invalid index!');
      } else {
        return target[property];
      }
    }
  })
}

let list = rewriteArray([1, 2, 3, 4, 5]);
console.log(list[-2]); // 4