手寫 new, call, apply, bind, reduce, currying, 防抖節流 源碼,並配上詳細分析

寫這些主要是爲了回顧與沉澱,再次編寫的過程當中也會提出本身的一些理解,有不恰當的地方但願指出。
在這裏推薦一個線上 IDE, 我沒事敲一些代碼 demo 就用它了 StackBlitz,仍是比較好使的,可是推薦在設置中改成保存的時候再更新,否則編寫的時候更新太頻繁會常常出問題。編程

實現 new

先用文字描述一下 new 的實現過程json

  • 定義一個 json 對象
  • 對象 繼承 構造函數的原型鏈
  • 將構造函數的 this 指向這個 json 對象
  • 根據構造函數的返回值類型返回結果,
function myNew(fn) {
    let obj = {}
    obj.__proto__ = Object.create(fn.prototype) 
    let args = Array.prototype.slice.call(arguments, 1) // 獲取除去fn以外的參數
    let result = fn.call(obj, ...args)
    return typeof result === 'object' ? result : obj;
  }
  function foo() {
    this.name = 'ciel'
    this.arg = arguments[0]
  }
  foo.prototype.callName = function() {
    console.log(this.name)
  }
  // 測試
  let test = myNew(foo, 'hhh', '123', 'saf')
  test.callName()
  console.log(test.arg)
複製代碼

這裏解釋一下 return typeof result === 'object' ? result : obj; 這句代碼:
在JavaScript構造函數中:若是return值類型,那麼對構造函數沒有影響,實例化對象返回空對象;若是return引用類型(數組,函數,對象),那麼實例化對象就會返回該引用類型; 能夠測試如下兩個構造函數在 new 以後返回的值就能夠理解這句話的意思了數組

function foo() {
   this.name = 'ciel'
   return function() {

   }
 }
 new foo() //  fn(){}
 function bar() {
   this.name = 'ciel'
   return 1
 }
 new bar() // {name: ciel}
複製代碼

實現 call

先看看僞代碼是如何使用 myCall 的 fn.myCall(obj, args) 分析下代碼應該怎麼實現bash

  • myCall 應該掛在 Function.prototype 上
  • fn 的 this 指向 爲 obj
  • myCall 的 args 透傳給 fn
Function.prototype.myCall = function(target, ...args) {
  // this 指向調用 myCall函數的對象
  if (typeof this !== "function") {
    throw new TypeError("not a function")
  }
  target = target || window
  target.fn = this // 隱式綁定,改變構造函數的調用者間接改變 this 指向
  let result = target.fn(...args)
  return result
};
// 測試
var obj = { name: 123 }
function foo(...args) {
  console.log(this.name, args)
}
var s = foo.myCall(obj, '111', '222')
複製代碼

實現 apply

回憶一下 apply 與 call 的區別: apply 參數要爲數組。 其餘和 call 實現同樣app

Function.prototype.myApply = function(target) {
  if (typeof this !== "function") {
    throw new TypeError("not a function");
  }
  if (!Array.isArray(arguments[1])) {
    throw new Error('arg not a array')
  }
  target = target || window
  target.fn = this
  let args = arguments[1]
  let result = target.fn(args)
  return result
};

var obj = { name: 123 };
function foo(...args) {
  console.log(this.name, args);
}
foo.prototype.name = 123;
var s1 = [1, 2, 3, 4, 5];
var s = foo.myApply(obj,s1);
複製代碼

實現 bind

  • 與 call 與 apply 的區別: fn.bind(obj) 不會當即執行 fn 函數,而 call, apply 會當即執行
  • bind 返回的新函數能夠普通調用也能夠構造函數方式調用,當爲構造函數時,this 是指向實例的
  • bind() 方法的參數具備一個特性,就是函數柯里化,簡單來講就是保留一個參數的位置,再第二次傳參的時候自動把參數存入到這個位置中
Function.prototype.mybind = function(thisArg) {
  if (typeof this !== 'function') {
    throw TypeError("Bind must be called on a function");
  }
  // 拿到參數,爲了傳給調用者
  const args = Array.prototype.slice.call(arguments, 1),
    self = this,
    // 構建一個乾淨的函數,用於保存原函數的原型
    nop = function() {},
    // 綁定的函數
    bound = function() {
      // this instanceof nop, 判斷是否使用 new 來調用 bound
      // 若是是 new 來調用的話,this的指向就是其實例,
      // 若是不是 new 調用的話,就改變 this 指向到指定的對象 o
      return self.apply(
        this instanceof nop ? this : thisArg,
        args.concat(Array.prototype.slice.call(arguments)) 
      );
    };
  // 箭頭函數沒有 prototype,箭頭函數this永遠指向它所在的做用域
  if (this.prototype) {
    nop.prototype = this.prototype;
  }
  // 修改綁定函數的原型指向
  bound.prototype = new nop();
  return bound;
}

