web前端面试题之JavaScript手写系列

926 阅读6分钟

手写new操作符

  • 当然了,在手写之前,我们一定要知道new操作符都做了哪些事情,这样有利于理解。

  •   它创建了一个全新的对象。
    
      设置原型链,让空对象的__proto__属性,指向函数的Prototype。
    
      它使this指向新创建的对象。并且执行函数体
    
      通过new创建的每个对象将最终被Prototype链接到这个函数的prototype对象上。
    
      如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用。
    
      function myNew(classN, ...arg) { 
              var obj = {}; //var obj=new Object(); 
              obj.__proto__ = classN.prototype; 
              var res = classN.call(obj, ...arg); 
              return typeof res === 'object' ? res : obj; 
           }
    

手写call

  •   将函数设为对象的属性
    
      执行&删除这个函数
    
      指定this到函数并传入给定参数执行函数
    
      如果不传入参数,默认指向 window
    
      Function.prototype.myCall = function (context, ...arg) {
          if(typeof context !=='object'&& typeof context !=='function' && context !==null ){ 
              arg.unshift(context) 
              context =window; 
          }
          var n = Symbol();
          context[n] = this;
          var res = context[n](...arg);
          delete context[n];
          return res;
        }
        function f2(a, b) {
              console.log(this);
              console.log(a + b);
          }
          var obj = {
              q: 123,
              w: 456,
              e: 789
          }
          // f2.myCall(obj, 3, 3);
    

手写apply

  • 实现思路和bind类似,所以在此就不多废话了
Function.prototype.myApply = function (context, arg) {
    context = context || window;
    var n = Symbol();
    context[n] = this;
    var res = context[n](...arg);
    delete context[n];
    return res;
}
function fn(a, b, c, d) {
    console.log(this);
    console.log(a + b + c + d);
}
var obj = {
    a: 1,  b: 2,  c: 3,  d: 5
}
 // fn.myApply(obj, [5, 6, 7, 8]);

手写bind

  • 返回一个函数,绑定this,传递预置参数
  • bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效
    Function.prototype.myBind = function (context=window, ...arg) {
         var _this=this; 
         return function(...arr) {
            return _this.apply(context,arg.concat(arr)) 
            // return _this.apply(context,[...arg,...arr]) 
         }
    }    
     var fun = function () {
            console.log(arguments);
            console.log(this);
        }
         // fun.bind(obj, 666, 888);     
    

实现节流throttle

  • 将一个函数的调用频率限制在一定时间内,例如 1s,那么 1s 内这个函数一定不会被调用两次
    function throttle(fn,wait=1000) {
        var flag=true;
        return function () {
            if(!flag)return;
            flag=false;
            setTimeout(() => {
                flag=true;
                fn.call(this,...arguments)
            }, wait);
        }
    }
    function f() {
        console.log(this);
    }
    window.onscroll=throttle(f)
    

实现防抖debounce

  • 当一次事件发生后,事件处理器要等一定的时间,如果这段时间过去后再也没有事件发生,就处理最后一次发生的事件。(典型例子:限制 鼠标连击 触发。)
 function debounce(fn,wait) {
        wait=wait||100;
        var timer=null;
        return function () {
            clearTimeout(timer);
            timer=setTimeout(()=>{
                fn.apply(this,arguments)//保证this指向,传递参数
            },wait)
        }
    }
    let fn=function () {console.log(666)}
    let f=debounce(fn,100,true)
    window.onscroll=f;

