jQuery.extend()의 실현 방식 상세 설명 및 인 스 턴 스
<script type="text/javascript" src="jquery-1.5.2.js"></script>
<script>
obj1 = { a : 'a', b : 'b' };
obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' };
$.extend(true, obj1, obj2);
alert(obj1.x.xxx); // "xxx"
obj2.x.xxx = 'zzz';
alert(obj2.x.xxx); // "zzz"
alert(obj1.x.xxx); // "xxx"
</script>
$.extend(true,obj 1,obj 2)는 obj 2 의 속성 확장 대상 obj 1 을 표시 하고 첫 번 째 매개 변 수 는 true 로 설정 하여 깊이 복 사 를 표시 합 니 다.obj 1 에는 원래'x'속성 이 없 었 지만 확장 을 거 친 후에 obj 1 은'x'속성 을 가지 게 되 었 을 뿐만 아니 라 obj 2 의'x'속성 에 대한 수정 도 obj 1 의'x'속성의 값 에 영향 을 주지 않 았 다.이것 이 바로'심 복사'이다.얕 은 복제 의 실현
얕 은 복사 만 이 필요 하 다 면 다음 과 같은 쓰기 방법 을 사용 할 수 있 습 니 다.
$ = {
extend : function(target, options) {
for (name in options) {
target[name] = options[name];
}
return target;
}
};
즉,options 의 속성 을 target 에 간단하게 복사 하 는 것 입 니 다.우 리 는 여전히 유사 한 코드 로 테스트 할 수 있 지만 결 과 는 다르다.딥 복제 의 실현
만약 우리 가'딥 복사'를 실현 하고 싶다 면 복사 한 대상 이 배열 이나 대상 일 때 extend 를 재 귀적 으로 호출 해 야 합 니 다.다음 코드 는'딥 복사'의 간단 한 실현 입 니 다.
<script type="text/javascript" src="jquery-extend.js"></script>
<script>
obj1 = { a : 'a', b : 'b' };
obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' };
$.extend(obj1, obj2);
alert(obj1.x.xxx); // "xxx"
obj2.x.xxx = 'zzz';
alert(obj2.x.xxx); // "zzz"
alert(obj1.x.xxx); // "zzz"
</script>
구체 적 으로 세 가지 상황 으로 나 눌 수 있 습 니 다.1.속성 이 배열 일 때 target[name]을 빈 배열 로 초기 화 한 다음 에 extend 를 재 귀적 으로 호출 합 니 다.2.속성 이 대상 일 때 target[name]을 빈 대상 으로 초기 화하 고 extend 를 재 귀적 으로 호출 합 니 다.3.그렇지 않 으 면 속성 을 직접 복사 합 니 다.테스트 코드 는 다음 과 같 습 니 다.
$ = {
extend : function(deep, target, options) {
for (name in options) {
copy = options[name];
if (deep && copy instanceof Array) {
target[name] = $.extend(deep, [], copy);
} else if (deep && copy instanceof Object) {
target[name] = $.extend(deep, {}, copy);
} else {
target[name] = options[name];
}
}
return target;
}
};
현재 딥 복사 로 지정 되면 obj 2 의 수정 은 obj 1 에 영향 을 주지 않 습 니 다.그러나 이 코드 에는'instanceof Array'가 IE5 에서 호 환 되 지 않 는 경우 가 있 을 수 있 습 니 다.jQuery 의 실현 은 사실상 더 복잡 할 것 이다.더욱 완전한 실현
아래 의 실현 은 jQuery 의 extend()와 좀 더 가 까 워 질 것 입 니 다.
<script type="text/javascript" src="jquery-extend.js"></script>
<script>
obj1 = { a : 'a', b : 'b' };
obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' };
$.extend(true, obj1, obj2);
alert(obj1.x.xxx); // "xxx"
obj2.x.xxx = 'zzz';
alert(obj2.x.xxx); // "zzz"
alert(obj1.x.xxx); // "xxx"
</script>
우선$= function(){...}();이 문법 은 아래 의 문법 과 유사 하 다 고 이해 할 수 있다.
$ = function() {
var copyIsArray,
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty;
class2type = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Object]' : 'object'
},
type = function(obj) {
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
},
isWindow = function(obj) {
return obj && typeof obj === "object" && "setInterval" in obj;
},
isArray = Array.isArray || function(obj) {
return type(obj) === "array";
},
isPlainObject = function(obj) {
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
},
extend = function(deep, target, options) {
for (name in options) {
src = target[name];
copy = options[name];
if (target === copy) { continue; }
if (deep && copy
&& (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
return target;
};
return { extend : extend };
}();
즉,함 수 를 즉시 실행 하고 결 과 를$에 부여 하 는 것 이다.이런 표기 법 은 function 을 이용 하여 작용 역 을 관리 할 수 있 으 며 국부 변수 나 국부 함수 가 전역 에 영향 을 주지 않도록 할 수 있다.또한,우 리 는 사용자 가$.extend()를 호출 하고 내부 에서 실 현 된 함 수 를 숨 기 기 를 바 랍 니 다.따라서 최종 적 으로 돌아 오 는 대상 에는 extend:
func = function(){...};
$ = func();
만 포함 되 어 있 습 니 다.그 다음 에 extend 함수 와 이전의 차 이 를 살 펴 보 겠 습 니 다.먼저 이 말 이 많 습 니 다.
return { extend : extend };
무한 순환 을 피하 기 위해 복사 할 속성 copy 가 target 과 같다 면,'자신'을'자신의 속성'으로 복사 해 예측 할 수 없 는 순환 을 초래 할 수 있다 는 것 이다.그 다음 에 대상 이 배열 인지 아 닌 지 를 판단 하 는 방식 입 니 다.
if (target === copy) { continue; }
브 라 우 저 에 내 장 된 Array.isArray 가 있 으 면 브 라 우 저 자체 의 실현 방식 을 사용 합 니 다.그렇지 않 으 면 대상 을 String 으로 전환 하여'[object Array]'인지 확인 합 니 다.마지막 으로 isPlainObject 의 실현 을 살 펴 보 자.
type = function(obj) {
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
},
isArray = Array.isArray || function(obj) {
return type(obj) === "array";
}
obj.node Type 을 정의 하면 DOM 요소 임 을 나타 낸다.이 코드 는 다음 과 같은 네 가지 상황 을 깊이 복사 하지 않 음 을 나타 낸다.1.대상 은 undefined 이다.2.String 으로 전환 할 때"[object Object]"가 아 닙 니 다.3.obj 는 DOM 요소 입 니 다.4.obj 는 window 입 니 다.DOM 요소 와 window 를 깊이 복사 하지 않 는 이 유 는 속성 이 너무 많 기 때 문 일 수 있 습 니 다.특히 window 대상 은 전역 에서 설명 하 는 모든 변 수 는 그 속성 이 고 내 장 된 속성 은 말 할 필요 도 없다.다음은 구조 함수 와 관련 된 테스트 입 니 다.
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
대상 이 구조 함 수 를 가지 고 있 지만 자신의 속성 이 아니라면 이 구조 함 수 는 prototye 를 통 해 계승 되 었 고 이런 상황 도 깊이 복사 되 지 않 는 다 는 것 을 설명 합 니 다.이 점 은 아래 의 코드 와 결합 하여 이해 할 수 있다.
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
이 몇 개의 코드 는 대상 의 속성 이 모두 자신의 것 인지 검사 하 는 데 사용 된다.대상 의 속성 을 옮 길 때 자신의 속성 부터 옮 겨 다 니 기 때문에 마지막 속성 이 자신의 것 인지 확인 하면 된다.이 는 대상 이 prototype 방식 으로 구조 함수 나 속성 을 계승 하면 이 대상 을 깊이 복사 하지 않 는 다 는 것 을 의미한다.이 는 이러한 대상 이 비교적 복잡 할 수 있 음 을 고려 하여 불확실 한 요 소 를 도입 하거나 대량의 속성 을 복제 하기 위해 많은 시간 을 들 이지 않도록 처리 한 것 일 수도 있 으 며,함수 명 에서 도 알 수 있 듯 이 심층 복 제 를 하 는 것 은'PlainObject'밖 에 없다.만약 에 우리 가 다음 과 같은 코드 로 테스트 를 한다 면
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
이런 상황 은 깊이 복사 하지 않 는 다 는 것 을 알 수 있다.한 마디 로 하면 jQuery 의 extend()실현 방식 은 브 라 우 저의 호환성 을 고려 하여 성능 이 너무 낮 지 않도록 하고 예측 할 수 없 는 오 류 를 도입 하지 않도록 하 는 등 요 소 를 고려 했다.