JavaScript手写代码面试题

一、书写代码部分

1.实现一个new操作符

function mynew(func, ...args) {
    const obj = {}
    obj.__proto__ = func.prototype
    // apply通常是构造方法上的一个方法
    // 执行的目的是将构造方法func的this指向obj,同时将args以参数的形式传入构造方法func中
    // 同时用result接收的目的是为了看执行func之后函数有没有返回值
    // 可以达到判断这个返回值是原始类型还是对象类型
    // 因为new中有固定,执行new关键字的构造函数,若返回值是基础类型,则略过
    // 若返回值是对象类型,则实例对象就是这个返回的对象
    let result = func.apply(obj, args)
    return result instanceof Object ? result : obj
}
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function () {
    console.log(this.name)
}

let p = mynew(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123}
p.say() // huihui

2.实现一个call或者apply函数:

2.1 call语法

         func.call(thisArg,arg1,arg2,...),调用一个函数,其具有一个指定的this指向和列表参数

2.2 call实现

基本思路:

  • 1.判断传入的是否为函数对象,不是的话提示错误信息
  • 2.判断传入的上下文是否存在,不存在则指向window
  • 3.处理传入的参数,即截取除了第一个参数后的所有参数
  • 4.将方法绑定到上下文对象的属性上(这是变得指定对象调用的关键)
  • 5.获取调用后的结果,并删除刚才新增的属性(context.fn)
       //下面是call函数的实现
        //注意:不管是call还是apply都必须是函数调用,只是传入call或者apply参数的可以是任意对象
        //没有Array.call
        Function.prototype.myCall = function (context) {
            var _this = this;
            var res = null; //用于存储结果
            //1.判断是否为函数调用
            if (typeof _this !== 'function') {
                throw new TypeError('出错了')
            }
            //2.判断传入的对象是否存在
            context = context || window
            context.fn = this;
            //3.获取参数
            var agrs = [...arguments].slice(1)
            //调用的函数添加为传入对象的一个属性
            res = context.fn(...args)
            //删除新增的属性
            delete context.fn;
            //返回结果
            return res
        }


        //*******下面是测试代码***********
        var obj1 = {
            name: 'hzz',
            age: 18,
            sayName: function (xx) {
                console.log(this.name)
            }
        }
        var obj2 = {
            name: 'xxx'
        }
        obj1.sayName.myCall(obj2, 'ss')//xxx,这里的'ss'是无用参数


        var arr1 = [1, 2, 3, 4]
        var arr2 = [4, 8, 9, 12]
        const arr3 = arr1.filter.myCall(arr2, function (item) {
            return item > 2
        })
        console.log(arr3)// [4, 8, 9, 12]

2.4 apply语法

       func.call(thisArg,[argsArray]),调用一个函数,其具有一个指定的this指向和数组参数(或类似数组对象)

2.5apply实现

基本思路:与call一样,只是处理参数方面有所差异

        Function.prototype.myApply = function (context) {
            var res = null;
            if (typeof this !== 'function') {
                throw new TypeError('error')
            }
            context = context || window;
            context.fn = this;
            var args = [...arguments].slice(1);
            //处理参数不一样,即判断当有参数的时候传入参数调用,没有参数的时候直接调用
            if (arguments[1]) {
                res = context.fn(...arguments[1])
            } else {
                res = context.fn()
            }
            delete context.fn
            return res
        }

        //********测试代码************
        const arr4 = arr2.filter.myApply(arr1, [function (item) {
            return item > 2
        }])
        console.log(arr4)

2.6 升级简约版的call、apply的实现

        Function.prototype.myCall = function (context, ...args) {
            context = context || window;
            //创造唯一值,作为我们构造的context内部的方法名
            let fn = Symbol();
            //this指向调用call的函数
            context[fn] = this;
            //执行函数 并返回结果(相当于把自身作为传入context的方法进行调用了
            return context[fn](...args)
        }

        Function.prototype.myApply = function (context, agrs) {
            context = context || window;
            var fn = Symbol();
            context[fn] = this;
            return context[fn](...agrs)
        }

3.bind函数的实现(待)

4.实现instanceof

        function myInstanceof(left, right) {
            while (true) {
                if (left === null) {
                    return false
                }
                if (left.__proto__ === right.prototype) {
                    return true
                }
                //循环的条件
                left = left.__proto__
            }
        }

5.数组去重

