轉(zhuǎn)
// 好像是 Angular 的源碼,下面用了 ngMinErr萝毛,是 Angular 的慣用命名法
function copy(source, destination, stackSource, stackDest) {
if (isWindow(source) || isScope(source)) {
throw ngMinErr('cpws',
"Can't copy! Making copies of Window or Scope instances is not supported.");
}
// destination 判 false 的情況,比如 0, "", NaN, undefined, null 等
// 具體參考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
if (!destination) { // 分支1
// 默認賦值滑黔,相當于在最后加 else [參考(1)>
destination = source;
if (source) {
if (isArray(source)) {
// 如果 source 是數(shù)據(jù)笆包,把 destination 初始化為數(shù)組,再遞歸調(diào)用 copy(會進入分支②)
destination = copy(source, [], stackSource, stackDest);
} else if (isDate(source)) {
// 如果是日期略荡,直接 source 的值構(gòu)造一個新的日期對象
destination = new Date(source.getTime());
} else if (isRegExp(source)) {
// 復制正則表達式
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
destination.lastIndex = source.lastIndex;
} else if (isObject(source)) {
// 如果是其它對象庵佣,把 destination 初始化為空對象,再遞歸調(diào)用 copy(會進入分支②)
destination = copy(source, {}, stackSource, stackDest);
}
// else { destination = source } <參考(1)]
}
} else { // 分支 2
if (source === destination) throw ngMinErr('cpi',
"Can't copy! Source and destination are identical.");
stackSource = stackSource || [];
stackDest = stackDest || [];
if (isObject(source)) {
// 看樣子這里是在處理遞歸引用汛兜,如果在 stackSource 里找到了 source
// 說明之前已經(jīng)產(chǎn)生了這個對象的副本巴粪,直接從 stackDest 返回那個副本就好
var index = indexOf(stackSource, source);
if (index !== -1) return stackDest[index];
// 壓棧,用于以后的遞歸引用檢查
stackSource.push(source);
stackDest.push(destination);
}
var result;
if (isArray(source)) {
destination.length = 0;
for (var i = 0; i < source.length; i++) {
// 這里遞歸調(diào)用 copy 主要是為了深度拷貝粥谬,
// 因為 source[i] 也有可能是一個多么復雜的對象肛根,或者數(shù)組,或者其它……
result = copy(source[i], null, stackSource, stackDest);
if (isObject(source[i])) {
// 拷貝完了記得壓入引用棧帝嗡,供以后檢查
stackSource.push(source[i]);
stackDest.push(result);
}
destination.push(result);
}
} else {
var h = destination.$$hashKey;
// 先重置 destination
if (isArray(destination)) {
// 如果是數(shù)組,清空
destination.length = 0;
} else {
// 如果不是數(shù)組璃氢,刪除所有屬性
forEach(destination, function(value, key) {
delete destination[key];
});
}
// 循環(huán)獲取 source 的每一個屬性哟玷,賦值給 destination
// 同樣需要深拷貝,同樣需要緩存到引用棧
for (var key in source) {
result = copy(source[key], null, stackSource, stackDest);
if (isObject(source[key])) {
stackSource.push(source[key]);
stackDest.push(result);
}
destination[key] = result;
}
// setHashKey 干啥的……我猜是 Angular 內(nèi)機制要用的
setHashKey(destination, h);
}
}
// 結(jié)果返回出去一也,注意到上面多處遞歸調(diào)用 copy 都是要取返回值的
return destination;
}
其實這段代碼的邏輯還是很清楚巢寡,只在 stackSource 和 stackDest 加上遞歸,會稍稍讓人暈乎椰苟∫衷拢拷貝,尤其是深拷貝舆蝴,需要特別注意遞歸引用(就是常說的循環(huán)引用)的問題谦絮,所以這兩個 stack 就是緩存引用,用來處理遞歸引用的洁仗。遞歸层皱,這個是編程的基本技能之一,如果不懂赠潦,就只好先去看看教科書了isWindow 可以猜出來是判斷是否 window 對象的叫胖,isScope 不是很明白,看它的源碼應該能懂她奥,不過這里不 care瓮增,反正就是為了判斷不能拷貝的對象怎棱。