手写常见面试题

手写常见面试题

手写常见面试题

防抖

防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

有两种情况:

  • 点击之后立即执行
  • 点击之后非立即执行
// 非立即执行
const debounce1 = (fn, delay) => {
  let timer = null;
  return (...args) => {
    if(timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay)
  }
}

// 立即执行
const debounce2 = (fn, delay) => {
  let timer = null;
  let emitNow = true;
  return (...args) => {
    if(timer) clearTimeout(timer);
    if(emitNow) {
      fn.apply(this, args);
      emitNow = false;
    } else {
      timer = setTimeout(() => {
        fn.apply(this, args);
        emitNow = true;
      }, delay)
    }
  }
}

// 通过参数控制是否立即执行
const debounce3 = (fn, delay, isImmediate) => {
  let timer = null;
  let emitNow = true;
  return (...args) => {
    if(timer) clearTimeout(timer);
    
    if(isImmediate) {
      if(emitNow) {
        fn.apply(this, args);
        emitNow = false;
      } else {
        timer = setTimeout(() => {
          fn.apply(this, args);
          emitNow = true;
        }, delay)
      }
    } else {
      timer = setTimeout(() => {
        fn.apply(this, args);
      }, delay)
    }
  }
}

节流

防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

有两种情况:

  • 点击之后立即执行
  • 点击之后非立即执行
// 非立即执行
const throttle1 = (fn, delay) => {
  let isEmit = false;
  return (...args) => {
    if(isEmit) return;
    isEmit = true;
    
    setTimeout(() => {
      fn.apply(this, args);
      isEmit = false;
    }, delay);
  }
}

// 立即执行
const throttle2 = (fn, delay) => {
  let isEmit = false;
  return (...args) => {
    if(isEmit) return;
    isEmit = true;

    fn.apply(this,args);
    setTimeout(() => {
      isEmit = false;
    },delay);
  }
}

// 通过参数控制是否立即执行
const throttle3 = (fn, delay, isImmediate) => {
  let isEmit = false;
  return (...args) => {
    if(isEmit) return;
    isEmit = true;
    
    if(isImmediate) {
      fn.apply(this, args);
      setTimeout(() => {
        isEmit = false;
      },delay);
    } else {
      setTimeout(() => {
        fn.apply(this, args);
        isEmit = false;
      }, delay);
    }
  }
}

深克隆

function deepCopy(obj) {
  if(typeof obj !== 'object') {
    return obj;
  };

  let cloneObj = obj.constructor=== Array ? [] : {};
  for(let property in obj) {
    cloneObj[property] = typeof obj[property] === 'object' ? deepCopy(obj[property]) : obj[property];
  }
  return cloneObj;
}

instanceOf

根据原型链的知识,我们能很快能知道根据对象的__proto__属性就能找到其构造函数。

const instanceOf = function(object, target) {
  // 取目标的原型对象
  const instance = target.prototype;
  // 取待检验的对象的隐式原型
  object = object.__proto__;
  while(true) {
    if(!object) return false;

    if(object === instance) return true;

    object = object.__proto__;
  }
}

new 操作符

new的作用:

  • 创建一个新对象
  • this执行创建的新对象
  • 创建的新对象会被链接到该函数的prototype对象上(新对象的__proto__属性指向函数的prototype);
  • 利用函数的call方法,将原本指向window的绑定对象this指向了obj。(这样一来,当我们向函数中再传递实参时,对象的属性就会被挂载到obj上。)
function createObject() {
  // 创建一个新对象
  const obj = {};
  // 获取构造函数,采用call方法使得arguments能够使用shift方法将第一个参数(构造函数)拿出来
  const Constructor = [].shift.call(arguments);
  // 将对象__proto__属性链接到构造函数的prototype属性中
  obj.__proto__ = Constructor.prototype;
  // 将构造函数中的this指向对象并传递参数
  const result = Constructor.apply(obj, arguments);
  // 确保返回值是一个对象
  return typeof ret === "object" ? result : obj;
}

实现call方法

我们都很清楚call这个方法就是用于修改this指向,但是有些同学可能不太懂其原理,我们来手写一个call方法帮助深入了解其原理。

Function.prototype.mycall = function(context) {
  // 默认上下文为window
  context = context || window;
  // 添加一个属性用于保存当前调用call的函数
  context.fn = this;
  // 将arguments转变成数组并移除第一个参数(上下文)
  const args = [...arguments].slice(1);
  // 这样调用函数时该函数内部的this就指向调用者(context);
  const result = context.fn(...args);
  delete context.fn;
  return result;
}

实现apply方法

apply原理与call很相似,唯一不同就是传参问题,apply方法的第二个参数是所有参数组合成的数组,而call方法除了第一个参数是context外,其他都是传入的参数。

Function.prototype.myapply = function(context, arr) {
  // 默认上下文为window
  context = context || window;
  // 添加一个属性用于保存当前调用call的函数
  context.fn = this;
  // 将arguments转变成数组并移除第一个参数(上下文)
  let result;
  if(!arr) {
    result = context.fn();
  } else {
    result = context.fn(arr);
  }
  delete context.fn;
  return result;
}

实现bind方法

相对于callapply而言,bind方法的返回值是一个改变了this的函数(即非立即调用)。当返回的函数被当作构造函数使用时,this失效,但是传入的参数依旧有效。

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

Function.prototype.mybind = function(context) {
  if(typeof this !== 'function') {
    throw new Error('Uncaught TypeError: not a function')
  }

  const args = [...arguments].slice(1);
  // 用于记录当前传入的函数的prototype;
  let Transit = function() {};
  const _ = this;
  const FunctionToBind = function() {
    const bindArgs = [...arguments];
    return _.apply(this instanceof Transit ? this : context, args.concat(bindArgs));
  }
  // 记录当前传入的函数的prototype;
  Transit.prototype = this.prototype;
  FunctionToBind.prototype = new Transit();
  return FunctionToBind;
}

