手寫面試題

甘先森發表於2020-02-24

1. new 的原始碼實現

function Student() {
      this.age = 40
}
Student.prototype.say = function () {}
var s = new Student()

function myNew(constructor, ...args) {
    // 1
    let obj = {}
    // 2
    constructor.apply(obj, args)
    // 3  核心程式碼
    obj.__proto__ = constructor.prototype
    // 4
    return obj
}
複製程式碼

2. 防抖與節流

const debounce = (fn,delay) => {  // delay 時間內函式只執行一次
  let timer = null
  return (...args) => {
    clearInterval(timer)
    timer = setTimeout(() => {
      fn.apply(this,...args)
    },delay)
  }
}

// 所謂節流,就是指連續觸發事件但是在 n 秒中只執行一次函式
const throttle = (fn,delay) => {
  let flag = true
  return (...args) => {
    // flag為false return
    if(!flag) return 
    flag = false
    setTimeout(() => {
      fn.apply(this,...args)
      flag = true
    },delay)
  }
}
複製程式碼

3. 深拷貝

let obj = {
    a: 1,
    field4: {
        child: 'child',
        child2: {
            child2: 'child2'
        }
    }
}
function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = {}
        //效能開銷
        for (const key in target) {
            cloneTarget[key] = clone(target[key])
        }
        return cloneTarget
    } else{
        return target
    }
}
var obj1 = clone(obj)
obj1.field4.child2.child2 = '7777'
console.log(obj)
console.log(obj1)

// obj2 = JSON.parse(JSON.stringify(obj)) 也可以用API方法
複製程式碼

4. 實現event(設計模式訂閱釋出)

// on監聽事件,off取消事件 ,trigger觸發事件,once只執行一次

class EventEmitter {
  constructor() {
    this._events = {}
  }
  emit(type,...args) {
    let handle = this._events[type]
    if(!handle) {
      return false
    }
    if(Array.isArray(handle)) {
      for(let i = 0;i < handle.length;i++) {
        handle[i].call(this,...args)
      }
    } else {
      handle.call(this,...args)
    }

  }
  on(type,fn) {
    let handle = this._events[type]
    if (typeof fn !== 'function') {
      return null
    }
    // 沒有就新增上
    if(!handle) {
      this._events[type] = fn
    } else {
      // 有就拼接上
      this._events[type] = [].concat(this._events[type],fn)
    }
  }
  off(type,fn) {
    if(fn === undefined) {
      this._events[type] = []
    } else {
      this._events[type] = this._events[type].filter(f => f !== fn)
    }
  }
  once(type,fn) {
    this._events[type] = fn
  }
}
複製程式碼

5. object.create

function create(proto) {
    function F() {}
    F.prototype = proto
    return new F()
}
複製程式碼

6. promise原生實現

class Promise {
  constructor (fn) {
    this.state = 'pending'
    this.value = undefined
    this.reason = undefined

    let resolve = value => {
      if (this.state === 'pending') { //啟動
        this.value = value
        this.state = 'fulfilled'//完成
      }
    }

    let reject = value => {
      if (this.state === 'pending') { //啟動
        this.value = value
        this.state = 'rejected'//失敗
      }
    }

    try {
      fn(resolve, reject)
    }catch (error){
      reject(error)
    }
  }
  then (onFulfilled, onRejected) {
      switch (this.state) {
        case 'fulfilled':
          onFulfilled()
          break;
        case 'rejected':
          onRejected()
          break;
      }
    }
}
let pro = new Promise()
複製程式碼

7. 原生ajax實現

let xhr = new XMLHttpRequest()

// 初始化配置請求
xhr.setRequestHeader("Content-Type", "application/json"); //設定請求頭
xhr.open(method,url,async)
// 配置請求體傳送到伺服器
xhr.send(data)
xhr.onreadystatechange = () => {
  // 0: 剛剛建立好了請求物件,但還未配置請求(未呼叫open方法)
  // 1: open方法已被呼叫
  // 2: send方法已被呼叫
  // 3: 正在接收伺服器的響應訊息體
  // 4: 伺服器響應的所有內容均已接收完畢
  
  // xhr.responseText: 獲取伺服器響應的訊息體文字

  // xhr.getResponseHeader("Content-Type") 獲取響應頭Content-Type
  if(xhr.readyState === 4 && xhr.status === 200) {
    // 成功接受
    console.log(xhr.responseText)
  }
}