//方法1 使用set
function uniqueArr(arrr{
    return [...new Set(arr)];
}

//方法二 使用filter
function uniqueArr(arr){
    return arr.filter((item,index)=>{
        return arr.indexOf(item)===index;
    }
}

6.数组扁平化

//直接使用flat函数,其中默认降2维,可以传入无限大数据使得不管多少维数组全部降为一维数组
const res1=arr.flat(Infinity)

7.实现一个你认为不错的js继承方式

这里实现的是寄生虫组合继承,思路即是两个构造函数之前的继承

实现代码如下:

    function Person(name, age) {
      this.name = name;
      this.age = age;
      this.sayName = function () {
        console.log(this.name)
      }
    }
    Person.prototype.running = function () {
      console.log('runnig')
    }

    Person.prototype.eating = function () {
      console.log('eating')
    }


    function Student(name, age, sno, friends) {
      Person.call(this, name, age);
      this.sno = sno;
      this.friends = friends;
    }

    Student.prototype = Object.create(Person.prototype)
    Student.prototype.constructor = Student//细分构造函数的类型
    console.log(Student.prototype)
    let obj = new Student('hzz', 18, '171010204', 'lhk')
    console.log(obj)
    obj.running()

8.防抖与节流

由于很早之前就有看过防抖节流的原理,源于当时看的时候刚接触前端,所以也是一知半解,更是分不清t防抖与节流的异同之处,今天在这里重新梳理一下其中的原理,并作于区分

8.1 防抖

1.理解

        防抖即是当你触发事件的时候,其对应的函数不会立即触发,而是被推迟一定的时间后触发;比如有个input输入框,我想输入"abc d"进行搜索的时候,如果你不做防抖处理,那么你每一此输入a、b、...都会触发事件,当你做了防抖处理的时候,①那么等你输入abc之后,间隔了一定的时间你没有再次输入,那么就会在这时触发事件,而假设你在输入abc之后,你停留了一定的时间,再次输入d的时候,当时停留的时间<你设置的时间,那么这个时间在你再次输入便会重新计算,然后重复①这样下去,就是防抖;

2.防抖的好处

        减少向服务端发送请求的次数,tigao性能

3.防抖的应用

  • input框输入
  • 点击提交事件的按钮
  • 屏幕的滚动

4.防抖的原理实现

    function debounce(fn, waitTime) {
      let timer = null;
      //返回这个函数,倒是触发事件的时候才触发这个函数
      return function (...args) {
        if (timer) {
          clearTimeout(timer)
        }
        timer = setTimeout(() => {
          //由于这个fn是传递进来的函数,且是独立调用的,因此
          //因此this指向并不是指向我触发的对象
          //且我们触发对象触发的函数实质是返回的函数,因此this指向我们可以从返回中的函数中获取
          //这里是箭头函数,所以可以直接向上查找
          fn.apply(this, args)
        }, waitTime)
      }
    }
   //下面是测试
    let count = 0
    const inputChange = function (event) {
      console.log(`触发了${++count}事件`, this, event)
    }
    //获取input原始
    let item = document.querySelector("input")
    //这里是没有做防抖的是,触发事件回立即执行inputChange函数
    // item.oninput = inputChange
    //这里是做了防抖处理
    item.oninput = debounce(inputChange, 1000)

5.上截图方便日后回头看便于理解

 8.2 节流

        节流函数即是控制一定时间内时间触发的频率,不管触发多少次,这个频率是固定的

应用:

    function thortter(fn, await) {
      //leading表示最后
      //记录上一次的开始时间,将lastTime放在外面的原因是,这个lastTime是变化的,我们需要记录其值
      //如果放在函数内部的话,那么每次触发事件函数的时候,这个lastTime都是新的
      let lastTime = 0;
      const _throtter = function (...args) {
        //每次触发函数的时候都可以得到最新的时间
        const nowTime = new Date().getTime()
        //当lastTime时间与当前时间差大于或者等于设置的频率时间的时候,就触发函数
        if (nowTime - lastTime >= await) {
          fn.apply(this, args)
          //更新改变
          lastTime = nowTime;
        }
      }
      return _throtter;
    }

9.实现深拷贝

        我们之前说的拷贝函数就是常规只能拷贝第一层,当拷贝的数据是引用类型的时候,拷贝的只是对象的引用,比如a={},b={}之间进行拷贝,那么由于a、b是引用类型的数据,那么两者之间数据的改变是互相影响的,因为拷贝的只是其中一个对象的引用地址,于是接下来实现深层拷贝,使得拷贝之后的数据互不影响

function deepCopy (target, hash = new WeakMap()) {
  if (typeof target !== 'object' || target === null) return target;
  if (hash.has(target)) return hash.get(target)
  let copyObj = Array.isArray(target) ? [] : {}
  console.log('copyObj',copyObj)
  for (const key in target) {
    if (target.hasOwnProperty(key)) {
      if (typeof target[key] === 'object' && target[key] !== null) {
        copyObj[key] = deepCopy(target[key], hash)
      } else {
        copyObj[key] = target[key]
      }
    }
  }
  return copyObj
}

10.promise是什么?能否手写一个?

  Promise是异步编程的一种解决方案

一个Promise有以下几种状态:

  • pending:初始等待状态,既不是成功,也不是失败的状态
  • fulfilled:以为这操作成完成
  • reject:以为这操作失败

而且需要注意的是,Promise的等待状态一旦改变,则不会再次发生改变,也就是说一旦变成fulfilled或者rejected状态,就不能再次改变。pending的状态一旦改变,Promise对象的then方法就会被调用,否则就会触发catch

 new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log(1);
        resolve()
      }, 100)
    }).then(res => {
      setTimeout(() => {
        console.log(2);
      }, 2000)
    }).then(res => {
      setTimeout(() => {
        console.log(3)
      }, 3000)
    }).catch((err) => {
      console.log(err)
    })

