module.exports = class Application extends Emitter {
constructor() {
super();
this.proxy = false;
this.middleware = [];
this.subdomainOffset = 2;
this.env = process.env.NODE_ENV || 'development';
// context,request,response是空對象,能訪問相應原型對象上的屬性和方法
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}
}
複製代碼
Application繼承自Emitterhtml
Emitter主要是觀察訂閱者模式,在index.js能夠這樣寫,會出現結果node
const app = new Koa();
app.on('a',(element)=>{
// 111
console.log(element)
})
app.emit('a',111)
複製代碼
const a = {a:1,b:2}
const b = Object.create(a); // {}
console.log(b.__proto__ === a) //true
console.log(b.a) //1
複製代碼
a在b的原型鏈上,b能訪問a的屬性git
toJSON() {
return only(this, [
'subdomainOffset',
'proxy',
'env'
]);
}
inspect() {
return this.toJSON();
}
複製代碼
only函數返回新對象,而且只返回包含key,key的值不爲nullgithub
module.exports = function(obj, keys){
obj = obj || {};
if ('string' == typeof keys) keys = keys.split(/ +/);
return keys.reduce(function(ret, key){
if (null == obj[key]) return ret;
ret[key] = obj[key];
return ret;
}, {});
};
複製代碼
//執行app.inspect()
describe('app.inspect()', () => {
it('should work', () => {
const app = new Koa();
const util = require('util');
const str = util.inspect(app);
// 相等則經過
assert.equal("{ subdomainOffset: 2, proxy: false, env: 'test' }", str);
});
});
複製代碼
這裏有個疑問,爲何在index.js 實例後的對象也只有這3個屬性???json
調用listen執行callbackcookie
listen() {
debug('listen');
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}
callback() {
const fn = compose(this.middleware);
if (!this.listeners('error').length) this.on('error', this.onerror);
const handleRequest = (req, res) => {
res.statusCode = 404;
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
fn(ctx).then(handleResponse).catch(onerror);
};
return handleRequest;
}
複製代碼
在這裏順便說一下,原生 http建立一個服務是這樣子滴app
const http = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('okay');
});
http.listen(8000)
複製代碼
createContext(req, res) {
// 以this.context爲藍本創建對象,此時引入的this.context已經兩次實例了。request和response同理,而且做爲屬性掛在context上
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
// context request response都能拿到this,req,res
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
// request response有context;
request.ctx = response.ctx = context;
// request和response能互相拿到各自的對象
request.response = response;
response.request = request;
// context 和 request的originalUrl有了req.url
context.originalUrl = request.originalUrl = req.url;
// context能拿到cookie
context.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.accept = request.accept = accepts(req);
context.state = {};
// 返回 context
return context;
}
複製代碼
通過中間件的處理,responddom
function respond(ctx) {
// allow bypassing koa
if (false === ctx.respond) return;
const res = ctx.res;
if (!ctx.writable) return;
//拿到body
let body = ctx.body;
const code = ctx.status;
// 若是body不存在,那麼返回默認not found
// status body
if (null == body) {
body = ctx.message || String(code);
if (!res.headersSent) {
ctx.type = 'text';
ctx.length = Buffer.byteLength(body);
}
return res.end(body);
}
// 若是body是二進制,是字符串,Stream流,直接返回
// responses
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);
// body: json json的處理
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
複製代碼
前面主要作了onerror和暴露屬性的處理 來看最重要的koa
/** * context可以使用response下attachment redirect的方法, * 可以設置respons下status message的屬性, * 能讀headerSent和writable的值 */
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');
複製代碼
function Delegator(proto, target) {
// 調用函數即返回實例後的本身,每調用一次都是不一樣的this,好處可以調用原型對象上的方法
if (!(this instanceof Delegator)) return new Delegator(proto, target);
this.proto = proto;
this.target = target;
this.methods = [];
this.getters = [];
this.setters = [];
this.fluents = [];
}
複製代碼
// 把target的上的方法掛載在proto上,而執行的this倒是target的
Delegator.prototype.method = function(name){
var proto = this.proto;
var target = this.target;
this.methods.push(name);
proto[name] = function(){
return this[target][name].apply(this[target], arguments);
};
return this;
};
複製代碼
proto[name]能讀取target[name]socket
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;
};
複製代碼
proto[name]能設置target[name]
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;
};
複製代碼
可讀可寫
Delegator.prototype.access = function(name){
return this.getter(name).setter(name);
};
複製代碼
一句話,request對象主要作了拿到請求的信息 咱們看幾個例子
// 返回this.req.headers信息
get header() {
return this.req.headers;
},
get(field) {
const req = this.req;
switch (field = field.toLowerCase()) {
case 'referer':
case 'referrer':
return req.headers.referrer || req.headers.referer || '';
default:
return req.headers[field] || '';
}
},
// 獲取請求頭的內容鏟毒
get length() {
const len = this.get('Content-Length');
if (len == '') return;
return ~~len;
},
//得到query信息
get querystring() {
if (!this.req) return '';
return parse(this.req).query || '';
}
複製代碼
response對象主要作了相應頭的的信息
set(field, val) {
if (2 == arguments.length) {
if (Array.isArray(val)) val = val.map(String);
else val = String(val);
this.res.setHeader(field, val);
} else {
for (const key in field) {
this.set(key, field[key]);
}
}
},
set lastModified(val) {
if ('string' == typeof val) val = new Date(val);
this.set('Last-Modified', val.toUTCString());
},
set etag(val) {
if (!/^(W\/)?"/.test(val)) val = `"${val}"`;
this.set('ETag', val);
}
redirect(url, alt) {
// location
if ('back' == url) url = this.ctx.get('Referrer') || alt || '/';
this.set('Location', url);
// status
if (!statuses.redirect[this.status]) this.status = 302;
// html
if (this.ctx.accepts('html')) {
url = escape(url);
this.type = 'text/html; charset=utf-8';
this.body = `Redirecting to <a href="${url}">${url}</a>.`;
return;
}
// text
this.type = 'text/plain; charset=utf-8';
this.body = `Redirecting to ${url}.`;
}
複製代碼
結合官網看更好
以上,🌹謝謝你的閱讀,你的點贊是我寫文章的動力。 git博客地址