這幾天我一直在思索,關於Koa
我能夠分享什麼?
分享Koa
中間件實現原理?
上一篇的《從源碼上理解express中間件》已經解釋的比較完善了,Koa
中間件實現也比較類似,只不過,是把中間件函數抽離出來爲koa-compose
,循環遍歷的時候將函數外邊套了tryCatch
,裏面加了Promise.resolve(fn)
而已。javascript
所以,這篇文章主要分享下Koa的數據劫持。java
如: 如下訪問器和 Request 別名等效。node
ctx.header
ctx.headers
ctx.method
ctx.method=
ctx.url
ctx.url=
ctx.originalUrl
ctx.origin
ctx.href
ctx.path
ctx.path=
ctx.query
ctx.query=
ctx.querystring
ctx.querystring=
ctx.host
ctx.hostname
ctx.fresh
ctx.stale
ctx.socket
ctx.protocol
ctx.secure
ctx.ip
ctx.ips
ctx.subdomains
ctx.is()
ctx.accepts()
ctx.acceptsEncodings()
ctx.acceptsCharsets()
ctx.acceptsLanguages()
ctx.get()
複製代碼
Koa
的源碼主要分爲四個部分:application
。context
。request
。response
。application
是繼承自Node
核心模塊events
,經過實例化Application
,獲得Koa
。application
在constructor
的時候會經過Object.create()
建立一個新對象,帶着指定的原型對象和屬性。
點擊這裏查看MDN。git
constructor() {
super();
this.proxy = false;
//中間件數組
this.middleware = [];
this.subdomainOffset = 2;
//設置環境變量,默認development
this.env = process.env.NODE_ENV || 'development';
//使用現有的對象來提供新建立的對象的__proto__,即this.context.__proto__ === context //true
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect;
}
}
複製代碼
當用戶執行app.listen
時,調用callback函數,中間件函數的執行和調用createContext()
。github
//啓動Koa服務器
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
//callback
callback() {
//處理Koa的中間件。
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
//建立context
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
//設置context的app、req、res、res、ctx
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.state = {};
return context;
}
複製代碼
Context
屬性代理一些參數主要是經過delegates
模塊實現的,這裏主要是以講述delegates
爲主。express
//建立context的原型
const proto = module.exports = {
...
}
/** * Response delegation. */
delegate(proto, 'response')
.method('attachment')
.method('redirect')
.method('remove')
.method('vary')
.method('set')
.method('append')
.method('flushHeaders')
.access('status')
.access('message')
.access('body')
.access('length')
.access('type')
.access('lastModified')
.access('etag')
.getter('headerSent')
.getter('writable');
複製代碼
Koa
的context
主要應用了delegates
的三個方法,分別是method
、access
、getter
方法。設計模式
初始化Context
的時候,會在createContext
中將response
和request
賦值給ctx
,所以context
含有request
和response
的key
。api
//設置context的app、req、res、res、ctx
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
複製代碼
在建立context
原型proto
時候會調用delegator
,將response
和request
的key
傳遞進去,再依次鏈式調用method
,access
,getter
,將request
和response
中須要代理的屬性依次傳入。數組
如:當用戶經過調用ctx.set()
時, 在此以前,在delegator
中調用了method
方法,已經將set
傳遞進去,proto[name]
能夠理解爲ctx['set']
,賦值給proto[name]
一個函數,因爲是ctx
調用set
,因此當前函數this
的指向是ctx
。服務器
/** * 委託方法的名字 * * @param {String} name * @return {Delegator} self * @api public */
Delegator.prototype.method = function(name){
// proto原型
var proto = this.proto;
//target 爲delegate的第二個參數,這裏是response | request
var target = this.target;
this.methods.push(name);
proto[name] = function(){
return this[target][name].apply(this[target], arguments);
};
return this;
};
複製代碼
而這個函數實際上就是經過將ctx.response.set
經過apply
進行調用,而後return
出去的值。
/** * Delegator accessor `name`. * * @param {String} name * @return {Delegator} self * @api public */
Delegator.prototype.access = function(name){
return this.getter(name).setter(name);
};
/** * Delegator getter `name`. * * @param {String} name * @return {Delegator} self * @api public */
Delegator.prototype.getter = function(name){
var proto = this.proto;
var target = this.target;
this.getters.push(name);
proto.__defineGetter__(name, function(){
return this[target][name];
});
return this;
};
/** * Delegator setter `name`. * * @param {String} name * @return {Delegator} self * @api public */
Delegator.prototype.setter = function(name){
var proto = this.proto;
var target = this.target;
this.setters.push(name);
proto.__defineSetter__(name, function(val){
return this[target][name] = val;
});
return this;
};
複製代碼
而access
方法是setter
、getting
方法的連續調用,經過設置Object.__defineGetter__
和Object.__defineSetter__
來進行數據劫持的。
因爲MDN並不推薦使用這種方法,所以這裏使用Object.defineProperty()
從新寫getter
和setter
方法。
//getter
Delegator.prototype.getter = function(name){
var proto = this.proto;
var target = this.target;
this.setters.push(name);
Object.defineProperty(proto, name, {
get: function() {
return this[target][name];
}
});
return this;
};
//setter
Delegator.prototype.setter = function(name){
var proto = this.proto;
var target = this.target;
this.setters.push(name);
Object.defineProperty(proto, name, {
set: function(val) {
return this[target][name] = val;
}
});
return this;
};
複製代碼
Koa的數據劫持主要是靠Object.__defineSetter__
和Object.__defineSetter__
的應用,不過提及來,Koa
總體的設計模式仍是很值得學習的。