最全js手写-建议死磕多练

最全js手写-建议死磕多练,第1张

手写题目 vue2数据劫持
function defineReactive(data) {
  if (!data || Object.prototype.toString.call(data) !== '[object Object]')
    return;
  for (let key in data) {
    let val = data[key];
    Object.defineProperty(data, key, {
      enumerable: true, //可枚举
      configurable: true, //可配置
      get: function() {
        track(data, key);
        return val;
      },
      set: function() {
        trigger(val, key);
      },
    });
    if (typeof val === "object") {
      defineReactive(val);
    }
  }
}
function trigger(val, key) {
  console.log("sue set", val, key);
}
function track(val, key) {
  console.log("sue set", val, key);
}


const data = {
  name:'better',
  firends:['1','2']
}
defineReactive(data)
console.log(data.name)
console.log(data.firends[1])
console.log(data.firends[0])
console.log(Object.prototype.toString.call(data))

vue3 proxy
function reactive(obj) {
  const handler = {
    get(target, prop, receiver) {
      track(target, prop);
      const value =  Reflect.get(...arguments);
      if(typeof value === 'Object') {
        reactive(value)
      }else {
        return value
      }
    },
    set(target,key, value, receiver) {
      trigger(target,key, value);
      return Reflect.set(...arguments);
    },
  };
  return new Proxy(obj,handler)
}
function track(data, key) {
  console.log("sue set", data, key);
}
function trigger(data, key,value) {
  console.log("sue set", key,':',value);
}
const dinner = {
  name:'haochi1'
}
const proxy  =reactive(dinner)
proxy.name
proxy.list = []
proxy.list.push(1)

防抖节流 防抖

使用场景: 输入框输入搜索,拖拽( mousemove )

效果: 不是每次 *** 作后执行函数.在频繁 *** 作的最后一次 *** 作结束后在设置的时间内没有触发 *** 作时才执行回调

两种思路

立即执行: 在第一次触发事件的时候立即执行当前 *** 作的回调,后面的 *** 作在最后一次 *** 作结束后在设置的时间内没有触发 *** 作时才执行回调无立即执行: 按最后一次 *** 作结束后的规定时间执行
function debounce(fn, delay, immediate) {
  let timer; //利用闭包保存同一个timer
  return function () {
    let self = this;
    let arg = arguments;
    clearTimeout(timer);
    if (immediate) {
      const callNow = !timer;
      timer = setTimeOut(() => {
        timer = null;
      }, delay);
      if (callNow) fn.apply(self.arg);
    } else {
      timer = setTimeout(() => {
        fn.apply(self, arg);
      }, delay);
    }
  };
}
节流

使用场景:滚动条滚动,频繁点击请求接口

效果:预定一个函数只有在大于等于执行周期时才执行

两种思路:

时间戳,先会立即执行,达到时间周期再执行
function throttle(fn, delay) {
  let t;
  return function () {
    let self = this;
    let arg = arguments;
    if (!t || Date.now() - t >= delay) {
      fn.apply(self, arg);
      t = new Date();
    }
  };
}
定时器,定时一定时间周期之后去执行,但是在这时间内中不停的调用,不让他的定时器清零重新计时,不会影响当前的结果,还是那时间继续等,等到达时间周期后触发(会出现停止 *** 作还是会触发)
function throttle(fn,delay) {
  let timer
  retrun function () {
    let self = this
    let arg = arguments

    if(timer) return
    timer = setTimeOut(()=> {
      fn.apply(fn,arg)
      timer = null
    },delay)
  }
}
call apply bind

三者都是用来改变 this 指向的

call
使用:
 function.call(thisArg,arg1,grg2,...)
thisArg 可选参数,function 执行时内部的 this 指向thisArgarg1,arg2,… 可选参数,传递给 function 的参数列表返回值:在指定的 this 值和所传递的参数下调用此函数的返回结果
注意: function 函数将会立即执行在执行时,会将函数内部的 this 指向 thisArg出 thisArg 外的所有剩余参数将全部传递给 function返回 function 函数执行后的结果
Function.prototype.myCall = function (context, ...arr) {
  console.log('调用mycall中的this', this);
  if (context === null || context === undefined) {
    context = window;
  } else {
    context = Object(context);
  }
  const specialPrototype = Symbol('特殊属性symbol');
  context[specialPrototype] = this; // this指向调用者
  // context[specialPrototype]执行函数调用时this指向context
  let result = context[specialPrototype](...arr);
  delete context[specialPrototype];
  return result;
};
// context :{
//   specialPrototype:this->调用者
// }
apply

