Js 笔试面试常见手写汇总与原理详解

256 阅读4分钟

前言

  • 某某原生 Api 的实现方式,如果让你实现,你会怎么做 ?
  • 你知道 new 具体做了哪些事儿嘛 ?
  • call 是如何改变 this 的指向的 ?
  • ......

有没有似曾相识 ~ 有没有苦不堪言 ~ 有没有 ?

new

引用 MDN 的描述就是:

  1. 创建一个空的JavaScript对象(即{})
  2. 空对象的_proto_指向构造函数的prototype,也就是将 obj.constructor 指向构造函数
  3. 将this指向创建的对象上下文
  4. 返回对象 5.(译注:关于对象的 constructor,参见 Object.prototype.constructor)
function _new(func) { 
    // 第一步 创建新对象 
    let obj= {};
    
    // 第二步 空对象的_proto_指向构造函数的prototype
    // 也就是将 obj.constructor 指向 func 
    obj.__proto__ = func.prototype;
    
    // 前两步可以简写成
    // let obj = Object.create(func.prototype) 

    // 第三步 使用 apply 将 this 指向 obj 对象上下文
    let result = func.apply(obj);

    // 第四步返回对象
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
    	// 如果构造函数执行的结果返回的是一个对象,那么返回这个对象
        return result;
    }
    // 如果构造函数返回的不是一个对象,返回创建的新对象
    return obj;
}

call

call 的作用是指定 this 值调用某个方法,也可以说是将某个方法作用到指定的this指向上,逻辑也比较简单

  1. 在Function 原型上挂载_call 方法,参数接收要指定this的目标对象和方法执行的传参
  2. 将 this 指向目标对象 target
  3. 执行 fn , 传入参数
  4. 删除目标对象的自定义临时变量 fn , 因为多次调用
  5. 返回执行结果
Function.prototype._call = function (target,...arg) {
    // 这里的 this 指向调用它的那个函数,就是 func._call(....) 里的 func 函数
    target.fn = this; 
    
    // 到这里 fn 指向上边的 func 函数,this 指向调用它的对象 => target 
    let result = target.fn(...arg); 
    
    // 移除 fn , 因为每次调用都会重新创建
    delete target.fn;
    
    // 返回结果,其实就是一句话 this 指向调用它的那个对象 
    return result;
}

apply

apply 同理,只是传参是数组

Function.prototype._apply = function (target,arg) {
    target.fn = this;
    let result = target.fn(arg);
    delete target.fn;
    return result;
}

Array.map

map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,方法按照原始数组元素顺序依次处理元素。逻辑也比较简单

  1. 在 Array 的原型上挂载一个_map方法,接收一个处理函数,处理函数接收三个值
    • 原始数组的每一个项
    • 下标
    • 原始数组
  2. 声明结果数组 result , 用于返回新的数组
  3. 判断调用_map 的是不是数组,然后进行遍历,依次调用处理函数 push 进结果数组 result
  4. 返回新数组
Array.prototype._map = function (fn) {
       var result = [];
       if(this instanceof Array){
            for(var i =0;i<this.length;i++){
                result.push(fn(this[i],i,this))
            }
        }
   return result;
}

注,这里的 this 是原始数组,因为 this 指向调用它的那个对象

instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。逻辑是这样的

  1. target 实例对象 , pro 构造函数,proto 是对象的隐式原型 , prototype 是函数的显示原型
  2. 使用一个 while 无线循环,判断构造函数的显示原型是否出现在实例对象的原型链上
  3. 每循环一次,将实例对象的__proto__赋值构造函数的当前隐式原型
  4. 直到原型链顶端 null 或者出现相等,返回结果
function _instance_of(target,pro) {
    while (true) {    
        if(target.__proto__ === pro.prototype){
            return true
        }
        target.__proto__ = pro.__proto__;
        if(target.__proto__  == null) {
             return false;  
        }
    }
}

如果这里对原型,原型链不太了解的童鞋,可以查看我的一篇Javascript 核心进阶,里边详细描述了原型,原项链Javascript 核心进阶

防抖

函数防抖其实是在规定时间内,频繁触发该事件,以最后一次触发为准; 实现方式就是创建一个定时器,每次执行的时候,清除旧定时器,并创建一个新的定时器重新记录时间

//参数:要执行的函数和间隔的毫秒数
function debounce(fn, time) {
   var timer = null; // 声明 timer 
    return function() {
        clearTimeout(timer) // 清除定时器
        timer = setTimeout(function() { // 创建定时器赋给局部变量 timer 
            fn.apply(this)
        }, time)
    }
}
// 在规定时间内,触发多次,以最后那一次为准,
// 因为每一次触发,旧的定时还没有执行就被清除了,又创建了新的定时器

节流

函数节流其实是在规定时间内,事件只被触发一次 ; 实现方式就是通过时间戳, 当前的时间戳 - 最后一次执行的时间戳 > 设置规定时间,则生效一次;也就说在频繁触发的情况下,该事件触发的频率会降低

// 参数:要执行的函数和毫秒数
function throttle(fn, time) {
    var lastTime = 0; // 初始化最后一次执行时间
    return function() {
        var nowTime = Date.now(); // 获取当前时间毫秒数 
        if (nowTime - lastTime > time) { // 当前时间毫秒数 - 最后一次执行时间毫秒数 > 设置规定时间
            fn.call(this);
          lastTime = nowTime; // 更新最后一字执行时间毫秒数
        }
    }
}
// 每一次执行,当前时间就会减去最后一次执行时间
// 如果大于设置时间,就触发一次,有效的节省了事件触发频率。

发布订阅

通俗一点说就是一个发布通知,一个接收通知做对应的事儿

先定义一个类 EventBus

class EventBus {
  constructor() {
    this.events = this.events || {};
  }
}

定义一个发布者

// 挂载在类 EventBus 的原型上
// type : 发布的事件类型,也可以说是事件名称
// args : 携带参数
EventBus.prototype.emit = function (type, ...args) {
  const e  = this.events[type]; // 返回一个数组
  if (e instanceof Array) {
	  e.forEach( c => {
	  	// 改变 this 调用对应的发布事件
	  	 c.apply(this, args);
	  })
  }
};

定义一个订阅者

// type : 订阅的事件类型,也可以说是事件名称
// 回调函数 : dosomething 
EventBus.prototype.on = function (type, fun) {
  const e = this.events[type]; // 同上
  if (!e) {
  	// 如果没有该订阅事件,则自定添加,并赋值对应的操作
    this.events[type] = [fun];
  } else {
    e.push(fun);
  }
};

定义一个移除事件

// 移除对应的事件
EventBus.prototype.off = function (type) {
  delete this.events[type];
};

实践

// new 一个实例
const ev = new EventBus();
//订阅 fn 事件 , 并接收参数
ev.on("fn",(args) => {console.log("订阅到了 fn 事件",args)})
// 发布 fn 事件 , 并传测试参数
ev.emit("fn",{arg:"测试参数"})
//移除 fn 事件
ev.off("fn")
// 打印看结果
console.log(ev);

这篇文章会持续迭代 ~ ~ ~ biu biu biu ~ ~ ~

欢迎点赞,小小鼓励,大大成长