一文幫你搞定 90- 的 JS 手寫題

還在害怕手寫題嗎,本文可以幫你擴展並鞏固自己的 JS 基礎,順便搞定 90% 的手寫題。在工作中還可以對常用的需求進行手寫實現,比如深拷貝、防抖節流等可以直接用於往後的項目中,提高項目開發效率。不說廢話了,下面就直接上代碼吧。

1.call 的實現

Function.prototype.myCall = function(context,...args){
    let cxt = context || window;
    //將當前被調用的方法定義在cxt.func上.(爲了能以對象調用形式綁定this)
    //新建一個唯一的Symbol變量避免重複
    let func = Symbol() 
    cxt[func] = this;
    args = args ? args : []
    //以對象調用形式調用func,此時this指向cxt 也就是傳入的需要綁定的this指向
    const res = args.length > 0 ? cxt[func](...args) : cxt[func]();
    //刪除該方法,不然會對傳入對象造成污染(添加該方法)
    delete cxt[func];
    return res;
}

2.apply 的實現

Function.prototype.myApply = function(context,args = []){
    let cxt = context || window;
    //將當前被調用的方法定義在cxt.func上.(爲了能以對象調用形式綁定this)
    //新建一個唯一的Symbol變量避免重複
    let func = Symbol()
    cxt[func] = this;
    //以對象調用形式調用func,此時this指向cxt 也就是傳入的需要綁定的this指向
    const res = args.length > 0 ? cxt[func](...args) : cxt[func]();
    delete cxt[func];
    return res;
}

3.bind 的實現

需要考慮:

實現方法:

Function.prototype.myBind = function (context, ...args) {
    //新建一個變量賦值爲this,表示當前函數
    const fn = this
    //判斷有沒有傳參進來,若爲空則賦值[]
    args = args ? args : []
    //返回一個newFn函數,在裏面調用fn
    return function newFn(...newFnArgs) {
        if (this instanceof newFn) {
            return new fn(...args, ...newFnArgs)
        }
        return fn.apply(context, [...args,...newFnArgs])
    }
}
let name = '小王',age =17;
let obj = {
    name:'小張',
    age: this.age,
    myFun: function(from,to){
        console.log(this.name + ' 年齡 ' + this.age+'來自 '+from+'去往'+ to)
    }
}
let db = {
    name: '德瑪',
    age: 99
}

//結果
obj.myFun.myCall(db,'成都','上海');     // 德瑪 年齡 99  來自 成都去往上海
obj.myFun.myApply(db,['成都','上海']);      // 德瑪 年齡 99  來自 成都去往上海
obj.myFun.myBind(db,'成都','上海')();       // 德瑪 年齡 99  來自 成都去往上海
obj.myFun.myBind(db,['成都','上海'])();   // 德瑪 年齡 99  來自 成都, 上海去往 undefined

4. 寄生式組合繼承

function Person(obj) {
    this.name = obj.name
    this.age = obj.age
}
Person.prototype.add = function(value){
    console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})

function Person1(obj) {
    Person.call(this, obj)
    this.sex = obj.sex
}
// 這一步是繼承的關鍵
Person1.prototype = Object.create(Person.prototype);
Person1.prototype.constructor = Person1;

Person1.prototype.play = function(value){
    console.log(value)
}
var p2 = new Person1({name:"雞蛋", age: 118, sex: "男"})

5.ES6 繼承

//class 相當於es5中構造函數
//class中定義方法時,前後不能加function,全部定義在class的protopyte屬性中
//class中定義的所有方法是不可枚舉的
//class中只能定義方法,不能定義對象,變量等
//class和方法內默認都是嚴格模式
//es5中constructor爲隱式屬性
class People{
  constructor(name='wang',age='27'){
    this.name = name;
    this.age = age;
  }
  eat(){
    console.log(`${this.name} ${this.age} eat food`)
  }
}
//繼承父類
class Woman extends People{ 
   constructor(name = 'ren',age = '27'){ 
     //繼承父類屬性
     super(name, age); 
   } 
    eat(){ 
     //繼承父類方法
      super.eat() 
    } 
} 
let wonmanObj=new Woman('xiaoxiami'); 
wonmanObj.eat();

