【知识梳理6】数据结构和算法_4.27

160 阅读4分钟

注:以下内容为随手笔记,属于暂未成形的文章,请勿参考

「手写代码」

模拟实现深拷贝

核心:

浅拷贝复制引用的拷贝方法,深拷贝就是指完全的拷贝一个对象。这里说的拷贝仅针对数组和对象。

function shallowCopy(obj) {
  // 基础类型返回
  if (typeof obj !== 'object' || obj === null) return obj;
  let newObj = obj instanceof Array ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

function deepCopy(obj) {
  // 基础类型返回
  if (typeof obj !== 'object' || obj === null) return obj;
  let newObj = obj instanceof Array ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
    }
  }
  return newObj;
}

实现节流和防抖

防抖

函数在被触发的n妙后执行,在n秒内又触发,则重新计数

  • search搜索,用户不断输入值时,用防抖来节约Ajax请求,也就是输入框事件
  • window触发resize时,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
function debounce(fn, delay, isImmediate) {
  let timer = null;

  return function () {
    let context = this;
    let args = arguments;

    if (timer) clearTimeout(timer);
    
    if (isImmediate) {
      if (!timer) fn.apply(context, args)
      timer = setTimeout(function () {
        timer = null;
      }, delay);
    } else {
      timer = setTimeout(function () {
        fn.apply(context, args)
      }, delay);
    }
  }
}

节流

一段时间内,只执行函数一次。如果这段时间内多次触发,也只执行一次函数

  • 鼠标的点击事件mousedown只触发一次
function throttle(fn, delay) {
  let base = 0;
  let timer = null;

  return function() {
    let context = this;
    let args = arguments;
    let now = +new Date();
    let remain = delay - (now - base);

    if(remain <= 0 || remain > delay) {
      if(timer) {
        clearTimeout(timer);
        timer = null;
      }
      base = now;
      fn.apply(context, args);
    } else if(!timer) {
      timer = setTimeout(() => {
        base = +new Date();
        timer = null;
        fn.apply(context, args);
      }, remain)
    }
  }
}

实现函数柯里化

把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数

const curry = (fn, ...args) => 
  args.length < fn.length ? (...arguments) => curry(fn, ...args, ...arguments) : fn(...args);

// 测试实例
function sumFn(a, b, c) {
  return a + b + c;
}
var sum = curry(sumFn);
console.log(sun(1)(2)(3));
console.log(sun(1)(2,3));

应用场景:参数复用。本质上是降低通用性,提高适用性。

$.ajax、 $.get、$.post

实现数组去重

function uniq(arr) {
  let newArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) === -1) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}
[...new Set(arr)];

实现数组扁平化

function flat2(arr) {
  let newArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] instanceof Array) {
      newArr = newArr.concat(flat2(arr[i]));
    } else {
      newArr.push(arr[i]);
    }
  }

  return newArr;
}

实现数组乱序

核心:

Fisher–Yates:遍历数组元素,然后将当前元素与以后随机位置的元素进行交换

// abnormalSort
function shuffle(arr) {
  for (let i = arr.length; i > 0; i--) {
    let j = Math.floor(Math.random() * i);
    [arr[i - 1], arr[j]] = [arr[j], arr[i - 1]];
  }
  return arr;
}

其他

实现一个observer递归绑定所有属性

function observer2(obj) {
  return bindProperty(obj);
}

function bindProperty(obj) {
  if(typeof obj !== 'object' || obj === null) {
    return
  }

  Object.keys(obj).forEach(key => {
    let value = obj[key];
    if(typeof value !== 'object') {
      Object.defineProperty(obj, key, {
        get() {
          return value
        },
        set(newVal) {
          console.log('set old value', value);
          console.log('set new value', newVal);
        }
      })
    } else {
      bindProperty(obj[key])
    }
  });
  return obj;
}

let test = observer2({ b: 1, c: 3, d: { e: 3 } });
test.b = 2;

模拟实现Promise

function myPromise(construtor) {
  let self = this;
  self.status = 'pending';
  self.value = undefined;
  self.error = undefined;
  // 初始化后立即执行
  try {
    construtor(resolve, rejected);
  } catch(e) {
    rejected(e);
  }

  function resolve(value) {
    if(self.status === 'pending') {
      self.status = 'resolved';
      self.value = value;
    }
  }

  function rejected(error) {
    if(self.status === 'pending') {
      self.status = 'rejected';
      self.error = error;
    }
  }
}

myPromise.prototype.then = (A, B) => {
  let self = this;
  if(self.status === 'resolved') {
    A(self.value);
  }

  if(self.status === 'rejected') {
    B(self.error);
  }
}

实现兼容IE的事件监听

// 兼容浏览器
function bindEvent(obj, type, fn) {
  if (obj.addEventListener) {
    obj.addEventListener(type, eventFn);
  } else {
    obj.attachEvent("on" + type, eventFn);
  }
  function eventFn(e) {
    const e = e || window.event;
    const target = e.target || e.srcElemet;
    fn && fn(target, e);
  }
}

手写XHR实现

const req = new XMLHttpRequest();
req.open('GEt', '/', true);
req.onreadystatechange = () => {
  if(req.readyState === 4 && req.status === 200) {
    console.log(res)
  }
}
req.send();

实现事件委托

function trust(element, type, fn) {
  element.addEventListener(type, e => {
    const current = e.target;
    if(element.contains(current)) {
      fn.call(current, e)
    }
  });
  return element;
}

实现trim函数