注意:

使用 apply 只支持两个参数,第一个为 thisArg,第二个是包括多个参数的数组
Function.prototype.myApply = function (context, arr) {
  console.log(this);
  if (context === null || context === undefined) {
    context = window;
  } else {
    context = Object(context);
  }
  const specialPrototype = Symbol('特殊属性symbol');
  context[specialPrototype] = this;
  let result = context[specialPrototype](...arr);
  delete context[specialPrototype];
  return result;
};
bind
使用:
 function.bind(thisArg,arg1,grg2,...)
thisArg 可选参数,function 执行时会生成一个包裹着function(...)的邦迪函数,并且将function(...)的 this 指向 thisArg,如果使用 new 运算符调用这个生成的绑定函数,则忽略thisArgarg1,arg2,… 可选参数,传递给 function 的参数列表返回值:在指定的 this 值和所传递的参数下调用此函数的返回结果
注意: bind 方法将创建并返回一个新的函数,新函数称为绑定函数,并且此绑定函数包裹着原始函数执行时,会显示将原始函数内部的 this 指向了thisArg除 thisArg 外所有剩余参数将全部传递给 function执行邦定函数时,如果传递了参数,这些参数将全部传递给原始函数 function如果使用 new 运算符调用生成的绑定函数,则忽略 thisArg
Function.prototype.mybind = function () {
  //判断调用bind的 是不是函数,抛出异常
  if (typeof this !== 'function') {
    throw new Error(
      'function.prototype.bind - what is trying to be bound is not  callable',
    );
  }

  // 将类数组的参数转换成数组然后截取第一个参数
  // const argsArr =Array.prototype.slice.call(arguments)
  const argsArr = [...arguments];
  const args = argsArr.shift();
  const self = this;
  const fToBind = function () {
    console.log('返回函数的参数', arguments);
    const isNew = this instanceof fToBind; // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
    const context = isNew ? this : Object(args); // new调用就绑定到this上,否则就绑定到传入的objThis上
    return self.apply(context, argsArr.concat([...arguments]));
  };
  if (self.prototype) {
    // 复制源函数的prototype给fToBind 一些情况下函数没有prototype,比如箭头函数
    fToBind.prototype = Object.create(self.prototype);
  }
  return fToBind;
};
函数柯里化
function add() {
  const _args = [...arguments];
  function fn() {
    _args.push(...arguments);
    return fn;
  }
  fn.toString = function() {
  // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    return _args.reduce((sum, cur) => sum + cur);
  }
  return fn;
}

const result = add(1)(2,4)
console.log(result)
console.log(`add`, add(1)(2)(3) )  // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

手写 new 关键字

作用
创建一个用户定义的对象类型的实例或者具有构造函数的内置对象的实例

特点
可以通过 new 一个构造函数的方式来创建一个对象实例,但是构造函数的差异导致创建的实例有一定的不同

构造函数的返回值不同

