天天看点

前端面试之手写代码篇1、手写instanceof方法2、手写new操作符3、手写Promise.all()4、手写防抖函数5、手写节流函数6、手写call、apply函数7、手写bind函数8、封装ajax9、手写深拷贝10、实现数组扁平化

手写代码

  • 1、手写instanceof方法
  • 2、手写new操作符
  • 3、手写Promise.all()
  • 4、手写防抖函数
  • 5、手写节流函数
  • 6、手写call、apply函数
  • 7、手写bind函数
  • 8、封装ajax
  • 9、手写深拷贝
  • 10、实现数组扁平化

1、手写instanceof方法

instanceof运算符用于判断构造函数的prototype属性是否出现在对象的原型链中的任何位置。

实现的步骤:

1、首先获取对象的原型

2、然后获取构造函数的原型

3、然后一直循环判断构造函数的原型是否等于对象的原型,直到对象原型为null,因为原型链的最终为null

<script>
         /* 手写 instanceof方法 */
         function myInstanceof(left,right){
          // 1、获取对象的原型
          var proto = Object.getPrototypeOf(left);
          // 2、获取构造函数的原型对象
          var obj = right.prototype; 
          // 3、循环判断对象的原型是否等于构造函数的原型,直到对象原型为null,因为原型链最终为null
          while(true){
               if(!proto) return false;
               if(obj === proto) return true;
               proto = Object.getPrototypeOf(proto); // 再取对象原型的原型
          }
         }
         console.log(myInstanceof(133,String)) // false 因为133是数字类型,如果改为字符串就对了
     </script>
           

2、手写new操作符

在调用 new 的过程中会发生以上四件事情:

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

<script>
    /* 手写new操作符 */
    function objectFunction(){
      // 初始化
      let newObj = null;
      // shif()方法是从数组中删除第一个元素,并返回该元素的值
      // constructor 获取arguments中的第一个参数
      let constructor = Array.prototype.shift.call(arguments)
      
      // 判断是否为构造函数
      if(typeof constructor !== 'function'){
        console.log("error!");
        return;
      }

      /* new操作符过程 */
      // 1、创建一个新对象并且把构造函数的原型赋给新对象
      newObj = Object.create(constructor.prototype); // 类似 newObj={};newObj.__proto__ = constructor.prototype
      // 2、改变构造函数的this指向,并执行函数
      let result = constructor.apply(newObj,arguments);
      // 3、根据result结果返回.如果result是个引用数据类型 则返回,如果不是则忽略返回newObj
      if(result &&(typeof result === 'object' || typeof result === 'function')){
        return result;
      }else{
        return newObj;
      }
      // 使用方法
      objectFunction(构造函数,初始化参数)
    }
  </script>
           

3、手写Promise.all()

核心思路:

1、接受一个Promise实例的数组或具有Iterator接口的对象作为参数;

2、这个方法返回一个新的promise对象;

3、遍历传入的参数,用promise.resolve()将参数“包一层”,使其变成一个promise对象;

4、参数所有回调才是成功,返回值数组与参数顺序一致;

5、参数数组其中一个失败,则触发失败的Promise错误作为Promise.all的错误

<script>
    /* 手写Promise.all */
    var promiseAll = function (promise) {
      return new Promise(function (resolve, reject) {
        /* 判断传递过来的参数是否为数组 */
        if (!Array.isArray(promise)) {
          throw new TypeError("参数必须是数组")
        }

        let len = 0;//用来记录数组中每个promise成功状态的的长度
        let result = [];// 用来记录数组中每个promise的结果
        let arrLen = promise.length; // 记录参数数组的长度

        for (let i = 0; i < arrLen; i++) {
          /* Promise.resolve() 将现有对象转为Promise对象,使用then来执行 */
          Promise.resolve(promise[i]).then(value => { // 成功的回调
            len++;
            // result.push(value);
            result[i] = value
            if (len == arrLen) {
              return resolve(result)
            }
          }, error => { // 失败的回调
            return reject("error")
          })
        }
      })
    }

    // 测试
    let p1 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve(1)
            }, 1000)
        })
        let p2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve(2)
            }, 2000)
        })
        let p3 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve(3)
            }, 3000)
        })
        // 返回的顺序就是你传参的顺序
        promiseAll([p3, p1, p2]).then(res => {
            console.log(res) // [3, 1, 2]
        })

  </script>
           

4、手写防抖函数