以下是Promise的手写实现,面试够用!

 //Promise的手写实现
    function muPromise(constructor) {
      let _this = this;
      //1.定义状态改变前的初始状态
      _this.status = 'pending';
      //2.分别订货状态为resolved和rejected的时候的状态
      _this.value = undefined;
      _this.reason = undefined;
      function resolve(value) {
        //使用“===”,保证了状态改变是不可逆的
        //这里表示不管你执行了resolve还是reject回调函数的时候,其其实的状态只能是‘pending’
        //所以需要提前判断,所以进入回调函数之后,改变其状态
        if (_this.status === 'pending') {
          _this.value = value;
          _this.status = 'resolved';
        }
      }
      function reject(reason) {
        if (_this.status === 'pending') {
          _this.reason = reason;
          _this.status = "rejected"
        }
      }
      //捕获构造函数异常
      try {
        //将resolve,reject作为回调函数的参数,这个回调函数又是Promise构造函数的参数
        //类似new Promise((resolve,reject)=>{})
        constructor(resolve, reject);
      } catch (e) {
        console.log(e)
      }
    }


    //定义链式调用then方法
    myPromise.prototype.then = function (onFullfilled, onRejected) {
      //传入的onFulfilled、onRejected均是回调函数
      let _this = this;
      switch (_this.status) {
        case 'resolved':
          //即将resolve(value)中的参数回调到onFullfilled回调函数中
          onFullfilled(_this.value);
          break;
        case "rejected":
          //即将resolve(value)中的参数回调到onRejected回调函数中
          onRejected(_this.reason);
          break;
        default:
      }
    }

11.手写Promise.all

        一般来数,Promise.all是用来处理多个并发请求,在一个页面所用到的不同接口的数据一起请求过来,不过在Promise.all中,如果其中有一个接口失败,那么多个请求也就失败了,页面可能啥也不来,这就看当前页面的耦合程度了。

实现思路:

  • 接受一个Promise实例的数组或者具有Iterator接口的对象作为参数
function promisAll(promises) {
      return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
          throw new TypeError(`argument must be a array`)
        }
        var resolvedCounter = 0;
        var promisesNum = promises.length
        var resolveResult = []//存储所有返回值结果
        for (let i = 0; i < promisesNum; i++) {
          Promise.resolve(promises[i]).then(value => {
            resolvedCounter++;
            resolveResult[i] = value
            if (resolvedCounter === promises.length) {
              return resolve(resolveResult)
            }
          }, error => {
            return reject(error)
          })
        }
      })
    }

    let p1 = new Promise((resolve, reject) => {
      setTimeout(function () {
        resolve(1)
      }, 1000)
    })
    let p2 = new Promise((resolve, reject) => {
      setTimeout(function () {
        resolve(2)
      }, 2000)
    })
    let p3 = new Promise((resolve, reject) => {
      setTimeout(function () {
        resolve(3)
      }, 3000)
    })

    promisAll([p1, p2, p3]).then(res => {
      console.log(res)
    })

12.实现Promise.race

13.实现bind函数

14.实现Ajax请求

        可参考:Ajax请求的五个步骤_weixin_45846357的博客-优快云博客_ajax请求的五个步骤

        ajax是异步请求的一种方式,同时ajax异步请求也不会刷新整个页面,也是局部更新

船舰ajax请求的步骤:

  1. 新创建一个XMLHttpRequest异步对象
var xhr = new XMLHttpRequest();

    2.在这个对象上使用open方法创建一个HTTP请求,open方法所需要的参数是请求方式、请求的地址、是否异步和用户的认证信息

// get请求如果有参数就需要在url后面拼接参数,
// post如果有参数,就在请求体中传递 xhr.open("get","validate.php?username="+name)
xhr.open("post","validate.php");

    3.设置请求体send()

// 1.get的参数在url拼接了,所以不需要在这个函数中设置
// 2.post的参数在这个函数中设置(如果有参数)
xhr.send(null) xhr.send("username="+name);

      4.让异步对象接收服务器的响应数据,一个成功的响应有两个条件:

  • 服务器成功响应了
  • 异步对象的响应状态为4(数据解析完毕可以使用了)
