js常见的手写算法实现--面试必备

深浅拷贝

浅拷贝-object.assign

1.Object.assign
思路分析:
object.assign实际上实现了浅拷贝,参数target,把若干个source的值复制给target
遍历参数,获取source的值,对于每一个source然后for … in 遍历传入的参数的属性,复制给新的属性

if (typeof Object.assign2 != 'function') {
  // Attention 1
  Object.defineProperty(Object, "assign2", {
    value: function (target) {
      'use strict';
      if (target == null) { // Attention 2
        throw new TypeError('Cannot convert undefined or null to object');
      }

      // Attention 3
      var to = Object(target);
        
      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) {  // Attention 2
          // Attention 4
          for (var nextKey in nextSource) {
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

深拷贝

思路分析:对于深拷贝实际上对于需要复制的对象,进行递归浅拷贝,如果属性是对象,则再次深拷贝,然后对于传入的边界值进行处理,更完善的需要处理递归保栈问题和循环引用


function cloneDeep1(source) {
    var target = {};
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (typeof source[key] === 'object') {
                target[key] = cloneDeep1(source[key]); // 注意这里
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
解决数组复制问题:
function cloneDeep2(source) {

    if (!isObject(source)) return source; // 非对象返回自身
      
    var target = Array.isArray(source) ? [] : {};
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep2(source[key]); // 注意这里
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
解决循环引用问题:
function cloneDeep3(source, hash = new WeakMap()) {

    if (!isObject(source)) return source; 
    if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表
      
    var target = Array.isArray(source) ? [] : {};
    hash.set(source, target); // 新增代码,哈希表设值
    
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep3(source[key], hash); // 新增代码,传入哈希表
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
解决无法复制symbol值的问题:
function cloneDeep4(source, hash = new WeakMap()) {

    if (!isObject(source)) return source; 
    if (hash.has(source)) return hash.get(source); 
      
    let target = Array.isArray(source) ? [...source] : { ...source }; // 改动 1
    hash.set(source, target);
    
  	Reflect.ownKeys(target).forEach(key => { // 改动 2
        if (isObject(source[key])) {
            target[key] = cloneDeep4(source[key], hash); 
        } else {
            target[key] = source[key];
        }  
  	});
    return target;
}
解决递归爆栈问题:
function cloneDeep5(x) {
    const root = {};

    // 栈
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];

    while(loopList.length) {
        // 广度优先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }

        for(let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循环
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

call apply bind new实现

call实现

4.手写call
思路:call实际上是改变了this的指向,关键是获取this和参数的值

Function.prototype.call2 = function (context) {
    context = context ? Object(context) : window; // 实现细节 1 和 2
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result; // 实现细节 2
}

apply实现

4.手写apply
思路:基本思路和call一样,但是区别在于处理参数,apply的参数是个数组


Function.prototype.apply = function (context, arr) {
    context = context ? Object(context) : window; 
    context.fn = this;
  
    let result;
    if (!arr) {
        result = context.fn();
    } else {
        result = context.fn(...arr);
    }
      
    delete context.fn
    return result;
}

bind实现

5.手写bind
思路:bind与call和apply的区别在于,bind返回了一个绑定上下文的函数,而apply和call是直接执行
Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

new实现

3.手写new

简易实现:
function create() {

        let obj=new Object()
        Con=[].shift.call(arguments)
        obj.__proto__=Con.prototype
        Con.apply(obj,arguments)
        return obj
      }
第二版:
// 第二版
function create() {
	// 1、获得构造函数,同时删除 arguments 中第一个参数
    Con = [].shift.call(arguments);
	// 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
    var obj = Object.create(Con.prototype);
	// 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
    var ret = Con.apply(obj, arguments);
	// 4、优先返回构造函数返回的对象
	return ret instanceof Object ? ret : obj;
};

节流防抖实现

节流

节流:一定时间间隔内函数只执行一次
基础思路:通过时间戳来判断函数是否需要执行,记录上次执行的时间戳,每次触发事件的时候,判断当前时间和上次执行的差值是否达到等待时间

// fn 是需要执行的函数
// wait 是时间间隔
const throttle = (fn, wait = 50) => {
  // 上一次执行 fn 的时间
  let previous = 0
  // 将 throttle 处理结果当作函数返回
  return function(...args) {
    // 获取当前时间,转换成时间戳,单位毫秒
    let now = +new Date()
    // 将当前时间和上一次执行函数的时间进行对比
    // 大于等待时间就把 previous 设置为当前时间并执行函数 fn
    if (now - previous > wait) {
      previous = now
      fn.apply(this, args)
    }
  }
}

// DEMO
// 执行 throttle 函数返回新函数
const betterFn = throttle(() => console.log('fn 函数执行了'), 1000)
// 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn
setInterval(betterFn, 10)

防抖

防抖:一定时间间隔内,只执行最后一次
基础思路:利用定时器,设定一定时间的定时器,

// 实现 2
// immediate 表示第一次是否立即执行
function debounce(fn, wait = 50, immediate) {
    let timer = null
    return function(...args) {
        if (timer) clearTimeout(timer)
      
      	// ------ 新增部分 start ------ 
      	// immediate 为 true 表示第一次触发后执行
      	// timer 为空表示首次触发
        if (immediate && !timer) {
            fn.apply(this, args)
        }
      	// ------ 新增部分 end ------ 
      	
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, wait)
    }
}

// DEMO
// 执行 debounce 函数返回新函数
const betterFn = debounce(() => console.log('fn 防抖执行了'), 1000, true)
// 第一次触发 scroll 执行一次 fn,后续只有在停止滑动 1 秒后才执行函数 fn
document.addEventListener('scroll', betterFn)

promise

核心思路:基础的promise,包含对于三种状态 pending fulfilled rejected 的转换,以及then函数的编写,成功调用成功回调函数,失败调用失败回调函数
第二步解决异步问题,在then函数中保存失败和成功的回调函数,一旦reject或者fulfill,立刻执行
第三步解决链式调用问题

class Promise {
        constructor (executor) {
          this.state = 'pending'
          this.value = undefined
          this.reason = undefined
          this.onFulfilledCallbacks = []
          this.onRejectedCallbacks = []

          let resolve = value => {
            if (this.state == 'pedding') {
              this.state = 'fulfilled'
              this.value = value
              this.onFulfilledCallbacks.forEach(fn => fn())
            }
          }
          let reject = reason => {
            if (this.state == 'pending') {
              this.state = 'rejected'
              this.value = reason
              this.onRejectedCallbacks.forEach(fn => fn())
            }
          }
          try {
            executor(resolve, reject)
          } catch (e) {
            reject(e)
          }
        }
        then (onFulfilled, onRejected) {
          onFulfilled=typeof onFulfilled==="function"?onFulfilled:value=>value
          onRejected=typeof onRejected==='function'?onRejected:error=>{throw error}
          let promise2=new Promise((resolve,reject)=>{
            if (this.state == 'fulfilled') {
              setTimeout(()=>{
                try{
                  let x = onFulfilled(this.value)
                  reslovePromise(promise2,x,resolve,reject)
                }catch (e) {
                  reject(e)
                }

              },0)

            }
            if (this.state == 'rejected') {
              setTimeout(()=>{
                try{
                  onRejected(this.reason)
                  reslovePromise(promise2,x,resolve,reject)

                }catch (e) {
                  reject(e)
                }

              })


            }
            if (this.state == 'pending') {
              setTimeout(()=>{
                try{
                  let x=this.onFulfilledCallbacks.push(() => { onFulfilled(this.value) })
                  reslovePromise(promise2,x,resolve,reject)

                  let x=this.onRejectedCallbacks.push(() => { onRejected(this.reason) })
                  reslovePromise(promise2,x,resolve,reject)
                  
                }catch (e) {
                  reject(e)
                }
                
              },0)
             

            }
          })
          return promise2

        }
      }
      function reslovePromise (promise2, x, reslove, reject) {
        if (x === Promise) {
          return reject(new TypeError('chaning cycle for promise'))
        }
        let called
        if (x != null && (typeof x === 'function')) {
        try{
          let then =x.then
          if(typeof then==="function") {
            then.call(x,y=>{
              if called return;
              called=true
              reslovePromise(promise2,y,reslove,reject)

            },err=>{
              if called return
              called=true
              reject(err)
            })

          }
          else{
            reslove(x)

          }

        }
        catch (e) {
          if (called) return
          called=true
          reject(e)

        }
        }
        else{
          reslove(x)
        }
      }

手写es6使用proxy实现arr[-1]访问

function createArray(...elements) {
  let handler = {
    get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
    }
  };

  let target = [];
  target.push(...elements);
  return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
arr[-1] // c
已标记关键词 清除标记
相关推荐
面试题包含了不同技术层面的面试问题,同时也能对一些没有面试开发经验的小白给予不可估量的包装, 让你的薪水绝对翻倍, 本人亲试有效.Java面试题84集、java面试专属及面试必问课程,所有的面试题有视屏讲解, 解答方案.以下是部分目录: java面试题01.面试的整体流程.mp4 │ Java面试题02.java的垮平台原理.mp4 │ Java面试题03.搭建一个java的开发环境.mp4 │ Java面试题04.java中int占几个字节.mp4 │ Java面试题05.java面向对象的特征.mp4 │ Java面试题06.装箱和拆箱.mp4 │ Java面试题07.==和equals的区别.mp4 │ Java面试题08.String.mp4 │ Java面试题09.讲一下java中的集合.mp4 │ Java面试题10.ArrayList LinkedList.mp4 │ Java面试题11.HashMap和HashTable的区别.mp4 │ Java面试题12.实现一个拷贝文件的类使用字节流还是字符串.mp4 │ Java面试题13.线程的实现方式 怎么启动线程怎么区分线程.mp4 │ Java面试题14.线程并发库和线程池的作用?.mp4 │ Java面试题15.设计模式和常用的设计模式.mp4 │ Java面试题16.http get post请求的区别.mp4 │ Java面试题17.说说你对Servlet的理解.mp4 │ Java面试题18.Servlet的生命周期.mp4 │ Java面试题19.forward和redirect的区别.mp4 │ Java面试题20.jsp和Servlet的相同点和不同点?.mp4 │ Java面试题21.内置对象和四大作用域和页面传值.mp4 │ Java面试题22.Session和Cookie的区别.mp4 │ Java面试题23.mvc模式和mvc各部分的实现.mp4 │ Java面试题24.数据库分类和常用数据库.mp4 │ Java面试题25.关系型数据库的三范式.mp4 │ Java面试题26.事务的四大特征.mp4 │ Java面试题27.mysql数据库最大连接数.mp4 │ Java面试题28.mysql和oracle的分页语句(着重说思路).mp4 │ Java面试题29.触发器的使用场景.mp4 │ Java面试题30.存储过程的优点.mp4 │ Java面试题31.jdbc调用存储过程.mp4 │ Java面试题32.简单说一下你对jdbc的理解.mp4 │ Java面试题33.写一个jdbc的访问oracle的列子.mp4 │ Java面试题34.jdbc中preparedStatement比Statement的好处.mp4 │ Java面试题35.数据库连接池的作用.mp4 │ Java面试题36.HTML.mp4 │ Java面试题37.简单介绍了一下Ajax.mp4 │ Java面试题38.js和JQuery的关系.mp4 │ Java面试题39.jQuery中的常用选择器.mp4 │ Java面试题40.jQuery中页面加载完毕事件.mp4 │ Java面试题41.jQuery中Ajax和原生js实现Ajax的关系.mp4 │ Java面试题42.简单说一下html5.mp4 │ Java面试题43.简单说一下css3.mp4 │ Java面试题44.bootstrap的是什么.mp4 │ Java面试题45.什么是框架.mp4 │ Java面试题46.简单介绍一下MVC模式.mp4 │ Java面试题47.简单说一下对mvc框架的理解.mp4 │ Java面试题48.struts2的执行流程或者struts2的原理.mp4 │ Java面试题49.Struts2的拦截器是什么?你都用它干什么?.mp4 │ Java面试题50.Spring MVC的执行流程.mp4 │ Java面试题51.SpringMVC和Struts2的不同.mp4 │ Java面试题52.简单介绍一下Spring或者Spring的两大核心.mp4 │ Java面试题53.AOP是什么?都用它做什么?.mp4 │ Java面试题54.Spring事务的传播特性和隔离级别.mp4 │ Java面试题55.ORM是什么?ORM框架是什么?.mp4 │ Java面试题56.ibatis和hibernate有什么不同.mp4 │ Java面试题57.hibernate对象状态及其转换.mp4 │ Java面试题58:hibernate的缓存.mp4 │
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页