//es5繼承先創建子類的實例對象,然後再將父類的方法添加到this上(Parent.apply(this))。 
//es6繼承是使用關鍵字super先創建父類的實例對象this,最後在子類class中修改this。

6.new 的實現

function Ctor(){
    ....
}

function myNew(ctor,...args){
    if(typeof ctor !== 'function'){
      throw 'myNew function the first param must be a function';
    }
    var newObj = Object.create(ctor.prototype); //創建一個繼承自ctor.prototype的新對象
    var ctorReturnResult = ctor.apply(newObj, args); //將構造函數ctor的this綁定到newObj中
    var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
    var isFunction = typeof ctorReturnResult === 'function';
    if(isObject || isFunction){
        return ctorReturnResult;
    }
    return newObj;
}

let c = myNew(Ctor);

7.instanceof 的實現

function myInstanceOf(a,b){
    let left = a.__proto__;
    let right = b.prototype;
    while(true){
        if(left == null){
            return false
        }
        if(left == right){
            return true
        }
        left = left.__proto__
    }
}

//instanceof 運算符用於判斷構造函數的 prototype 屬性是否出現在對象的原型鏈中的任何位置。
function myInstanceof(left, right) {
    let proto = Object.getPrototypeOf(left), // 獲取對象的原型
    prototype = right.prototype; // 獲取構造函數的 prototype 對象
    // 判斷構造函數的 prototype 對象是否在對象的原型鏈上
    while (true) {
        if (!proto) return false;
        if (proto === prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
}

8.Object.create() 的實現

//簡略版
function myCreate(obj){
    // 新聲明一個函數
    function C(){};
    // 將函數的原型指向obj
    C.prototype = obj;
    // 返回這個函數的實力化對象
    return new C()
}
//官方版Polyfill
if (typeof Object.create !== "function") {
    Object.create = function (proto, propertiesObject) {
        if (typeof proto !== 'object' && typeof proto !== 'function') {
            throw new TypeError('Object prototype may only be an Object: ' + proto);
        } else if (proto === null) {
            throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
        }

        if (typeof propertiesObject !== 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");

        function F() {}
        F.prototype = proto;

        return new F();
    };
}

9. 實現 Object.assign

Object.assign2 = function(target, ...source) {
    if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object')
    }
    let ret = Object(target) 
    source.forEach(function(obj) {
        if (obj != null) {
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    ret[key] = obj[key]
                }
            }
        }
    })
    return ret
}

10.Promise 的實現

實現 Promise 需要完全讀懂 Promise A+ 規範,不過從總體的實現上看,有如下幾個點需要考慮到:

