前端手写题

42 阅读10分钟

1.深浅拷贝

1.1浅拷贝

浅拷贝拷贝的是栈的地址,修改拷贝后的数据,原数据会改变

image.png

image.png

  let obj={
      name:'ikun',
      age:32,
      hobby:['讲课','敲代码'],
      class:{
          name:'武汉1',
          salary:[18928,2933,1000]
      }
  }
  //浅拷贝
  let obj1=obj
  obj1.name='yy'
  console.log(obj1,obj)//输出内容一样

1.2深拷贝

深拷贝是拷贝堆的数据,修改拷贝后的数据,对原数据没有影响.

image.png

  
  //深拷贝有两种的方式//1.简单方式(实际开发中用的),只需要把js对象先转成json,然后转成js
  let json=JSON.stringify(obj)
  console.log(json);
  let js= JSON.parse(json)
  console.log(js)
  console.log(obj===js)//false
  //简写成为一行
  // let js=JSON.parse(JSON.stringify(obj))//2.复杂数据,递归实现
   let obj={
      name:'ikun',
      age:32,
      hobby:['讲课','敲代码'],
      class:{
          name:'武汉1',
          salary:[18928,2933,1000]
      }
  }
  ​
  //深拷贝有两种的方式//1.简单方式(实际开发中用的),只需要把js对象先转成json,然后转成js
  // let json=JSON.stringify(obj)
  // console.log(json)
  // let js= JSON.parse(json)
  // console.log(js)
  // console.log(obj===js)//false
  //简写成为一行
  // let js=JSON.parse(JSON.stringify(obj))//2.复杂数据,递归实现(对数组和对象类的数据,拷贝的还是地址)
  ​
    // function depcopy(newobj,obj){
    //   for(let key in obj){
    //   newobj[key]=obj[key]
    //   }
    // }
    // let newobj={}
    // depcopy(newobj,obj)
    // console.log(newobj===obj)//false
    // newobj.class.name="数学"//obj也会变
    // console.log(newobj.class===obj.class)//true 因为class是引用数据类型,拷贝的还是地址
  ​
    //改进做法:
    function depcopy(newobj,obj){
      for(let key in obj){
        if(obj[key] instanceof Array){
          newobj[key]=[]
          depcopy(newobj[key],obj[key])
          
        }else if(obj[key] instanceof Object){
  ​
          newobj[key]={}
          depcopy(newobj[key],obj[key])
        }else{
          newobj[key]=obj[key]
  ​
        }
      }
    }
    let newobj={}
    depcopy(newobj,obj)
    console.log(newobj===obj)//false
    newobj.class.name="数学"//obj不会变
    console.log(newobj.class===obj.class)//false
  ​
  ​

2.数组扁平化(5)

2.1 flat 方法实现

注:数组拍平方法 Array.prototype.flat() 也叫数组扁平化、数组拉平、数组降维。 本文统一叫:数组拍平

  let arr = [1, 2, [3, 4, [5, 6]]];
  let newArr = arr.flat(); // 默认情况下,只展开一层
  console.log(newArr); // 输出: [1, 2, 3, 4, [5, 6]]// 指定深度
  let Arr = arr.flat(Infinity);
  console.log(Arr); // 输出: [1, 2, 3, 4, 5, 6]
  ​
  ​
  let arr=[[1,2,3],4,5]
  let newarr=arr.flat(Infinity)
  console.log(newarr)//

Array.prototype.flat() 特性总结

  • Array.prototype.flat() 用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
  • 不传参数时,默认“拉平”一层,可以传入一个整数,表示想要“拉平”的层数。
  • 传入 <=0 的整数将返回原数组,不“拉平”
  • Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
  • 如果原数组有空位,Array.prototype.flat() 会跳过空位。

