评论

收藏

[JavaScript] 一文帮你搞定90%的JS手写题

开发技术 开发技术 发布于:2021-07-02 10:29 | 阅读数:349 | 评论:0

  
  还在害怕手写题吗,本文可以帮你扩展并巩固自己的JS基础,顺便搞定90%的手写题。在工作中还可以对常用的需求进行手写实现,比如深拷贝、防抖节流等可以直接用于往后的项目中,提高项目开发效率。不说废话了,下面就直接上代码吧。
1.call的实现

  • 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 String、Number、Boolean
  • 为了避免函数名与上下文(context)的属性发生冲突,使用Symbol类型作为唯一值
  • 将函数作为传入的上下文(context)属性执行
  • 函数执行完成后删除该属性
  • 返回执行结果
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的实现

  • 前部分与call一样
  • 第二个参数可以不传,但类型必须为数组或者类数组
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的实现
  需要考虑:

  • bind() 除了 this 外,还可传入多个参数;
  • bind 创建的新函数可能传入多个参数;
  • 新函数可能被当做构造函数调用;
  • 函数可能有返回值;
  实现方法:

  • bind 方法不会立即执行,需要返回一个待执行的函数;(闭包)
  • 实现作用域绑定(apply)
  • 参数传递(apply 的数组传参)
  • 当作为构造函数的时候,进行原型继承
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的实现

  • 一个继承自 Foo.prototype 的新对象被创建。
  • 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  • 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。
  • 一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤
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的实现

  • instanceof 是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。
  • instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
  • 不能检测基本数据类型,在原型链上的结果未必准确,不能检测null,undefined
  • 实现:遍历左边变量的原型链,直到找到右边变量的 prototype,如果没有找到,返回 false
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()的实现

  • MDN文档
  • Object.create()会将参数对象作为一个新创建的空对象的原型, 并返回这个空对象
//简略版function myCreate(obj){    // 新声明一个函数    function C(){};    // 将函数的原型指向obj    C.prototype = obj;    // 返回这个函数的实力化对象    return new C()}//官方版Polyfillif (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+ 规范,不过从总体的实现上看,有如下几个点需要考虑到:

  • Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆
  • then 需要支持链式调用
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

  • Promsie.resolve(value) 可以将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值本身是 Promise 则会原样返回它。
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.resolve() 类似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。
Promise.reject = function(reason) {    return new Promise((resolve, reject) => reject(reason))}
Promise.all

  • 传入的所有 Promsie 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;
  • 只要有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值;
  • 只要有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;
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 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。
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)

  • 判断类型,正则和日期直接返回新对象
  • 空或者非对象类型,直接返回原值
  • 考虑循环引用,判断如果hash中含有直接返回hash中的值
  • 新建一个相应的new obj.constructor加入hash
  • 遍历对象递归(普通key和key是symbol情况)
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)//10sum(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

  • 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}]

  • 首先写一个函数把对象中的key排序,然后再转成字符串
  • 遍历数组利用Set将转为字符串后的对象去重
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.查找字符串中出现最多的字符和个数

  • 例: abbcccddddd -> 字符最多的是d,出现了5次
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.lengthconst 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)
参考资料

  • 高频 JavaScript 手写
  • 初、中级前端应该要掌握的手写代码实现
  • 22 道高频 JavaScript 手写
  • 死磕 36 个 JS 手写题(搞懂后,提升真的大)


  
关注下面的标签,发现更多相似文章