xhr.onreadystatechange = function(){ 
if(xhr.status == 200 && xhr.readyState == 4){ 
 console.log(xhr.responseText);
 }

具体实现如下:

    const baseURL = '/server';
    let xhr = new XHRHttpRequest();
    xhr.open('get', baseURL, true);
    xhr.send(null)
    xhr.onreadystatechange = function () {
      if (this.status === 200 && this.readystate === 4) {
        console.log(this.response)
      } else {
        console.log(this.statusText)
      }
    }

 15.使用promise封装ajax

    //使用promise封装ajax请求
    function getJson(url) {
      return new Promise((resolve, reject) => {
        let xhr = new XHRHttpRequest();
        xhr.open('get', url, true);
        xhr.send(null);
        xhr.onreadystatechange = function () {
          if (this.status === 200 && this.readystate === 4) {
            resolve(this.response);
          } else {
            reject(this.statusText)
          }
        }
      })
    }

16.实现浅拷贝

        浅拷贝指的是一个新的对象对原始对象属性值进行精准的拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用类型,那么拷贝的就是引用类型的引用地址,如果其中一个对象的引用地址发生改变,那么另一个对象也会发生变化

实现浅拷贝的方式有如下:

1.Object.assign()

2.扩展运算符:{...obj}

3.数组方法实现数组浅拷贝.slice(),这个方法可以从数组中返回选定的元素,用法:array.slice(start,end),该方法不会改变原数组,两个参数可选,都不写的时候可以实现一个数组的拷贝var copyArr=[1,2,3].slice()

17.手写Object.create

先来看看其作用:

    const f = myCreate(obj)
    console.log(f.name)
    const person = {
      isHuman: false,
      printIntroduction: function () {
        console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
      }
    };
    const me = Object.create(person); // me.__proto__ === person
    me.name = "Matthew"; // name属性被设置在新对象me上,而不是现有对象person上
    me.isHuman = true; // 继承的属性可以被重写
    me.printIntroduction(); // My name is Matthew. Am I human? true

思路:将传入的对象做为原型

    function myCreate(obj) {
      function F() { }
      F.prototype = obj;
      return new F()
    }

二、情景反映部分

1.用promise实现图片的异步加载

    let imageAsync = (url) => {
      return new Promise((resolve, reject) => {
        let img = new Image();
        img.src = url;
        img.onload = () => {
          console.log('图片请求成功,此处理进行通用操作')
          resolve(image)
        }
        img.onerror = (err) => {
          console.log('失败,处处进行失败的通用操作')
          reject(err)
        }
      })
    }

    imageAsync("url").then(() => {
      console.log("加载成功")
    }, () => {
      console.log("加载失败")
    })

2.实现双向数据绑定

    let obj = {}
    let input = document.querySelector('input')
    let span = document.querySelector("span")
    //数据劫持
    Object.defineProperty(obj, "text", {
      configurable: true,
      enumerable: true,
      get() {
        console.log('获取数据了')
      },
      set(newVal) {
        console.log('数据更新了')
        input.value = newVal;
        span.innerHTML = newVal;
      }
    })
    //输入监听
    input.addEventListener('keyup', function (e) {
      obj.text = e.target.value
    })

3.使用setTimeout实现setInterval

        setInterval的作用是每隔一段时间执行一个函数,但是这个执行不是真的到了时间会立即执行,他的真正作用是每隔一段时间将时间加入时间队列中去,只有当当前执行栈为空的时候,才去从时间队列中取出时间执行,所有可能会出现这样的情况,就是当执行长执行的时间很长时,导致事件队列里面积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能带间隔一段时间执行的效果

        针对setInterval的这个缺点,我们可以使用setTimeout递归调用来模拟setInterval,这样我们就的确保了只有一个事件结束了,我们就会触发下一个定时器,便解决了setInterval的问题

实现思路时使用递归函数,不断去执行setTimeout从而达到setInterval效果

function mySetInterval(fn, timeout) {
  // 控制器,控制定时器是否继续执行
  var timer = {
    flag: true
  };
  // 设置递归函数,模拟定时器执行。
  function interval() {
    if (timer.flag) {
      fn();
      setTimeout(interval, timeout);
    }
  }
  // 启动定时器
  setTimeout(interval, timeout);
  // 返回控制器
  return timer;
}

4.实现jsonp

    function addScript(scr, callBack) {
      const script = document.createElement('script');
      script.src = scr;
      script.type = "text/javascript";
      document.appendChild(script)
    }
    function handleRes(res) {
      console.log(res)
    }
    addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
    // 接口返回的数据格式
    handleRes({ a: 1, b: 2 });

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值