2.2使用递归

  
   const arr=[1,2,[3,4,[5,6]]]
  ​
    function flatten(arr) {
     let res = []; // 用于存储扁平化后的数组
     for (let i = 0; i < arr.length; i++) {
         if (Array.isArray(arr[i])) { // 判断当前元素是否是数组
             res = res.concat(flatten(arr[i])); // 递归调用 flatten,将子数组展开并合并
         } else {
             res.push(arr[i]); // 不是数组,直接加入 res
         }
     }
     return res; // 返回最终展开的一维数组
  }
  ​
  ​
     const  newarr=flatter(arr)
     console.log(newarr)
     

2.3 toString方法实现

注意:toString() 会自动展开嵌套数组,因此 [4, [5]] 被展开为 4,5

缺点:只对全为数字的数组有效

str.split(','):将字符串 str 按逗号 , 分割成一个字符串数组。结果是:["1", "2", "3", "4", "5"]

  
  const arr = [1, 2, [3, 4, [5, 6]]];
  ​
  let str = arr.toString(); // 得到字符串'1,2,3,4,5,6'
  // let arr2 = str.split(','); // 得到数组['1', '2', '3', '4', '5', '6'] 
  let newArr = str.split(',').map(item => {
    return Number(item);
  }); // [1, 2, 3, 4, 5, 6]
  console.log(newArr);
  ​

2.4reduce()

image.png

  
  //简写function flatten(arr){
    return arr.reduce((pre,item)=>{
      return  pre.cancat(Array.isArray(item)?flatten(item):item)
    },[])
  }

3.前端设计模式

3.1单例模式(*)

单例模式的核心是确保一个类只有一个实例,并提供全局访问点。在前端,可能用于管理全局状态、缓存、弹窗等。

4.数组去重

4.1Set方法

  
   //扩展运算符
  var arr = [1,1,13,4,4,5,6,77,8,8,9]
   console.log([...new Set(arr)]) 
   //[1, 13, 4, 5, 6, 77, 8, 9]//或者用Array.from
  let resultArr = Array.from(new Set(arr));

4.2indexOf用法

indexOf判断元素是否存在.

没有考虑到NaN

  
  function delequal(arr){
    let res=[]
    for(let i=0;i<arr.length;i++){
    if(res.indexOf(arr[i])===-1){
      res.push(arr[i])
    }
  }
  return res
  }
  let arr=[12,12,3,3,4,5]
  console.log(delequal(arr)); 
  //输出[12, 3, 4, 5]
  ​
  ​
  let arr1=[1, "1", 2, true, "true", false, null, {}, {}, "abc", undefined, NaN, NaN]
  console.log(delequal(arr1));
  //输出[1, '1', 2, true, 'true', false, null, {…}, {…}, 'abc', undefined, NaN, NaN]

4.3include

考虑到了NaN

  
  function delequal(arr){
    let res=[]
    for(let i=0;i<arr.length;i++){
      if(!res.includes(arr[i])){
        res.push(arr[i])
      }
  ​
    }
    return res
  }
  let arr=[1, "1", 2, true, "true", false, null, {}, {}, "abc", undefined, NaN, NaN]
  console.log(delequal(arr));
  //输出[1, '1', 2, true, 'true', false, null, {…}, {…}, 'abc', undefined, NaN]

4.3Map

第一种

let originalArray=[1, "1", 2, true, "true", false, null, {}, {}, "abc", undefined, NaN, NaN]
  const resultArr = new Array();
  for (let i = 0; i < originalArray.length; i++) {
      // 没有该 key 值
      if (!map.has(originalArray[i])) {
          map.set(originalArray[i], true);
          resultArr.push(originalArray[i]);
      }
  }
          
  console.log(resultArr);
  // [1, "1", 2, true, "true", false, null, {…}, {…}, "abc", undefined, NaN]

第二种

map.keys() 返回 Map 中所有键的迭代器对象。 map.keys(); // MapIterator {12, 3, 4, 5}

使用扩展运算符 ...MapIterator 迭代器转换成数组: [...map.keys()] // [12, 3, 4, 5]

