资讯专栏INFORMATION COLUMN

前端笔记(二) 对象的深浅拷贝,函数的防抖与节流,函数柯里化 ,图片的预加载与懒加载

dongxiawu / 2830人阅读

摘要:对象是无法通过这种方式深拷贝。这就是函数防抖和节流要做的事情。函数防抖当触发频率过高时函数基本停止执行而函数节流则是按照一定的频率执行事件。

对象的深浅拷贝

对象的深拷贝与浅拷贝的区别:

浅拷贝:仅仅复制对象的引用, 而不是对象本身。

深拷贝:把复制的对象所引用的全部对象都复制一遍

浅拷贝的实现:

var obj = {
   age : 18,
   person : {
     hobby : "movie",
     skill : "Java"
   }
}
//方法一
function shallowClone(initial) {
   var obj = {};
   for( var i in initial ) {
      obj[i] = initial[i];
   }
   return obj;
}
//方法二
var newobj = Object.assign({}, obj);
console.log(newobj);

var clone = shallowClone(obj);
console.log(clone.age); //18
clone.person.skill = "JavaScript";
console.log(obj.person.skill); //JavaScript

深拷贝的实现:

var obj = {
   age : 18,
   person : {
     hobby : "movie",
     skill : "Java"
   }
}

/* 方法一:
 * 这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象。
 * 即那些能够被 json 直接表示的  数据结构。RegExp对象是无法通过这种方式深拷贝。
 */
function deepCopy(initial) {
   var obj = {};
   obj = JSON.parse(JSON.stringify(initial));
   return obj;
}
var copy = deepCopy(obj);
console.log(copy);

//方法二 (递归拷贝)
function deepClone(initial, final) {
    var obj = final || {};
    for( var i in initial ) {
       var prop = initial[i];
       //避免相互引用导致死循环
       if( prop === obj ) {
           continue;
       }
       if( typeof prop === "object" ) {
           obj[i] = ( prop.constructor === Array ) ? prop : Object.create(prop);
       }else {
           obj[i] = prop;
       }
    }
  return obj;
}
var now = {}
deepClone(obj, now);
now.person.hobby = "sport";
console.log(obj.person.hobby); //movie


函数的防抖与节流

我们经常会遇到这样一种情景, 用户高频率触发一些JS事件。但是在一定时间内执行代码的次数太多往往会导致浏览器的性能下降甚至造成卡顿的现象, 所以我们可以把js执行代码的次数控制在合理的范围内, 在实现相同效果的情况下使页面交互变得更加流畅。这就是函数防抖和节流要做的事情。

函数防抖:

//debounce
function debounce(func, context) {
   clearTimeout(func.setTime);
   func.setTime = setTimeout(() => {
      func.call(context); 
   }, 300);
}

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

function doSomething() {
   console.log("函数防抖");
   //执行一些耗费性能的事件...
}

从上面代码可以看出函数防抖的核心思想是在调用定时器执行某个函数之前首先清除这个定时器。当函数多次被调用时, 每一次都会将之前的定时器清除, 即只有在执行函数的请求停止了一段时间之后才会真正执行函数。

函数节流:

//throttle
function throttle(func, time, context) {
   let start = Date.now();
   return function() {
      if (Date.now() - start > time && time > 0) {
          func.call(context);
          start = Date.now();
      }
   }
}
window.onscroll = throttle(doSomething, 300);

function doSomething() {
   console.log("函数节流");
   //执行一些耗费性能的事件...
}

函数节流的思想是设置一个执行函数间隔时间time, 当多次触发某个事件时便将执行函数的频率降低到time。

这样一来就达到了我们所想要的效果了。

/*函数节流的另一种实现方式*/
var flag = true;

function throttle(fn, context) {
   if(!flag) {
      return;
   }
   flag = false;
   setTimeout(() => {
      fn.call(context);
      flag = true;
   }, 300)
}

window.onscroll = function() {
   throttle(doSomething);
}

function doSomething() {
   console.log("函数节流");
   //执行一些耗费性能的事件...
}

值得注意的是这两种方法在具体浏览器中的运行下效果有所不同。函数防抖当触发频率过高时函数基本停止执行, 而函数节流则是按照一定的频率执行js事件。

/* @防抖与节流混合版 
--- 有第三个参数时为节流效果, 若没有则为防抖效果 ---
*/
var tdmixer = function(fn, delay, reqDelay, context) {
    var timer = null;
    var start;
    return function() {
        var args = arguments;
        var current = +new Date();
        clearTimeout(timer);
        if ( !start ) {
            start = current;
        }
        if ( current - start >= reqDelay ) {
            fn.apply(context, args);
            start = current;
        }else {
            timer = setTimeout( function() {
                fn.apply(context, args);
            }, delay);
        }
    }
}

window.onscroll = tdmixer(doSomething, 100, 300);

function doSomething() {
   console.log("This is a mix version.");
   //执行一些耗费性能的事件...
}


函数柯里化

它与函数绑定紧密相关, 用于创建已经设置好了一个或多个参数的函数, 其具体做法时使用一个闭包返回一个函数, 当函数被调用时, 返回的函数还需要设置一些传入的参数。

柯里化的三个作用 : 1.参数复用 2. 提前返回 3.延迟计算

function curry(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        var innerargs = Array.prototype.slice.call(arguments);
        var finalargs = args.concat(innerargs);
        return fn.apply(null, finalargs);
    }
}

function addAll(x,y,z) {
    return x + y + z;
}
var excute = curry(addAll,5,10);
excute(50); //65

ES5中的bind方法也用到过柯里化, 下面是简单的函数绑定的实现。

function bind(fn, context) {
    var args = Array.prototype.slice.call(arguments, 2);
    return function() {
        var innerargs = Array.prototype.slice.call(arguments);
        var finalargs = args.concat(innerargs);
        return fn.apply(context, finalargs);
    }
}
var handler = {
    message: "PIPI",
    handleClick(name) {
        console.log(name + "and" + this.message);
    }
}
var excute = bind(handler.handleClick, handler);
excute("POP");  //POPandPIPI
图片预加载与懒加载

预加载:

顾名思义, 图片的预加载就是将图片预先加载到浏览器的本地缓存中, 当需要时直接从本地加载图片到页面中, 如此一来就很好的提高了用户的体验。但缺点是增加了服务器端的开销。

懒加载:

也叫延迟加载, 即延迟加载图片或者当符合某些条件时才开始加载图片, 它与预加载相反, 其作用是对服务器端的性能优化, 减少请求数或延迟请求数, 从而达到缓解服务器端压力的效果。

--- preload code ---

//对预加载图片进行一些回调事件处理
function preLoadImg(url, callback) {
    var img = new Image();
    if ( img.complete ) { //若图片已经在本地缓存, 则直接调用回调函数
        callback.call(img);
        return;
    }
    img.onload = function() { //图片下载完之后异步调用callback
        img.onload = null;
        callback.call(img);
    }
    img.src = url; 
}

//大量图片预加载
var arr = ["pic1.png", "pic2.png", "pic3.png"];
function fn() { console.log("Do something...") };

function preLoadImages(urls, callback) {
    var wrap = Array.prototype.slice.call(arguments, 0, 1);
    var urls = [].concat.apply([], wrap);  //将其转化为一维数组
    for ( var i = 0; i < urls.length; i++) { 
        var img = new Image(); 
        img.onload = function() {
            callback.call(img);
        }
        img.src = urls[i];   
    }
}
preLoadImages(arr, fn);

--- lazyload code ---

//懒加载的实现
var lazyload = {
    //初始化
    init() {
        this.container = document.querySelector("Container"); //获取容器元素
        this.images = this.getImages();
        this.update();
        this.bindEvent();
    },
    //获取图片
    getImages() {
        var arr = [];
        var images = this.container.querySelectorAll("img");
        images.forEach( (img) => {
            arr.push(img);
        });
        return arr;
    },
    //加载图片
    update() {
        if ( !this.images.length ) { return };
        var i  = this.images.length;
        for ( i--; i >= 0; i-- ) {
            if ( this.couldShow(i) ) {
                this.images[i].src = this.images[i].getAttribute("data-src"); //需事先设置路径
                this.images.splice(i, 1);
            }
        }
    },
    //判断图片是否在可视区域并赋予src值
    couldShow(i) {
        var img = this.images[i];
        scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        scrollBottom = scrollTop + document.documentElement.clientHeight;
        imgTop = this.rectY(img);
        imgBottom = imgTop + img.offsetHeight;
        if ( imgBottom < scrollBottom && imgBottom > scrollTop 
        || imgTop > scrollTop && imgTop < scrollBottom ) {
            return true;
        }else {
            return false;
        }
    },
    //递归调用获取图片顶部到整个页面的最顶端的距离
    rectY(el) {
        if ( el.offsetParent ) {
            return el.offsetTop + this.rectY(el.offsetParent);
        }else {
            return el.offsetTop;
        }
    },
    //事件绑定
    bindEvent() {
        var that = this;
        that.on(window, "scroll", () => {
          var fn = tdmixer(that.update, 100, 300, that); //函数节流
          fn();
        } ) 
    },
    //监听
    on(el, type, fn) {
        if ( el.addEventListener ) {
            el.addEventListener(type, fn);
        }else {
            el.attachEvent("on" + type, fn);
        }
    }
}