// ajax
function ajax(options) {
  // 請求地址
  const url = options.url
  const method = options.method.toLocaleLowerCase() || 'get'

  // 預設非同步
  const async = options.async

  // 請求引數
  const data = options.data

  const xhr = new XMLHttpRequest() || new ActiveXObject('Microsoft','XMLHTTP')
  // 請求超時
  if(options.timeout && options.timeout > 0) {
    xhr.timeout = options.timeout
  }

  // 返回一個promise例項
  return new Promise((resolve,reject) => {
    xhr.ontimeout = () => reject && reject('請求超時')
    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4) {
        // 請求成功 200-300 狀態碼
        if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
          resolve(xhr.responseText)
        } else {
          reject && reject()
        }
      }
    }

    // 錯誤回撥
    xhr.onerror = (err) => {
      return reject && reject(err)
    }
    let paramArr = []
    let encodeData
    
    // 處理請求引數
    if(data instanceof Object) {
      for (let key in data) {
        // 引數拼接要通過 encodeURIComponent 進行編碼 
        paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
      }
      encodeData = paramArr.join('&')
    }
    // get 請求拼接引數
    if (method === 'get') {
      const index = url.indexOf('?')
      if(index === -1) url += "?"
      else if(index !== url.length -1) url += "&"
      // 拼接url
      url += encodeData
    }
    /*
    引數1:method--->get post
    引數2:請求的地址
    引數3:布林值 預設是false 非同步true同步
    */
    xhr.open(method,url,async) 

    if(method === 'get') {
      xhr.send(null)
    } else {
      xhr.setRequestHeader('Content-type','applicaton/x-www-form-urlencoded;chatset:utf-8')
      xhr.send(encodeData)
    }
  })
}
複製程式碼

8. 路由

// hash 路由
class Route {
  constructor() {
    // 路由儲存物件
    this.routes = {}
    // 當前的hash
    this.currentHash = ''
    
    this.freshRoute = this.freshRoute.bind() // 返回值是個函式

    // 監聽
    window.addEventListener('load',this.freshRoute,false)
    window.addEventListener('hashchange',this.freshRoute,false)
  }

  // 儲存
  storeRoute(path,cb) {
    this.routes[path] = cb || function () {}
  }

  // 更新
  freshRoute () {
    this.currentHash = location.hash.slice(1) || '/'
    this.routes[this.currentHash]()
  }
}
複製程式碼

9. 預編譯

# 預編譯 四部曲 發生在函式執行之前
1. 建立AO物件
2. 找形參和變數宣告,將變數申明和引數作為AO屬性名,值為undefined
3. 將實參值和形參值統一
4. 在函式體裡找函式申明,將函式申明作為AO物件屬性名,值賦予函式體

# 預編譯也發生在全域性
1. 建立GO物件
2. 找形參和變數宣告,將變數申明和引數作為GO屬性名,值為undefined
3. 在全域性裡找函式申明,將函式申明作為GO物件屬性名,值賦予函式體

先找AO,再找GO
複製程式碼

10. 函式柯里化

// 1. 
function add() {
    // 第一次執行時,定義一個陣列專門用來儲存所有的引數
    var _args = Array.prototype.slice.call(arguments);

    // 在內部宣告一個函式,利用閉包的特性儲存_args並收集所有的引數值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隱式轉換的特性,當最後執行時隱式轉換,並計算最終的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}
const a = add(1)(2)(3)()
console.log(a)

// 2. 
// curry 函式, 返回新函式, 逐漸的消化,
function curry(fn, args) {
    var length = fn.length;
    args = args || [];
    return function() {
        var _args = args.slice(0),
            arg, i;
        for (i = 0; i < arguments.length; i++) {
            arg = arguments[i];
            _args.push(arg);
        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}
var fn = curry(function(a, b, c) {
    console.log([a, b, c]);
});
複製程式碼

11. instanceof實現

function instance_of(L,R) {
    var O = R.prototype  //建構函式的原型物件
    L = L.__proto__;
    while(true) {
        if (L === null) return false
        if (L === O) {
            return true
        }
        L = L.__proto__
    }
}
複製程式碼

koa洋蔥模型

let middleWare1 = async function(ctx,next) {
  console.log(1)
  await next()
  console.log(2)
}

let middleWare2 = async function(ctx,next) {
  console.log(3)
  await next()
  console.log(4)
}

let middleWares = [middleWare1,middleWare2]
run()
function run() {
  const dispatch = (index) => {
    const fn = middleWares[index]
    // 處理 next 讓他去到下一個中介軟體
    fn({},() => {
      dispatch(index + 1)
    })
  }
  dispatch(0)
}
複製程式碼

redux 中介軟體

// 組合
const mid1 = next => action => {
  console.log(1)
  next()
  console.log(2)
}
// mid1(mid2())
// 需要 next 
// 指向下一項 mid2
// 組合 從右到左 組合完之後 把mid2 交給 mid1
const mid2 = next => action => {
  console.log(3)
  next()
  console.log(4)
}
const mids = [mid1,mid2]

// compose 組合
let fn1 = str => str.split('')
let fn2 = str => str.toLocaleLowerCase()

// 從右到左
function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
let opt = compose(fn1,fn2)
// console.log(opt('ABCDE'))

const chain = compose(...mids)
/**
 * redux dispatch 
 * 只能是 dispatch 一個物件
 * 但是 有了thunk之後,dispatch 函式
 * if(action === function) {
 * if(action ===promise {
//  * 執行原始的 dispatch})}
 */
let nbDispatch = chain(() => [
  console.log('我是最原始的dispatch')
])
nbDispatch()

複製程式碼

redux-thunk

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}
複製程式碼

promisify 的實現

 function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, data) => {
        if(err) {
          reject(err);
        }
        resolve(data)
      })
    })
  }
}
複製程式碼

相關文章