1.调用ajax的一种套路
调用ajax的一种套路 //exports.emit("pick","failure")和exports.emit("pick","timeout") C.failure = C.timeout = function() { if (!m) m = setTimeout(function() { exports.emit("error", { type: "offline" }), s() } , I) //I为13S } //exports.emit("error", "stop"); S.stop = function() { exports.emit("error", { type: "offline" }), s() } function n(e) { if (exports.emit("pick", "success"),!A) { if (e.fields && Array.isArray(e.fields)) S.ack = e.ack || S.ack, e.fields.forEach(function(e) { if (w[e.command]) w[e.command](e) } ); if (!A) exports.emit("pick", "response", { status: 0, bid: S.bid, session: S.session, seq: S.seq, ack: S.ack }) } } function r(e) { if (exports.emit("pick", "failure"),"offline" == e || "kicked" == e) exports.emit("error", "stop"); else if (!A) exports.emit("pick", "response", { status: 1, bid: S.bid, session: S.session, seq: S.seq, ack: S.ack }) } function o() { //超时操作 exports.emit("pick", "timeout"), clearTimeout(y); var e = v; // e为发出去的XMLHttpRequest对象。撤销aja请求,轮询a();重新去取数据 if (v = null ,e.abort(),!v) a(); } //为什么要通过i封装一次,原因是提供统一接口。 function i(e, t, i, n, r) { if ("function" == typeof i) r = n, n = i; if (e == g.TYPE_HI) i = i || {}, i.session = S.session, i.seq = S.seq++; //调用ajax,也是对ajax的封装 return g.get(e, t, i, n, r) } function s(e, t) { return function() { var i = Array.prototype.slice.call(arguments); //闭包来判断私有静态变量b与返回的数据进行对比,e,t闭包起来。 if (e === b) { //有返回,清除y。 if (v = null ,clearTimeout(y),"success" == t) //调用成功 n.apply(null , i); else //调用失败 r.apply(null , i); //A初始时为false,立即重新调用a(), 什么时候A为true;error为stop,或者二次调用失败。 if (!A) setTimeout(function() { a() } , 0) } } } function a() { if (!v) //v全局变量,初始为NULL。 b++, //b初始为0 v = i(g.TYPE_HI, "pick", { imuss: S.bid, ucid: S.ucid, ack: S.ack || "" }, //这里的b传递到回调函数中。 s(b, "success"), s(b, "failure")), //对发出的请求超时监控C.PICK_TIMEOUT=4e4 y = setTimeout(o, C.PICK_TIMEOUT) } //程序入口 a();
2.函数包装器
在看express的源码中,经常看到函数包装的写法,有点难理解,函数包装器的作为是对一个函数进行包装,返回另外一个函数,在包装的过程中,对初始传递参数和目前传递的参数进行改造加工。一般模式是:
// oldF 旧函数, newF 新函数,callback回调函数 options 参数 newF = function(oldF,callback, options) { return function(){ // 在调用newF是,获取newF的参数 var args = Array.prototype.slice(arguments); // this为newF运行的环境 callback.apply(this,args); } } newF(x1,x2)
这里非常类似函数的函数柯里化,函数柯里化就是接受多个参数的函数变换成接受一个单个参数的函数。如:
//针对第一重函数和第二重函数通过第一重函数fn进行处理。 function curry(fn){ // 1...n, var args = Array.prototype.slice.apply(arguments,1); return function(){ var innerArgs = Array.prototype.slice.apply(arguments); var allArgs = args.concat(innerArgs); fn.apply(null, allArgs); } }
在express源码中,有如下函数进行包装了:
// restore obj props after function function restore(fn, obj) { var props = new Array(arguments.length - 2); var vals = new Array(arguments.length - 2); for (var i = 0; i < props.length; i++) { props[i] = arguments[i + 2]; //通过闭包把fn,obj之后的参数存储在一个数组中,vals数组存储obj(res)对象中以参数为key的value。后续调用返回的函数时,该数组不会消失。 vals[i] = obj[props[i]]; } return function(err){ // restore vals,还原闭包遍历vals,也就是obj中的值还原到以前的状态。 for (var i = 0; i < props.length; i++) { obj[props[i]] = vals[i]; } //执行fn,和不用restore来包装一样。 return fn.apply(this, arguments); }; }
还有:
//done和参数done区别在于回调函数第一个参数是参数done,执行done(),实现一个函数代理过程。 done = wrap(done, function(old, err) { if (err || options.length === 0) return old(err); sendOptionsResponse(res, options, old); }); // wrap a function function wrap(old, fn) { //新done函数会接受一些参数,这些参数和wrap函数的参数fn组成新的数组,供apply来调用。 return function proxy() { var args = new Array(arguments.length + 1); args[0] = old; for (var i = 0, len = arguments.length; i < len; i++) { args[i + 1] = arguments[i]; } //在express中this为undefined,args[0] = old,也就是done, fn.apply(this, args); //这里的数组转到回调函数时,参数被打散。 }; } // send an OPTIONS response function sendOptionsResponse(res, options, next) { try { var body = options.join(','); res.set('Allow', body); res.send(body); } catch (err) { next(err); } }
JS对于这个闭包简直是无处不在,看下插件finalHandler的源码:
function finalhandler (req, res, options) { var opts = options || {} // get environment var env = opts.env || process.env.NODE_ENV || 'development' // get error callback var onerror = opts.onerror //返回的函数,这个函数保持了定义时的成员变量,req、res. return function (err) { var headers = Object.create(null) var status // ignore 404 on in-flight response if (!err && res._header) { debug('cannot 404 after headers sent') return } // unhandled error if (err) { // respect status code from error status = getErrorStatusCode(err) || res.statusCode // default status code to 500 if outside valid range if (typeof status !== 'number' || status < 400 || status > 599) { status = 500 } // respect err.headers if (err.headers && (err.status === status || err.statusCode === status)) { var keys = Object.keys(err.headers) for (var i = 0; i < keys.length; i++) { var key = keys[i] headers[key] = err.headers[key] } } // production gets a basic error message var msg = env === 'production' ? statuses[status] : err.stack || err.toString() msg = escapeHtml(msg) .replace(/ /g, '<br>') .replace(/x20{2}/g, ' ') + ' ' } else { status = 404 msg = 'Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl || req.url) + ' ' } debug('default %s', status) // schedule onerror callback if (err && onerror) { defer(onerror, err, req, res) } // cannot actually respond if (res._header) { debug('cannot %d after headers sent', status) req.socket.destroy() return } // send response send(req, res, status, headers, msg) } }
3.函数的静态私有变量
var x = (function(){ function e(e){ this.id = e.toUpperCase(); } var t; //特權函數,給對象x的私有變量賦值,也是函数e的公有静态方法 e.init = function(x){ t = x; } e.prototype.key = function(){ return t +"_" + this.id; } return e; })(); //初始化函数X的静态私有变量 x.init("123456"); var _ = new x("core"); console.log(_.key()); //123456_CORE
又如:
var f1 = function(){ var i = 0; //f1的静态私有变量 return { getI:function(){ return i; }, setI:function(){ i++; } } } var obj = f1(); obj.setI(); console.log(obj.getI()); //1 obj.setI(); console.log(obj.getI()); //2
3.once函数(来源于vue-router)
function once (fn) { let called = false return function (...args) { if (called) return called = true return fn.apply(this, args) } }