一、首先拋出兩個問題
問題一:在angular中我們綁定數據最基本的方式是用兩個大括號將$scope的變量包裹起來,那么如果想將大括號換成其他什么符號,比如換成[{與}],可不可以呢,如果可以在哪里配置呢?
問題二:綁定的數據是如何被解析的呢?我們通過對$parse的分析,應該猜到綁定到模版的表達式最終會被傳給$parse服務來處理,那么是誰將表達式從html字符串中給讀取出來的呢?
二、$interpolate的功能
$interpolate是一個angular的內部服務,專門給$compile(等把$compile所依賴的服務講完,我們就會分析$compile的代碼了)調用的,而他的作業也比較簡單:就是重字符中綁定的數據給解析出來。其中,它本身只完成獲取數據表達式,表達式的解析將交給$parse服務來完成。
為什么要說$interpolate和$parse干的一樣是臟活累活呢?其實這里主要指的的累活,$interpolate將會被頻繁調用,對代碼的質量要求比較高。
三、源代碼
1.$InterpolateProvider提供修改綁定數據時用的插入標記(interpolation markup)的能力
var startSymbol = '{{';
var endSymbol = '}}';
this.startSymbol = function(value) {
if (value) {
startSymbol = value;
return this;
} else {
return startSymbol;
}
};
this.endSymbol = function(value) {
if (value) {
endSymbol = value;
return this;//如果設置成功,返回對象的引用,提供優雅的鏈式書寫能力
} else {
return endSymbol;
}
};
解決第一個問題,我們可以通過在模塊的配置代碼中寫入$InterpolateProvider.startSymbol('[{').endSymbol('}]')來將默認的插入標記改為[{和}]。
2.插入標記被用來表示了綁定數據的開始和結束,那么,我們怎么來表示他們本身呢。
請看下面的代碼,初次看的時候,這里的代碼有些難懂。
var escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),//startSymbol.replace(/./g, escape)會給startSymbol插入三個反斜杠
escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
function escape(ch) {
return '\\\\\\' + ch;//因為轉義的原因,ch前面的字符將是三個反斜杠符號
}
function unescapeText(text) {
return text.replace(escapedStartRegexp, startSymbol).
replace(escapedEndRegexp, endSymbol);
}
假設插入標記就是默認的{{和}},escapedStartRegexp和escapedEndRegexp將會是什么?
3.從字符串中解出表達式
function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
allOrNothing = !!allOrNothing;
var startIndex,
endIndex,
index = 0,
expressions = [],
parseFns = [],
textLength = text.length,
exp,
concat = [],
expressionPositions = [];
while (index < textLength) {
if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
if (index !== startIndex) {
concat.push(unescapeText(text.substring(index, startIndex)));
}
exp = text.substring(startIndex + startSymbolLength, endIndex);
expressions.push(exp);
parseFns.push($parse(exp, parseStringifyInterceptor));
index = endIndex + endSymbolLength;
expressionPositions.push(concat.length);
concat.push('');
} else {
// we did not find an interpolation, so we have to add the remainder to the separators array
if (index !== textLength) {
concat.push(unescapeText(text.substring(index)));
}
break;
}
}
...
從上面的代碼實現來看,作者還是沒有采用效率比較的低的正則表達式來完成表達式的識別。上面的代碼完成的功能是將字符串分組(壓入concat數組),分組的邊界時表達式插入符,表達式本身用空格占位,表達式本則壓入另一個數據(parseFns),並且記錄下表達式在concat的位置。這里看到表達式的解析依然是調用的$parse服務。
4.$interpolate最終返回的是什么?
if (trustedContext && concat.length > 1) {
$interpolateMinErr.throwNoconcat(text);
}
if (!mustHaveExpression || expressions.length) {
var compute = function(values) { //計算表達式值,並且拼接為字符串
for (var i = 0, ii = expressions.length; i < ii; i++) {
if (allOrNothing && isUndefined(values[i])) return;
concat[expressionPositions[i]] = values[i];
}
return concat.join('');
};
var getValue = function(value) { //獲取值
return trustedContext ?
$sce.getTrusted(trustedContext, value) : //利用$sce進行檢查
$sce.valueOf(value);
};
return extend(function interpolationFn(context) {
var i = 0;
var ii = expressions.length;
var values = new Array(ii);
try {
for (; i < ii; i++) {
values[i] = parseFns[i](context);
}
return compute(values);
} catch (err) {
$exceptionHandler($interpolateMinErr.interr(text, err));
}
}, {
// all of these properties are undocumented for now
exp: text, //just for compatibility with regular watchers created via $watch
expressions: expressions,
$$watchDelegate: function(scope, listener) { //監聽的代理,通過Scope.$watchGroup對text中的所有表達式進行監聽
var lastValue;
return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
var currValue = compute(values);
if (isFunction(listener)) {
listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
}
lastValue = currValue;
});
}
});
}
function parseStringifyInterceptor(value) {
try {
value = getValue(value);
return allOrNothing && !isDefined(value) ? value : stringify(value);
} catch (err) {
$exceptionHandler($interpolateMinErr.interr(text, err));
}
}
}
$interpolate將返回一個函數,這個函數能夠獲取到text被解析后的值。這個函數並且綁定了exp,expressions和$$watchDelegate三個屬性。
上一期:angular源碼分析:angular中入境檢察官$sce
下一期:angular源碼分析:$compile服務——directive他媽
ps:在下一期中,我們會講解$compile服務,將講解指令是如何實現的,梳理指令的整個執行流程。