lazyload.init();

//上文所给出的混合节流函数
var tdmixer = function(fn, delay, reqDelay, context) {
    var timer = null;
    var start;
    return function() {
        var args = arguments;
        var current = +new Date();
        clearTimeout(timer);
        if ( !start ) {
            start = current;
        }
        if ( current - start >= reqDelay ) {
            fn.apply(context, args);
            start = current;
        }else {
            timer = setTimeout( function() {
                fn.apply(context, args);
            }, delay);
        }
    }
}

从上面的两段代码可以看出, 图片预加载实现起来要简单许多, 当然两种功能都有很多种不同的实现方法, 有简单的也有复杂的, 这都需要根据具体的情景来编写代码。预加载一次性就加载需要的图片到本地储存从而提高了用户的体验却也加大了服务器端的负担, 而懒加载则需要根据某些具体的条件来判断何时向服务器端请求图片数据, 虽然减少了服务器端的开销, 但具体实现的步骤也变得更加复杂。所以在实际情况下两者最好混合使用且用在正确的地方上最为合适。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107442.html

相关文章

  • 小菊花课堂之JS抖与节流

    摘要:文章来源详谈防抖和节流轻松理解函数节流和函数防抖函数防抖和节流好啦,今天的小菊花课堂之的防抖与节流的内容就告一段落啦,感各位能耐心看到这里。 前言 陆游有一首《冬夜读书示子聿》——古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。,其中的意思想必大家都能明白,在学习或工作中,不断的印证着这首诗的内涵。所以,又有了此篇小菊花文章。 详解 在前端开发中,我们经常会碰到一些会持...

    leoperfect 评论0 收藏0
  • 小菊花课堂之JS抖与节流

    摘要:文章来源详谈防抖和节流轻松理解函数节流和函数防抖函数防抖和节流好啦,今天的小菊花课堂之的防抖与节流的内容就告一段落啦,感各位能耐心看到这里。 前言 陆游有一首《冬夜读书示子聿》——古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。,其中的意思想必大家都能明白,在学习或工作中,不断的印证着这首诗的内涵。所以,又有了此篇小菊花文章。 详解 在前端开发中,我们经常会碰到一些会持...

    Yangder 评论0 收藏0
  • JavaScript专题系列文章

    摘要:专题系列共计篇,主要研究日常开发中一些功能点的实现,比如防抖节流去重类型判断拷贝最值扁平柯里递归乱序排序等,特点是研究专题之函数组合专题系列第十六篇,讲解函数组合,并且使用柯里化和函数组合实现模式需求我们需要写一个函数,输入,返回。 JavaScript 专题之从零实现 jQuery 的 extend JavaScritp 专题系列第七篇,讲解如何从零实现一个 jQuery 的 ext...

    Maxiye 评论0 收藏0
  • 说说JavaScript中函数防抖 (Debounce) 与节流 (Throttle)

    摘要:基础防抖我们现在写一个最基础的防抖处理标记事件也做如下改写现在试一下,我们会发现只有我们停止滚动秒钟的时候,控制台才会打印出一行随机数。 为何要防抖和节流 有时候会在项目开发中频繁地触发一些事件,如 resize、 scroll、 keyup、 keydown等,或者诸如输入框的实时搜索功能,我们知道如果事件处理函数无限制调用,会大大加重浏览器的工作量,有可能导致页面卡顿影响体验;后台...

    yanwei 评论0 收藏0
  • JavaScript专题系列20篇正式完结!

    摘要:写在前面专题系列是我写的第二个系列,第一个系列是深入系列。专题系列自月日发布第一篇文章,到月日发布最后一篇,感谢各位朋友的收藏点赞,鼓励指正。 写在前面 JavaScript 专题系列是我写的第二个系列,第一个系列是 JavaScript 深入系列。 JavaScript 专题系列共计 20 篇,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里...

    sixleaves 评论0 收藏0

发表评论

0条评论

dongxiawu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<