实现深克隆

  • 面试版
  //数据初始化
  function Person(name) {
    this.name = name;
  }
  const WHO = new Person('who');
  const obj = {
      a: 1,
      b: function (arg) {
      console.log('嘤嘤嘤');
      },
      c: {
          d: 3,
          e: {
              f: [1,[2,[3,[4,[5]]]]],
              g: {
                  h: 5
              }
          }
      },
      date: [new Date(1536627600000), new Date(1540047600000)],
      reg: new RegExp('\\w+'),
      num: [NaN, Infinity, -Infinity],
      person: Jack,
  };
  //深克隆函数
  function deepClone(obj) {
    if (obj === null) return null;
    if (typeof obj !== 'object') return obj;
    if (obj.constructor === Date) return new Date(obj);
    if (obj.constructor === RegExp) return new RegExp(obj);
    const newObj = new obj.constructor ();  //保持继承链
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {   //不遍历其原型链上的属性
        const val = obj[key];
        newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合。
      }
    }
    return newObj;
  }
  //判断代码
  const obj2 = deepClone(obj);
  console.log(obj);
  console.log(obj2);
  obj.c.e.f = 1000;
  obj2.c.e.g.h = 2000;
  console.log(obj.c.e.f, obj2.c.e.f);
  console.log( obj.c.e.g.h, obj2.c.e.g.h);
  • 最简单的操作(弊端)

  •   他无法实现对函数 、RegExp等特殊对象的克隆
      会抛弃对象的constructor,所有的构造函数会指向Object
      对象有循环引用,会报错
    
       var newObj = JSON.parse( JSON.stringify( someObj ) )
    

数组扁平化

  • 利用flat
console.log(ary.flat(Infinity));
  • 利用tostring
 function flatten(ary) {
    return ary.toString().split(',').map(item=>+item)
}
console.log(flatten(ary));
  • 利用some
 function flatten(arr) {
    while (arr.some(item=>Array.isArray(item))) {
        arr=[].concat.apply([],arr);
    }
    return arr;
}
console.log(flatten(ary));
  • 利用扩展运算符
function flatten(arr) {
    while (ary.some(Array.isArray)) {
        ary = [].concat(...ary);
    }
    return ary;
    }
console.log(flatten(ary));
  • 利用foreach循环把括号去掉
function flat(arr) {
    let temp = [];
    function fn(ary) {
        ary.forEach(item => {
            if (typeof item == 'object') {
                fn(item)
            } else {
                temp.push(item)
            }
        })
    }
    fn(arr)
    return temp;
}
console.log(flat(ary));

实现Instanceof

function myInstance(temp, classN) {
    // temp通过__proto__向上查找的时候,若某次的__proto__ === classN.prototype  返回true
    // 当某次的__proto__ === null ;   这时返回false;
    let str=typeof temp;
    if(str !=='object' && str !=='function')return false
    var left = temp.__proto__,
        right = classN.prototype;
    while (left) {
        if (left === right) return true;
        left = left.__proto__;
    }
    return false
}
myInstance([], Number) //false

实现JSON.parse

  • 直接用eval,极易容易被xss攻击
    var json = '{"name":"cxk", "age":25}';
    var obj = eval("(" + json + ")");
    // JSON.parse(text[, reviver])
  • eval与 Function都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用。
    var jsonString = '{ "age": 18, "name": "ying" }'
    var json = (new Function('return ' + jsonString))();

实现JSON.stringify

  • 主要实现递归调用
    // 数据类型判断
    function getType(attr) {
      let type = Object.prototype.toString.call(attr);
      let newType = type.substr(8, type.length - 9);
      return newType;
    }
    // 转换函数
    function StringIfy(obj) {
      // 如果是非object类型 or null的类型直接返回 原值的String
      if (typeof obj !== "object" || getType(obj) === null) {
        return String(obj);
      }
      // 声明一个数组
      let json = [];
      // 判断当前传入参数是对象还是数组
      let arr = obj ? getType(obj) === "Array" : false;
      // 循环对象属性
      for (let key in obj) {
        // 判断属性是否在对象本身上
        if (obj.hasOwnProperty(key)) {
          // console.log(key, item);
          // 获取属性并且判断属性值类型
          let item = obj[key];
          if (item === obj) {
            console.error(new TypeError("Converting circular structure to JSON"));
            return false;
          }
          if (/Symbol|Function|Undefined/.test(getType(item))) {
            delete obj[key];
            continue;
          }
          // 如果为object类型递归调用
          if (getType(item) === "Object") {
            // consoarrle.log(item)
            item = StringIfy(item);
          }
          let IsQueto =
            getType(item) === "Number" ||
            getType(item) === "Boolean" ||
            getType(item) === "Null"
              ? ""
              : '"';
          // 拼接数组字段
          json.push((arr ? IsQueto : '"' + key + '": "') + String(item) + IsQueto);
        }
      }
      console.log(arr, String(json));
      // 转换数组字段为字符串
      return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
    }
    let aa = StringIfy([1, 2, 4]);
    let test = {
      name: "name",
      age: undefined,
      func: function() {},
      sym: Symbol("setter")
    };
    let newTest = StringIfy(test);
    console.log(aa, newTest);
    var firstObj = {
      name: "firstObj"
    };
    firstObj.newKey = firstObj;
    StringIfy(firstObj);