function unique(arr) {
      let map = new Map(); // 创建一个空的 Map 对象
  ​
      for (let item of arr) { 
          map.set(item, true); // 将每个元素作为键存入 Map
      }
  ​
      return [...map.keys()]; // 返回 Map 的键,去重后的结果
  }
  ​
  let arr = [12, 12, 3, 3, 4, 5];
  console.log(unique(arr)); // [12, 3, 4, 5]

4.4flitter和include

filter() 是 JavaScript 数组的一个高阶方法,用于筛选满足特定条件的元素,并返回一个新数组。

indexOf() 是 JavaScript 中的一个数组方法,用来查找数组中某个元素的第一个出现位置的索引

image.png


  let unique= arr.filter((item,index) =>arr.indexOf(item) ===index) ;//筛选为true的元素
  ​
  ​
  //indexOf返回第一个出现的位置索引
  const arr = [10, 20, 30, 40, 50, 20];
  ​
  console.log(arr.indexOf(20)); // 1 -> 第一个20出现在索引1
  console.log(arr.indexOf(30)); // 2 -> 30出现在索引2
  console.log(arr.indexOf(100)); // -1 -> 100不存在

4.5双层for循环+切割原数组实现

同样还是采用两个for循环,但与上面不同的是通过两个先后指针i、j来循环同一个数组,遇到相同的元素进行删除,然后j后退一位,因为删除一位的话,后面的元素会补上,所以j后退以为,以便循环的时候不会漏掉补位的元素

  
      
  function unique(arr){
      for(let i=0;i<arr.length;i++){
          for(let j=i+1;j<arr.length;j++){
              if(arr[i] === arr[j]){
                  arr.splice(j,1)
                  j--
              }
          }
      }
      return arr
  }
  ​

5.promise.all和promise.race(*)

6.模拟实现new

7.实现call/apply/bind

7.1call

语法: function.call(thisArg, arg1, arg2, ...)。 其中thisArg是要设置为函数执行上下文的对象,也就是this要指向的对象,从第二个参数开始,arg1, arg2, ... 是传递给函数的参数。通过使用call方法,可以将一个对象的方法应用到另一个对象上。

          let obj = {
              name: "一个"
          }
  ​
          function allName(firstName, lastName) {
              console.log(this)
              console.log(`我的全名是“${firstName}${this.name}${lastName}”`)
          }
          // 很明显此时allName函数是没有name属性的
          allName('我是', '前端') //我的全名是“我是前端”  this指向window
          allName.call(obj, '我是', '前端') //我的全名是“我是一个前端” this指向obj// 定义一个对象
  const person1 = {
    name: 'Alice',
    greet: function() {
      console.log(`Hello, ${this.name}!`);
    }
  };
  ​
  // 定义另一个对象
  const person2 = {
    name: 'Bob'
  };
  ​
  // 使用call方法将person1的greet方法应用到person2上
  person1.greet.call(person2); // 输出:Hello, Bob!
  ​
  ​

手写实现

  • 原理:
  1. 首先,通过 Function.prototype.myCall 将自定义的 myCall 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。
  2. myCall 方法内部,首先通过 typeof this !== "function" 判断调用 myCall 的对象是否为函数。如果不是函数,则抛出一个类型错误。
  3. 然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象;ES11 引入了 globalThis,它是一个统一的全局对象,无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
  4. 接下来,使用 Symbol 创建一个唯一的键 fn,用于将调用 myCall 的函数绑定到上下文对象的新属性上。
  5. 将调用 myCall 的函数赋值给上下文对象的 fn 属性,实现了将函数绑定到上下文对象上的效果。
  6. 调用绑定在上下文对象上的函数,并传入 myCall 方法的其他参数 args
  7. 将绑定在上下文对象上的函数删除,以避免对上下文对象造成影响。
  8. 返回函数调用的结果。
  
   <script>
      Function.prototype.Mycall = function (context, ...args) {
        if (typeof this !== "function") {
          throw new TypeError("Function.prototype.myBind - 被调用的对象必须是函数");
        }
   
        if (!Array.isArray(args)) {
          throw new TypeError("Function.prototype.myApply - 第二个参数必须是数组");
        }
        console.log(args);
  ​
        context = context || globalThis
        let fn = Symbol("key")
        context[fn] = this
        const result = context[fn](...args)
  ​
        delete context[fn]
        return result
  ​
      }
  ​
      const test = {
        name: "xxx",
        hello: function () {
          console.log(`hello,${this.name}!`);
        },
        add: function (a, b) {
          return a + b;
        },
      };
      const obj = { name: "world" };
      test.hello.Mycall(obj); //hello,world!
      test.hello.call(obj);//hello,world!
      console.log(test.add.Mycall(null, 1, 2));//3
      console.log(test.add.call(null, 1, 2));//3
  ​
  ​
    </script>

