关于javascript:社招前端二面必会手写面试题总结

53次阅读

共计 14695 个字符,预计需要花费 37 分钟才能阅读完成。

字符串查找

请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。

a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
function isContain(a, b) {for (let i in b) {if (a[0] === b[i]) {
      let tmp = true;
      for (let j in a) {if (a[j] !== b[~~i + ~~j]) {tmp = false;}
      }
      if (tmp) {return i;}
    }
  }
  return -1;
}

setTimeout 与 setInterval 实现

setTimeout 模仿实现 setInterval

题目形容: setInterval 用来实现循环定时调用 可能会存在肯定的问题 能用 setTimeout 解决吗

实现代码如下:

function mySetInterval(fn, t) {
  let timerId = null;
  function interval() {fn();
    timerId = setTimeout(interval, t); // 递归调用
  }
  timerId = setTimeout(interval, t); // 首次调用
  return {
    // 利用闭包的个性 保留 timerId
    cancel:() => {clearTimeout(timerId)
    }
  }
}
// 测试
var a = mySetInterval(()=>{console.log(111);
},1000)
var b = mySetInterval(() => {console.log(222)
}, 1000)

// 终止定时器
a.cancel()
b.cancel()

为什么要用 setTimeout 模仿实现 setIntervalsetInterval 的缺点是什么?

setInterval(fn(), N);

下面这句代码的意思其实是 fn() 将会在 N 秒之后被推入工作队列。在 setInterval 被推入工作队列时,如果在它后面有很多工作或者某个工作等待时间较长比方网络申请等,那么这个定时器的执行工夫和咱们预约它执行的工夫可能并不统一

// 最常见的呈现的就是,当咱们须要应用 ajax 轮询服务器是否有新数据时,必定会有一些人会应用 setInterval,然而无论网络情况如何,它都会去一遍又一遍的发送申请,最初的间隔时间可能和原定的工夫有很大的出入

// 做一个网络轮询,每一秒查问一次数据。let startTime = new Date().getTime();
let count = 0;

setInterval(() => {
    let i = 0;
    while (i++ < 10000000); // 假如的网络提早
    count++;
    console.log(
        "与原设定的距离时差了:",
        new Date().getTime() - (startTime + count * 1000),
        "毫秒"
    );
}, 1000)

// 输入:// 与原设定的距离时差了:567 毫秒
// 与原设定的距离时差了:552 毫秒
// 与原设定的距离时差了:563 毫秒
// 与原设定的距离时差了:554 毫秒(2 次)
// 与原设定的距离时差了:564 毫秒
// 与原设定的距离时差了:602 毫秒
// 与原设定的距离时差了:573 毫秒
// 与原设定的距离时差了:633 毫秒

再次强调,定时器指定的工夫距离,示意的是何时将定时器的代码增加到音讯队列,而不是何时执行代码。所以真正何时执行代码的工夫是不能保障的,取决于何时被主线程的事件循环取到,并执行。

setInterval(function, N)
// 即:每隔 N 秒把 function 事件推到音讯队列中

上图可见,setInterval 每隔 100ms 往队列中增加一个事件;100ms 后,增加 T1 定时器代码至队列中,主线程中还有工作在执行,所以期待,some event 执行完结后执行 T1定时器代码;又过了 100msT2 定时器被增加到队列中,主线程还在执行 T1 代码,所以期待;又过了 100ms,实践上又要往队列里推一个定时器代码,但因为此时 T2 还在队列中,所以 T3 不会被增加(T3 被跳过),后果就是此时被跳过;这里咱们能够看到,T1 定时器执行完结后马上执行了 T2 代码,所以并没有达到定时器的成果

setInterval 有两个毛病

  • 应用 setInterval 时,某些距离会被跳过
  • 可能多个定时器会间断执行

能够这么了解 :每个setTimeout 产生的工作会间接 push 到工作队列中;而 setInterval 在每次把工作 push 到工作队列前,都要进行一下判断 (看上次的工作是否仍在队列中)。因此咱们个别用setTimeout 模仿setInterval,来躲避掉下面的毛病