实现柯里化函数

function currying(fn, length) {
    length = length || fn.length; //传参数的话,就是传进来的参数,没有的话,就是函数的形参个数
    return function (...arg) {
        if (arg.length >= length) {
            // 如果实参的个数 大于等于 传递的参数
            return fn.apply(this, arg)
        } else {
            return currying(fn.bind(this, ...arg), length - arg.length);
            // bind 返回新函数体,不执行;
        }
    }
}
let f3 = function (a, b, c) {
    return a + b + c
}
let add = currying(f3);
// currying  柯里化函数
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));
console.log(add(1, 2, 3));

实现类的继承

  • 最理想的继承方式
    function Parent(name) {
        this.parent = name
    }
    Parent.prototype.say = function() {
        console.log(`${this.parent}: 你像ying`)
    }
    function Child(name, parent) {
        // 将父类的构造函数绑定在子类上
        Parent.call(this, parent)
        this.child = name
    }

    /** 
     1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
     2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
    3. Object.create是创建了父类原型的副本,与父类原型完全隔离
    */
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.say = function() {
        console.log(`${this.parent}好,我是${this.child}`);
    }
    
    // 注意记得把子类的构造指向子类本身
    Child.prototype.constructor = Child;
    
    var parent = new Parent('father');
    parent.say() // father: 你像ying
    
    var child = new Child('yingge', 'father');
    child.say() // father好,我是yingge
    实现create创建对象
    
    Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
    
    function create(proto) {
      function F() {}
      F.prototype = proto;
      return new F();
    }

解析URL为对象

  • split版
String.prototype.queryParams = function (key) {
    let str = this.split('?')[1];
    let arr = str.split('&');
    let obj = {};
    arr.forEach(item => {
        let a = item.split('=');
        obj[a[0]] = a[1];
    })
    return key ? obj[key] : obj;
}
let url2 = "http://www.zhufengpeixun.cn/?lx=1&from=wx";
console.log(url2.queryParams("from")); //=>"wx"
  • replace版
    var str = 'http://www.baidu.com?a=12&b=13&c=34';
    function getParam(str) {
        var o = {};
        str.replace(/([^?=&]+)=([^?=&]+)/g, (a, b, c) => o[b] = c);
        return o;
    }
    var obj = getParam(str);
    console.log(obj); //{a: "12", b: "13", c: "34"}

获取cookie

String.prototype.getCookie = function (key) {
    var reg=/([^; =]+)=([^; =]+)/;
    // console.log(reg.exec(cookie));
    var ary=reg.execAll(this);
    // console.log(ary);
    var obj={};
    ary.forEach(item => {
        obj[item[1]]=item[2]
    });
    return key ? obj[key] : obj
}
    var cookie =
    'BAIDUID=F02A1FE2F5665FB8E3520A8143BAEBE8:FG=1; BIDUPSID=F02A1FE2F5665FB8E3520A8143BAEBE8; PSTM=1555149539; BD_UPN=12314753; sugstore=1; MCITY=-131%3A; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BD_HOME=1; H_PS_PSSID=29634_1432_21125_20698_29522_29518_29720_29568_29221_29589; delPer=0; BD_CK_SAM=1; PSINO=1; H_PS_645EC=2172HsArbSEz1xcCOqM%2BZo9cSofi5X3%2B7EyRbM50a9N%2F%2FvGByg4%2F%2FEpbSsw';
    var res1=cookie.getCookie();
    var res2=cookie.getCookie('BIDUPSID');
    console.log(res1,res2);