// 測試

 let obj = { name: "ciel" }
    function test(x,y,z) {
      console.log(this.name) // ciel
      console.log(x+y+z) // 6
    }
    var Bound = test.mybind(obj, 1, 2)
    Bound(3) // 6
複製代碼

實現 reduce

arr.reduce((res,cur, index, arr) => res+cur, 0)函數

  • 參數: 一個回調函數,一個初始化參數 (非必須)
  • 回調函數參數有 4 個值(res: 表明累加值,cur: 目前值,index: 第幾個,arr 調用 reduce 的數組)
  • 總體返回 res 累加值
Array.prototype.myReduce = function(cb, initValue) {
  if (!Array.isArray(this)) {
    throw new TypeError("not a array")
  }
  // 數組爲空,而且有初始值,報錯
  if (this.length === 0 && arguments.length < 2) {
    throw new TypeError('Reduce of empty array with no initial value')
  }
  let arr = this
  let res = null
  // 判斷有沒有初始值
  if (arguments.length > 1) {
    res = initValue
  } else {
    res = arr.splice(0,1)[0] //沒有就取第一個值
  }
  arr.forEach((item, index) => {
    res = cb(res, item, index, arr) // cb 每次執行完都會返回一個新的 res值,覆蓋以前的 res
  })
  return res
};

// 測試結果
let arr = [1,2,3,4]
let result = arr.myReduce((res, cur) => {
  return res + cur
})
console.log(result) // 10
複製代碼

tip: 平時在工做中 處理數據的時候常常會用到 reduce, 實現一個數據處理本來屢次遍歷,由 reduce 實現可能就只須要遍歷一次測試

實現 Currying

什麼是柯里化? 將複雜問題分解爲多個可編程的小問題,實現多參函數提供了一個遞歸降解的實現思路——把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數 結合一個例子,實現以下效果ui

sum(1,2) // 3
sum(1,2)(3) // 6
sum(4,5)(10) // 19
複製代碼

實現代碼this

function sum() {
  let allArgs = Array.prototype.slice.call(arguments);
  let add = function(){
    allArgs.push(...arguments) // 每次調用 sum 函數都收集參數
    return add
  }
  // 重寫 toString 方法,函數執行的時候會自動調用toString()方法,計算返回全部參數結果
  add.toString = function () {
    return allArgs.reduce((a, b) => a+b)
  }
  return add
}

複製代碼

測試結果spa

實現防抖

防抖:觸發高頻事件後 n 秒內函數只會執行一次,若是 n 秒內高頻事件再次被觸發,則從新計算時間(取最後一次) 思路:每次觸發前都取消以前的延時調用方法

function debounce(fn, delay) {
   let timer = null
   return function() {
     let self = this // 這獲取 this 是由於 debounce() 返回的是內部函數,在這才能捕獲到 this。
     let args = Array.prototype.slice.call(arguments)
     if (timer) clearTimeout(timer) // 取消以前的 timer
     setTimeout(function () {
       fn.call(self, ...args) // 防止 this 指向改變,確保上下文爲當前的this,傳遞參數
     }, delay)
   }
 }
 function testFn() {
  console.log('被點擊了', this)
 }
 // 測試
document.addEventListener('click', debounce(testFn, 1000)) 
複製代碼

實現節流

節流:高頻事件觸發,但在 n 秒內只會執行一次,因此節流會稀釋函數的執行頻率 思路:每次觸發事件時都判斷當前是否有等待執行的延時函數,須要一個標記

function throtting(fn, delay) {
   let timer = null
   let isCancel = false
   return function() {
     if (isCancel) return
     isCancel = true
     clearTimeout(timer)
     let self = this;
     let args = Array.prototype.slice.call(arguments)
     if (timer) clearTimeout(timer)
     setTimeout(function () {
       fn.call(self, ...args)
       isCancel = false
     }, delay)
   }
 }
 function testFn() {
  console.log('輸入了', this)
 }
document.addEventListener('input', throtting(testFn, 1000)) 
複製代碼

在必定時間內只執行一次,判斷當前是否有等待執行的延時函數,有就返回

相關文章
相關標籤/搜索