setInterval 模仿实现 setTimeout

const mySetTimeout = (fn, t) => {const timer = setInterval(() => {clearInterval(timer);
    fn();}, t);
};
// 测试
// mySetTimeout(()=>{//   console.log(1);
// },1000)

数组中的数据依据 key 去重

给定一个任意数组,实现一个通用函数,让数组中的数据依据 key 排重:

const dedup = (data, getKey = () => {}) => {// todo}
let data = [{ id: 1, v: 1},
  {id: 2, v: 2},
  {id: 1, v: 1},
];

// 以 id 作为排重 key,执行函数失去后果
// data = [//   { id: 1, v: 1},
//   {id: 2, v: 2},
// ];

实现

const dedup = (data, getKey = () => {}) => {const dateMap = data.reduce((pre, cur) => {const key = getKey(cur)
        if (!pre[key]) {pre[key] = cur
        }
        return pre
    }, {})
    return Object.values(dateMap)
}

应用

let data = [{ id: 1, v: 1},
    {id: 2, v: 2},
    {id: 1, v: 1},
];
console.log(dedup(data, (item) => item.id))

// 以 id 作为排重 key,执行函数失去后果
// data = [//   { id: 1, v: 1},
//   {id: 2, v: 2},
// ];

实现 Array.isArray 办法

Array.myIsArray = function(o) {return Object.prototype.toString.call(Object(o)) === '[object Array]';
};

console.log(Array.myIsArray([])); // true

实现一个繁难的 MVVM

实现一个繁难的 MVVM 我会分为这么几步来:

  1. 首先我会定义一个类 Vue,这个类接管的是一个options,那么其中可能有须要挂载的根元素的id,也就是el 属性;而后应该还有一个 data 属性,示意须要双向绑定的数据
  2. 其次我会定义一个 Dep 类,这个类产生的实例对象中会定义一个 subs 数组用来寄存所依赖这个属性的依赖,曾经增加依赖的办法 addSub,删除办法removeSub,还有一个notify 办法用来遍历更新它 subs 中的所有依赖,同时 Dep 类有一个动态属性 target 它用来示意以后的观察者,当后续进行依赖收集的时候能够将它增加到 dep.subs 中。
  3. 而后设计一个 observe 办法,这个办法接管的是传进来的 data,也就是options.data,外面会遍历data 中的每一个属性,并应用 Object.defineProperty() 来重写它的 getset,那么这外面呢能够应用 new Dep() 实例化一个 dep 对象,在 get 的时候调用其 addSub 办法增加以后的观察者 Dep.target 实现依赖收集,并且在 set 的时候调用 dep.notify 办法来告诉每一个依赖它的观察者进行更新
  4. 实现这些之后,咱们还须要一个 compile 办法来将 HTML 模版和数据联合起来。在这个办法中首先传入的是一个 node 节点,而后遍历它的所有子级,判断是否有 firstElmentChild,有的话则进行递归调用 compile 办法,没有firstElementChild 的话且该 child.innderHTML 用正则匹配满足有 /\{\{(.*)\}\}/ 项的话则示意有须要双向绑定的数据,那么就将用正则 new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm') 替换掉是其为 msg 变量。
  5. 实现变量替换的同时,还须要将 Dep.target 指向以后的这个 child,且调用一下this.opt.data[key],也就是为了触发这个数据的get 来对以后的 child 进行依赖收集,这样下次数据变动的时候就能告诉 child 进行视图更新了,不过在最初要记得将 Dep.target 指为 null 哦(其实在 Vue 中是有一个 targetStack 栈用来寄存 target 的指向的)
  6. 那么最初咱们只须要监听 documentDOMContentLoaded而后在回调函数中实例化这个 Vue 对象就能够了

coding :

须要留神的点:

  • childNodes会获取到所有的子节点以及文本节点(包含元素标签中的空白节点)
  • firstElementChild示意获取元素的第一个字元素节点,以此来辨别是不是元素节点,如果是的话则调用 compile 进行递归调用,否则用正则匹配
  • 这外面的正则真的不难,大家能够看一下

残缺代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>MVVM</title>
  </head>
  <body>
    <div id="app">
      <h3> 姓名 </h3>
      <p>{{name}}</p>
      <h3> 年龄 </h3>
      <p>{{age}}</p>
    </div>
  </body>
</html>
<script>
  document.addEventListener(
    "DOMContentLoaded",
    function () {let opt = { el: "#app", data: { name: "期待批改...", age: 20} };
      let vm = new Vue(opt);
      setTimeout(() => {opt.data.name = "jing";}, 2000);
    },
    false
  );
  class Vue {constructor(opt) {
      this.opt = opt;
      this.observer(opt.data);
      let root = document.querySelector(opt.el);
      this.compile(root);
    }
    observer(data) {Object.keys(data).forEach((key) => {let obv = new Dep();
        data["_" + key] = data[key];

        Object.defineProperty(data, key, {get() {Dep.target && obv.addSubNode(Dep.target);
            return data["_" + key];
          },
          set(newVal) {obv.update(newVal);
            data["_" + key] = newVal;
          },
        });
      });
    }
    compile(node) {[].forEach.call(node.childNodes, (child) => {if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {let key = RegExp.$1.trim();
          child.innerHTML = child.innerHTML.replace(new RegExp("\\{\\{\\s*" + key + "\\s*\\}\\}", "gm"),
            this.opt.data[key]
          );
          Dep.target = child;
          this.opt.data[key];
          Dep.target = null;
        } else if (child.firstElementChild) this.compile(child);
      });
    }
  }

  class Dep {constructor() {this.subNode = [];
    }
    addSubNode(node) {this.subNode.push(node);
    }
    update(newVal) {this.subNode.forEach((node) => {node.innerHTML = newVal;});
    }
  }
</script>

简化版 2

function update(){console.log('数据变动~~~ mock update view')
}
let obj = [1,2,3]
// 变异办法 push shift unshfit reverse sort splice pop
// Object.defineProperty
let oldProto = Array.prototype;
let proto = Object.create(oldProto); // 克隆了一分
['push','shift'].forEach(item=>{proto[item] = function(){update();
    oldProto[item].apply(this,arguments);
  }
})
function observer(value){ // proxy reflect
  if(Array.isArray(value)){
    // AOP
    return value.__proto__ = proto;
    // 重写 这个数组里的 push shift unshfit reverse sort splice pop
  }
  if(typeof value !== 'object'){return value;}
  for(let key in value){defineReactive(value,key,value[key]);
  }
}
function defineReactive(obj,key,value){observer(value); // 如果是对象 持续减少 getter 和 setter
  Object.defineProperty(obj,key,{get(){return value;},
    set(newValue){if(newValue !== value){observer(newValue);
            value = newValue;
            update();}
    }
  })
}
observer(obj); 
// AOP
// obj.name = {n:200}; // 数据变了 须要更新视图 深度监控
// obj.name.n = 100;
obj.push(123);
obj.push(456);
console.log(obj);

实现 Object.freeze

Object.freeze解冻一个对象,让其不能再增加 / 删除属性,也不能批改该对象已有属性的可枚举性、可配置可写性,也不能批改已有属性的值和它的原型属性,最初返回一个和传入参数雷同的对象

function myFreeze(obj){
  // 判断参数是否为 Object 类型,如果是就关闭对象,循环遍历对象。去掉原型属性,将其 writable 个性设置为 false
  if(obj instanceof Object){Object.seal(obj);  // 关闭对象
    for(let key in obj){if(obj.hasOwnProperty(key)){
        Object.defineProperty(obj,key,{writable:false   // 设置只读})
        // 如果属性值仍然为对象,要通过递归来进行进一步的解冻
        myFreeze(obj[key]);  
      }
    }
  }
}

参考 前端进阶面试题具体解答

实现 JSONP 办法

利用 <script> 标签不受跨域限度的特点,毛病是只能反对 get 申请

  • 创立 script 标签
  • 设置 script 标签的 src 属性,以问号传递参数,设置好回调函数 callback 名称
  • 插入到 html 文本中
  • 调用回调函数,res参数就是获取的数据
function jsonp({url,params,callback}) {return new Promise((resolve,reject)=>{let script = document.createElement('script')

    window[callback] = function (data) {resolve(data)
      document.body.removeChild(script)
    }
    var arr = []
    for(var key in params) {arr.push(`${key}=${params[key]}`)
    }
    script.type = 'text/javascript'
    script.src = `${url}?callback=${callback}&${arr.join('&')}`
    document.body.appendChild(script)
  })
}
// 测试用例
jsonp({
  url: 'http://suggest.taobao.com/sug',
  callback: 'getData',
  params: {
    q: 'iphone 手机',
    code: 'utf-8'
  },
}).then(data=>{console.log(data)})
  • 设置 CORS: Access-Control-Allow-Origin:*
  • postMessage

类数组转化为数组的办法

const arrayLike=document.querySelectorAll('div')

// 1. 扩大运算符
[...arrayLike]
// 2.Array.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.prototype.concat.apply([], arrayLike)

用 Promise 实现图片的异步加载

let imageAsync=(url)=>{return new Promise((resolve,reject)=>{let img = new Image();
                img.src = url;
                img.οnlοad=()=>{console.log(` 图片申请胜利,此处进行通用操作 `);
                    resolve(image);
                }
                img.οnerrοr=(err)=>{console.log(` 失败,此处进行失败的通用操作 `);
                    reject(err);
                }
            })
        }

imageAsync("url").then(()=>{console.log("加载胜利");
}).catch((error)=>{console.log("加载失败");
})

原生实现

function ajax() {let xhr = new XMLHttpRequest() // 实例化,以调用办法
  xhr.open('get', 'https://www.google.com')  // 参数 2,url。参数三:异步
  xhr.onreadystatechange = () => {  // 每当 readyState 属性扭转时,就会调用该函数。if (xhr.readyState === 4) {  //XMLHttpRequest 代理以后所处状态。if (xhr.status >= 200 && xhr.status < 300) {  //200-300 申请胜利
        let string = request.responseText
        //JSON.parse() 办法用来解析 JSON 字符串,结构由字符串形容的 JavaScript 值或对象
        let object = JSON.parse(string)
      }
    }
  }
  request.send() // 用于理论收回 HTTP 申请。不带参数为 GET 申请}

手写 Promise.then

then 办法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,咱们应用一个 callbacks 数组先把传给 then 的函数暂存起来,等状态扭转时再调用。

那么,怎么保障后一个 **then** 里的办法在前一个 **then**(可能是异步)完结之后再执行呢? 咱们能够将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的成果:

  • 承前:以后一个 promise 实现后,调用其 resolve 变更状态,在这个 resolve 里会顺次调用 callbacks 里的回调,这样就执行了 then 里的办法了
  • 启后:上一步中,当 then 里的办法执行实现后,返回一个后果,如果这个后果是个简略的值,就间接调用新 promiseresolve,让其状态变更,这又会顺次调用新 promisecallbacks 数组里的办法,周而复始。。如果返回的后果是个 promise,则须要等它实现之后再触发新 promiseresolve,所以能够在其后果的 then 里调用新 promiseresolve
then(onFulfilled, onReject){
    // 保留前一个 promise 的 this
    const self = this; 
    return new MyPromise((resolve, reject) => {
      // 封装前一个 promise 胜利时执行的函数
      let fulfilled = () => {
        try{const result = onFulfilled(self.value); // 承前
          return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); // 启后
        }catch(err){reject(err)
        }
      }
      // 封装前一个 promise 失败时执行的函数
      let rejected = () => {
        try{const result = onReject(self.reason);
          return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
        }catch(err){reject(err)
        }
      }
      switch(self.status){
        case PENDING: 
          self.onFulfilledCallbacks.push(fulfilled);
          self.onRejectedCallbacks.push(rejected);
          break;
        case FULFILLED:
          fulfilled();
          break;
        case REJECT:
          rejected();
          break;
      }
    })
   }

留神:

  • 间断多个 then 里的回调办法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考下面的例子和图)
  • 注册实现后开始执行构造函数中的异步事件,异步实现之后顺次调用 callbacks 数组中提前注册的回调

字符串查找

请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。

a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);

function isContain(a, b) {for (let i in b) {if (a[0] === b[i]) {
      let tmp = true;
      for (let j in a) {if (a[j] !== b[~~i + ~~j]) {tmp = false;}
      }
      if (tmp) {return i;}
    }
  }
  return -1;
}

实现数组的 push 办法

let arr = [];
Array.prototype.push = function() {for( let i = 0 ; i < arguments.length ; i++){this[this.length] = arguments[i] ;
    }
    return this.length;
}

debounce(防抖)

触发高频工夫后 n 秒内函数只会执行一次, 如果 n 秒内高频工夫再次触发, 则从新计算工夫。

const debounce = (fn, time) => {
  let timeout = null;
  return function() {clearTimeout(timeout)
    timeout = setTimeout(() => {fn.apply(this, arguments);
    }, time);
  }
};

防抖常利用于用户进行搜寻输出节约申请资源,window触发 resize 事件时进行防抖只触发一次。

将 VirtualDom 转化为实在 DOM 构造

这是以后 SPA 利用的外围概念之一

// vnode 构造:// {
//   tag,
//   attrs,
//   children,
// }

//Virtual DOM => DOM
function render(vnode, container) {container.appendChild(_render(vnode));
}
function _render(vnode) {
  // 如果是数字类型转化为字符串
  if (typeof vnode === 'number') {vnode = String(vnode);
  }
  // 字符串类型间接就是文本节点
  if (typeof vnode === 'string') {return document.createTextNode(vnode);
  }
  // 一般 DOM
  const dom = document.createElement(vnode.tag);
  if (vnode.attrs) {
    // 遍历属性
    Object.keys(vnode.attrs).forEach(key => {const value = vnode.attrs[key];
      dom.setAttribute(key, value);
    })
  }
  // 子数组进行递归操作
  vnode.children.forEach(child => render(child, dom));
  return dom;
}