7.2apply

语法:function.apply(thisArg, [argsArray])。 其中thisArg是要设置为函数执行上下文的对象,也就是this要指向的对象,argsArray是一个包含参数的数组。通过使用apply方法,可以将一个对象的方法应用到另一个对象上,并使用数组作为参数。

  
  function greet(name) {
    console.log(`Hello, ${name}!`);
      console.log(`Hello, ${this.name}!`);
  }
  ​
  const person = { name: 'John' };
  greet.apply(person, ['Mary']); // 输出:Hello, Mary!

手写实现

  • 原理:apply的实现思路跟call类似,就是apply传入参数是以数组的形式传入,所以多了一步判断传入的参数是否为数组以及在调用方法的时候使用扩展运算符 ... 将传入的参数数组 argsArr 展开
  <script>
  ​
      Function.prototype.myApply = function (context, argsArr) {
        // 判断调用myApply的是否为函数
        if (typeof this !== "function") {
          throw new TypeError("Function.prototype.myApply - 被调用的对象必须是函数");
        }
  ​
        // 判断传入的参数是否为数组
        if (argsArr && !Array.isArray(argsArr)) {
          throw new TypeError("Function.prototype.myApply - 第二个参数必须是数组");
        }
  ​
        // 如果没有传入上下文对象,则默认为全局对象
        // ES11 引入了 globalThis,它是一个统一的全局对象
        // 无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
        context = context || globalThis;
  ​
        //如果第二个参数省略则赋值空数组
        argsArr = argsArr || [];
  ​
        // 用Symbol来创建唯一的fn,防止名字冲突
        let fn = Symbol("key");
  ​
        // this是调用myApply的函数,将函数绑定到上下文对象的新属性上
        context[fn] = this;
  ​
        // 传入myApply的多个参数
        const result = context[fn](...argsArr)
  ​
        // 将增加的fn方法删除
        delete context[fn];
  ​
        return result;
      };
  ​
  ​
      const test = {
        name: "xxx",
        hello: function () {
          console.log(`hello,${this.name}!`);
        },
      };
      const obj = { name: "world" };
      test.hello.myApply(obj); //hello,world!
      test.hello.apply(obj); //hello,world!
  ​
  ​
  ​
      console.log(Math.max.apply(null, arr));//9
  ​
    </script>

7.3bind

语法:function.bind(thisArg, arg1, arg2, ...)。 其中thisArg是要绑定到函数执行上下文的对象,也就是this要指向的对象,从第二个参数开始,arg1, arg2, ...是传递给函数的参数。与call和apply方法不同,bind方法并不会立即执行函数,而是返回一个新函数,可以稍后调用。这对于事件处理程序和setTimeout函数等场景非常有用。

  function greet(name) {
    console.log("Hello, " + name);
  }
  ​
  const delayedGreet = greet.bind(null, "John");
  setTimeout(delayedGreet, 2000);  // 2秒后输出:Hello, John
  ​
  ​
  //例二
  ​
          let obj = {
              name: "一个"
          }
  ​
          function allName(firstName, lastName, flag) {
              console.log(this)
              console.log(`我的全名是"${firstName}${this.name}${lastName}"我的座右铭是"${flag}"`)
          }   
          allName.bind(obj) //不会执行
          let fn = allName.bind(obj)
          fn('我是', '前端', '好好学习天天向上')
  ​
          // 也可以这样用,参数可以分开传。bind后的函数参数默认排列在原函数参数后边
          fn = allName.bind(obj, "你是")
          fn('前端', '好好学习天天向上')
  ​

