数组的map办法

      Array.prototype.myMap = function(fn, thisValue) {            let res = []            thisValue = thisValue||[]            let arr = this            for(let i=0; i<arr.length; i++) {                res.push(fn.call(thisValue, arr[i],i,arr))            }            return res        }        // 2.reduce实现Array.prototype.myMap = function(fn,thisValue){     var res = [];     thisValue = thisValue||[];     this.reduce(function(pre,cur,index,arr){         return res.push(fn.call(thisValue,cur,index,arr));     },[]);     return res;}

手写reduce

 function reduce(arr, cb, initialValue){     var num = initValue == undefined? num = arr[0]: initValue;     var i = initValue == undefined? 1: 0     for (i; i< arr.length; i++){        num = cb(num,arr[i],i)     }     return num }  function fn(result, currentValue, index){     return result + currentValue } var arr = [2,3,4,5] var b = reduce(arr, fn,10)  console.log(b)   // 24

数组扁平化

1. es6提供的新办法 flat(depth)

let a = [1,[2,3]]; a.flat(); // [1,2,3] a.flat(1); //[1,2,3]

无需晓得数组的维度,depth的值设置为Infinity。

let a = [1,[2,3,[4,[5]]]]; a.flat(Infinity); // [1,2,3,4,5]  a是4维数组

2. 利用cancat