var str = '   324 ';
String.prototype.trim2 = function() {
  return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
str.trim2();

实现大数相加

function addStrs(str1, str2) {
  let i = str1.length - 1, j = str2.length - 1, add = 0;
  const arr = [];
  while( i >= 0 || j >= 0 || add !== 0) {
    const x = parseInt(str1.charAt(i));
    const y = parseInt(str1.charAt(i));
    const res = x + y + add;
    arr.push(res % 10);
    add = Math.floor(res / 10);
    i--, j--;
  }
  return arr.reverse().join('');
}
// 测试
var str1 = '13265456465', str2 = '26521132';
addStrs(str1, str2);

实现拖拽

// position: relative;
let dragging = false
let position = null

const xxx = document.querySelector('#xxx');
xxx.addEventListener('mousedown', function(e) {
  dragging = true
  position = [e.clientX, e.clientY]
});


document.addEventListener('mousemove', function(e) {
  if(dragging === false) return null
  const x = e.clientX
  const y = e.clientY
  const deltaX = x - position[0]
  const deltaY = y - position[1]
  const left = parseInt(xxx.style.left || 0)
  const top = parseInt(xxx.style.top || 0)
  xxx.style.left = left + deltaX + 'px'
  xxx.style.top = top + deltaY + 'px'
  position = [x, y]
});

document.addEventListener('mouseup', function(e){
  dragging = false
});

实现Event类,on off emit

class Event {
  constructor () {
    // 储存事件的数据结构
    // 为查找迅速, 使用对象(字典)
    this._cache = {}
  }

  // 绑定
  on(type, callback) {
    // 为了按类查找方便和节省空间
    // 将同一类型事件放到一个数组中
    // 这里的数组是队列, 遵循先进先出
    // 即新绑定的事件先触发
    let fns = (this._cache[type] = this._cache[type] || [])
    if(fns.indexOf(callback) === -1) {
      fns.push(callback)
    }
    return this
  }

  // 触发
  // emit
  trigger(type, data) {
    let fns = this._cache[type]
    if(Array.isArray(fns)) {
      fns.forEach((fn) => {
        fn(data)
      })
    }
    return this
  }
  
  // 解绑
  off (type, callback) {
    let fns = this._cache[type]
    if(Array.isArray(fns)) {
      if(callback) {
        let index = fns.indexOf(callback)
        if(index !== -1) {
          fns.splice(index, 1)
        }
      } else {
        // 全部清空
        fns.length = 0
      }
    }
    return this
  }
}

实现getValue

function deepGet(obj, keys, defaultVal) {
  return (
    (!Array.isArray(keys)
      ? keys.replace(/\[/g, '.').replace(/\]/g, '').split('.')
       : keys
    ).reduce((o, k) => (o || {})[k], obj) || defaultVal
  );
}
// 测试实例
var obj = {
  a: [
    {
      b: {
        c: 3,
      },
    },
  ],
  e: {
    f: 1,
  },
};
console.log(deepGet(obj, 'e.f')); // 1
console.log(deepGet(obj, ['e','f'])) // 1
console.log(deepGet(obj, 'a.x')); // undefined
console.log(deepGet(obj, 'a.x', '--')) // -- 
console.log(deepGet(obj, 'a[0].b.c')) // 3
console.log(deepGet(obj, ['a', 0, 'b', ,'c'])) // 3

参考

「数据结构」

数组和链表

栈和队列

普通二叉树、平衡二叉树、完全二叉树、二叉搜索树

二叉树的便利

「算法」

排序算法

冒泡

时间复杂度O(n*n)

  1. 比较相邻的元素,前者比后者大的话,两者交换位置。循环操作
  2. 除去最后一个元素,剩下的重复上述步骤
function bubbleSort(arr) {
  const len = arr.length;
  for(let i = 0; i < len - 1; i++) {
    for(let j = 0; j < len - 1 -i; j++) {
      if(arr[j] > arr[j+1]) {
        [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
      }
    }
  }
  return arr;
}

快速

时间复杂度:O(nlogn)

  1. 取中间那个数做基数,准备两个数组容器,剩下元素逐个与基数比对,较小的放左边容器,较大的放右边容器
  2. 递归处理两个容器的元素,并将处理后的数据与基数按大小合并成一个数组
function quickSort(arr) {
  let len = arr.length;
  if(len <= 1) {
    return arr;
  }
  let index = Math.floor(len / 2);
  let middle = arr.splice(index, 1);
  let left = [], right = [];
  for(let i = 0; i < arr.length; i++) {
    if(arr[i] < middle[0]) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat(middle, quickSort(right));
}

插入

时间复杂度: O(n*n)

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
function insertSort(arr) {
  for(let i = 0; i < arr.length; i++) {
    let preIndex = i - 1,
    cur = arr[i];
    while(preIndex >= 0 && arr[preIndex] > cur) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex + 1] = cur;
  }
  return arr;
}

选择

时间复杂度O(n*n)

每一次从待排序的数组元素中选择最大(最小)的一个元素作为首元素,直到排完为止

function selectSort(arr) {
  let len = arr.length, temp = 0;
  for(let i = 0; i < len; i++) {
    temp = i;
    for(let j = i + 1; j < len; j++) {
      if(arr[j] < arr[temp]) {
        temp = j;
      }
    }

    if(temp !== i) {
      [arr[i], arr[temp]] = [arr[temp], arr[i]];
    }

    return arr;
  }
}

动态规划

搜索算法

深度优先搜索算法

(简称为 DFS)

广度优先搜索算法

(简称为 BFS)