我正在使用Connect / Express.js构建一个Node.js应用程序,我希望拦截res.render(视图,选项)函数,以便在将其转发到原始渲染函数之前运行一些代码。
app.get('/someUrl', function(req, res) {
res.render = function(view, options, callback) {
view = 'testViews/' + view;
res.prototype.render(view, options, callback);
};
res.render('index', { title: 'Hello world' });
});
它看起来像一个人为的例子,但它确实适合我正在构建的整体框架。
我对JavaScript的OOP和Prototypal继承的了解有点弱。我该怎么办呢?
更新:经过一些实验,我想出了以下内容:
app.get('/someUrl', function(req, res) {
var response = {};
response.prototype = res;
response.render = function(view, opts, fn, parent, sub){
view = 'testViews/' + view;
this.prototype.render(view, opts, fn, parent, sub);
};
response.render('index', { title: 'Hello world' });
});
似乎有效。不确定这是否是最好的解决方案,因为我正在为每个请求创建一个新的响应包装器对象,这会有问题吗?
答案 0 :(得分:25)
您可以使用/编写中间件。这个概念起初对我来说有点令人生畏,但经过一些阅读后它更有意义。而对于其他读这篇文章的人来说,覆盖res.render的动机是提供全局视图变量。我希望session
在我的所有模板中都可用,而不必在每个res对象中输入它。
基本的中间件格式是。
app.use( function( req, res, next ) {
//....
next();
} );
下一个参数和函数调用对执行至关重要。 next
是回调函数,允许多个中间件在不阻塞的情况下执行其操作。为了更好explanation read here
然后可以使用它来覆盖渲染逻辑
app.use( function( req, res, next ) {
// grab reference of render
var _render = res.render;
// override logic
res.render = function( view, options, fn ) {
// do some custom logic
_.extend( options, {session: true} );
// continue with original render
_render.call( this, view, options, fn );
}
next();
} );
我使用express 3.0.6测试了此代码。它应该与4.x一起使用而没有问题。 您还可以使用
覆盖特定的URL组合app.use( '/myspcificurl', function( req, res, next ) {...} );
答案 1 :(得分:12)
使用中间件覆盖每个实例的响应或请求方法并不是一个好主意,因为中间件是针对每个请求执行的,每次调用它时都使用cpu作为记忆因为你正在创造一个新的功能。
你可能知道javascript是一种基于Prototype的语言,每个对象都有一个原型,比如响应和请求对象。通过查看代码(表达4.13.4 ),您可以找到他们的原型:
req => express.request
res => express.response
因此,当您想要为每个响应实例覆盖一个方法时,在它的原型中覆盖它会好得多,因为它在每个实例中都可用。响应:
var app = (global.express = require('express'))();
var render = express.response.render;
express.response.render = function(view, options, callback) {
// desired code
/** here this refer to the current res instance and you can even access req for this res: **/
console.log(this.req);
render.apply(this, arguments);
};
答案 2 :(得分:7)
response
对象没有原型。这应该有效(将Ryan的想法放在中间件中):
var wrapRender = function(req, res, next) {
var _render = res.render;
res.render = function(view, options, callback) {
_render.call(res, "testViews/" + view, options, callback);
};
};
但是,破解ServerResponse.prototype可能更好:
var express = require("express")
, http = require("http")
, response = http.ServerResponse.prototype
, _render = response.render;
response.render = function(view, options, callback) {
_render.call(this, "testViews/" + view, options, callback);
};
答案 3 :(得分:3)
我最近发现自己需要做同样的事情,为每个模板提供特定于配置的Google Analytics媒体资源ID和Cookie域。
这里有很多很棒的解决方案。
我选择了与Lex提出的解决方案非常接近的方法,但遇到了对res.render()的调用尚未包含现有选项的问题。例如,以下代码在对extend()的调用中导致异常,因为选项未定义:
return res.render('admin/refreshes');
我添加了以下内容,它考虑了可能的各种参数组合,包括回调。类似的方法可以与其他人提出的解决方案一起使用。
app.use(function(req, res, next) {
var _render = res.render;
res.render = function(view, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
} else if (!options) {
options = {};
}
extend(options, {
gaPropertyID: config.googleAnalytics.propertyID,
gaCookieDomain: config.googleAnalytics.cookieDomain
});
_render.call(this, view, options, callback);
}
next();
});
编辑事实证明,当我需要实际运行某些代码时,这一切都很方便,这是一种非常简单的方法来完成我想要做的事情。我再次查看Express的源代码和文档,结果发现app.locals用于呈现每个模板。所以在我的情况下,我最终用以下任务替换了上面的所有中间件代码:
app.locals.gaPropertyID = config.googleAnalytics.propertyID;
app.locals.gaCookieDomain = config.googleAnalytics.cookieDomain;