class Promise {
    callbacks = [];
    state = 'pending';//增加狀態
    value = null;//保存結果
    constructor(fn) {
        fn(this._resolve.bind(this), this._reject.bind(this));
    }
    then(onFulfilled, onRejected) {
        return new Promise((resolve, reject) ={
            this._handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    }
    _handle(callback) {
        if (this.state === 'pending') {
            this.callbacks.push(callback);
            return;
        }
 
        let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
 
        if (!cb) {//如果then中沒有傳遞任何東西
            cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
            cb(this.value);
            return;
        }
 
        let ret = cb(this.value);
        cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
        cb(ret);
    }
    _resolve(value) {
 
        if (value && (typeof value === 'object' || typeof value === 'function')) {
            var then = value.then;
            if (typeof then === 'function') {
                then.call(value, this._resolve.bind(this), this._reject.bind(this));
                return;
            }
        }
 
        this.state = 'fulfilled';//改變狀態
        this.value = value;//保存結果
        this.callbacks.forEach(callback => this._handle(callback));
    }
    _reject(error) {
        this.state = 'rejected';
        this.value = error;
        this.callbacks.forEach(callback => this._handle(callback));
    }
}

Promise.resolve

Promise.resolve(value) {
  if (value && value instanceof Promise) {
    return value;
  } else if (value && typeof value === 'object' && typeof value.then === 'function') {
    let then = value.then;
    return new Promise(resolve ={
      then(resolve);
    });
  } else if (value) {
    return new Promise(resolve => resolve(value));
  } else {
    return new Promise(resolve => resolve());
  }
}

Promise.reject

Promise.reject = function(reason) {
    return new Promise((resolve, reject) => reject(reason))
}

Promise.all

Promise.all = function(promiseArr) {
    let index = 0, result = []
    return new Promise((resolve, reject) ={
        promiseArr.forEach((p, i) ={
            Promise.resolve(p).then(val ={
                index++
                result[i] = val
                if (index === promiseArr.length) {
                    resolve(result)
                }
            }err ={
                reject(err)
            })
        })
    })
}

Promise.race

Promise.race = function(promiseArr) {
    return new Promise((resolve, reject) ={
        promiseArr.forEach(p ={
            Promise.resolve(p).then(val ={
                resolve(val)
            }err ={
                rejecte(err)
            })
        })
    })
}

11.Ajax 的實現

function ajax(url,method,body,headers){
    return new Promise((resolve,reject)=>{
        let req = new XMLHttpRequest();
        req.open(methods,url);
        for(let key in headers){
            req.setRequestHeader(key,headers[key])
        }
        req.onreadystatechange(()=>{
            if(req.readystate == 4){
                if(req.status >= '200' && req.status <= 300){
                    resolve(req.responeText)
                }else{
                    reject(req)
                }
            }
        })
        req.send(body)
    })
}

12. 實現防抖函數(debounce)

let debounce = (fn,time = 1000) ={
    let timeLock = null

    return function (...args){
        clearTimeout(timeLock)
        timeLock = setTimeout(()=>{
            fn(...args)
        },time)
    }
}

13. 實現節流函數(throttle)

let throttle = (fn,time = 1000) ={
    let flag = true;

    return function (...args){
        if(flag){
            flag = false;
            setTimeout(()=>{
                flag = true;
                fn(...args)
            },time)
        }
    }
}

14. 深拷貝(deepclone)

function deepClone(obj,hash = new WeakMap()){
    if(obj instanceof RegExp) return new RegExp(obj);
    if(obj instanceof Date) return new Date(obj);
    if(obj === null || typeof obj !== 'object') return obj;
    //循環引用的情況
    if(hash.has(obj)){
        return hash.get(obj)
    }
    //new 一個相應的對象
    //obj爲Array,相當於new Array()
    //obj爲Object,相當於new Object()
    let constr = new obj.constructor();
    hash.set(obj,constr);
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            constr[key] = deepClone(obj[key],hash)
        }
    }
    //考慮symbol的情況
    let symbolObj = Object.getOwnPropertySymbols(obj)
    for(let i=0;i<symbolObj.length;i++){
        if(obj.hasOwnProperty(symbolObj[i])){
            constr[symbolObj[i]] = deepClone(obj[symbolObj[i]],hash)
        }
    }
    return constr
}

15. 數組扁平化的實現 (flat)

let arr = [1,2,[3,4,[5,[6]]]]
console.log(arr.flat(Infinity))//flat參數爲指定要提取嵌套數組的結構深度,默認值爲 1
//用reduce實現
function fn(arr){
   return arr.reduce((prev,cur)=>{
      return prev.concat(Array.isArray(cur)?fn(cur):cur)
   },[])
}

16. 函數柯里化

function sumFn(a,b,c){return a+ b + c};
let sum = curry(sumFn);
sum(2)(3)(5)//10
sum(2,3)(5)//10
function curry(fn,...args){
  let fnLen = fn.length,
      argsLen = args.length;
  //對比函數的參數和當前傳入參數
  //若參數不夠就繼續遞歸返回curry
  //若參數夠就調用函數返回相應的值
  if(fnLen > argsLen){
    return function(...arg2s){
      return curry(fn,...args,...arg2s)
    }
  }else{
    return fn(...args)
  }
}

17. 使用閉包實現每隔一秒打印 1,2,3,4

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

18. 手寫一個 jsonp

const jsonp = function (url, data) {
    return new Promise((resolve, reject) ={
        // 初始化url
        let dataString = url.indexOf('?') === -1 ? '?' : ''
        let callbackName = `jsonpCB_${Date.now()}`
        url += `${dataString}callback=${callbackName}`
        if (data) {
            // 有請求參數,依次添加到url
            for (let k in data) {
                url += `${k}=${data[k]}`
            }
        }
        let jsNode = document.createElement('script')
        jsNode.src = url
        // 觸發callback,觸發後刪除js標籤和綁定在window上的callback
        window[callbackName] = result ={
            delete window[callbackName]
            document.body.removeChild(jsNode)
            if (result) {
                resolve(result)
            } else {
                reject('沒有返回數據')
            }
        }
        // js加載異常的情況
        jsNode.addEventListener('error'() ={
            delete window[callbackName]
            document.body.removeChild(jsNode)
            reject('JavaScript資源加載失敗')
        }false)
        // 添加js節點到document上時,開始請求
        document.body.appendChild(jsNode)
    })
}
jsonp('http://192.168.0.103:8081/jsonp'{
    a: 1,
    b: 'heiheihei'
})
.then(result ={
    console.log(result)
})
.catch(err ={
    console.error(err)
})

19. 手寫一個觀察者模式

class Subject{
  constructor(name){
    this.name = name
    this.observers = []
    this.state = 'XXXX'
  }
  // 被觀察者要提供一個接受觀察者的方法
  attach(observer){
    this.observers.push(observer)
  }

  // 改變被觀察着的狀態
  setState(newState){
    this.state = newState
    this.observers.forEach(o=>{
      o.update(newState)
    })
  }
}

class Observer{
  constructor(name){
    this.name = name
  }

  update(newState){
    console.log(`${this.name}say:${newState}`)
  }
}

// 被觀察者 燈
let sub = new Subject('燈')
let mm = new Observer('小明')
let jj = new Observer('小健')
 
// 訂閱 觀察者
sub.attach(mm)
sub.attach(jj)
 
sub.setState('燈亮了來電了')

20.EventEmitter 實現

class EventEmitter {
    constructor() {
        this.events = {};
    }
    on(event, callback) {
        let callbacks = this.events[event] || [];
        callbacks.push(callback);
        this.events[event] = callbacks;
        return this;
    }
    off(event, callback) {
        let callbacks = this.events[event];
        this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);
        return this;
    }
    emit(event, ...args) {
        let callbacks = this.events[event];
        callbacks.forEach(fn ={
            fn(...args);
        });
        return this;
    }
    once(event, callback) {
        let wrapFun = function (...args) {
            callback(...args);
            this.off(event, wrapFun);
        };
        this.on(event, wrapFun);
        return this;
    }
}

21. 生成隨機數的各種方法?

function getRandom(min, max) {
  return Math.floor(Math.random() * (max - min)) + min   
}

22. 如何實現數組的隨機排序?

let arr = [2,3,454,34,324,32]
arr.sort(randomSort)
function randomSort(a, b) {
  return Math.random() > 0.5 ? -1 : 1;
}

23. 寫一個通用的事件偵聽器函數。

const EventUtils = {
  // 視能力分別使用dom0||dom2||IE方式 來綁定事件
  // 添加事件
  addEvent: function(element, type, handler) {
    if (element.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent("on" + type, handler);
    } else {
      element["on" + type] = handler;
    }
  },
  // 移除事件
  removeEvent: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent("on" + type, handler);
    } else {
      element["on" + type] = null;
    }
  },
 // 獲取事件目標
  getTarget: function(event) {
    return event.target || event.srcElement;
  },
  // 獲取 event 對象的引用,取到事件的所有信息,確保隨時能使用 event
  getEvent: function(event) {
    return event || window.event;
  },
 // 阻止事件(主要是事件冒泡,因爲 IE 不支持事件捕獲)
  stopPropagation: function(event) {
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
  },
  // 取消事件的默認行爲
  preventDefault: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
  }
};

24. 使用迭代的方式實現 flatten 函數。

var arr = [1, 2, 3, [4, 5][6, [7, [8]]]]
/** * 使用遞歸的方式處理 * wrap 內保
存結果 ret * 返回一個遞歸函數 **/
function wrap() {
    var ret = [];
    return function flat(a) {
        for (var item of
            a) {
                if (item.constructor === Array) {
                    ret.concat(flat(item))
                } else {
                    ret.push(item)
                }
        }
        return ret
    }
} 
console.log(wrap()(arr));

25. 怎麼實現一個 sleep

function sleep(delay) {
  var start = (new Date()).getTime();
  while ((new Date()).getTime() - start < delay) {
    continue;
  }
}

function test() {
  console.log('111');
  sleep(2000);
  console.log('222');
}

test()

26. 實現正則切分千分位(10000 => 10,000)

//無小數點
let num1 = '1321434322222'
num1.replace(/(\d)(?=(\d{3})+$)/g,'$1,')
//有小數點
let num2 = '342243242322.3432423'
num2.replace(/(\d)(?=(\d{3})+\.)/g,'$1,')

27. 對象數組去重

輸入:
[{a:1,b:2,c:3},{b:2,c:3,a:1},{d:2,c:2}]
輸出:
[{a:1,b:2,c:3},{d:2,c:2}]
function objSort(obj){
    let newObj = {}
    //遍歷對象,並將key進行排序
    Object.keys(obj).sort().map(key ={
        newObj[key] = obj[key]
    })
    //將排序好的數組轉成字符串
    return JSON.stringify(newObj)
}

function unique(arr){
    let set = new Set();
    for(let i=0;i<arr.length;i++){
        let str = objSort(arr[i])
        set.add(str)
    }
    //將數組中的字符串轉回對象
    arr = [...set].map(item ={
        return JSON.parse(item)
    })
    return arr
}

28. 解析 URL Params 爲對象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 結果
{ user: 'anonymous',
  id: [ 123, 456 ], // 重複出現的 key 要組裝成數組,能被轉成數字的就轉成數字類型
  city: '北京', // 中文需解碼
  enabled: true, // 未指定值得 key 約定爲 true
}
*/
function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 後面的字符串取出來
  const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割後存到數組中
  let paramsObj = {};
  // 將 params 存到對象中
  paramsArr.forEach(param ={
    if (/=/.test(param)) { // 處理有 value 的參數
      let [key, val] = param.split('='); // 分割 key 和 value
      val = decodeURIComponent(val); // 解碼
      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉爲數字

      if (paramsObj.hasOwnProperty(key)) { // 如果對象有 key,則添加一個值
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else { // 如果對象沒有這個 key,創建 key 並設置值
        paramsObj[key] = val;
      }
    } else { // 處理沒有 value 的參數
      paramsObj[param] = true;
    }
  })

  return paramsObj;
}

29. 模板引擎實現

let template = '我是{{name}},年齡{{age}},性別{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年齡18,性別undefined
function render(template, data) {
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正則
  if (reg.test(template)) { // 判斷模板裏是否有模板字符串
    const name = reg.exec(template)[1]; // 查找當前模板裏第一個模板字符串的字段
    template = template.replace(reg, data[name]); // 將第一個模板字符串渲染
    return render(template, data); // 遞歸的渲染並返回渲染後的結構
  }
  return template; // 如果模板沒有模板字符串直接返回
}

30. 轉化爲駝峯命名

var s1 = "get-element-by-id"
// 轉化爲 getElementById
var f = function(s) {
    return s.replace(/-\w/g, function(x) {
        return x.slice(1).toUpperCase();
    })
}

31. 查找字符串中出現最多的字符和個數

let str = "abcabcabcbbccccc";
let num = 0;
let char = '';

 // 使其按照一定的次序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"

// 定義正則表達式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) ={
    if(num < $0.length){
        num = $0.length;
        char = $1;        
    }
});
console.log(`字符最多的是${char},出現了${num}`);

32. 圖片懶加載

let imgList = [...document.querySelectorAll('img')]
let length = imgList.length

const imgLazyLoad = function() {
    let count = 0
    return (function() {
        let deleteIndexList = []
        imgList.forEach((img, index) ={
            let rect = img.getBoundingClientRect()
            if (rect.top < window.innerHeight) {
                img.src = img.dataset.src
                deleteIndexList.push(index)
                count++
                if (count === length) {
                    document.removeEventListener('scroll', imgLazyLoad)
                }
            }
        })
        imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
    })()
}

// 這裏最好加上防抖處理
document.addEventListener('scroll', imgLazyLoad)

參考資料

作者:xpsilvester

https://juejin.cn/post/6963167124881670152

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/gpZmJ2ZljlW83Pb-TCnm3A