神策会在内部集成sdk,并且在window上挂在一个方法(sd),sd内部封装了很多方法和初始化变量。在神策官网中可以自动生成sdk来帮助我们更好的使用他们暴露的接口。
- UUID
神策数据的接口在每次发送数据时都会生成一个键值对,来保证每次发送的不同。
它会在内部生成UUID标识的方法,来完成每次数据发送的携带。
_.UUID = (function() {
var T = function() {
// 当前系统时间 * 1
var d = 1 * new Date(),
i = 0;
while (d == 1 * new Date()) {
i++;
}
return d.toString(16) + i.toString(16);
};
var R = function() {
return Math.random().toString(16).replace('.', '');
};
var UA = function(n) {
var ua = navigator.userAgent,
i, ch, buffer = [],
ret = 0;
function xor(result, byte_array) {
var j, tmp = 0;
for (j = 0; j < byte_array.length; j++) {
tmp |= (buffer[j] << j * 8);
}
return result ^ tmp;
}
for (i = 0; i < ua.length; i++) {
ch = ua.charCodeAt(i);
buffer.unshift(ch & 0xFF);
if (buffer.length >= 4) {
ret = xor(ret, buffer);
buffer = [];
}
}
if (buffer.length > 0) {
ret = xor(ret, buffer);
}
return ret.toString(16);
};
return function() {
var se = String(screen.height * screen.width);
if (se && /\d{5,}/.test(se)) {
se = se.toString(16);
} else {
se = String(Math.random() * 31242).replace('.', '').slice(0, 8);
}
var val = (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T());
if (val) {
return val;
} else {
return (String(Math.random()) + String(Math.random()) + String(Math.random())).slice(2, 15);
}
};
})();
- 对原生xhr对象进行了底层封装
_.xhr = function(cors) {
// 如需要跨域资源共享
if (cors) {
// 如果浏览器支持XMLHttpRequest
if (typeof window.XMLHttpRequest !== 'undefined' && ("withCredentials" in new XMLHttpRequest())) {
return new XMLHttpRequest();
// 大部分浏览器已经支持cors跨域请求,但在ie8,9还是不支持,可以使用XDomainRequest实例对象
} else if (typeof XDomainRequest !== "undefined") {
return new XDomainRequest();
} else {
return null;
}
// 如不需要
} else {
if (typeof window.XMLHttpRequest !== 'undefined') {
return new XMLHttpRequest();
}
// 判断浏览器是否支持ActiveX对象,通过实例化ActiveX对象的新实例来创建xhr对象
if (window.ActiveXObject) {
try {
// 这个对IE7一下会不支持,需要往下兼容恶心的IE
return new('Msxml2.XMLHTTP')
} catch (d) {
try {
// 可以兼容IE7以前的版本
return new ActiveXObject('Microsoft.XMLHTTP')
} catch (d) {
sd.log(d);
}
}
}
}
};
- 发送ajax请求的封装
_.ajax = function(para) {
para.timeout = para.timeout || 20000;
para.credentials = (typeof para.credentials) === 'undefined' ? true : para.credentials;
// 对象化data传的参数(数据)
function getJSON(data) {
if (!data) {
return '';
}
try {
return JSON.parse(data);
} catch (e) {
return {};
}
}
// 是否支持跨域
var g = _.xhr(para.cors);
if (!g) {
return false;
}
if (!para.type) {
para.type = para.data ? 'POST' : 'GET';
}
// 扩展para参数
para = _.extend({
success: function() {},
error: function() {}
}, para);
try {
if (typeof g === 'object' && ('timeout' in g)) {
g.timeout = para.timeout;
} else {
setTimeout(function() {
g.abort();
}, para.timeout + 500);
}
} catch (e) {
try {
setTimeout(function() {
g.abort();
}, para.timeout + 500);
} catch (e2) {
sd.log(e2);
};
};
// xhr对象的请求过程状态码
g.onreadystatechange = function() {
try {
if (g.readyState == 4) {
if ((g.status >= 200 && g.status < 300) || g.status == 304) {
para.success(getJSON(g.responseText));
} else {
para.error(getJSON(g.responseText), g.status);
}
g.onreadystatechange = null;
g.onload = null;
}
} catch (e) {
g.onreadystatechange = null;
g.onload = null;
};
};
// 定义ajax类型,地址等
g.open(para.type, para.url, true);
try {
if (para.credentials) {
g.withCredentials = true;
}
if (_.isObject(para.header)) {
for (var i in para.header) {
g.setRequestHeader(i, para.header[i]);
}
}
// 定义请求头部
if (para.data) {
if (!para.cors) {
g.setRequestHeader("X-Requested-With", "XMLHttpRequest");
}
if (para.contentType === 'application/json') {
g.setRequestHeader("Content-type", "application/json; charset=UTF-8");
} else {
g.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
}
}
} catch (e) {
sd.log(e);
};
// 发送请求
g.send(para.data || null);
};
- extend扩展方法
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (source[prop] !== void 0) {
obj[prop] = source[prop];
}
}
});
return obj;
};
最后会在神策封装的sdk里暴露出发送数据接口的方法,比如 var methods = ['track', 'quick', 'register', 'registerPage', 'registerOnce', 'trackSignup', 'setProfile', 'setOnceProfile', 'appendProfile', 'incrementProfile', 'deleteProfile', 'unsetProfile', 'identify', 'login', 'logout', 'trackLink', 'clearAllRegister'];
- 然后会遍历这些放在神策暴露的对象中
var methods = ['track', 'quick', 'register', 'registerPage', 'registerOnce', 'trackSignup', 'setProfile', 'setOnceProfile', 'appendProfile', 'incrementProfile', 'deleteProfile', 'unsetProfile', 'identify', 'login', 'logout', 'trackLink', 'clearAllRegister'];
_.each(methods, function(method) {
var oldFunc = sd[method];
sd[method] = function() {
if (!sd.readyState.getState()) {
try {
console.error('请先初始化神策JS SDK');
} catch (e) {
sd.log(e);
}
return;
}
return oldFunc.apply(sd, arguments);
};
});
if (typeof window['sensorsDataAnalytic201505'] === 'string') {
sd.setPreConfig(window[sensorsDataAnalytic201505]);
window[sensorsDataAnalytic201505] = sd;
window['sensorsDataAnalytic201505'] = sd;
sd.init();
} else if (typeof window['sensorsDataAnalytic201505'] === 'undefined') {
window['sensorsDataAnalytic201505'] = sd;
return sd;
} else {
return window['sensorsDataAnalytic201505'];
}
} catch (err) {
if (typeof console === 'object' && console.log) {
try {
console.log(err)
} catch (e) {
sd.log(e);
};
}
}
比如调用 window.sensors.quick('trackAllHeatMap')
方法后会调用sd.quick = function()
这个方法
sd.quick = function() {
// Array.prototype.slice.call(arguments)能将具有length属性的对象(key值为数字)转成数组
// Array.prototype.slice.call()可以理解为:改变数组的slice方法的作用域,在特定作用域中去调用slice方法,call()方法的第二个参数表示传递给slice的参数即截取数组的起始位置。
var arg = Array.prototype.slice.call(arguments);
var arg0 = arg[0];
// arg1是个数组
var arg1 = arg.slice(1);
// 如果arg0是字符串类型并且有这个方法
if (typeof arg0 === 'string' && commonWays[arg0]) {
// 相当于返回commonWays.trackHeatMap这个函数执行结果,apply方法会改变函数内的this指向,指向commonWays
return commonWays[arg0].apply(commonWays, arg1);
} else if (typeof arg0 === 'function') {
arg0.apply(sd, arg1);
} else {
sd.log('quick方法中没有这个功能' + arg[0]);
}
};
在quick中会调用commonWays
对象中的trackHeatMap
的方法
trackHeatMap: function(target, props, callback) {
if ((typeof target === 'object') && target.tagName) {
var tagName = target.tagName.toLowerCase();
var parent_ele = target.parentNode.tagName.toLowerCase();
if (tagName !== 'button' && tagName !== 'a' && parent_ele !== 'a' && parent_ele !== 'button' && tagName !== 'input' && tagName !== 'textarea' && !_.hasAttribute(target, 'data-sensors-click')) {
heatmap.start(null, target, tagName, props, callback);
}
}
},
在commonWays.trackHeatMap
方法中调用了heatmap.start
方法
start: function(ev, target, tagName, customProps, callback) {
var userCustomProps = _.isObject(customProps) ? customProps : {};
var userCallback = _.isFunction(callback) ? callback : _.isFunction(customProps) ? customProps : undefined;
if (sd.para.heatmap && sd.para.heatmap.collect_element && !sd.para.heatmap.collect_element(target)) {
return false;
}
var selector = this.getDomSelector(target);
var prop = _.getEleInfo({
target: target
});
prop.$element_selector = selector ? selector : '';
if (sd.para.heatmap && sd.para.heatmap.custom_property) {
var customP = sd.para.heatmap.custom_property(target);
if (_.isObject(customP)) {
prop = _.extend(prop, customP);
}
}
prop = _.extend(prop, userCustomProps);
// 在最后会判断点击事件是在和标签上发生的,来根据标签来判断发生的发送数据类型
if (tagName === 'a' && sd.para.heatmap && sd.para.heatmap.isTrackLink === true) {
_.trackLink({
event: ev,
target: target
}, '$WebClick', prop);
} else {
sd.track('$WebClick', prop, userCallback);
}
},