无返回值:生成一个实例化对象,构造函数中的 this 指向该对象返回一个对象:return 之前的都被覆盖了,new 生成是 return 的对象返回一个非对象:跟没有 return 是一样的结果
// new关键字可以创建对象的实例
// 产生一个新的对象
function myNew(fn, ...args) {
  const obj = new Object();
  obj._proto_ = fn.prototype;
  // Object.create()方法创建一个新对象,使用现有的对象来提供新创建对象的_proto_
  // const obj = Object.create(fn.prototype)

  // 执行fn并把fn中的this指向新创建的对象
  let res = fn.apply(obj, args);
  // 判断构造函数的返回值是不是object,是object则使用retrun的对象,不是的话就使用生成的对象
  return typeof ret === 'object' ? ret || obj : obj;
}
instanceof
function myInstanceof(A, B) {
  // 遍历链表
  let p = A;
  while (p) {
    p = p.__proto__;
    // B的 prototype 属性是否出现在A实例对象的原型链上
    if (p === B.prototype) {
      return true;
    }
  }
  return false;
}
function Foo() {}
var f = new Foo();
console.log(myInstanceof(f, Foo)); // true
console.log(myInstanceof(f, Object)); // true
console.log(myInstanceof([1, 2], Array)); // true
console.log(myInstanceof({ a: 1 }, Array)); // false
console.log(myInstanceof(Array, Object)); // true
console.log(Array instanceof Object); // true
深浅克隆
function shallowClone(source) {
  const target = {};
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      target[key] = source[key];
    }
  }
  return target;
}
// 深拷贝1.0
function deeClone1(source) {
  if (typeof source === 'object') {
    let target = Array.isArray(source) ? [] : {};
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        // 如果属性是对象类型则递归再次遍历赋值
        target[key] = deeClone1(source[key]);
      }
    }
    return target;
  } else {
    return source;
  }
}
const textObject = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child',
  },
  field4: [2, 4, 8],
};
const deepResult = deeClone1(textObject);
const shallowResult = shallowClone(textObject);
console.log('深克隆', deepResult);
console.log('浅克隆', shallowResult);
deepResult.field4.push(1);
console.log('深克隆', deepResult, textObject);

// 深拷贝2.0 解决循环引用问题

const textObject2 = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child',
  },
  field4: [2, 4, 8],
};
textObject2.textObject2 = textObject2;
// 使用1.0克隆克隆循环引用的值会出现爆栈的现象
// const deepResult2 = deeClone1(textObject2);

// 深拷贝2.0 使用Map
// 检查map中有无克隆过的对象
// 有 - 直接返回
// 没有 - 将当前对象作为key,克隆对象作为value进行存储
// 继续克隆
function deeCloneMap(source, map = new Map()) {
  if (typeof source === 'object') {
    let target = Array.isArray(source) ? [] : {};
    // 检查map中有无克隆过的对象
    if (map.get(source)) {
      // 有 - 直接返回
      return map.get(source);
    }
    // 没有 - 将当前对象作为key,克隆对象作为value进行存储
    map.set(source, target);
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        // 如果属性是对象类型则递归再次遍历赋值
        target[key] = deeCloneMap(source[key], map);
      }
    }
    return target;
  } else {
    return source;
  }
}
const deepResult2 = deeCloneMap(textObject2);
console.log('mapClone', deepResult2);
// 深拷贝2.1 使用是WeakMap弱引用关系,当下一次垃圾回收机制执行时,这块内存就会被释放掉。

function deeCloneWeakMap(source, map = new WeakMap()) {
  if (typeof source === 'object') {
    let target = Array.isArray(source) ? [] : {};
    // 检查map中有无克隆过的对象
    if (map.get(source)) {
      // 有 - 直接返回
      return map.get(source);
    }
    // 没有 - 将当前对象作为key,克隆对象作为value进行存储
    map.set(source, target);
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        // 如果属性是对象类型则递归再次遍历赋值
        target[key] = deeCloneMap(source[key], map);
      }
    }
    return target;
  } else {
    return source;
  }
}
// while循环的性能高 使用while来实现一个通用的forEach遍历,iteratee是遍历的回掉函数,他可以接收每次遍历的value和index两个参数:
function forEach(array, iteratee) {
  let index = -1;
  const length = array.length;
  while (++index < length) {
    iteratee(array[index], index);
  }
  return array;
}
// 深拷贝3.0 使用是WeakMap弱引用关系,当下一次垃圾回收机制执行时,这块内存就会被释放掉。

function deeCloneWhile(source, map = new WeakMap()) {
  // 1.判断是否为null 或undefined
  if (typeof source == null) return source;
  // 2.判断是否为日期Date
  if (source instanceof Date) return new Date(osourcebj);
  // 3.判断是否为正则 typeof /\d+/ === 'object'
  if (source instanceof RegExp) return new RegExp(source);

  if (typeof source === 'object') {
    const isArray = Array.isArray(source);
    let target = isArray ? [] : {};
    // 检查map中有无克隆过的对象
    if (map.get(source)) {
      // 有 - 直接返回
      return map.get(source);
    }
    // 没有 - 将当前对象作为key,克隆对象作为value进行存储
    const keys = isArray ? undefined : Object.keys(source);
    map.set(source, target);
    forEach(keys || source, (value, key) => {
      if (keys) {
        key = value;
      }
      target[key] = deeCloneWhile(source[key], map);
    });
    // for (let key in source) {
    //   if (source.hasOwnProperty(key)) {
    //     // 如果属性是对象类型则递归再次遍历赋值
    //     target[key] = deeCloneMap(source[key],map);
    //   }
    // }
    return target;
  } else {
    return source;
  }
}
const textObject3 = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child',
  },
  field4: [2, 4, 8],
  f: {
    f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: {} } } } } } } } } } },
  },
};
textObject3.textObject3 = textObject3;
const deepResult3 = deeCloneWhile(textObject3);
console.log('deeCloneWhile', deepResult3);
promise Promise.allSettled

