`
裴小星
  • 浏览: 260765 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
8ccf5db2-0d60-335f-a337-3c30d2feabdb
Java NIO翻译
浏览量:27552
F3e939f0-dc16-3d6e-8c0b-3315c810fb91
PureJS开发过程详解
浏览量:71780
07a6d496-dc19-3c71-92cf-92edb5203cef
MongoDB Java ...
浏览量:61921
社区版块
存档分类
最新评论

jQuery Utilities 分类下的函数(或属性)的实现方式分析

阅读更多
jQuery Utilities 分类下的函数(或属性)的实现方式分析


  本文将介绍jQuery Utilities 分类下的函数(或属性)的实现方式。

  首先,可以先从 jQuery 官方 API 中找到 Utilities 分类的介绍:
  http://api.jquery.com/category/utilities/

  其中,不进行分析的函数(或属性)包括:
  jQuery.boxMode:已经不推荐使用,可以用 jQUery.support.boxModel 代替
  queue相关函数:与动画相关,并且也属于 “Custom” 分类,等分析 “Custom” 分类下的函数时再一并分析

  已经分析过的函数(或属性):
  jQuery.browser  :http://xxing22657-yahoo-com-cn.iteye.com/blog/1035780
  jQuery.data()      :http://xxing22657-yahoo-com-cn.iteye.com/blog/1042440
  jQuery.extend()  :http://xxing22657-yahoo-com-cn.iteye.com/blog/1025297
  jQuery.unique()  :http://xxing22657-yahoo-com-cn.iteye.com/blog/1038480
  jQuery.support   :http://xxing22657-yahoo-com-cn.iteye.com/blog/1044984

  另外,jQuery.isArray(), jQuery.isPlainOject(), jQuery.isWindow(), jQUery.type() 在分析 jQuery.extend() 时已经一并分析。

  这里更给出的实现都是简化的版本,没有考虑性能优化、异常处理和某些特殊情况。实际上,jQuery的完整实现完全可以自己在未压缩版的源代码(这里分析的版本是 jQuery 1.6)中 search,因为这里主要讲解实现思路,作为引导和参考,所以下面的分析中将忽略一些次要因素,以避免冲淡主题。

jQuery.globalEval()

  通常情况下使用 eval,this 所指向的都是当前对象;有的时候我们希望  this 指向全局对象( window ),就可以使用 jQuery.globalEval() 了。这在动态加载外部 JavaScript 文件并执行时十分有用。

  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
a = {
	func : function() {
		eval("alert(this === window)");
	},
	globalFunc: function() {
		$.globalEval("alert(this === window)");
	}
}

a.func();     // alert( flase )
a.globalFunc();    // alert( true )
</script>

  可以看到,通常情况下调用附加在对象上的函数,并在函数中使用 eval() 时, this 并不指向 window;但如果使用 globalEval() ,那么 this 就会指向 window 。

  简单的实现方式如下:
$ = {
    globalEval: function(data) {
        (function() { eval(data); }())
    }
}

  在匿名函数内使用 eval 。由于匿名函数不指向任何一个对象,因此其指向的对象也就是全局对象( window )了。jQuery 的源代码中还先检查了 window.execScript 是否存在,如果存在则优先使用 window.execScript 。

jQuery.parseJSON() 与 jQuery.parseXML()

  jQuery 与后台的数据交互最好采用某种规范的格式,经常使用的就是 JSON 和 XML 格式了。从后台得到的数据通常是字符串,还需要将字符串转为 JavaScript 对象,这也就是 jQuery.parJSON() 和 jQuery.parseXML() 的功能了。

  jQuery.parseJSON() 的功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
obj = $.parseJSON('{ "s": "string value", "i": 1234 }');
document.write('obj.s = ' + obj.s);
document.write('<br />');
document.write('obj.i = ' + obj.i);
</script>

  显示结果如下:
obj.s = string value
obj.i = 1234

  jQuery.parseXML() 的功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
str = "<root><customer name='John'></customer></root>";
xml = $.parseXML(str);

customer = xml.getElementsByTagName ("customer")[0];
name = customer.getAttribute ("name");
document.write("customer.name = "+ name);
</script>

  显示结果如下:
customer.name = John

  这两个函数的简单实现如下:
$ = {
    parseJSON: function(data) {
        return new Function("return " + data)();
    },

    parseXML : window.DOMParser ?
    function(data) {
		var parser = new DOMParser();
		return parser.parseFromString(data, "text/xml");
	} :
    function(data) {
		xml = new ActiveXObject("Microsoft.XMLDOM");
		xml.async = "false";
		xml.loadXML(data);
		return xml;
	}
}

  parseJSON() 只需要直接创建一个 Function ,并将内容设置为 "return " + data 就可以了。这类似于 eval 。因为 JSON 本身就是 JavaScript 的对象(或数组)的格式,因此转换起来异常简单。同样地,jQuery 的源代码中优先检查了 window.JSON.parse ,如果存在则使用浏览器内置的解析器。虽然可能在性能上略有提升,但也可能导致不同浏览器中的解析行为有所不同(通常我会把这个检查注释掉)。
  parseXML() 需要分两种情况,大多数浏览器都可以使用 DOMParser 进行解析,但 IE 中需要使用 ActiveXObject。jQuery 源代码中还有一些抛出 error 的处理。

jQuery.trim()

  jQuery.trim() 函数用于去除字符串中的空白字符。

  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
alert($.trim('    aaaa\r\n\t'));    // aaaa
</script>

  我们可以用正则表达式实现这个功能:
$ =  {
    trim : function(text) {
        return text == null ? '' :
            text.toString()
                .replace(/^\s+/, '')
                .replace(/\s+$/, '');
    }
};

  jQuery的源代码中还对 String.prototype.trim 进行检查,如果浏览器本身支持这个函数,就优先使用浏览器自带的 trim 函数。

jQuery.isFunction()

  该函数用于检查一个变量是否用于表示一个函数的引用。

  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
function a() {}
alert($.isFunction(a));    // true
</script>

  实现方式如下:
$ = function(){
    // implemention of class2type and type(obj) ...

    return {
        isFunction: function( obj ) {
            return type(obj) === "function";
        }
    }
}();

  其中 type 和 class2type 的实现请参考:  http://xxing22657-yahoo-com-cn.iteye.com/blog/1025297

jquery.isEmptyObject()

  该函数用于检查一个对象是否为空对象。

  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
a = {}
alert($.isEmptyObject(a));    // true
</script>

  实现方式如下:
$ = {
    isEmptyObject: function(obj) {
        for (var name in obj) {
            return false;
        }
        return true;
    }
};


jQuery.isXMLDoc()

  该函数用于检查一个对象是否表示一个 XML 文档。

  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
str = "<root><customer name='John'></customer></root>";
xml = $.parseXML(str);

customer = xml.getElementsByTagName ("customer")[0];
name = customer.getAttribute ("name");
document.write("customer.name = "+ name);
</script>

  简单实现如下:
$ = {
	// parseXML : ...,
    isXMLDoc: function( elem ) {
        var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
        return documentElement ? documentElement.nodeName !== "HTML" : false;
    }
};

  parseXML() 的实现请参考之前的分析。
  isXMLDoc() 的实现方式为检查元素的 documentElement 是否存在,存在且不为 HTML ,则表示这是一个 XML 文档。

jQuery.contains()

  检查一个 DOM Element 是否包含了另一个 DOM Element。

  功能测试代码如下:
<div id="a">
	<div id="b" />
</div>

<script type="text/javascript" src="jquery.js"></script>
<script>
window.onload = function() {
	var a = document.getElementById('a');
	var b = document.getElementById('b');
	
	alert($.contains(a, b)); // true
	alert($.contains(b, a)); // false
}
</script>

  简单实现如下:
$ = {
    contains: document.documentElement.contains ?
    function(a, b) {
        return a !== b && (a.contains ? a.contains(b) : true);
    } :
    function(a, b) {
        return !!(a.compareDocumentPosition(b) & 16);
    }
}

  根据不同浏览器的支持情况,借助浏览器内置的 contains() 或 compareDocumentPosition() 函数来实现这个功能。

jQuery.inArray()

  命名为 indexOf 可能会更合适些。返回一个元素在数组中的序号,不存在则返回  -1 。

  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
alert( $.inArray( 3, [1, 2, 3, 4] ) );    // 2
alert( $.inArray( 5, [1, 2, 3, 4] ) );    // -1
</script>

  简单实现如下:
$ = {
    inArray: function(elem, array) {
        for (var i = 0, length = array.length; i < length; i++) {
            if (array[i] === elem) {
                return i;
            }
        }
        return -1;
    }
};

  遍历数组,检查是否包含所查找的元素,找到则返回对应的序号,否则返回 -1。除了数组,也可以用于遍历 “类数组”的对象,如 {0:"aaa", 1: "bbb", length: 2}, 函数调用时的隐藏变量 “arguments” 就是典型的“类数组”对象。
  jQuery 源代码的实现中,在浏览器支持 Array.prototype.indexOf 的情况下,会优先采用浏览器自带的 indexOf 函数。

jQuery.merge()

  合并两个数组或“类数组”的对象( arguments 就是典型的“类数组”)。
  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
a = ['aaa', 'bbb'];
b = ['ccc', 'ddd'];
$.merge(a, b);

for (i in a) {
	alert(a[i]); // aaa, bbb, ccc, ddd
}


a = ['aaa', 'bbb'];
b = {0: 'ccc', 1: 'ddd'}
$.merge(a, b);

for (i in a) {
	alert(a[i]); // aaa, bbb, ccc, ddd
}
</script>

  简单实现如下:
$ = {
	merge : function(a, b) {
		for (var i = a.length, j = 0; b[j] !== undefined; ++i, ++j) {
			a[i] = b[j];
		}
		a.length = i;
		return a;
	}
}

  jQuery 的源代码中的分类更详细些,数组用  for (i = 0; i < leng; ++i) 的方式进行遍历。这可能也是一种性能优化吧,不过实际上并不必要。

jQuery.makeArray()

  将单个对象或“类数组”对象转为数组。
  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
a = $.makeArray('aaa');
a.push('bbb');
alert(a)    // aaa, bbb

b = $.makeArray({0:'aaa', 1:'bbb', length: 2});
b.push('ccc');

alert(b)    /// aaa, bbb, ccc
</script>

  简单实现如下:
$ = function(){
    // implemention of class2type, type(obj), isWindow(obj), merge(a, b) ...

    function makeArray(array) {
        var ret = [];
        if ( array != null ) {
            var t = type(array);
            if ( array.length == null || t === "string" || t === "function" || t === "regexp" || isWindow(array) ) {
                ret.push(array);
            } else {
                merge(ret, array);
            }
        }
    
        return ret;
    };

    return { merge: merge, makeArray: makeArray }
}();

  首先检查输入参数是否为“单个”对象,是的话就  push 到数组中,不是的话就 merge 到数组中。
  class2type, type(obj), isWindow(obj) 的实现请参考:  http://xxing22657-yahoo-com-cn.iteye.com/blog/1025297
  meger(a, b) 的实现请参考上一小节。

jQuery.each()

  该函数提供一种遍历数组和对象的函数,支持传入一个回调函数,在遍历过程中执行。
  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
a = ['aaa', 'bbb'];
$.each(a, function(i, value){
	alert('a[' + i + '] = ' + value);    // a[0] = aaa, a[1] = bbb
})

b = {a: 'aaa', b: 'bbb'}
$.each(b, function(name, value){
	alert('b.' + name + ' = ' + value);    // b.a = aaa, b.b = bbb
})
</script>

  简单实现如下:
$ = {
    each : function(obj, callback) {
        for (var n in obj ) {
            if (callback.call(obj[n], n, obj[n]) === false) {
                break;
            }
        }
    }
};

  同样,jQuery 的源代码对数组和对象采用了不同的遍历方式。

    更正:
  这里区分数组(或“类数组”)和对象还是有必要的。
  $('body')方式产生的对象就是一个“类数组”,有length,有下标等,但又有其他属性(及函数),直接用 for (i in $('body'))遍历是有问题的(会遍历到我们不关心的“隐藏”属性),还是应该检查 length 属性,用 for (i = 0; i < xxx.length; ++i) 遍历。

$ = {
	// isFunction() { ... }
    each : function(obj, callback) {
    	var length = obj.length,
    		isObj = length === undefined || isFunction(obj);
    	
    	if (isObj) {
			for (var n in obj ) {
				if (callback.call( obj[n], n, obj[n]) === false ) {
					break;
				}
			}
		} else {
			for (var i = 0; i < length; ++i) {
				if (callback.call(obj[i], i, obj[i]) === false) {
					break;
				}
			}
		}
    }
};


jQuery.grep()

  用于过滤数组,第一个参数表示被过滤的数组,第二个参数表示用于判断是否被过滤的函数。
  第三个参数为 true ,表示执行过滤的函数的返回值为 false 时保留;否则表示执行过滤的函数的返回值为 true 时保留。
  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
var arr = [ 1, 2, 3, 4 ];

ret = $.grep(arr, function(value, i){
  return (value != 3 && i >= 1);
});
alert(ret);     // 2, 4

ret = $.grep(arr, function(value, i){
  return (value != 3 && i >= 1);
}, true);
alert(ret);     // 1, 3
</script>

  简单实现如下:
$ = {
    grep : function(elems, callback, inv) {
        var ret = [];
        inv = !!inv;

        for (var i = 0; i < elems.length; ++i) {
            if (inv !== !!callback(elems[i], i)) {
                ret.push(elems[i]);
            }
        }

        return ret;
    }
};


jQuery.map()

  该函数提供对每个元素进行变换并产生一个新数组的功能。
  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
var arr = {a: false, b: 111, c: 'ccc', d: 'ddd'};

ret = $.map(arr, function(value, i){
	if (!value || i === 'd') { return undefined; }
	if (typeof value === 'number') {
		return value * 2;
	} else {
		return [value, value];
	}
});
alert(ret);     // 222, ccc, ccc
</script>

  可以看出,如果返回值为 undefined 或 null 时,表示该元素不会被保留;因此,我们完全可以用 jQuery.map() 代替 jQuery.grep() 。
  简单实现如下:
$ =  {
    map: function(elems, callback) {
        var ret = [];

        for (var key in elems) {
            value = callback(elems[key], key);
            if ( value != null ) {
                ret.push(value);
            }
        }

        return ret.concat.apply([], ret);
    }
};

  对每个 key 调用 callback 并检查返回值,如果不为 undefined 或 null 则 push 到结果中。
  这个函数很容易让人想起大名鼎鼎的 MapReduce,实际上,还真的有人做了 jQuery 的 MapReduce Plugin:
  http://plugins.jquery.com/project/MapReduce


jQuery.proxy()

  用于改变函数中 this 指向的对象。有两种使用方式
  1.用第一个参数表示 this 指向的对象,第二个参数表示函数名称(必须为参数一的成员函数)
  2.用第一个参数表示被改变上下文的函数,第二个参数表示 this 指向的对象
  功能测试代码如下:
<script type="text/javascript" src="jquery.js"></script>
<script>
a = { name: 'name of a' }
b = {
	name: 'name of b', 
	getName: function(){
		return this.name;
	}
}

alert(b.getName());    // name of b
proxy = $.proxy(b.getName, a);
alert(proxy());    // name of a


a.onclick = b.getName;
alert(a.onclick());   // name of a
a.onclick = $.proxy(b, 'getName');
alert(a.onclick());    // name of b
</script>

  实现方式如下:
$ =  {
    proxy: function(fn, context) {
        return typeof context === 'string' ?
        function() {
            return fn[context].apply(fn, arguments);       
        } :
        function() {
            return fn.apply(context, arguments);
        };
    }
};

  实际上是利用了 finction.apply 可以指定上下文环境( this 指向的对象)的特性。根据参数类型,使用不同的 function.applay 方式。
  finction.apply 的第一个参数表示 this 指向的对象,第二个参数为调用函数时的参数数组。

jQuery.removeData()

  移除附加在对象上的数据。由于一些浏览器不允许直接 delete DOM Elemnt 的属性,因此 jQuery 进行了对象类型和浏览器支持的检查,选择性地采用 delete 和直接置为 undefined 两种方式来移除数据。但下面的简单实现中统一采用将要移除的数据置为 undefined 的方法。
  功能测试代码如下:

  移除 name , value 方式附加的数据:
<script type="text/javascript" src="jquery.js"></script>
<script>
obj = {};
$.data(obj, 'name', 'value');
document.write("$.data(obj, 'name') = " + $.data(obj, 'name') + '<br />');

$.removeData(obj, 'name');
document.write("$.data(obj, 'name') = " + $.data(obj, 'name'));
</script>


  移除通过对象附加的数据:
<script type="text/javascript" src="jquery.js"></script>
<script>
obj = {};
$.data(obj, {name1: 'value1', name2: 'value2'});

document.write("$.data(obj, 'name1') = " + $.data(obj, 'name1')  + '<br />' );
document.write("$.data(obj, 'name2') = " + $.data(obj, 'name2') + '<br /><br />');

$.removeData(obj, 'name1');
document.write("$.data(obj, 'name1') = " + $.data(obj, 'name1')  + '<br />' );
document.write("$.data(obj, 'name2') = " + $.data(obj, 'name2') + '<br /><br />');

$.removeData(obj);
document.write("$.data(obj, 'name1') = " + $.data(obj, 'name1')  + '<br />' );
document.write("$.data(obj, 'name2') = " + $.data(obj, 'name2'));
</script>

  移除附加到 DOM Element 上的数据:
<div id="div_test" />

<script type="text/javascript" src="jquery.js"></script>
<script>
window.onload = function() {
	div = document.getElementById('div_test');
	$.data(div, 'name', 'value');
	document.write($.data(div, 'name'));

	document.write('<br />');

	$.removeData(div, 'name');
	document.write($.data(div, 'name'));
}
</script>

  实现方式如下:
$ = function() {
    // implemention of globalCache, getCache(obj) ...
    // function data(obj, name, value) { ... }
    // function isEmptyObject(obj) { ... }

    function removeData(obj, name, value) {
        if (name) {
            getCache(obj)[name] = undefined;
            if (!isEmptyObject(obj)) {
                return;
            }
        }

        if (obj.nodeType) {
            globalCache[obj[expando]] = undefined;
        } else {
            obj[expando] = undefined;
        }
    }

	return {
        isEmptyObject : isEmptyObject,
		data : data,
        removeData : removeData
	}
}();

  globalCache, getCache(obj), data(obj, name, value) 的实现方式请参考:
  http://xxing22657-yahoo-com-cn.iteye.com/blog/1042440
  isEmptyObject 的实现请参考之前的小节。

jQUery.now()


  返回当前时间:
function() { return (new Date()).getTime(); }

jQuery.noop()


  空函数:
noop: function() {}

  以上就是就是我对 jQuery Utilities 分类下的函数的实现方式的分析了。

题外话

  最近一直在看 jQuery 的源代码,jQuery 源码算是一个学习JavaScript编程模式的经典范例吧。之所以决定花时间钻研JavaScript,一个重要的原因是最近 server-side JavaScript 和 一些 “JavaScript Friendly” 的 NoSQL 数据库似乎在慢慢兴起。仔细想想, JavaScript 也算是一门各大厂商都在努力改善的语言了(浏览器之争推动了 JavaScript 性能的提升),并且 JavaScript 的一些语言特性也比较有吸引力。
  分析 jQuery 源码的思路,是先从 API 入手,“自底向上”地进行分析,先把基础函数了解清楚,再去看“高阶函数”的实现。具体分析某个函数(或属性)时,先测试这个函数(或属性)的功能,再想办法自己实现一遍,然后与jQuery的实现进行比较。
  我简单地搜索了一下,这边分析过jQuery源代码的朋友也不少,能否分享一下经验?
20
12
分享到:
评论
2 楼 裴小星 2011-05-19  
突然想到,区分数组(或“类数组”)和对象还是有必要的。

$('body')方式产生的对象就是一个“类数组”,有length,有下标等,
但又有其他属性(及函数),
直接用 for (i in $('body'))遍历是有问题的,
还是应该检查 length 属性,用 for (i = 0; i < xxx.length; ++i) 遍历
1 楼 denger 2011-05-16  
不错,分析的很详实。

相关推荐

    jQuery1.7.2 js+帮助文档

    jQuery1.7.2 jQuery 是一个 JavaScript 函数库。 jQuery 库包含以下特性: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM 遍历和修改 AJAX Utilities

    jQuery1.11.0_20140330_jquery_

    jQuery是一个JavaScript函数库。jQuery是一个轻量级的&quot;写的少,做的多&quot;的JavaScript库。jQuery库包含以下功能: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM ...

    初步认识JavaScript函数库jQuery

    jQuery 是一个 JavaScript 函数库。 jQuery 库包含以下特性: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM 遍历和修改 AJAX Utilities 向您的页面添加 jQuery 库 ...

    jquery-3.4.1.min.zip

    jQuery是一个JavaScript函数库。 jQuery是一个轻量级的"写的少,做的多"的JavaScript库。 jQuery库包含以下功能: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM 遍历和...

    jquery-1.11.0.min.zip

    jQuery是一个JavaScript函数库。 jQuery是一个轻量级的"写的少,做的多"的JavaScript库。 jQuery库包含以下功能: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM 遍历和...

    jquery-1.11.3.js

    jquery-1.11.3 - 特性 HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM 遍历和修改 AJAX Utilities

    jQuery 1.6 中文API

    jQuery 是一个 JavaScript 函数库。 jQuery 库包含以下特性:HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM 遍历和修改 AJAX Utilities 而本API文档是JQuery使用者开发...

    JQuery学习总结

    jQuery库包含以下功能: HTML 元素选取、HTML 元素操作、CSS 操作、HTML 事件函数、JavaScript 特效和动画、HTML DOM 遍历和修改、AJAX、Utilities 提示: 除此之外,Jquery还提供了大量的插件。

    jQuery总结.docx

    jQuery是一个JavaScript函数库。 jQuery是一个轻量级的"写的少,做的多"的JavaScript库。 jQuery库包含以下功能: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM ...

    Jquery-3.6.3.min.js

    Jquery-3.6.3.min.js当前最新版本 ...jQuery 是一个 JavaScript 函数库。 jQuery 库包含以下特性: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM 遍历和修改 AJAX Utilities

    适合有一定javascript基础的初学者,Jquery教程

    jQuery是一个JavaScript函数库。 jQuery是一个轻量级的"写的少,做的多"的JavaScript库。 jQuery库包含以下功能: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效和动画 HTML DOM 遍历和修改 ...

Global site tag (gtag.js) - Google Analytics