jQuery 缓存Data对象及队列queue原理分析

384 阅读3分钟

data()方法

jQuery.data() 方法向被选元素储存数据,或者从被选元素获取数据

$(".box").data("key", "hello")	//储存数据
console.log($(".box").data("key"))	//获取数据,打印hello

data()方法在dom元素存储数据,并不是直接将数据绑定到dom元素上,dom元素常驻于内存当中,不会被销毁(除非关页面),如果在dom元素上绑定大量数据,势必会导致内存泄漏。那么jQuer如何缓存数据呢?

  1. dom元素生成一个uuid属性,值就是ID,dom[uuid] = ID
  2. UUID = expando,expando是jQuery内部生成随机数方法,expando="jQuery" + (Math.random()).replace(/\D/g, "")
  3. jQuery 内部 Data 对象会创建一个 Cache 对象用于储存数据,key 对应 ID,value 对应数据, Cache = { ID : { //数据 }}

通过这种方式,实现数据跟 dom 的绑定,数据是储存于 Cache,而不是 dom 元素上

Data对象

jQuer内部的Data对象,主要有三个方法,get、key、以及set,核心方法是key方法,key的作用主要是根据 dom[UUID] 返回 Cache 中的 ID,如果dom没有UUID属性,则在dom上添加UUID属性,并将Cache添加ID属性,值为空

function Data(){
    this.expando = jQuery.expando + Math.random()
    this.cache = {}
}
Data.uid = 1
//创建缓存及返回
Data.prototype = {
    //key方法,获取ID
    //同时在this.cache里面建立一个 uid: {}
    key(elem){
        var descriptor = {},
            unlock = elem[this.expando]
        
        if(!unlock){
            unlock = Data.uid++
            descriptor[this.expando] = {
                value: unlock
            }
            /*
            Dom扩展属性,相当于
                elem[this.expando] = unlock
            */
            Object.defineProperties(elem, descriptor)
        }

        if(!this.cache[unlock]){
            this.cache[unlock] = {}
        }

        return unlock
    },
    get(elem, key){
        //找到或者创建缓存
        var cache = this.cache[this.key(elem)]
        //key 有值直接在缓存中读取
        return key === undefined ? cache : cache[key]
    },
    set(owner, key, value){
        var unlock = this.key(owner),
            cache = this.cache[unlock],
            prop
        if(typeof key === 'string'){
            cache[key] = value
        }
        //如果key是一个对象
        if(jQuery.isPlainObject(key)){
            for( prop in key ){
                cache[prop] = key[prop]
            }
        }
    }
}

data()方法的实现

//缓存内部数据
var data_user = new Data(); //创建Data对象
data(key, value){
    var _this = this    //this代表$(".box")
    return access(this, function(key, value){
        if(value === undefined){    //获取数据
            var data = data_user.get(this, key)
            if (data!==undefined) {
                return data
            }
        }

        _this.each(function(){
            data_user.set(this, key, value) //在data_user上储存数据并在获取的dom上添加属性UUID
        })
    }, key, value)
},
access: function(elems, func, key, value){
    var len = elems.length
    var testting = key === null
    var cache

    if(value !== undefined){
        //说明value有值,则做set操作
        if(testting){
            cache = func
            func = function(key, value){
                cache.call(this, key, value)
            }
        }
        for(var i = 0; i < len; i++){
            func.call(elems[i], key, value)
        }
    }
    return func.call(elems[0], key, value)
},

队列queue

queue() 方法是基于data()实现的,它是显示或操作在匹配元素上执行的函数队列,通常与 dequeue() 方法一起使用

var box = $('box')

function fn1(){
	console.log('这是方法1')
}
function fn2(){
	console.log('这是方法2')
}

//给dom元素box上存储名为first的函数队列
jQuery.queue(box, "first", fn1) //将fn1添加到first函数队列
jQuery.queue(box, "first", fn2) //将fn2添加到first函数队列

console.log(jQuery.queue(box, "first")) //打印包含fn1及fn2的函数数组:[fn,fn]

//执行队列
jQuery.dequeue(box, "first")    //打印:这是方法1
jQuery.dequeue(box, "first")    //打印:这是方法2

实现

//缓存内部数据
var data_user = new Data(); //创建Data对象

queue(elem, type, data){
    var queue
    if(elem){
        type = type + "queue"    //firstqueue
        queue = data_user.get(elem, type)   //获取Cache里的缓存数据
        if(data){
            if(!queue){
                //由于queue储存的是函数队列,jQuery.markArray()方法是将传入的数据变成数组[data]
                data_user.set(elem, type, jQuery.markArray(data))
            } else {
                queue.push(data)
            }
        }
        return queue
    }
},
dequeue(elem, type){
    type = type || "fx"
    var queue = jQuery.queue(elem, type),
        next = function(){
            jQuery.dequeue(elem, type)
        },
        fn = queue.shift()
        fn.call(elem, next)
}

next()

在 dequeue() 方法中有一个 next() 方法,其本身就是 dequeue() 方法。它会在函数中传入 next 参数,用于继续执行函数队列,相当于再次执行 jQuery.dequeue() 方法

var box = $('box')
function fn1(next){
	console.log('这是方法1')
	next()  //继续执行fn2方法,打印:这是方法2
}
function fn2(){
	console.log('这是方法2')
}
jQuery.queue(box, "first", fn1)
jQuery.queue(box, "first", fn2)

//执行队列
jQuery.dequeue(box, "first")    //打印:这是方法1