1. 程式人生 > >淺談Express中的app.locals物件字面量

淺談Express中的app.locals物件字面量

原文:http://cnodejs.org/topic/57a5b34300bb7f2c700c7b9c

為什麼app.locals定義的鍵值對能在模板中直接訪問呢

不知道大家在使用express框架開發的過程中,有沒有過這樣的疑問,在app.locals這個物件字面量中定義的鍵值對,是可以直接在模板中使用的,就和res.render時開發者傳入的模板渲染引數一樣,那麼為什麼能這樣操作呢,本文就是從原始碼角度淺析下這個問題。

res.render做了些什麼

其實要探討大標題的問題,必須先弄明白,當我們在路由函式中使用:

res.render(view, data)

在服務端渲染輸出頁面的時候,express做了些什麼。 這本身就是一個很有意思的問題,因為,大部分Express開發者都遇到過如下二種寫法:

res.render(view, data);
res.render(view, data, (err, text)=>if(!err)=>res.send(text));

很有意思吧,也就是說,當我們呼叫res的render方法時,如果不傳第三個回撥函式,則render結束後將結果HTML自動傳送給瀏覽器;如果我們傳入第三個回撥函式,則伺服器端的render頁面結果HTML字串會以該回調函式的第二個引數的形式返回(上述程式碼樣例中的text),此時何時返回呼叫res.send方法將HTML給瀏覽器由開發者自己決定。 接下來我們就深入Express的原始碼來理解下,為什麼可以這樣進行編碼。經過查閱express的原始碼,可以發現res.render方法最終是在express/lib/response.js中實現的。至於在形如:

app.get(‘/index’, (req, res, next)=>res.render(‘index’))

的常規路由函式中,初始的http伺服器處理控制代碼中傳入的原始req和res何時被express自動包裝,提供了諸如res.send(), res.json()等方法呼叫鏈,這裡只提供大略的原始碼路線,不作詳細展開:其實此處大家可以自己看lib/application.js,從第一個中介軟體載入開始(或者沒有中介軟體,直接開始載入路由),都會執行一個lazyrouter方法,那麼原始的req和res正是在這裡使用原型鏈賦值的方式進行初始化包裝了express提供的lib/request.js和lib/response.js方法的。 有點扯遠了,迴歸正途,首先貼一下res.render的精簡後的原始碼:

function render(view, options, callback) {
	var app = this.req.app;
	var done = callback;
	var opts = options || {};
	var req = this.req;
	var self = this;
	done = done || function (err, str) {
		if (err) return req.next(err);
		self.send(str);
	};
	app.render(view, opts, done);
};

這段程式碼非常容易理解,也解釋了本節最開始提出的問題,兩種寫法都支援的原因,就是:

var done = callback;
done = done || function (err, str) {
	if (err) return req.next(err);
	self.send(str);
};

顯然,如果使用者傳入了回撥函式,則done就是使用者傳入的回撥函式,如果使用者沒有傳入回撥函式,則express框架自動給你添加了一個渲染完成沒有錯誤自動將渲染後的HTML返回給瀏覽器的回撥函式,當然這個自動新增的回撥函式也提供了簡單的異常處理,比如渲染出錯,就走next(err),返回500給瀏覽器。 接下來就是

var app = this.req.app;
app.render(view, opts, done);

這裡的app可以通過this.req.app來獲取來獲取的原因,就是上面提到的原始req和res在lazyrouter中進行了一系列初始化的結果,具體不展開了。而得到的app,就是原始的express生成的app。 所以,res.render方法,最終呼叫的就是app.render方法,並且傳入了三個引數view,opts,done。其中view依舊是模板路徑,opts則是渲染該模板傳入的引數,最後的done就是回撥函式。

app.render

上面一節分析了這麼多,其實正主在這裡。我們可以看到,最終所有的render方法,收口的地方在app.render函式中。這個函式也能找到本文的主題:app.locals中定義的鍵值對為何能在模板中直接使用,真正原因。 核心的程式碼如下:

function render(name, options, callback) {
	…
	var renderOptions = {};
	var opts = options;
	…
	merge(renderOptions, this.locals);
	if (opts._locals) {
    		merge(renderOptions, opts._locals);
	}
	merge(renderOptions, opts);
	…
	tryRender(view, renderOptions, done);
}

我把和大標題無關的view生成程式碼都去掉了,可以看到,最後呼叫tryRender方法進行渲染傳入的引數renderOptions,其實是由app.locals,options._locals(如果存在的話)和真正的由開發者傳入的渲染頁面所需要的引數options,這三者merge而成的。 那麼在模板中,真正輸出給模板的引數是這個包含上述三者的renderOptions,故而,我們可以在模板中和呼叫自己傳入引數一樣的方式,直接呼叫app.locals中定義的鍵值對,這些鍵值對一般是公共模板方法或者公共模板變數。