实现模板引擎

    function render(template,data) {
    var str=template.replace(/\{\{(\w+)\}\}/g,function (context,$1) {
        return data[$1];        
    })
    return str
    }
    let template='我是{{name}},年龄{{age}},性别{{sex}}';
    let data ={
        name:'lili',age:18,sex:'man'
    }
    console.log (render(template,data))

实现千位分隔符

  • 也就是保留三位小数
 function millimeter() {
    return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content => content + ',');
 }
//补充下 怎么将它扩展到字符串的原型上呢?
["millimeter"].forEach(item => {
     String.prototype[item] = eval(item);
});

快速排序并去重

  • 利用双for
Array.prototype.unique = function () {
    for (var i = 0; i < this.length; i++) {
        var temp = this[i];
        for (var j = i + 1; j < this.length; j++) {
            if (temp == this[j]) {
                this.splice(j, 1) j--
            }
        }
    }
    return this
}
var ary = [1, 2, 1, 2, 1, 2, 1, 555, 55, 8, 1, 2, 8];
ary.unique();
  • es6的新增方法set
Array.prototype.unique = function () {
    var temp = new Set(this);
    return [...temp]
}
var ary = [1, 2, 1, 2, 1, 2, 1, 555, 55, 8, 1, 2, 8];
ary.unique();
  • 利用filter
var array = [1, 2, 1, 1, '1'];
function unique(array) {
    var res = array.filter(function(item, index, array){
        return array.indexOf(item) === index;
})
return res;
}
console.log(unique(array));
  • 利用concat
var array = [1, 2, 1, 1, '1'];
function unique(array) {
    return array.concat().sort().filter(function(item, index, array){
        return !index || item !== array[index - 1]
})
}
console.log(unique(array));
  • indexOf
var array = [1, 1, '1'];
function unique(array) {
    var res = [];
    for (var i = 0, len = array.length; i < len; i++) {
        var current = array[i];
        if (res.indexOf(current) === -1) {
            res.push(current)
        }
    }
    return res;
}
console.log(unique(array));

查找字符串中出现次数最多的字符

  • 利用去重思想
    let str = "zhufengpeixunzhoulaoshi";
    let obj = {};
    [].forEach.call(str, char => {
        if (typeof obj[char] !== "undefined") {
            obj[char]++;
            return;
        }
        obj[char] = 1;
    });
    let max = 1,
        res = [];
    for (let key in obj) {
        let item = obj[key];
        item > max ? max = item : null;
    }
    for (let key in obj) {
        let item = obj[key];
        if (item === max) {
            res.push(key);
        }
    }
    console.log(`出现次数最多的字符:${res},出现了${max}次`);
  • 原生方法,不推荐使用,但是逻辑比较清晰
    var str='aijiuxianglantianbaiyunqingkongwanli';
    var ary=str.split('').sort((a,b)=>a.localeCompare(b)).join('');
        console.log(ary);//aaaaaabgggiiiiiiijkllnnnnnnnoqtuuwxy
    var reg=/(\w)\1+/g;
    var res=ary.match(reg).sort((a,b)=>b.length-a.length);
        console.log(res); // ["iiiiiii", "nnnnnnn", "aaaaaa", "ggg", "ll", "uu"]
    var max=res[0].length;
        console.log(max);  //7
    var text=[res[0].substr(0,1)];
        console.log(text); //i
    for (let i = 1; i < res.length; i++) {
        var temp=res[i];
            console.log(temp);
        if(temp.length<max){
            break;
        }
        text.push(temp.substr(0,1))
    }
    console.log(`出现次数最多的字符是:${res}, ${max}次`);