手写实现

  • 原理:
  1. 首先,通过 Function.prototype.myBind 将自定义的 myBind 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。
  2. myBind 方法内部,首先通过 typeof this !== "function" 判断调用 myBind 的对象是否为函数。如果不是函数,则抛出一个类型错误。
  3. 然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象;ES11 引入了 globalThis,它是一个统一的全局对象,无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
  4. 保存原始函数的引用,使用 _this 变量来表示。
  5. 返回一个新的闭包函数 fn 作为绑定函数。这个函数接受任意数量的参数 innerArgs。(关于闭包的介绍可以看这篇文章->闭包的应用场景
  6. 在返回的函数 fn 中,首先判断是否通过 new 关键字调用了函数。这里需要注意一点,如果返回出去的函数被当作构造函数使用,即使用 new 关键字调用时,this 的值会指向新创建的实例对象。通过检查 this instanceof fn,可以判断返回出去的函数是否被作为构造函数调用。这里使用 new _this(...args, ...innerArgs) 来创建新对象。
  7. 如果不是通过 new 调用的,就使用 apply 方法将原始函数 _this 绑定到指定的上下文对象 context 上。这里使用 apply 方法的目的是将参数数组 args.concat(innerArgs) 作为参数传递给原始函数。
  1. this instanceof fn 的原理

  • instanceof 运算符用于检查对象的原型链中是否存在某个构造函数的 prototype
  • fn 通过 new 调用时,thisfn 的实例,因此 this.__proto__ === fn.prototype
  • 所以,this instanceof fntrue

示例:

  
  function fn() {}
  ​
  const instance = new fn();
  console.log(instance instanceof fn); // 输出: true

2. myBind 中的 if (this instanceof fn)

myBind 的实现中:

  • fn 是返回的新函数。
  • 如果 fn 通过 new 调用,this 会指向 fn 的实例。
  • 因此,this instanceof fntrue,表示当前是通过 new 调用的。

代码解析:

  
  return function fn(...innerArgs) {
      if (this instanceof fn) {
          // 如果是通过 new 调用,则忽略绑定的上下文
          return new _this(...args, ...innerArgs);
      }
      // 否则,绑定上下文并调用原函数
      return _this.apply(context, args.concat(innerArgs));
  };
  
  <script>
  ​
  ​
      Function.prototype.myBind = function (context, ...args) {
        if (typeof this !== "function") {
          throw new TypeError("Function.prototype.myBind - 被调用的对象必须是函数");
        }
        context = context || globalThis
  ​
        const _this = this
  ​
        return function fn(...extralargs) {
          if (this instanceof fn) {
            return new _this(...args, ...extralargs)
          }
          return _this.apply(context, args.concat(...extralargs))
  ​
        }
  ​
      }
  ​
      const test = {
        name: "xxx",
        hello: function (a, b, c) {
          console.log(`hello,${this.name}!`, a + b + c);
        },
      };
      const obj = { name: "world" };
      let hello1 = test.hello.myBind(obj, 1);
      let hello2 = test.hello.bind(obj, 1);
      hello1(2, 3)//hello,world! 6
      hello2(2, 3)//hello,world! 6
      console.log(new hello1(2, 3));
      //hello,undefined! 6
      // hello {}
      console.log(new hello2(2, 3));
      //hello,undefined! 6
      // hello {}
  ​
    </script>

8.模拟Object.create()的实现

9.千分位分隔符

10.实现三角形

11.实现三栏布局/双栏布局