特点
接收一个数组作为参数,数组的每一项都是一个Promise对象,返回一个新的Promise对象,只有等到参数数组的所有的Promise对象都发生状态改变,返回的Promise对象才会发生变更

Promise.allSettled = function (arr) {
  let result = [];
  return new Promise((resolve, reject) => {
    arr.forEach((item, index) => {
      Promise.resolve(item)
        .then((res) => {
          result.push({
            status: 'fulfilled',
            value: res,
          });
          result.length === arr.length && resolve(result);
        })
        .catch((error) => {
          result.push({
            status: 'rejected',
            value: error,
          });
          result.length === arr.length && resolve(result);
        });
    });
  });
};
Promise.all()

Promise.all()方法用于将多个 promise 实例包装成一个新的 Promise 实例

Promise.all = function (arr) {
  let index = 0,
    result = [];
  return new Promise((resolve, reject) => {
    arr.forEach((item, i) => {
      Promise.resolve(item)
        .then((res) => {
          index++;
          result[i] = res;
          if (index === arr.length) resolve(res);
        })
        .catch((error) => reject(error));
    });
  });
};
实现链表
//节点类
class Node{
    constructor(data){
        this.data = data
        this.next = null
    }
}
//链表类
class SinglyLinkedList{
    constructor(){
        this.head = new Node() //head指向头节点
    }
    //在链表组后添加节点
    add(data){
        let node = new Node(data)
        let current = this.head
        while(current.next){
            current = current.next
        }
        current.next = node
    }
    //添加到指定位置
    addAt(index, data){
        let node = new Node(data)
        let current = this.head
        let counter = 1
        while(current){
            if(counter === index){
                node.next = current.next
                current.next = node
            }
            current = current.next
            counter++
        }
    }
    //删除某个位置的节点
    removeAt(index){
        let current = this.head
        let counter = 1
        while(current.next){
            if(counter === index){
                current.next = current.next.next
            }
            current = current.next
            counter++
        }
    }
    //反转链表
    reverse(){
        let current = this.head.next
        let prev = this.head
        while(current){
            let next = current.next
            //反转:改变当前节点指针。若当前节点是第一个(即头节点后面的)节点,
            //则此节点的next为null,否则next指向他的上一个节点
            current.next = prev===this.head ? null : prev
            prev = current
            current = next
        }
        this.head.next = prev
    }
}


前序
var Traversal = function(root) {
    const stack = [];
    while (root || stack.length){
      while(root){
        stack.push(root);
        root = root.left;
      }
      root = stack.pop();
      root = root.right;
    }
    return res;
};

中序
// 中序遍历(非递归,迭代)
const inorder2 = (root) => {
  if (!root) return;
  const res = []
  const track = []
  // let p = root
  while (track.length ||root) {
      // 把左子树全入栈
      while (root) {
          track.push(root)
          root = root.left
      }
      root = track.pop()
      res.push(root.val)
      // 遍历根节点左边的节点的右侧
      root = root.right
      console.log('root',root)
  }
  return res
};
后序遍历
var postorderTraversal = function(root) {
  // 初始化数据
    const res =[];
    const stack = [];
    while (root || stack.length){
      while(root){
        stack.push(root);
        res.unshift(root.val);
        root = root.right;
      }
      root = stack.pop();
      root = root.left;
    }
    return res;
};