function flatten(arr) {     var res = [];     for (let i = 0, length = arr.length; i < length; i++) {     if (Array.isArray(arr[i])) {     res = res.concat(flatten(arr[i])); //concat 并不会扭转原数组     //res.push(...flatten(arr[i])); //或者用扩大运算符      } else {         res.push(arr[i]);       }     }     return res; } let arr1 = [1, 2,[3,1],[2,3,4,[2,3,4]]]flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

函数柯里化

柯里化的定义:接管一部分参数,返回一个函数接管残余参数,接管足够参数后,执行原函数。

/** * 将函数柯里化 * @param fn    待柯里化的原函数 * @param len   所需的参数个数,默认为原函数的形参个数 */function curry(fn,len = fn.length) { return _curry.call(this,fn,len)}/** * 直达函数 * @param fn    待柯里化的原函数 * @param len   所需的参数个数 * @param args  已接管的参数列表 */function _curry(fn,len,...args) {    return function (...params) {         let _args = [...args,...params];         if(_args.length >= len){             return fn.apply(this,_args);         }else{          return _curry.call(this,fn,len,..._args)         }    }}

咱们来验证一下:

let _fn = curry(function(a,b,c,d,e){ console.log(a,b,c,d,e)});_fn(1,2,3,4,5);     // print: 1,2,3,4,5_fn(1)(2)(3,4,5);   // print: 1,2,3,4,5_fn(1,2)(3,4)(5);   // print: 1,2,3,4,5_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

手写call

Function.prototype.myCall=function(context=window){  // 函数的办法,所以写在Fuction原型对象上 if(typeof this !=="function"){   // 这里if其实没必要,会主动抛出谬误    throw new Error("不是函数") } const obj=context||window   //这里可用ES6办法,为参数增加默认值,js严格模式全局作用域this为undefined obj.fn=this      //this为调用的上下文,this此处为函数,将这个函数作为obj的办法 const arg=[...arguments].slice(1)   //第一个为obj所以删除,伪数组转为数组 res=obj.fn(...arg) delete obj.fn   // 不删除会导致context属性越来越多 return res}
//用法:f.call(obj,arg1)function f(a,b){ console.log(a+b) console.log(this.name)}let obj={ name:1}f.myCall(obj,1,2) //否则this指向windowobj.greet.call({name: 'Spike'}) //打进去的是 Spike

手写apply(arguments[this, [参数1,参数2.....] ])

Function.prototype.myApply=function(context){  // 箭头函数从不具备参数对象!!!!!这里不能写成箭头函数 let obj=context||window obj.fn=this const arg=arguments[1]||[]    //若有参数,失去的是数组 let res=obj.fn(...arg) delete obj.fn return res} function f(a,b){ console.log(a,b) console.log(this.name)}let obj={ name:'张三'}f.myApply(obj,[1,2])  //arguments[1]

手写bind

this.value = 2var foo = { value: 1};var bar = function(name, age, school){ console.log(name) // 'An' console.log(age) // 22 console.log(school) // '家里蹲大学'}var result = bar.bind(foo, 'An') //预置了局部参数'An'result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中

简略版本

Function.prototype.bind = function(context, ...outerArgs) { var fn = this; return function(...innerArgs) {   //返回了一个函数,...rest为理论调用时传入的参数 return fn.apply(context,[...outerArgs, ...innerArgs]);  //返回扭转了this的函数, //参数合并 }}

new失败的起因:

例:

// 申明一个上下文let thovino = { name: 'thovino'}// 申明一个构造函数let eat = function (food) { this.food = food console.log(`${this.name} eat ${this.food}`)}eat.prototype.sayFuncName = function () { console.log('func name : eat')}// bind一下let thovinoEat = eat.bind(thovino)let instance = new thovinoEat('orange')  //实际上orange放到了thovino外面console.log('instance:', instance) // {}

生成的实例是个空对象

new操作符执行时,咱们的thovinoEat函数能够看作是这样:

function thovinoEat (...innerArgs) { eat.call(thovino, ...outerArgs, ...innerArgs)}

在new操作符进行到第三步的操作thovinoEat.call(obj, ...args)时,这里的obj是new操作符本人创立的那个简略空对象{},但它其实并没有替换掉thovinoEat函数外部的那个上下文对象thovino。这曾经超出了call的能力范畴,因为这个时候要替换的曾经不是thovinoEat函数外部的this指向,而应该是thovino对象。

换句话说,咱们心愿的是new操作符将eat内的this指向操作符本人创立的那个空对象。然而实际上指向了thovinonew操作符的第三步动作并没有胜利

可new可继承版本

Function.prototype.bind = function (context, ...outerArgs) { let that = this;function res (...innerArgs) {     if (this instanceof res) {         // new操作符执行时         // 这里的this在new操作符第三步操作时,会指向new本身创立的那个简略空对象{}         that.call(this, ...outerArgs, ...innerArgs)     } else {         // 一般bind         that.call(context, ...outerArgs, ...innerArgs)     }     }     res.prototype = this.prototype //!!!     return res}

手动实现new

  1. 创立一个空对象 obj;
  2. 将空对象的隐式原型(proto)指向构造函数的prototype。
  3. 应用 call 扭转 this 的指向
  4. 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么间接间接返回该对象。
function Person(name,age){ this.name=name this.age=age}Person.prototype.sayHi=function(){ console.log('Hi!我是'+this.name)}let p1=new Person('张三',18)////手动实现newfunction create(){ let obj={} //获取构造函数 let fn=[].shift.call(arguments)  //将arguments对象提出来转化为数组,arguments并不是数组而是对象    !!!这种办法删除了arguments数组的第一个元素,!!这里的空数组外面填不填元素都没关系,不影响arguments的后果      或者let arg = [].slice.call(arguments,1) obj.__proto__=fn.prototype let res=fn.apply(obj,arguments)    //扭转this指向,为实例增加办法和属性 //确保返回的是一个对象(万一fn不是构造函数) return typeof res==='object'?res:obj}let p2=create(Person,'李四',19)p2.sayHi()

细节:

[].shift.call(arguments)  也可写成: let arg=[...arguments] let fn=arg.shift()  //使得arguments能调用数组办法,第一个参数为构造函数 obj.__proto__=fn.prototype //扭转this指向,为实例增加办法和属性 let res=fn.apply(obj,arg)

手写promise(常考promise.all, promise.race)

const STATUS = { PENDING: 'pending', FULFILLED: 'fulfilled', REJECTED: 'rejected'}class MyPromise { // 构造函数接管一个执行回调 constructor(executor) {     this._status = STATUS.PENDING // Promise初始状态     this._value = undefined // then回调的值     this._resolveQueue = [] // resolve时触发的胜利队列     this._rejectQueue = [] // reject时触发的失败队列     // 应用箭头函数固定this(resolve函数在executor中触发,不然找不到this) const resolve = value => {     const run = () => {         if (this._status === STATUS.PENDING) {             this._status = STATUS.FULFILLED // 更改状态             this._value = value // 贮存以后值,用于then回调                         // 执行resolve回调             while (this._resolveQueue.length) {                 const callback = this._resolveQueue.shift()                 callback(value)             }         }     }     //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以实现promise异步调用的个性(标准上是微工作,这里是宏工作)     setTimeout(run) } // 同 resolve const reject = value => {     const run = () => {         if (this._status === STATUS.PENDING) {         this._status = STATUS.REJECTED         this._value = value                 while (this._rejectQueue.length) {             const callback = this._rejectQueue.shift()             callback(value)         }     } }     setTimeout(run) }     // new Promise()时立刻执行executor,并传入resolve和reject     executor(resolve, reject) } // then办法,接管一个胜利的回调和一个失败的回调 function then(onFulfilled, onRejected) {  // 依据标准,如果then的参数不是function,则疏忽它, 让值持续往下传递,链式调用持续往下执行  typeof onFulfilled !== 'function' ? onFulfilled = value => value : null  typeof onRejected !== 'function' ? onRejected = error => error : null  // then 返回一个新的promise  return new MyPromise((resolve, reject) => {    const resolveFn = value => {      try {        const x = onFulfilled(value)        // 分类探讨返回值,如果是Promise,那么期待Promise状态变更,否则间接resolve        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)      } catch (error) {        reject(error)      }    }  }}  const rejectFn = error => {      try {        const x = onRejected(error)        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)      } catch (error) {        reject(error)      }    }    switch (this._status) {      case STATUS.PENDING:        this._resolveQueue.push(resolveFn)        this._rejectQueue.push(rejectFn)        break;      case STATUS.FULFILLED:        resolveFn(this._value)        break;      case STATUS.REJECTED:        rejectFn(this._value)        break;    } }) } catch (rejectFn) {  return this.then(undefined, rejectFn)}
// promise.finally办法finally(callback) {  return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {    MyPromise.resolve(callback()).then(() => error)  })}  // 动态race办法 static race(promiseArr) {      return new MyPromise((resolve, reject) => {        promiseArr.forEach(p => {          MyPromise.resolve(p).then(value => {            resolve(value)          }, error => {            reject(error)          })        })      })    }}
// 动态all办法 static all(promiseArr) {      let count = 0      let result = []      return new MyPromise((resolve, reject) =>   {        if (!promiseArr.length) {          return resolve(result)        }        promiseArr.forEach((p, i) => {          MyPromise.resolve(p).then(value => {            count++            result[i] = value            if (count === promiseArr.length) {              resolve(result)            }          }, error => {            reject(error)          })        })      })    }

11. 手写原生AJAX

  1. 创立 XMLHttpRequest 实例
  2. 收回 HTTP 申请
  3. 服务器返回 XML 格局的字符串
  4. JS 解析 XML,并更新部分页面
    不过随着历史进程的推动,XML 曾经被淘汰,取而代之的是 JSON。
function ajax(url) {  const p = new Promise((resolve, reject) => {    let xhr = new XMLHttpRequest()    xhr.open('get', url)    xhr.onreadystatechange = () => {      if (xhr.readyState == 4) {        if (xhr.status >= 200 && xhr.status <= 300) {          resolve(JSON.parse(xhr.responseText))        } else {          reject('申请出错')        }      }    }    xhr.send()  //发送hppt申请  })  return p}let url = '/data.json'ajax(url).then(res => console.log(res))  .catch(reason => console.log(reason))

12. 手写节流防抖函数

函数节流与函数防抖都是为了限度函数的执行频次,是一种性能优化的计划,比方利用于window对象的resize、scroll事件,拖拽时的mousemove事件,文字输出、主动实现的keyup事件。

节流:间断触发事件然而在 n 秒中只执行一次函数

例:(连续不断动都须要调用时用,设一时间距离),像dom的拖拽,如果用消抖的话,就会呈现卡顿的感觉,因为只在进行的时候执行了一次,这个时候就应该用节流,在肯定工夫内屡次执行,会晦涩很多。

防抖:指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会从新计算函数执行工夫。

例:(连续不断触发时不调用,触发完后过一段时间调用),像仿百度搜寻,就应该用防抖,当我连续不断输出时,不会发送申请;当我一段时间内不输出了,才会发送一次申请;如果小于这段时间持续输出的话,工夫会从新计算,也不会发送申请。

防抖的实现:

function debounce(fn, delay) {     if(typeof fn!=='function') {        throw new TypeError('fn不是函数')     }     let timer; // 保护一个 timer     return function () {         var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)         var args = arguments;         if (timer) {            clearTimeout(timer);         }         timer = setTimeout(function () {            fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);         }, delay);     };}// 调用input1.addEventListener('keyup', debounce(() => { console.log(input1.value)}), 600)

节流的实现:

function throttle(fn, delay) {  let timer;  return function () {    var _this = this;    var args = arguments;    if (timer) {      return;    }    timer = setTimeout(function () {      fn.apply(_this, args); // 这里args接管的是外边返回的函数的参数,不能用arguments      // fn.apply(_this, arguments); 须要留神:Chrome 14 以及 Internet Explorer 9 依然不承受类数组对象。如果传入类数组对象,它们会抛出异样。      timer = null; // 在delay后执行完fn之后清空timer,此时timer为假,throttle触发能够进入计时器    }, delay)  }}div1.addEventListener('drag', throttle((e) => {  console.log(e.offsetX, e.offsetY)}, 100))

手写Promise加载图片

function getData(url) {  return new Promise((resolve, reject) => {    $.ajax({      url,      success(data) {        resolve(data)      },      error(err) {        reject(err)      }    })  })}const url1 = './data1.json'const url2 = './data2.json'const url3 = './data3.json'getData(url1).then(data1 => {  console.log(data1)  return getData(url2)}).then(data2 => {  console.log(data2)  return getData(url3)}).then(data3 =>  console.log(data3)).catch(err =>  console.error(err))

14. 函数实现一秒钟输入一个数

ES6:用let块级作用域的原理实现

for(let i=0;i<=10;i++){   //用var打印的都是11 setTimeout(()=>{    console.log(i); },1000*i)}

不必let的写法: 原理是用立刻执行函数发明一个块级作用域

for(var i = 1; i <= 10; i++){    (function (i) {        setTimeout(function () {            console.log(i);        }, 1000 * i)    })(i);}

15. 创立10个标签,点击的时候弹出来对应的序号?

var afor(let i=0;i<10;i++){ a=document.createElement('a') a.innerHTML=i+'<br>' a.addEventListener('click',function(e){     console.log(this)  //this为以后点击的<a>     e.preventDefault()  //如果调用这个办法,默认事件行为将不再触发。     //例如,在执行这个办法后,如果点击一个链接(a标签),浏览器不会跳转到新的 URL 去了。咱们能够用 event.isDefaultPrevented() 来确定这个办法是否(在那个事件对象上)被调用过了。     alert(i) }) const d=document.querySelector('div') d.appendChild(a)  //append向一个已存在的元素追加该元素。}

16. 实现事件订阅公布(eventBus)

实现EventBus类,有 on off once trigger性能,别离对应绑定事件监听器,解绑,执行一次后解除事件绑定,触发事件监听器。 这个题目面字节和快手都问到了,最近忙,答案会在后续更新

class EventBus {    on(eventName, listener) {}    off(eventName, listener) {}    once(eventName, listener) {}    trigger(eventName) {}}const e = new EventBus();// fn1 fn2e.on('e1', fn1)e.once('e1', fn2)e.trigger('e1') // fn1() fn2()e.trigger('e1') // fn1()e.off('e1', fn1)e.trigger('e1') // null

实现:

      //申明类      class EventBus {        constructor() {          this.eventList = {} //创建对象收集事件        }        //公布事件        $on(eventName, fn) {          //判断是否公布过事件名称? 增加公布 : 创立并增加公布          this.eventList[eventName]            ? this.eventList[eventName].push(fn)            : (this.eventList[eventName] = [fn])        }        //订阅事件        $emit(eventName) {          if (!eventName) throw new Error('请传入事件名')          //获取订阅传参          const data = [...arguments].slice(1)          if (this.eventList[eventName]) {            this.eventList[eventName].forEach((i) => {              try {                i(...data) //轮询事件              } catch (e) {                console.error(e + 'eventName:' + eventName) //收集执行时的报错              }            })          }        }        //执行一次        $once(eventName, fn) {          const _this = this          function onceHandle() {            fn.apply(null, arguments)            _this.$off(eventName, onceHandle) //执行胜利后勾销监听          }          this.$on(eventName, onceHandle)        }        //勾销订阅        $off(eventName, fn) {          //不传入参数时勾销全副订阅          if (!arguments.length) {            return (this.eventList = {})          }          //eventName传入的是数组时,勾销多个订阅          if (Array.isArray(eventName)) {            return eventName.forEach((event) => {              this.$off(event, fn)            })          }          //不传入fn时勾销事件名下的所有队列          if (arguments.length === 1 || !fn) {            this.eventList[eventName] = []          }          //勾销事件名下的fn          this.eventList[eventName] = this.eventList[eventName].filter(            (f) => f !== fn          )        }      }      const event = new EventBus()      let b = function (v1, v2, v3) {        console.log('b', v1, v2, v3)      }      let a = function () {        console.log('a')      }      event.$once('test', a)      event.$on('test', b)      event.$emit('test', 1, 2, 3, 45, 123)      event.$off(['test'], b)      event.$emit('test', 1, 2, 3, 45, 123)