实现类的继承

实现类的继承 - 简版

类的继承在几年前是重点内容,有 n 种继承形式各有优劣,es6 遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。

// 寄生组合继承
function Parent(name) {this.name = name}
Parent.prototype.say = function() {console.log(this.name + ` say`);
}
Parent.prototype.play = function() {console.log(this.name + ` play`);
}

function Child(name, parent) {
  // 将父类的构造函数绑定在子类上
  Parent.call(this, parent)
  this.name = name
}

/** 
 1. 这一步不必 Child.prototype = Parent.prototype 的起因是怕共享内存,批改父类原型对象就会影响子类
 2. 不必 Child.prototype = new Parent()的起因是会调用 2 次父类的构造方法(另一次是 call),会存在一份多余的父类实例属性
3. Object.create 是创立了父类原型的正本,与父类原型齐全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {console.log(this.name + ` say`);
}

// 留神记得把子类的结构指向子类自身
Child.prototype.constructor = Child;
// 测试
var parent = new Parent('parent');
parent.say() 

var child = new Child('child');
child.say() 
child.play(); // 继承父类的办法

ES5 实现继承 - 具体

第一种形式是借助 call 实现继承

function Parent1(){this.name = 'parent1';}
function Child1(){Parent1.call(this);
    this.type = 'child1'    
}
console.log(new Child1);

这样写的时候子类尽管可能拿到父类的属性值,然而问题是父类中一旦存在办法那么子类无奈继承。那么引出上面的办法

第二种形式借助原型链实现继承:

function Parent2() {
    this.name = 'parent2';
    this.play = [1, 2, 3]
  }
  function Child2() {this.type = 'child2';}
  Child2.prototype = new Parent2();

  console.log(new Child2());

看似没有问题,父类的办法和属性都可能拜访,但实际上有一个潜在的有余。举个例子:

var s1 = new Child2();
  var s2 = new Child2();
  s1.play.push(4);
  console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]

明明我只扭转了 s1 的 play 属性,为什么 s2 也跟着变了呢?很简略,因为两个实例应用的是同一个原型对象

第三种形式:将前两种组合:

function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
  }
  function Child3() {Parent3.call(this);
    this.type = 'child3';
  }
  Child3.prototype = new Parent3();
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]

之前的问题都得以解决。然而这里又徒增了一个新问题,那就是 Parent3 的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是咱们不愿看到的。那么如何解决这个问题?

第四种形式: 组合继承的优化 1

function Parent4 () {
    this.name = 'parent4';
    this.play = [1, 2, 3];
  }
  function Child4() {Parent4.call(this);
    this.type = 'child4';
  }
  Child4.prototype = Parent4.prototype;

这里让将父类原型对象间接给到子类,父类构造函数只执行一次,而且父类属性和办法均能拜访,然而咱们来测试一下

var s3 = new Child4();
  var s4 = new Child4();
  console.log(s3)

子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4。

第五种形式(最举荐应用):优化 2

function Parent5 () {
    this.name = 'parent5';
    this.play = [1, 2, 3];
  }
  function Child5() {Parent5.call(this);
    this.type = 'child5';
  }
  Child5.prototype = Object.create(Parent5.prototype);
  Child5.prototype.constructor = Child5;

这是最举荐的一种形式,靠近完满的继承。

Promise.race

Promise.race = function(promiseArr) {return new Promise((resolve, reject) => {
    promiseArr.forEach(p => {
      // 如果不是 Promise 实例须要转化为 Promise 实例
      Promise.resolve(p).then(val => resolve(val),
        err => reject(err),
      )
    })
  })
}

实现一下 hash 路由

根底的 html 代码:

<html>
  <style>
    html, body {
      margin: 0;
      height: 100%;
    }
    ul {
      list-style: none;
      margin: 0;
      padding: 0;
      display: flex;
      justify-content: center;
    }
    .box {
      width: 100%;
      height: 100%;
      background-color: red;
    }
  </style>
  <body>
  <ul>
    <li>
      <a href="#red"> 红色 </a>
    </li>
    <li>
      <a href="#green"> 绿色 </a>
    </li>
    <li>
      <a href="#purple"> 紫色 </a>
    </li>
  </ul>
  </body>
</html>

简略实现:

<script>
  const box = document.getElementsByClassName('box')[0];
  const hash = location.hash
  window.onhashchange = function (e) {const color = hash.slice(1)
    box.style.background = color
  }
</script>

封装成一个 class:

<script>
  const box = document.getElementsByClassName('box')[0];
  const hash = location.hash
  class HashRouter {constructor (hashStr, cb) {
      this.hashStr = hashStr
      this.cb = cb
      this.watchHash()
      this.watch = this.watchHash.bind(this)
      window.addEventListener('hashchange', this.watch)
    }
    watchHash () {let hash = window.location.hash.slice(1)
      this.hashStr = hash
      this.cb(hash)
    }
  }
  new HashRouter('red', (color) => {box.style.background = color})
</script>

实现 findIndex 办法

var users = [{id: 1, name: '张三'},
  {id: 2, name: '张三'},
  {id: 3, name: '张三'},
  {id: 4, name: '张三'}
]

Array.prototype.myFindIndex = function (callback) {// var callback = function (item, index) {return item.id === 4}
  for (var i = 0; i < this.length; i++) {if (callback(this[i], i)) {
      // 这里返回
      return i
    }
  }
}

var ret = users.myFind(function (item, index) {return item.id === 2})

console.log(ret)

throttle(节流)

高频工夫触发, 但 n 秒内只会执行一次, 所以节流会浓缩函数的执行频率。

const throttle = (fn, time) => {
  let flag = true;
  return function() {if (!flag) return;
    flag = false;
    setTimeout(() => {fn.apply(this, arguments);
      flag = true;
    }, time);
  }
}

节流常利用于鼠标一直点击触发、监听滚动事件。

正文完
 0