乐趣区

关于前端:前端面试高频面试题JavaScript手写代码

前言

大家好,我是梁木由,是一个曾经躺平了两年的废柴,目前在当初这家公司曾经工作了近两年半了。没加薪,没升职,没走的起因是因为工作气氛很难受,制度很宽松。这不 2023 来了,我及时觉悟,要有指标,目前就先为年后跳槽,做些筹备,先温习回顾下一些手写代码,

目录

  • 实现 new 函数
  • 实现 instanceof
  • 实现 call 函数
  • 实现 apply 函数
  • 实现 bind 函数
  • 实现 debounce 防抖函数
  • 实现 throttle 节流函数
  • 实现数组对象去重
  • 实现数组扁平化
  • 实现深拷贝

1. 实现 new 函数

首先须要理解 new 做了什么事件:

  • 首先创立了一个空对象。
  • 将空对象 proto 指向构造函数的原型prototype
  • 使 this 指向新创建的对象,并执行构造函数。
  • 执行后果有返回值并且是一个对象,返回执行的后果,否则返回新创建的对象。
// 代码实现
function mu_new(fn,...arg){
    // 首先创立空对象
    const obj = {};
    // 将空对象的原型 proto 指向构造函数的原型 prototype
    Object.setPrototypeOf(obj, fn.prototype)
    // 将 this 指向新创建的对象,并且执行构造函数
    const result = fn.apply(obj,arg);
    // 执行后果有返回值并且是一个对象,返回执行的后果,否侧返回新创建的对象
    return result instanceof Object ? result : obj;
}

// 验证 mu_new 函数
function Dog(name){
    this.name = name;
    this.say = function(){console.log('my name is' + this.name);
    }
}

const dog = mu_new(Dog, "傻🐶");
dog.say() //my name is 傻🐶

2. 实现 instanceof

优缺点:

  • 「长处」:可能辨别 Array、Object 和 Function,适宜用于判断自定义的类实例对象
  • 「毛病」:Number,Boolean,String 根本数据类型不能判断

实现步骤:

  • 传入参数为左侧的实例 L,和右侧的构造函数 R
  • 解决边界,如果要检测对象为根本类型则返回 false
  • 别离取传入参数的原型
  • 判断左侧的原型是否取到了 null,如果是 null 返回 false;如果两侧原型相等,返回 true,否则持续取左侧原型的原型。
// 传入参数左侧为实例 L, 右侧为构造函数 R
function mu_instanceof(L,R){
    // 解决边界:检测实例类型是否为原始类型
    const baseTypes = ['string','number','boolean','symbol','undefined'];

    if(baseTypes.includes(typeof L) || L === null) return false;

    // 别离取传入参数的原型
    let Lp = L.__proto__;
    let Rp = R.prototype; // 函数才领有 prototype 属性

    // 判断原型
    while(true){if(Lp === null) return false;
        if(Lp === Rp) return true;
        Lp = Lp.__proto__;
    }
}

// 验证
const isArray = mu_instanceof([],Array);
console.log(isArray); //true
const isDate = mu_instanceof('2023-01-09',Date);
console.log(isDate); // false

3. 实现 call 函数

实现步骤:

  • 解决边界:

    • 对象不存在,this 指向 window;
  • 将「调用函数」挂载到「this 指向的对象」的 fn 属性上。
  • 执行「this 指向的对象」上的 fn 函数,并传入参数,返回后果。
Function.prototype.mu_call = function (context, ...args) {
    //obj 不存在指向 window
    if (!context || context === null) {context = window;}
    // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
    let fn = Symbol();
​
    //this 指向调用 call 的函数
    context[fn] = this;
​
    // 执行函数并返回后果 相当于把本身作为传入的 context 的办法进行调用了
    return context[fn](...args);
  };
​
  // 测试
  var value = 2;
  var obj1 = {value: 1,};
  function bar(name, age) {
    var myObj = {
      name: name,
      age: age,
      value: this.value,
    };
    console.log(this.value, myObj);
  }
  bar.mu_call(null); // 打印 2 {name: undefined, age: undefined, value: 2}
  bar.mu_call(obj1, 'tom', '110'); // 打印 1 {name: "tom", age: "110", value: 1}

4. 实现 apply 函数

实现步骤:

  • 与 call 统一
  • 区别于参数的模式