实现Object.create方法

Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

语法:Object.create(proto[, propertiesObject])
proto : 必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是null, 对象, 函数的prototype属性 (创建空的对象时需传null , 否则会抛出TypeError异常)
propertiesObject : 可选。 添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数,创建非空对象的属性描述符默认是为false的,而构造函数或字面量方法创建的对象属性的描述符默认为true

new关键词是通过构造函数来创建对象, 添加的属性是在自身实例下。
Object.create()创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。

// new Object() 方式创建
var a = {  rep : 'apple' }
var b = new Object(a)
console.log(b) // {rep: "apple"}
console.log(b.__proto__) // {}
console.log(b.rep) // {rep: "apple"}

// Object.create() 方式创建
var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b)  // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // {rep: "apple"}

上面讲了这么多Object.create的知识,下面我们实现一下该方法:

Object.prototype.mycreate = function(proto, propertiesObject) {
  function F() {};
  F.prototype = proto;
  const obj = new F();
  if(propertiesObject) {
    Object.defineProperties(obj, propertiesObject);
  }
  return obj
}

实现原理就是通过创建一个空构造函数并把其prototype指向传入的对象,最后返回该构造函数的实例。

实现promise

const statusMap = {
  PENDING: "pending",
  FULFILLED: "fulfilled",
  REJECTED: "rejected"
}

class MyPromise{
  constructor(handler) {
    if(Object.prototype.toString.call(handler) !== '[object Function]') {
      throw new Error('the first parameter should be a function');
    }
    this.status = statusMap.PENDING;
    this.result = null;
    // 用于执行then方法
    this.fulfilledQueues = [];
    this.rejectedQueues = [];

    try{
      // 执行两个方法
      handler(this._resolve.bind(this), this._reject.bind(this));
    } catch(err) {
      this._reject(err);
    }
  }

  _resolve(val) {
    if(this.status !== statusMap.PENDING) return;

    const run = () => {
      this.status = statusMap.FULFILLED;
      this.result = val;
      let cb;
      while(cb = this.fulfilledQueues.shift()) {
        cb(val);
      }
    }
    setTimeout(() => run(), 0);
  }

  _reject(err) {
    if(this.status !== statusMap.PENDING) return;

    const run = () => {
      this.status = statusMap.REJECTED;
      this.result = err;
      let cb;
      while(cb = this.rejectedQueues.shift()) {
        cb(err);
      }
    }

    setTimeout(() => run(), 0);
  }
}

MyPromise.prototype.then = function(onFulfilled, onRejeceted) {
  const { status, result } = this;

  return new MyPromise((onFulfilledNext, onRejecetedNext) => {
    let fulfilled = value => {
      try {
        if(Object.prototype.toString.call(onFulfilled) !== '[object Function]') {
          onFulfilledNext(value);
        } else {
          let res = onFulfilled(value);

          // 返回结果还是MyPromise的实例
          if(res instanceof MyPromise) {
            res.then(onFulfilledNext, onRejecetedNext);
          } else {
            onFulfilledNext(res);
          }
        }
      } catch(e) {
        onRejecetedNext(e);
      }
    }

    let rejected = error => {
      try {
        if(Object.prototype.toString.call(onRejeceted) !== '[Object Function]') {
          onRejecetedNext(error);
        } else {
          let res = onRejeceted(error);

          // 返回结果还是MyPromise的实例
          if(res instanceof MyPromise) {
            res.then(onFulfilledNext, onRejecetedNext);
          } else {
            onFulfilledNext(res);
          }
        }
      } catch(e) {
        onRejecetedNext(e);
      }
    }

    switch(status) {
      case statusMap.PENDING:
        this.fulfilledQueues.push(fulfilled);
        this.rejectedQueues.push(rejected)
        break;

      case statusMap.FULFILLED:
        this.fulfilledQueues.push(fulfilled);
        break;
      
      case statusMap.REJECTED:
        this.rejectedQueues.push(rejected);
        break;
    }
  })
}

数组扁平化

Array的方法flat很多浏览器还未能实现,而且浏览器支持的flat方法不能处理嵌套的数组。写一个flat方法,实现扁平化嵌套数组。

// 最简单的方案
Array.prototype.flat = function (arr) {
  return arr
    .toString()
    .split(',')
    .map((item) => +item);
};

Array.prototype.flat = function (arr) {
  return arr.reduce((prev, item) => {
    return prev.concat(Array.isArray(item) ? flatten(item) : item);
  }, []);
};

数组去重

对于去除1次以上的重复item,可以使用Set

function delRepeat(arr) {
  return Array.from(new Set(arr));
}

但是去除2次以上就不能用set了。

// 已知数组
var arr = [1,1,1,1,1,1,1,3,3,3,3,3,5,5];

// 方法一
function delRepeat(arr) {
  arr = arr.sort();
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] == arr[i + 2]) {
      arr.splice(i, 1);
      i--;
    }
  }
  return arr;
}

// 方法二
function delRepeat(arr) {
  var newArr = [];
  var obj = {};
  arr.map((item) => {
    if (obj[item]) {
      obj[item] += 1;
    } else {
      obj[item] = 1;
    }
    obj[item] <= 2 ? newArr.push(item) : '';
  });
  return newArr;
}

代码地址:https://github.com/leopord-lau/EasyPresentation

你可能感兴趣的:(javascript,javascript)