• 高级前端手写面试题


    树形结构转成列表(处理菜单)

    [
        {
            id: 1,
            text: '节点1',
            parentId: 0,
            children: [
                {
                    id:2,
                    text: '节点1_1',
                    parentId:1
                }
            ]
        }
    ]
    转成
    [
        {
            id: 1,
            text: '节点1',
            parentId: 0 //这里用0表示为顶级节点
        },
        {
            id: 2,
            text: '节点1_1',
            parentId: 1 //通过这个字段来确定子父级
        }
        ...
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    实现代码如下:

    function treeToList(data) {
      let res = [];
      const dfs = (tree) => {
        tree.forEach((item) => {
          if (item.children) {
            dfs(item.children);
            delete item.children;
          }
          res.push(item);
        });
      };
      dfs(data);
      return res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    前端手写面试题详细解答

    对象数组列表转成树形结构(处理菜单)

    [
        {
            id: 1,
            text: '节点1',
            parentId: 0 //这里用0表示为顶级节点
        },
        {
            id: 2,
            text: '节点1_1',
            parentId: 1 //通过这个字段来确定子父级
        }
        ...
    ]
    
    转成
    [
        {
            id: 1,
            text: '节点1',
            parentId: 0,
            children: [
                {
                    id:2,
                    text: '节点1_1',
                    parentId:1
                }
            ]
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    实现代码如下:

    function listToTree(data) {
      let temp = {};
      let treeData = [];
      for (let i = 0; i < data.length; i++) {
        temp[data[i].id] = data[i];
      }
      for (let i in temp) {
        if (+temp[i].parentId != 0) {
          if (!temp[temp[i].parentId].children) {
            temp[temp[i].parentId].children = [];
          }
          temp[temp[i].parentId].children.push(temp[i]);
        } else {
          treeData.push(temp[i]);
        }
      }
      return treeData;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    实现ES6的extends

    function B(name){
      this.name = name;
    };
    function A(name,age){
      //1.将A的原型指向B
      Object.setPrototypeOf(A,B);
      //2.用A的实例作为this调用B,得到继承B之后的实例,这一步相当于调用super
      Object.getPrototypeOf(A).call(this, name)
      //3.将A原有的属性添加到新实例上
      this.age = age; 
      //4.返回新实例对象
      return this;
    };
    var a = new A('poetry',22);
    console.log(a);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    实现apply方法

    apply原理与call很相似,不多赘述

    // 模拟 apply
    Function.prototype.myapply = function(context, arr) {
      var context = Object(context) || window;
      context.fn = this;
    
      var result;
      if (!arr) {
        result = context.fn();
      } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
          args.push("arr[" + i + "]");
        }
        result = eval("context.fn(" + args + ")");
      }
    
      delete context.fn;
      return result;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    手写 Promise.race

    该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.

    Promise.race = function (args) {
      return new Promise((resolve, reject) => {
        for (let i = 0, len = args.length; i < len; i++) {
          args[i].then(resolve, reject)
        }
      })
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    debounce(防抖

    触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间。

    const debounce = (fn, time) => {
      let timeout = null;
      return function() {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          fn.apply(this, arguments);
        }, time);
      }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    防抖常应用于用户进行搜索输入节约请求资源,window触发resize事件时进行防抖只触发一次。

    实现数组的push方法

    let arr = [];
    Array.prototype.push = function() {
        for( let i = 0 ; i < arguments.length ; i++){
            this[this.length] = arguments[i] ;
        }
        return this.length;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    实现深拷贝

    • 浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用  Object.assign 和展开运算符来实现。
    • 深拷贝: 深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败
    (1)JSON.stringify()
    • JSON.parse(JSON.stringify(obj))是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringifyjs对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象。
    • 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。
    let obj1 = {  a: 0,
                  b: {
                     c: 0
                     }
                };
    let obj2 = JSON.parse(JSON.stringify(obj1));
    obj1.a = 1;
    obj1.b.c = 1;
    console.log(obj1); // {a: 1, b: {c: 1}}
    console.log(obj2); // {a: 0, b: {c: 0}}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    (2)函数库lodash的_.cloneDeep方法

    该函数库也有提供_.cloneDeep用来做 Deep Copy

    var _ = require('lodash');
    var obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    var obj2 = _.cloneDeep(obj1);
    console.log(obj1.b.f === obj2.b.f);// false
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    (3)手写实现深拷贝函数
    // 深拷贝的实现
    function deepCopy(object) {
      if (!object || typeof object !== "object") return;
    
      let newObject = Array.isArray(object) ? [] : {};
    
      for (let key in object) {
        if (object.hasOwnProperty(key)) {
          newObject[key] =
            typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
        }
      }
    
      return newObject;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    递归反转链表

    // node节点
    class Node {
      constructor(element,next) {
        this.element = element
        this.next = next
      } 
    }
    
    class LinkedList {
     constructor() {
       this.head = null // 默认应该指向第一个节点
       this.size = 0 // 通过这个长度可以遍历这个链表
     }
     // 增加O(n)
     add(index,element) {
       if(arguments.length === 1) {
         // 向末尾添加
         element = index // 当前元素等于传递的第一项
         index = this.size // 索引指向最后一个元素
       }
      if(index < 0 || index > this.size) {
        throw new Error('添加的索引不正常')
      }
      if(index === 0) {
        // 直接找到头部 把头部改掉 性能更好
        let head = this.head
        this.head = new Node(element,head)
      } else {
        // 获取当前头指针
        let current = this.head
        // 不停遍历 直到找到最后一项 添加的索引是1就找到第0个的next赋值
        for (let i = 0; i < index-1; i++) { // 找到它的前一个
          current = current.next
        }
        // 让创建的元素指向上一个元素的下一个
        // 看图理解next层级 ![](http://img-repo.poetries.top/images/20210522115056.png)
        current.next = new Node(element,current.next) // 让当前元素指向下一个元素的next
      }
    
      this.size++;
     }
     // 删除O(n)
     remove(index) {
      if(index < 0 || index >= this.size) {
        throw new Error('删除的索引不正常')
      }
      this.size--
      if(index === 0) {
        let head = this.head
        this.head = this.head.next // 移动指针位置
    
        return head // 返回删除的元素
      }else {
        let current = this.head
        for (let i = 0; i < index-1; i++) { // index-1找到它的前一个
          current = current.next
        }
        let returnVal = current.next // 返回删除的元素
        // 找到待删除的指针的上一个 current.next.next 
        // 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可
        current.next = current.next.next 
    
        return returnVal
      }
     }
     // 查找O(n)
     get(index) {
      if(index < 0 || index >= this.size) {
        throw new Error('查找的索引不正常')
      }
      let current = this.head
      for (let i = 0; i < index; i++) {
        current = current.next
      }
      return current
     }
     reverse() {
      const reverse = head=>{
        if(head == null || head.next == null) {
          return head
        }
        let newHead = reverse(head.next)
        // 从这个链表的最后一个开始反转,让当前下一个元素的next指向自己,自己指向null
        // ![](http://img-repo.poetries.top/images/20210522161710.png)
        // 刚开始反转的是最后两个
        head.next.next = head
        head.next = null
    
        return newHead
      }
      return reverse(this.head)
     }
    }
    
    let ll = new LinkedList()
    
    ll.add(1)
    ll.add(2)
    ll.add(3)
    ll.add(4)
    
    // console.dir(ll,{depth: 1000})
    
    console.log(ll.reverse())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    实现一个padStart()或padEnd()的polyfil

    String.prototype.padStartString.prototype.padEndES8中新增的方法,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。我们先看下使用语法:

    String.padStart(targetLength,[padString])
    
    • 1

    用法:

    'x'.padStart(4, 'ab') // 'abax'
    'x'.padEnd(5, 'ab') // 'xabab'
    
    // 1. 若是输入的目标长度小于字符串原本的长度则返回字符串本身
    'xxx'.padStart(2, 's') // 'xxx'
    
    // 2. 第二个参数的默认值为 " ",长度是为1的
    // 3. 而此参数可能是个不确定长度的字符串,若是要填充的内容达到了目标长度,则将不要的部分截取
    'xxx'.padStart(5, 'sss') // ssxxx
    
    // 4. 可用来处理日期、金额格式化问题
    '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
    '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    polyfill实现:

    String.prototype.myPadStart = function (targetLen, padString = " ") {
      if (!targetLen) {
        throw new Error('请输入需要填充到的长度');
      }
      let originStr = String(this); // 获取到调用的字符串, 因为this原本是String{},所以需要用String转为字符串
      let originLen = originStr.length; // 调用的字符串原本的长度
      if (originLen >= targetLen) return originStr; // 若是 原本 > 目标 则返回原本字符串
      let diffNum = targetLen - originLen; // 10 - 6 // 差值
      for (let i = 0; i < diffNum; i++) { // 要添加几个成员
        for (let j = 0; j < padString.length; j++) { // 输入的padString的长度可能不为1
          if (originStr.length === targetLen) break; // 判断每一次添加之后是否到了目标长度
          originStr = `${padString[j]}${originStr}`;
        }
        if (originStr.length === targetLen) break;
      }
      return originStr;
    }
    console.log('xxx'.myPadStart(16))
    console.log('xxx'.padStart(16))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    还是比较简单的,而padEnd的实现和它一样,只需要把第二层for循环里的${padString[j]}${orignStr}换下位置就可以了。

    Array.prototype.map()

    Array.prototype.map = function(callback, thisArg) {
      if (this == undefined) {
        throw new TypeError('this is null or not defined');
      }
      if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
      }
      const res = [];
      // 同理
      const O = Object(this);
      const len = O.length >>> 0;
      for (let i = 0; i < len; i++) {
        if (i in O) {
          // 调用回调函数并传入新数组
          res[i] = callback.call(thisArg, O[i], i, this);
        }
      }
      return res;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    用正则写一个根据name获取cookie中的值的方法

    function getCookie(name) {
      var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]*)'));
      if (match) return unescape(match[2]);
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 获取页面上的cookie可以使用 document.cookie

    这里获取到的是类似于这样的字符串:

    'username=poetry; user-id=12345; user-roles=home, me, setting'
    
    • 1

    可以看到这么几个信息:

    • 每一个cookie都是由 name=value 这样的形式存储的

    • 每一项的开头可能是一个空串''(比如username的开头其实就是), 也可能是一个空字符串' '(比如user-id的开头就是)

    • 每一项用";"来区分

    • 如果某项中有多个值的时候,是用","来连接的(比如user-roles的值)

    • 每一项的结尾可能是有";"的(比如username的结尾),也可能是没有的(比如user-roles的结尾)

    1. 所以我们将这里的正则拆分一下:
    • '(^| )'表示的就是获取每一项的开头,因为我们知道如果^不是放在[]里的话就是表示开头匹配。所以这里(^| )的意思其实就被拆分为(^)表示的匹配username这种情况,它前面什么都没有是一个空串(你可以把(^)理解为^它后面还有一个隐藏的'');而|表示的就是或者是一个" "(为了匹配user-id开头的这种情况)

    • +name+这没什么好说的

    • =([^;]*)这里匹配的就是=后面的值了,比如poetry;刚刚说了^要是放在[]里的话就表示"除了^后面的内容都能匹配",也就是非的意思。所以这里([^;]*)表示的是除了";"这个字符串别的都匹配(*应该都知道什么意思吧,匹配0次或多次)

    • 有的大佬等号后面是这样写的'=([^;]*)(;|$)',而最后为什么可以把'(;|$)'给省略呢?因为其实最后一个cookie项是没有';'的,所以它可以合并到=([^;]*)这一步。

    1. 最后获取到的match其实是一个长度为4的数组。比如:
    [
      "username=poetry;",
      "",
      "poetry",
      ";"
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 第0项:全量
    • 第1项:开头
    • 第2项:中间的值
    • 第3项:结尾

    所以我们是要拿第2项match[2]的值。

    1. 为了防止获取到的值是%xxx这样的字符序列,需要用unescape()方法解码。

    滚动加载

    原理就是监听页面滚动事件,分析clientHeightscrollTopscrollHeight三者的属性关系。

    window.addEventListener('scroll', function() {
      const clientHeight = document.documentElement.clientHeight;
      const scrollTop = document.documentElement.scrollTop;
      const scrollHeight = document.documentElement.scrollHeight;
      if (clientHeight + scrollTop >= scrollHeight) {
        // 检测到滚动至页面底部,进行后续操作
        // ...
      }
    }, false);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用 reduce 求和

    arr = [1,2,3,4,5,6,7,8,9,10],求和

    let arr = [1,2,3,4,5,6,7,8,9,10]
    arr.reduce((prev, cur) => { return prev + cur }, 0)
    
    
    • 1
    • 2
    • 3

    arr = [1,2,3,[[4,5],6],7,8,9],求和

    let arr = [1,2,3,4,5,6,7,8,9,10]
    arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0)
    
    
    • 1
    • 2
    • 3

    arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和

    let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] 
    
    arr.reduce((prev, cur) => {
        return prev + cur["a"];
    }, 0)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    实现AJAX请求

    AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。

    创建AJAX请求的步骤:

    • 创建一个 XMLHttpRequest 对象。
    • 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
    • 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。
    • 当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。
    const SERVER_URL = "/server";
    let xhr = new XMLHttpRequest();
    // 创建 Http 请求
    xhr.open("GET", SERVER_URL, true);
    // 设置状态监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当请求成功时
      if (this.status === 200) {
        handle(this.response);
      } else {
        console.error(this.statusText);
      }
    };
    // 设置请求失败时的监听函数
    xhr.onerror = function() {
      console.error(this.statusText);
    };
    // 设置请求头信息
    xhr.responseType = "json";
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 Http 请求
    xhr.send(null);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    JSONP

    script标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好但仅限于GET请求

    const jsonp = ({ url, params, callbackName }) => {
      const generateUrl = () => {
        let dataSrc = '';
        for (let key in params) {
          if (Object.prototype.hasOwnProperty.call(params, key)) {
            dataSrc += `${key}=${params[key]}&`;
          }
        }
        dataSrc += `callback=${callbackName}`;
        return `${url}?${dataSrc}`;
      }
      return new Promise((resolve, reject) => {
        const scriptEle = document.createElement('script');
        scriptEle.src = generateUrl();
        document.body.appendChild(scriptEle);
        window[callbackName] = data => {
          resolve(data);
          document.removeChild(scriptEle);
        }
      })
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    实现Object.freeze

    Object.freeze冻结一个对象,让其不能再添加/删除属性,也不能修改该对象已有属性的可枚举性、可配置可写性,也不能修改已有属性的值和它的原型属性,最后返回一个和传入参数相同的对象

    function myFreeze(obj){
      // 判断参数是否为Object类型,如果是就封闭对象,循环遍历对象。去掉原型属性,将其writable特性设置为false
      if(obj instanceof Object){
        Object.seal(obj);  // 封闭对象
        for(let key in obj){
          if(obj.hasOwnProperty(key)){
            Object.defineProperty(obj,key,{
              writable:false   // 设置只读
            })
            // 如果属性值依然为对象,要通过递归来进行进一步的冻结
            myFreeze(obj[key]);  
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    类数组转化为数组

    类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。

    方法一:Array.from
    Array.from(document.querySelectorAll('div'))
    
    
    • 1
    • 2
    方法二:Array.prototype.slice.call()
    Array.prototype.slice.call(document.querySelectorAll('div'))
    
    
    • 1
    • 2
    方法三:扩展运算符
    [...document.querySelectorAll('div')]
    
    
    • 1
    • 2
    方法四:利用concat
    Array.prototype.concat.apply([], document.querySelectorAll('div'));
    
    
    • 1
    • 2

    图片懒加载

    可以给img标签统一自定义属性data-src='default.png',当检测到图片出现在窗口之后再补充src属性,此时才会进行图片资源加载。

    function lazyload() {
      const imgs = document.getElementsByTagName('img');
      const len = imgs.length;
      // 视口的高度
      const viewHeight = document.documentElement.clientHeight;
      // 滚动条高度
      const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
      for (let i = 0; i < len; i++) {
        const offsetHeight = imgs[i].offsetTop;
        if (offsetHeight < viewHeight + scrollHeight) {
          const src = imgs[i].dataset.src;
          imgs[i].src = src;
        }
      }
    }
    
    // 可以使用节流优化一下
    window.addEventListener('scroll', lazyload);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    实现数组去重

    给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。

    ES6方法(使用数据结构集合):

    const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
    
    Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
    
    
    • 1
    • 2
    • 3
    • 4

    ES5方法:使用map存储不重复的数字

    const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
    
    uniqueArray(array); // [1, 2, 3, 5, 9, 8]
    
    function uniqueArray(array) {
      let map = {};
      let res = [];
      for(var i = 0; i < array.length; i++) {
        if(!map.hasOwnProperty([array[i]])) {
          map[array[i]] = 1;
          res.push(array[i]);
        }
      }
      return res;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    java计算机毕业设计手办周边商城源程序+mysql+系统+lw文档+远程调试
    [OC学习笔记]Block三种类型
    Re:用webpack从零开始的vue-cli搭建‘生活‘
    Vue项目案例-头条新闻
    【leetcode 1】LinkList and Queue and Stack
    谷歌浏览器 input 阻止自动填充 以及填充的默认样式
    EA&UML日拱一卒 用例图开篇
    # 聚类系列(一)——什么是聚类?
    mac 查看GPU使用
    static_assert
  • 原文地址:https://blog.csdn.net/helloworld1024fd/article/details/126900725