反转二叉树
// 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */

  var invertTree = function(root) {
    let now = [root]
    // 广度优先遍历,利用层次遍历
    // 设置当前存储节点的数组,初始化是根节点
    
    while (now.length) {
        const next = []
        now.forEach(item => {
            // 节点为null直接返回
            if (item === null) return
            // 定义第三变量交换左右子节点
            let n = item.left
            item.left = item.right
            item.right = n
            // 将左右子节点放进数组
            next.push(item.left)
            next.push(item.right)
        })
        now = next
    }
    return root
};
二分查找
// 704. 二分查找
/**
 * 
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
提示:二分查找算法
1. 定义头尾两个指针 left = 0 ,right = length 中间索引 mid = left + (right - left) / 2
2. 当left <= right 如果mid上的值等于target, 返回mid, 如果小于targt, left = mid + 1(砍掉左半边)
如果大于target , right = mid - 1(砍掉右半边)
3. 如果while 循环结束后都没有找到target , 返回-1
 */
const search = (nums, target) => {
  let i = 0
  let j = nums.length - 1
  let midIndex = 0

  while (i <= j) {
    midIndex = Math.floor((i + j) / 2)
    const midValue = nums[ midIndex ]
    if (midValue === target) {
      return midIndex
    } else if (midValue < target) {
      i = midIndex + 1
    } else {
      j = midIndex - 1
    }
  }

  return -1
}
console.log(search([-1,0,3,5,9,12], 9)) // 4
最大子序和
var maxSubArray = function(nums) {
  let pre = 0, maxAns = nums[0];
  nums.forEach((x) => {
      pre = Math.max(pre + x, x);
      maxAns = Math.max(maxAns, pre);
  });
  return maxAns;
};
爬楼梯
function climbStairs(n) {
  // 因为状态转移只和上一次迭代和上上次迭代的结果有关,所以只用两个变量存储,不需要用数组,减少了空间
  if (n === 0) return 0;
  let pre = 0;
  let cur = 0;
  let sum = 1;
  for (let i = 1; i <= n; i++) {
    pre = cur;
    cur = sum;
    sum = pre + cur;
  }
  return sum;
}
console.log(climbStairs(4));

快排
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function (nums) {
    //   const length = nums.length
    //   if (length <= 1) {
    //     return nums
    //   }
    //   const midIndex = Math.floor(length / 2)
    //   const midValue = nums.splice(midIndex, 1)[ 0 ]
    //   let leftArray = []
    //   let rightArray = []
    //   let index = 0
    //   while (index < length - 1) {
    //     const curValue = nums[ index ]
    //     if (curValue <= midValue) {
    //       leftArray.push(curValue)
    //     } else {
    //       rightArray.push(curValue)
    //     }
    //     index++
    //   }
    //   return sortArray(leftArray).concat([ midValue ], sortArray(rightArray))

    let left = 0,
        right = nums.length - 1;
    //console.time('QuickSort');
    main(nums, left, right);
    //console.timeEnd('QuickSort');
    return nums;
    function main(nums, left, right) {
        // 递归结束的条件,直到数组只包含一个元素。
        if (nums.length === 1) {
            // 由于是直接修改arr,所以不用返回值。
            return;
        }
        // 获取left指针,准备下一轮分解。
        let index = partition(nums, left, right);
        if (left < index - 1) {
            // 继续分解左边数组。
            main(nums, left, index - 1);
        }
        if (index < right) {
            // 分解右边数组。
            main(nums, index, right);
        }
    }
    // 数组分解函数。
    function partition(nums, left, right) {
        // 选取中间项为参考点。
        let pivot = nums[Math.floor((left + right) / 2)];
        // 循环直到left > right。
        while (left <= right) {
            // 持续右移左指针直到其值不小于pivot。
            while (nums[left] < pivot) {
                left++;
            }
            // 持续左移右指针直到其值不大于pivot。
            while (nums[right] > pivot) {
                right--;
            }
            // 此时左指针的值不小于pivot,右指针的值不大于pivot。
            // 如果left仍然不大于right。
            if (left <= right) {
                // 交换两者的值,使得不大于pivot的值在其左侧,不小于pivot的值在其右侧。
                [nums[left], nums[right]] = [nums[right], nums[left]];
                // 左指针右移,右指针左移准备开始下一轮,防止arr[left]和arr[right]都等于pivot然后导致死循环。
                left++;
                right--;
            }
        }
        // 返回左指针作为下一轮分解的依据。
        return left;
    }

};

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1297036.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存