angular.copy 深拷贝源码

638 阅读1分钟

深拷贝

  1. copyElement 简单类型(非对象)直接返回
  2. 缓存里面有没有,有直接返回
  3. copyType Boolean Date等return new source.constructor(source.valueOf());
  4. RegExp: new RegExp(source.source, source.toString().match(/[^/]*$/)[0])
  5. cloneNode: 是dom return source.cloneNode(true);
  6. 数组或对象:destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
  7. copyRecurse 递归:如果是数组或者对象执行for循环或for in循环。
var toString          = Object.prototype.toString,
	getPrototypeOf    = Object.getPrototypeOf;
var isArray = Array.isArray;

function isObject(value) {
	// http://jsperf.com/isobject4
	return value !== null && typeof value === 'object';
}


function isValidObjectMaxDepth(maxDepth) {
	return isNumber(maxDepth) && maxDepth > 0;
}


function isBlankObject(value) {
	return value !== null && typeof value === 'object' && !getPrototypeOf(value);
}

function isFunction(value) {return typeof value === 'function';}

function isNumber(value) {return typeof value === 'number';}



function copy(source, destination, maxDepth) {
	var stackSource = [];
	var stackDest = [];
	maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN;

	if (destination) {

		stackSource.push(source);
		stackDest.push(destination);
		return copyRecurse(source, destination, maxDepth);
	}

	return copyElement(source, maxDepth);

	function copyRecurse(source, destination, maxDepth) {
		maxDepth--;
		if (maxDepth < 0) {
			return '...';
		}
		var key;
		if (isArray(source)) {
			for (var i = 0, ii = source.length; i < ii; i++) {
				destination.push(copyElement(source[i], maxDepth));
			}
		} else if (isBlankObject(source)) {
			// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
			for (key in source) {
				destination[key] = copyElement(source[key], maxDepth);
			}
		} else if (source && typeof source.hasOwnProperty === 'function') {
			// Slow path, which must rely on hasOwnProperty
			for (key in source) {
				if (source.hasOwnProperty(key)) {
					destination[key] = copyElement(source[key], maxDepth);
				}
			}
		} else {
			// Slowest path --- hasOwnProperty can't be called as a method
			for (key in source) {
				if (hasOwnProperty.call(source, key)) {
					destination[key] = copyElement(source[key], maxDepth);
				}
			}
		}
		return destination;
	}

	function copyElement(source, maxDepth) {
		// Simple values
		if (!isObject(source)) {
			return source;
		}

		// Already copied values
		var index = stackSource.indexOf(source);
		if (index !== -1) {
			return stackDest[index];
		}

		var needsRecurse = false;
		var destination = copyType(source);

		if (destination === undefined) {
			destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
			needsRecurse = true;
		}

		stackSource.push(source);
		stackDest.push(destination);

		return needsRecurse
			? copyRecurse(source, destination, maxDepth)
			: destination;
	}

	function copyType(source) {
		switch (toString.call(source)) {
			case '[object Int8Array]':
			case '[object Int16Array]':
			case '[object Int32Array]':
			case '[object Float32Array]':
			case '[object Float64Array]':
			case '[object Uint8Array]':
			case '[object Uint8ClampedArray]':
			case '[object Uint16Array]':
			case '[object Uint32Array]':
				return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length);

			case '[object ArrayBuffer]':
				// Support: IE10
				if (!source.slice) {
					// If we're in this case we know the environment supports ArrayBuffer
					/* eslint-disable no-undef */
					var copied = new ArrayBuffer(source.byteLength);
					new Uint8Array(copied).set(new Uint8Array(source));
					/* eslint-enable */
					return copied;
				}
				return source.slice(0);

			case '[object Boolean]':
			case '[object Number]':
			case '[object String]':
			case '[object Date]':
				return new source.constructor(source.valueOf());

			case '[object RegExp]':
				var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]);
				re.lastIndex = source.lastIndex;
				return re;

			case '[object Blob]':
				return new source.constructor([source], {type: source.type});
		}

		if (isFunction(source.cloneNode)) {
			return source.cloneNode(true);
		}
	}
}