Function.prototype.mu_apply = function (context, args) {
  //obj 不存在指向 window
  if (!context || context === null) {context = Window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();

  //this 指向调用 call 的函数
  context[fn] = this;

  // 执行函数并返回后果 相当于把本身作为传入的 context 的办法进行调用了
  return context[fn](...args);
};

// 测试
var value = 2;
var obj1 = {value: 1,};
function bar(name, age) {
  var myObj = {
    name: name,
    age: age,
    value: this.value,
  };
  console.log(this.value, myObj);
}
bar.mu_apply(obj1, ["tom", "110"]); // 打印 1 {name: "tom", age: "110", value: 1}

5. 实现 bind 函数

Function.prototype.mu_bind = function (context, ...args) {if (!context || context === null) {context = window;}
    // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
    let fn = Symbol();
    context[fn] = this;
    let _this = this;
    //  bind 状况要简单一点
    const result = function (...innerArgs) {
      // 第一种状况 : 若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符应用,则不绑定传入的 this,而是将 this 指向实例化进去的对象
      // 此时因为 new 操作符作用  this 指向 result 实例对象  而 result 又继承自传入的_this 依据原型链常识可得出以下论断
      // this.__proto__ === result.prototype   //this instanceof result =>true
      // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
      if (this instanceof _this === true) {
        // 此时 this 指向指向 result 的实例  这时候不须要扭转 this 指向
        this[fn] = _this;
        this[fn](...[...args, ...innerArgs]); // 这里应用 es6 的办法让 bind 反对参数合并
        delete this[fn];
      } else {
        // 如果只是作为一般函数调用  那就很简略了 间接扭转 this 指向为传入的 context
        context[fn](...[...args, ...innerArgs]);
        delete context[fn];
      }
    };
    // 如果绑定的是构造函数 那么须要继承构造函数原型属性和办法
    // 实现继承的形式: 应用 Object.create
    result.prototype = Object.create(this.prototype);
    return result;
  };
  function Person(name, age) {console.log(name); //'我是参数传进来的 name'
    console.log(age); //'我是参数传进来的 age'
    console.log(this); // 构造函数 this 指向实例对象
  }
  // 构造函数原型的办法
  Person.prototype.say = function () {console.log(123);
  };
​
  // 一般函数
  function normalFun(name, age) {console.log(name); //'我是参数传进来的 name'
    console.log(age); //'我是参数传进来的 age'
    console.log(this); // 一般函数 this 指向绑定 bind 的第一个参数 也就是例子中的 obj
    console.log(this.objName); //'我是 obj 传进来的 name'
    console.log(this.objAge); //'我是 obj 传进来的 age'
  }
​
  let obj = {
    objName: '我是 obj 传进来的 name',
    objAge: '我是 obj 传进来的 age',
  };
​
  // 先测试作为结构函数调用
  //   let bindFun = Person.mu_bind(obj, '我是参数传进来的 name');
  //   let a = new bindFun('我是参数传进来的 age');
  //   a.say(); //123
​
  //   再测试作为一般函数调用 a;
  let bindFun = normalFun.mu_bind(obj, '我是参数传进来的 name');
  bindFun('我是参数传进来的 age');

6. 实现 debounce 防抖函数

函数防抖是在事件被触发 n 秒后再执行回调,如果在「n 秒内又被触发」,则「从新计时」

function debounce(fn, wait) {
    let timer = null;
    return function () {if (timer != null) {clearTimeout(timer);
      }
      timer = setTimeout(() => {fn();
      }, wait);
    };
  }
  // 测试
  function handle() {console.log(Math.random());
  }
  // 窗口大小扭转,触发防抖,执行 handle
  window.addEventListener('resize', debounce(handle, 1000));

7. 实现 throttle 节流函数

当事件触发时,保障肯定时间段内只 调用一次 函数。例如页面滚动的时候,每隔一段时间发一次申请

实现步骤:

  • 传入参数为执行函数 fn,等待时间 wait。
  • 保留初始工夫 now。
  • 返回一个函数,如果超过等待时间,执行函数,将 now 更新为以后工夫。
function throttle(fn, wait, ...args) {var pre = Date.now();
    return function () {
      // 函数可能会有入参
      var context = this;
      var now = Date.now();
      if (now - pre >= wait) {
        // 将执行函数的 this 指向以后作用域
        fn.apply(context, args);
        pre = Date.now();}
    };
  }
​
  // 测试
  var name = 'mu';
  function handle(val) {console.log(val + this.name);
  }
  // 滚动鼠标,触发防抖,执行 handle
  window.addEventListener('scroll', throttle(handle, 1000, '木由'));

8. 实现数组对象去重

利用 map 的键不能反复,去掉某个属性雷同的项

function uniqBy(arr, key) {return [...new Map(arr.map((item) => [item[key], item])).values()];
  }
  const list = [{ id: 1, name: 'tom'},
    {id: 1, name: 'jey'},
    {id: 2, name: 'joy'},
  ];
  console.log(uniqBy(list, 'id')); // [{id:1,name:"jey"},{id:2,name:"joy"}]

9. 实现数组扁平化

flat

let arr = [1,2,[3,4],[5,6,[7,8,9]]]
console.log(arr.flat(Infinity))//[1, 2, 3, 4, 5, 6, 7, 8, 9]

join/ split

let arr = [1,2,[3,4],[5,6,[7,8,9]]]
console.log(arr.toString().split(",").map(Number))//[1, 2, 3, 4, 5, 6, 7, 8, 9] 
console.log(arr.join().split(",").map(Number))//[1, 2, 3, 4, 5, 6, 7, 8, 9]

函数版

let arr = [1,2,[3,4],[5,6,[7,8,9]]]
function flatter(arr) {if (!arr.length) return;
     while (arr.some((item) => Array.isArray(item))) {arr = [].concat(...arr);
     }
     return arr;
 }
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

10. 实现深拷贝

对象的深拷贝,本质上就是递归办法实现深度克隆:遍历对象、数组直到里边都是根本数据类型,而后再去复制。

简略版

function deepCopy(obj){
    // 判断是否是简略数据类型,if(typeof obj == "object"){
        // 简单数据类型
        var result = obj.constructor == Array ? [] : {};
        for(let i in obj){result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
        }
    }else {
        // 简略数据类型 间接 == 赋值
        var result = obj;
    }
    return result;
}

进阶版

function deepClone(obj, hash = new WeakMap()) {
  // 如果是 null 或者 undefined 我就不进行拷贝操作
  if (obj == null) return obj;
​
  if (obj instanceof Date) return new Date(obj);
​
  if (obj instanceof RegExp) return new RegExp(obj);
​
  // 可能是对象或者一般的值  如果是函数的话是不须要深拷贝
  if (typeof obj !== 'object') return obj;
​
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
​
  // 找到的是所属类原型上的 constructor, 而原型上的 constructor 指向的是以后类自身
  let cloneObj = new obj.constructor();
​
  hash.set(obj, cloneObj);
​
  for (let key in obj) {if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}
​
let obj = {name: 1, address: { x: 100} };
obj.o = obj; // 对象存在循环援用的状况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
退出移动版