let input =document.querySelector('input');
    // 调用封装好的防抖
    input.oninput = debounce(function(){
      console.log(this.value) 
    },500)


    /* 封装防抖 */
    // 防抖:在规定时间后才执行,如果在这段时间内有触发,是会重新计算,只执行最后一次
    function debounce(fn,wait){
      let time = null; // 初始一个定时器
      return function(){
        /* 如果发现之前有定时器,就清空 */
        if(time != null){
          clearTimeout(time); // 清除定时器
        }

        time = setTimeout(()=>{
          // console.log(this); // 这里的this指的是 input事件

          // 参数传递过来的fn this指向是window对象,我们需要让它指向input事件
          // 这里使用 call()改变指针指向,让fn寻找input为主人,这样input就可以使用fn函数
          fn.call(this) // 这里面的this指的就是 input事件
        },wait)
      }
    }
  </script>
           
前端面试之手写代码篇1、手写instanceof方法2、手写new操作符3、手写Promise.all()4、手写防抖函数5、手写节流函数6、手写call、apply函数7、手写bind函数8、封装ajax9、手写深拷贝10、实现数组扁平化

5、手写节流函数

<style>
  body{
    height: 2000px;
  }
</style>
<body>
  <script>
    /* 使用滚动条来触发 节流 */
    window.onscroll = throttle(function(){
      console.log("触发滚动条")
    },500)

    /* 封装节流 */
   // 节流:一段时间内只会执行一次,将频繁操作转换为少量操作
    function throttle(fn,delay){
      let flag = true; // 标记
      return function(){
        if(flag){
          // 设置一个定时器
          setTimeout(()=>{
            // 这里跟防抖是一样的,要让fn找滚动条事件为主人,改变fn的this指向
            fn.call(this); // 这里的this指的是 滚动条事件

            /* 这里为什么要设置flag=true? */
            /* 就好比一个接单兼职,刚开始是没有单子做,所以刚开始flag=true,表示我可以接单,一次接一个单子
            我要等 delay时间才能完成,这时就把牌牌flag=false,表示我还在完成单子任务,当我完成之后,我就把
            flag=true,继续开始接单。
             */
            // 这样去记忆会好些
            flag = true;
          },delay)
        }
        flag = false;
      }
    }
  </script>
           
前端面试之手写代码篇1、手写instanceof方法2、手写new操作符3、手写Promise.all()4、手写防抖函数5、手写节流函数6、手写call、apply函数7、手写bind函数8、封装ajax9、手写深拷贝10、实现数组扁平化

6、手写call、apply函数

手写call的实现步骤:

1、判断调用对象是否为函数

2、判断传入上下文对象是否存在,如果不存在,则设置为window

3、处理传入的参数,slice截取第一个参数后的所有参数

4、将函数作为上下文对象的一个属性

5、使用上下文对象来调用这个方法,并保存返回结果

6、删除刚才新增的属性

7、返回结果

apply函数的实现步骤与call函数的实现步骤其实差不多,只是处理传入参数那步不太同,注释我都在代码里面写的很清楚,如果还有不懂得地方,大家也可以给我留言
<script>
    /* 手写call函数 */
     Function.prototype.myCall = function(obj) {
      /* 这里的this我猜应该是函数调用者 */
       if (typeof this != 'function') {
         console.error("调用者不是函数")
    }

       // 如果没有传值过来就是window
       obj = obj || window; // obj 代表第一个参数
       let arr = [...arguments].slice(1); // 获取从第二个参数后面的数据
       obj.fn = this; // 给我们所在的obj函数添加的fn变量赋值this,而这个this就是我们调用myCall的函数

       // 执行 相当于 把arr参数传给调用myCall函数上去执行
       let result = obj.fn(...arr) // 扩张运算符 ... 可以把数组转为参数序列
       delete obj.fn; // 删除我们自己所在obj上添加的fn这个属性
       return result; // 返回结果
     }

    /* 手写apply函数 */
    // apply 与 call 不同点就是第二个参数。call 传递的是参数序列;apply传递的是一个数组 
    Function.prototype.myApply = function(obj){
      if(typeof this != 'function'){
        console.error("调用者不是函数")
      }

      let result = null;
      obj = obj || window;
      obj.fn = this;

      if(arguments[1]){
        result = obj.fn(...arguments[1])
      }else{
        result = obj.fn()
      }

      return result;
    }

    // 测试
    let dog = {
      name: '狗',
      eat(food1, food2) {
        console.log(this.name + '爱吃' + food1 + food2);
      }
    }
    let cat = {
      name: '猫',
    }
    dog.eat.call(cat, '鱼', '肉');     // 猫爱吃鱼肉
    dog.eat.myCall(cat, '鱼', '肉');   // 猫爱吃鱼肉
    dog.eat.myApply(cat, ['鱼', '肉']); // 猫爱吃鱼肉

  </script>

           

7、手写bind函数

function Text(a, b, c) {
      console.log("this--", this);
      console.log(a, b, c)
    }

   /* 手写bind函数 */
    /* 
    1、改变this指向
    2、第一个参数是 this的值,后面的参数是 函数接收的参数的值
    3、返回值不变
    */
