Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端性能优化之渲染之奇技淫巧 #28

Open
zhuanyongxigua opened this issue Oct 15, 2018 · 0 comments
Open

前端性能优化之渲染之奇技淫巧 #28

zhuanyongxigua opened this issue Oct 15, 2018 · 0 comments

Comments

@zhuanyongxigua
Copy link
Owner

zhuanyongxigua commented Oct 15, 2018

节流和防抖

节流和防抖其实差不多,从编程的角度说,都是防止一个函数被过多地调用。都是降低频率,只是程度不同,防抖降的很彻底,彻底到只有一次,而节流还是会多次,只不过是我们可以接受的多次。

防抖

把持续发生的事情堵住,只处理最后一次

防抖的应用场景:文本输入验证(连续输入AJAX请求进行验证,验证一次就可以了)。

防抖的最简单的实现

function debounce(method, context) {
  clearTimeout(method.tId);
  method.tId = setTimeout(function() {
    method.call(context);
  }, 1000);
}

function print() {
  console.log('hello world');
}

window.onscroll = function() {
  debounce(print);
};

节流

把持续发生的事情堵住,每隔一段时间处理一次

比如做一个拖拽的交互,如果每移动一个像素都执行一遍回调,这个性能消耗是惊人的。比较好的处理方法就是降低回调的频率(更准确的说是降低拖拽交互的频率,回调还是一直都在放生的)。

节流其他的应用场景:搜索联想(keyup);监听滚动事件判断是否到达页面底部,然后自动加载更多。

节流的实现:
第一种方法,当 scroll 事件刚触发时,打印一个 hello world,然后设置个 1000ms 的定时器,此后每次触发 scroll 事件触发回调,如果已经存在定时器,则回调不执行方法,直到定时器触发,handler 被清除,然后重新设置定时器。:

function Throttle(method, context) {
  if (method.tId) return;
  method.tId = setTimeout(function() {
    method.call(context);
    clearTimeout(method.tId);
    method.tId = false
  }, 1000);
}

function print() {
  console.log('hello world');
}

window.onscroll = function() {
  Throttle(print);
};

第二种方法,是用时间戳来判断是否已到回调该执行时间,记录上次执行的时间戳,然后每次触发 scroll 事件执行回调,回调中判断当前时间戳距离上次执行时间戳的间隔是否已经到达 1000ms,如果是,则执行,并更新上次执行的时间戳,如此循环;:

var stamp = Date.now();
function Throttle(method, context) {
  if (Date.now() - stamp < 1000) return;
  method.call(context);
  stamp = Date.now();
}

function print() {
  console.log('hello world');
}

window.onscroll = function() {
  Throttle(print);
};

延迟加载和预加载

延迟加载使用的较多的就是图片的延迟加载,没有访问到的图片不会加载,会给服务器省流量的钱。

一个比较简单的思路:

在HTML标签中,img标签是这样的:

<img src="" data-src="https://.....">

data-src属性中放的是真实的地址,这样图片目前是不会加载的,

具体的执行的函数:

function lazyload() {
  const images = document.getElementsByTagName('img')
  const len = images.length
  let n = 0
  return function() {
    const seeHeight = document.documentElement.clientHeight
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
    for (let i = n; i < len; i++) {
      if (images[i].offsetTop < seeHeight + scrollTop) {
        if (images[i].getAttribute('src') === 'https://8.url.cn/edu/lego_modules/edu-ui/0.0.1/img/nohash/loading.gif') {
          images[i].src = images[i].getAttribute('data-src')
        }
        n = n + 1
      }
    }
  }
}
var loadImages = lazyload()
window.onload = function () {
  loadImages()
  window.addEventListener('scroll', loadImages, false)
}

等到window.onload触发,或者滚动事件触发的时候,把data=src中的路径放到src里面去。

其实还可以做的更好,加一个有效性验证:

function lazyload() {
  const images = document.getElementsByTagName('img')
  const len = images.length
  let n = 0
  return function() {
    const seeHeight = document.documentElement.clientHeight
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
    for (let i = n; i < len; i++) {
      if (images[i].offsetTop < seeHeight + scrollTop) {
        if (images[i].getAttribute('src') === 'https://8.url.cn/edu/lego_modules/edu-ui/0.0.1/img/nohash/loading.gif') {
          // 有效性验证
          var curImg = new Image
          curImg.src = images[i].getAttribute('data-src')
          // 如果地址找不到图片,就不会触发onload
          curImg.onload = function() {
            images[i].src = images[i].getAttribute('data-src')
            // 释放内存
            curImg = null;
          }
        }
        n = n + 1
      }
    }
  }
}
var loadImages = lazyload()
window.onload = function () {
  loadImages()
  window.addEventListener('scroll', loadImages, false)
}

对于预加载,会用到的地方,如”查看更多“的功能,可以在空闲的时候先把“查看更多”的内容拿到,点击了之后直接显示就可以了。

大列表优化

一般是出现在移动端的无线列表。

例子:

var data = [];
var limit = 1e5;
for (var i = 1; i < limit; i++) data.push(i);
var $list = document.getElementById('list');
function update() {
  item_height = 24;
  $list.style.height = (data.length * item_height) + 'px';
  var show_counts = Math.ceil(window.innerHeight / item_height) + 10;
  var update_list = function() {
    var scrollTop = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
    var start = Math.max(Math.floor(scrollTop / item_height) - 3, 0);
    var show_data = data.slice(start, start + show_counts);
    var html = '';
    // 占位符,用于是滚动条正常使用
    if (start !== 0) {
      html += `<li style="height:${start * item_height}px"></li>`;
    }
    show_data.forEach(v => {
      html += `<li>${v}</li>`;
    });
    $list.innerHTML = html;
  };
  window.onscroll = throttle(update_list, 100);
  update_list();
}

完整的例子在这里

考虑了滚动条的位置。DOM中有一个占位符,在<li>的最上面,这个占位符的高度与已经划过去的列表的高度相同,目的是就是占用之前的列表的位置,从而使当前的滚动条的状态不变,划过去的列表实际上已经不在了,这样做的目的是节省资源,不会渲染太多的东西。

其实就是减少可视区域外的DOM数量。可视区域外的用占位符代替。没有移除的就容易卡顿,如果数据不多的话问题不大。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant