【知识图谱】【编码题】-- 原理类

1. 深拷贝

    1.1 WeakMap(ES6新增):提供了一种主动解决内存回收的方式,TODO,先不展开

        属性:WeakMap.prototype.constructor

        方法:set、delete、has、get

    1.2 浅拷贝方法:Object.assign()、{...obj}、Array.prototype.slice()、Array.prototype.concat()

    1.3 循环引用问题:TODO

    1.4 递归爆栈问题:TODO

    1.5 正解:(4步走,1.定义判断数组和对象,2.简单类型直接返,3.数组对象用扩展,4.利用递归搞循环(嵌套))

点击查看代码
function deepClone(obj,) {
  // 1. 定义判断对象&数组的函数
  function isObject(obj) {
    return Object.prototype.toString.call(obj) === '[object Object]';
  }
     
  function isArray(obj) {
    return Array.isArray(obj);
  }

  let result;
  // 2. 拷贝简单类型,直接返回
  if (!isObject(obj) && !isArray(obj)) {
    return obj;
  }

  // 3. 对象&数组情况,(利用扩展运算符是浅拷贝,只拷贝第一层)
  if (isObject(obj)) {
    result = { ...obj };
  }
  
  if (isArray(obj)) {
    result = [...obj];
  }

  // 4. 递归处理[重点][非常好,就是用for...in进行循环遍历]
  for (var key in obj) {
    if ( isObject(obj[key]) || isArray(obj[key]) ) {
      result[key] = deepClone(obj[key]);
    } else {
      result[key] = obj[key];
    }
  }

  return result;
}

参考链接:面试题之如何实现一个深拷贝  [经典!]

2. 防抖:// 防抖函数:防止多次调用,在时间间隔内只调用一次。(口诀:每次执行前都要清空定时器,return一个闭包)
               // 节流函数:防止多次调用,每隔一段时间进行一次调用。(口诀:每次执行前都要检查定时器,非空则返回,return一个闭包)

点击查看代码
// 这是防抖函数
function debounce(callback,timeout,immediate){
   // 1. 声明定时器ID
   let id;
   return function(...arguments){
     // 2. 清空定时器(所以防抖要清空)    
     clearInterval(id);
     if(immediate){
      // 3. apply立即执行
      callback.apply(this,arguments);
      immediate = false;
     }else {
      // 4. 间隔执行
      id = setTimeout(()=>{
           		callback.apply(this,arguments);
       	   },timeout);
     }
   }
}

3. 节流:场景:被频繁调用的,window.onresize()、mousemove事件、上传进度等

点击查看代码
// 1.自我实现1 利用时间戳
function throttle(callback,timeout){
  let lastExceTime = 0;
  return function(...args){
    let now = +new Date();
    if(now - lastExceTime > timeout){
      callback.apply(this,args);
      lastExceTime = now;
    }
  }
}

// 2.自我实现2 检查此时定时器是否为空[推荐]
// immediate是否立即执行
function throttle(callback,timeout,immediate = true){
  let throttleId;
  return function(...args){
    if(throttleId) return;
    if(immediate){
      callback.apply(this,args);
      immediate = false;
      return;
    }
    throttleId = setTimeout(()=>{
      callback.apply(this,args);
      throttleId = null;
    },timeout);
  }
}

4. 手写splice

    4.1 this是原数组,通过slice进行截取

    4.2 通过this[i] = newArr[i]的形式进行改写

    4.3 this.length = newArr.length,完成改造

点击查看代码
Array.prototype.mysplice = function(start, nums, ...args) {    
    let deleteCounts = this.slice(start,start+nums);// 被删除元素
    let newArr = [...this.slice(0, start), ...args, ...this.slice(start+nums)];//删除元素后原数组
    
    for(let i = 0; i< newArr.length; i++) {
      // 改变原数组的核心步骤
      this[i] = newArr[i];
    }
    // 改变原数组的核心步骤
    this.length = newArr.length;
    return deleteCounts;
}

let arr = [1,2,3,4,5,6];
console.log(arr.mysplice(-2,1),arr);

5. 手写call、apply、bind函数

    5.1 arguments是类数组,里面是所有的入参,[...arguments]可以转换成真正的数组;
    5.2 this是待调用函数,
    5.3 核心逻辑:改变this对象(context),把调用函数新增到新对象上(context),执行完再删除。

点击查看代码call
Function.prototype.myCall = function (context) {
  var context = context || window;
  // 1. [添函数]给 context 添加函数
  context.fn = this;
  // 2. [取参数]将 context 后面的参数取出来
  var args = [...arguments].slice(1);
  var result = context.fn(...args);
  // 3. [删函数]删除函数
  delete context.fn;
  return result;
}

function a(){
    console.log(this.name);
}
let b = {
    name: 'zhangsan',
}
a.myCall(b,'1','2');
点击查看代码apply
Function.prototype.myApply = function (context) {
  context = context || window;
  context.fn = this;

  let result
  // 需要判断是否存储第二个参数
  // 如果存在,就将第二个参数展开
  if(arguments[1]){
    result = context.fn(...arguments[1]);
  }else {
    result = context.fn();
  }

  delete context.fn
  return result
}

bind是个难点,要晕了!TODO

参考链接1  参考链接2

点击查看代码bind
Function.prototype.mybind = function(ctx, ...rest) {
  // 根本不需要进行判断,如果是其他类型,会因为找不到mybind函数而报错的
  //if (typeof this !== 'function') {
  //  throw new Error('只能由 function 调用');
  //}
  // 步骤1:把this执行赋值新变量
  const func = this;
  // 步骤2:返回一个新函数,函数里面用apply
  let result = function(...params) {
    return func.apply(
      // 问题1. 为什么要判断是不是构造函数的实例?[不然的话,new操作会改变ctx对象的属性的,其实是想改变实例的属性值]
      // 如果是 new 对象出的,this 绑定的应该绑定为构造函数
      this instanceof result ? this : ctx,
      rest.concat(params)
    );
  };
  // 问题2. 为什么要设置一个空函数?
     [不改的话,是个对象]
  // 问题3. 为什么要设置原型链接?[不然new出来的实例的构造函数就是result了,其实应该是调用函数,要回本溯源,new会返回一个新对象]
  // 步骤3:巧设空函数当桥梁,返回函数的原型是空函数的原型
  let fNOP = function() {};
  fNOP.prototype = func.prototype;
  result.prototype = new fNOP();
  console.log(result,result.prototype);
  return result;
};

6. Promise的实现:TODO

    参考:JavaScript异步与Promise实现

    6.1 概念:什么是Promise,一种可以链式调用、解决之前回调地域的异步编程方式

    6.2 回调地域:指一层嵌套一层,代码冗余、难以维护、可读性差的异步编程方式

    6.3 Promise用法:

    // then()返回的也是个Promise对象

    let p1 = new Promise((resolve,reject)=>{
        resolve();// 是个函数

        reject();// 是个函数
    });

    p1.then((resolve,reject)=>{

        resolve();// 是个函数

        reject();// 是个函数
        }

    )

    .then()

    .then()

7. 手写instanceof:判断实例对象的原型链(__proto__)上是否有构造函数的原型prototype (口诀:要是new出来的实例对象)

MDN

点击查看代码
function myInstanceof(left,right){
	left = left.__proto__;
    let prototype = right.prototype;
    while(true){
    	if(left === null){
        	return false;
        }
        if(left === prototype){
        	return true;
        }
      
      	left = left.__proto__;
    }
}

 

posted @ 2021-06-30 08:58  惊沙男孩  阅读(75)  评论(0)    收藏  举报