Function.protoype.myBind = function(){
	if(typeof this != "function"){
		console.error("调用者不是函数")
    }
    let self = this; // this指的是函数调用者 Text函数
    let arr = [...arguments]; // 使用扩展运算符转为数组
    let fn = arr.shift(1); // 获取数组 arr的第一项参数,也就是 {name:"xuxi"}; 此时的arr数组变成了 [1,2,3]
	
	return function(){
	   return self.apply(fn.arr); // 改变this的指向
    }
}	
let bind = Text.myBind({ name: 'xuxi' }, 1, 2, 3)
    bind(); //bind接受的是一个函数,所以要进行调用
           
前端面试之手写代码篇1、手写instanceof方法2、手写new操作符3、手写Promise.all()4、手写防抖函数5、手写节流函数6、手写call、apply函数7、手写bind函数8、封装ajax9、手写深拷贝10、实现数组扁平化

8、封装ajax

/* 封装ajax */
    function myAjax(option) {

      // 1、创建 XMLHttpRequset对象
      let xhr = new XMLHttpRequest();

      option = option || {};
      // 初始化
      option.type = (option.type || 'GET').toUpperCase();
      option.dataType = option.dataType || 'json';
      let param = option.data; // 只有一个参数


      // 2、判断请求类型
      if (option.type === 'GET') {
        // 3、建立链接
        xhr.open(option.type, option.url + '?' + param, true);
        // 4、发送请求
        xhr.send(null);
      } else {
        xhr.open(option.type, option.url, true);
        xhr.send(param);
      }

      // 5、更加返回参数做出响应
      xhr.onreadystatechange = function () {
        let readystate = xhr.readyState;
        if (readystate === 4) { // 整个请求过程完毕
          let status = xhr.status;
          if (status >= 200 && status < 300) { // 200~299 成功的请求
            option.success && option.success(xhr.responseText, xhr.responseXML)
          }
        } else {
          option.fail && option.fail("error!!!")
        }
      }

    }

    // 调用
    myAjax({
      type: 'post',
      dataType: 'json',
      data: {},
      url: 'http://XXXX',
      success: function (text, xml) {//请求成功后的回调函数
        console.log(text)
      },
      fail: function (status) { //请求失败后的回调函数
        console.log(status)
      }
    })
           

9、手写深拷贝

/* 手写深拷贝 */
    // 第一种 JSON.stringify()
    /* var deepCopy = function(obj){
      if(!obj || typeof obj != 'object') return;
        return JSON.parse(JSON.stringify(obj))
    } */

    // 第二种方法 _.cloneDeep
   /*  var _ = require('lodash')
    var deepCopy = function(obj){
      if(!obj || typeof obj != 'object') return;

      return _.cloneDeep(obj)
    } */

    // 第三种 递归实现
    var deepCopy = function(obj){
      if(!obj || typeof obj != 'object') return;

      let newObj = Array.isArray(obj)? [] :{};

      for(let i in obj){
        if(obj[i] instanceof Object){
          newObj[i] = deepCopy(obj[i]); // 如果对象中的属性也是对象,那么就递归循环
        }else{
          newObj[i] = obj[i]
        }
      }

      return newObj;

    }




    let student = {
      name: '小明',
      age: 10,
      grrifriend: {
        name: '小麻花'
      }
    }

    let obj = deepCopy(student);
    student.grrifriend.name = "小茜";
    console.log("student---",student);
    console.log("obj---",obj)
  </script>
           
前端面试之手写代码篇1、手写instanceof方法2、手写new操作符3、手写Promise.all()4、手写防抖函数5、手写节流函数6、手写call、apply函数7、手写bind函数8、封装ajax9、手写深拷贝10、实现数组扁平化

10、实现数组扁平化

/* 第一种方法 递归实现 */
    var flatten = function(arr){
      let result = [];
      for(let i=0; i<arr.length; i++){
        if(Array.isArray(arr[i])){
          // 如果遇到数组中的元素为数组时,则把该数组元素扁平化,与之前进行连接,并且重新赋值给reslut
          result = result.concat(flatten(arr[i]));
        }else{
          result.push(arr[i]);
        }
      }
      return result;
    }

    /* 第二种方法 split和toString*/
    /*   var flatten = function(arr){
      return arr.toString().split(",")
    } */

    /* 第三种方法 ES6中的flat*/
    // 语法 arr.flat([depth]) 其中depth是flat的参数,depth是可以传递数组的展开深度(默认不填,数值为1),
    // 即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开。
   /*  var flatten = function(arr){
      return arr.flat(Infinity)
    }
 */
    let arr = [1, [2, [3, 4, 5]]];
    console.log(flatten(arr)); // [1,2,3,4,5]

           

继续阅读