Jquery의 전체 아키텍처에 대한 자세한 예
오픈소스 프레임워크를 배우면서 아이들이 가장 배우고 싶어하는 것은 디자인 아이디어와 구현 기술입니다. 최근에는 jQuery 소스 코드를 연구하고 jQuery에 대한 나의 이해와 경험을 기록하여 모든 사람과 공유했기 때문에 이를 출발점으로 삼을 수 있습니다.
전체적인 jQuery 프레임워크는 매우 복잡하고 이해하기 쉽지 않습니다. 저는 지난 며칠 동안 이 무겁고 강력한 프레임워크를 연구해 왔습니다. jQuery의 전체 아키텍처는 입력 모듈, 하단 모듈 및 기능 모듈로 나눌 수 있습니다. 여기서는 분석을 위한 예로 jquery-1.7.1을 사용합니다.
jquery의 전체 아키텍처
코드는 다음과 같습니다.
16 (function( window, undefined ) { // 构造 jQuery 对象 22 var jQuery = (function() { 25 var jQuery = function( selector, context ) { 27 return new jQuery.fn.init( selector, context, rootjQuery ); 28 }, // 一堆局部变量声明 97 jQuery.fn = jQuery.prototype = { 98 constructor: jQuery, 99 init: function( selector, context, rootjQuery ) { ... }, // 一堆原型属性和方法 319 }; 322 jQuery.fn.init.prototype = jQuery.fn; 324 jQuery.extend = jQuery.fn.extend = function() { ... }; 388 jQuery.extend({ // 一堆静态属性和方法 892 }); 955 return jQuery; 957 })(); // 省略其他模块的代码 ... 9246 window.jQuery = window.$ = jQuery; 9266 })( window );
위 코드를 분석해 본 결과 jquery는 익명 함수 자체 실행 방식을 채택하고 있어 문제를 효과적으로 방지할 수 있다는 장점이 있었습니다. 네임스페이스와 변수 오염. 위 코드를 축약하면 다음과 같습니다.
코드는 다음과 같습니다.
(function(window, undefined) { var jQuery = function() {} // ... window.jQuery = window.$ = jQuery; })(window);
Parameter window
익명 함수는 두 개의 매개변수를 전달합니다. 하나는 window이고 다른 하나는 정의되지 않습니다. 우리는 js의 변수에 범위 체인이 있다는 것을 알고 있습니다. 전달된 두 변수는 익명 함수의 로컬 변수가 되며 더 빠르게 액세스됩니다. window 객체를 전달하면 window 객체를 지역 변수로 사용할 수 있으며, 그러면 함수의 매개변수도 지역 변수가 됩니다. jquery에서 window 객체에 액세스할 때 범위 체인을 맨 위로 반환할 필요가 없습니다. -수준 범위이므로 창 개체에 더 빠르게 액세스할 수 있습니다.
Parameter undefine
js가 변수를 찾을 때, js 엔진은 먼저 함수 자체의 범위에서 변수를 찾습니다. 변수가 없으면 계속해서 찾습니다. , 변수를 찾을 수 없으면 정의되지 않은 값을 반환합니다. undefound는 window 객체의 속성입니다. 값을 할당하지 않고 undefound 매개변수를 전달하면 undefed를 검색할 때 범위 체인이 단축될 수 있습니다. 자체 호출 익명 함수의 범위 내에서 정의되지 않음이 실제로 정의되지 않았는지 확인하세요. 정의되지 않음을 덮어쓰고 새 값을 부여할 수 있기 때문입니다.
jquery.fn이 무엇인가요?
코드는 다음과 같습니다.
jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { ... }, // 一堆原型属性和方法 };
위 코드를 분석한 결과 jQuery.fn은 jQuery.prototype이라는 것을 알 수 있었습니다. 이렇게 작성하면 길이가 더 짧다는 장점이 있습니다. 나중에 우리는 jquery가 단순함을 위해 jquery 대신 $ 기호를 사용하는 것을 보았습니다. 따라서 jquery 프레임워크를 사용할 때 $(),
constructor jQuery()
이미지 설명
jQuery 객체를 사용하는 경우가 많습니다. 는 새 jQuery를 통해 생성되지 않고 새 jQuery.fn.init를 통해 생성됩니다.
코드는 다음과 같습니다.
var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context, rootjQuery ); }
변수 jQuery는 여기에 정의되어 있으며 해당 값은 955행의 jQuery 생성자입니다( 상위 코드)는 jQuery 변수
jQuery.fn.init
jQuery.fn(위의 97행)을 반환하고 할당합니다. 이는 생성자 jQuery(), jQuery.fn.init()의 프로토타입 객체입니다. jQuery 프로토타입입니다. 생성자라고도 불리는 메서드입니다. 매개변수 선택기 및 컨텍스트의 유형을 구문 분석하고 해당 검색을 수행하는 역할을 담당합니다.
매개변수 컨텍스트: jQuery 객체, DOM 요소 또는 일반 js 객체 중 하나를 전달할 수 없습니다.
매개변수 rootjQuery: document.getElementById() 검색 실패와 같은 상황에 사용되는 문서 객체를 포함하는 jQuery 객체입니다.
코드는 다음과 같습니다.
jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype jQuery(selector [,context])
기본적으로 일치하는 요소에 대한 검색은 루트 요소 문서 개체에서 시작됩니다. 즉, 검색 범위는 전체 문서 트리이지만 두 번째 매개 변수 컨텍스트는 다음과 같을 수도 있습니다. 검색 범위를 제한하기 위해 전달되었습니다. 예:
코드는 다음과 같습니다.
$('p.foo').click(function () { $('span',this).addClass('bar');//限定查找范围,即上面的context }); jQuery.extend()和jQuery.fn.extend()
jQuery.extend(object) 및 jQuery.fn.extend(object) 메서드는 두 개 이상의 개체를 첫 번째 개체로 병합하는 데 사용됩니다. 해당 소스 코드는 다음과 같습니다(일부).
코드는 다음과 같습니다.
jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone,//定义的一组局部变量 target = arguments[0] || {}, i = 1, length = arguments.length, deep = false;
jQuery.extend(object); jQuery 클래스에 클래스 메서드를 추가하는 것은 정적 메서드를 추가하는 것으로 이해될 수 있습니다. 예를 들면 다음과 같습니다.
코드는 다음과 같습니다.
$.extend({ add:function(a,b){returna+b;} });
add라는 "정적 메서드"를 jQuery에 추가합니다. 그러면 jQuery가 도입된
$.add(3,4) 메서드를 사용할 수 있습니다. //return 7
jQuery.fn.extend(object), 공식 웹사이트에서 다음과 같은 코드 데모를 확인하세요:
코드는 다음과 같습니다:
<label><input type="checkbox" name="foo"> Foo</label> <label><input type="checkbox" name="bar"> Bar</label>
<script> jQuery.fn.extend({ check: function() { return this.each(function() { this.checked = true; }); }, uncheck: function() { return this.each(function() { this.checked = false; }); } }); // Use the newly created .check() method $( "input[type='checkbox']" ).check(); </script>
CSS 선택 엔진 Sizzle
될 수 있습니다. jQuery가 DOM을 운영하기 위한 것이라고 말했습니다. jQuery가 그토록 강력한 이유는 CSS 선택기 엔진 Sizzle 때문입니다. 구문 분석 규칙은 인터넷의 예를 참조합니다:
selector: "p > p + p.aaron input[type ="체크박스"]"
解析规则:
1 按照从右到左
2 取出最后一个token 比如[type="checkbox"]
{
matches : Array[3]
type : "ATTR"
value : "[type="
checkbox "]"
}
3 过滤类型 如果type是 > + ~ 空 四种关系选择器中的一种,则跳过,在继续过滤
4 直到匹配到为 ID,CLASS,TAG 中一种 , 因为这样才能通过浏览器的接口索取
5 此时seed种子合集中就有值了,这样把刷选的条件给缩的很小了
6 如果匹配的seed的合集有多个就需要进一步的过滤了,修正选择器 selector: "p > p + p.aaron [type="checkbox"]"
7 OK,跳到一下阶段的编译函数
deferred对象
开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。
通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。
但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象。
简单说,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。
回顾一下jQuery的ajax操作的传统写法:
代码如下:
$.ajax({ url: "test.html", success: function(){ alert("哈哈,成功了!"); }, error:function(){ alert("出错啦!"); } });
在上面的代码中,$.ajax()接受一个对象参数,这个对象包含两个方法:success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。
$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。
现在,新的写法是这样的:
代码如下:
$.ajax("test.html") .done(function(){ alert("哈哈,成功了!"); }) .fail(function(){ alert("出错啦!"); });
为多个操作指定回调函数
deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。
请看下面的代码,它用到了一个新的方法$.when():
代码如下:
$.when($.ajax("test1.html"), $.ajax("test2.html")) .done(function(){ alert("哈哈,成功了!"); }) .fail(function(){ alert("出错啦!"); });
这段代码的意思是,先执行两个操作$.ajax("test1.html")和$.ajax("test2.html"),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。
jQuery.Deferred( func ) 的实现原理
内部维护了三个回调函数列表:成功回调函数列表、失败回调函数列表、消息回调函数列表,其他方法则围绕这三个列表进行操作和检测。
jQuery.Deferred( func ) 的源码结构:
代码如下:
jQuery.extend({ Deferred: function( func ) { // 成功回调函数列表 var doneList = jQuery.Callbacks( "once memory" ), // 失败回调函数列表 failList = jQuery.Callbacks( "once memory" ), // 消息回调函数列表 progressList = jQuery.Callbacks( "memory" ), // 初始状态 state = "pending", // 异步队列的只读副本 promise = { // done, fail, progress // state, isResolved, isRejected // then, always // pipe // promise }, // 异步队列 deferred = promise.promise({}), key; // 添加触发成功、失败、消息回调函列表的方法 for ( key in lists ) { deferred[ key ] = lists[ key ].fire; deferred[ key + "With" ] = lists[ key ].fireWith; } // 添加设置状态的回调函数 deferred.done( function() { state = "resolved"; }, failList.disable, progressList.lock ) .fail( function() { state = "rejected"; }, doneList.disable, progressList.lock ); // 如果传入函数参数 func,则执行。 if ( func ) { func.call( deferred, deferred ); } // 返回异步队列 deferred return deferred; }, }
jQuery.when( deferreds )
提供了基于一个或多个对象的状态来执行回调函数的功能,通常是基于具有异步事件的异步队列。
jQuery.when( deferreds ) 的用法
如果传入多个异步队列对象,方法 jQuery.when() 返回一个新的主异步队列对象的只读副本,只读副本将跟踪所传入的异步队列的最终状态。
一旦所有异步队列都变为成功状态,“主“异步队列的成功回调函数被调用;
如果其中一个异步队列变为失败状态,主异步队列的失败回调函数被调用。
代码如下:
/* 请求 '/when.do?method=when1' 返回 {"when":1} 请求 '/when.do?method=when2' 返回 {"when":2} 请求 '/when.do?method=when3' 返回 {"when":3} */ var whenDone = function(){ console.log( 'done', arguments ); }, whenFail = function(){ console.log( 'fail', arguments ); }; $.when( $.ajax( '/when.do?method=when1', { dataType: "json" } ), $.ajax( '/when.do?method=when2', { dataType: "json" } ), $.ajax( '/when.do?method=when3', { dataType: "json" } ) ).done( whenDone ).fail( whenFail );
图片描述
异步队列 Deferred
解耦异步任务和回调函数
为 ajax 模块、队列模块、ready 事件提供基础功能。
原型属性和方法
原型属性和方法源代码:
代码如下:
97 jQuery.fn = jQuery.prototype = { 98 constructor: jQuery, 99 init: function( selector, context, rootjQuery ) {} 210 selector: "", 213 jquery: "1.7.1", 216 length: 0, 219 size: function() {}, 223 toArray: function() {}, 229 get: function( num ) {}, 241 pushStack: function( elems, name, selector ) {}, 270 each: function( callback, args ) {}, 274 ready: function( fn ) {}, // 284 eq: function( i ) {}, 291 first: function() {}, 295 last: function() {}, 299 slice: function() {}, 304 map: function( callback ) {}, 310 end: function() {}, 316 push: push, 317 sort: [].sort, 318 splice: [].splice 319 };
属性selector用于记录jQuery查找和过滤DOM元素时的选择器表达式。
属性.length表示当前jquery对象中元素的个数。
方法.size()返回当前jquery对象中元素的个数,功能上等同于属性length,但应该优先使用length,因为他没有函数调用开销。
.size()源码如下:
代码如下:
size():function(){ return this.length; }
方法.toArray()将当前jQuery对象转换为真正的数组,转换后的数组包含了所有元素,其源码如下:
代码如下:
toArray: function() { return slice.call( this ); },
方法.get(index)返回当前jQuery对象中指定位置的元素,或包含了全部元素的数组。其源
码如下:
代码如下:
get: function( num ) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object ( num < 0 ? this[ this.length + num ] : this[ num ] ); },
如果没有传入参数,则调用.toArray()返回了包含有锁元素的数组;如果指定了参数index,则返回一个单独的元素,index从0开始计数,并且支持负数。
首先会判断num是否小于0,如果小于0,则用length+num重新计算下标,然后使用数组访问操作符([])获取指定位置的元素,这是支持下标为负数的一个小技巧;如果大于等于0,直接返回指定位置的元素。
eg()和get()使用详解:jquery常用方法及使用示例汇总
方法.each()用于遍历当前jQuery对象,并在每个元素上执行回调函数。方法.each()内部通过简单的调用静态方法jQuery.each()实现:
代码如下:
each: function( callback, args ) { return jQuery.each( this, callback, args ); },
回调函数是在当前元素为上下文的语境中触发的,即关键字this总是指向当前元素,在回调函数中return false 可以终止遍历。
方法.map()遍历当前jQuery对象,在每个元素上执行回调函数,并将回调函数的返回值放入一个新jQuery对象中。该方法常用于获取或设置DOM元素集合的值。
代码如下:
map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); },
原型方法.pushStack()创建一个新的空jQuery对象,然后把DOM元素集合放入这个jQuery对象中,并保留对当前jQuery对象的引用。
原型方法.pushStack()是核心方法之一,它为以下方法提供支持:
jQuery对象遍历:.eq()、.first()、.last()、.slice()、.map()。
DOM查找、过滤:.find()、.not()、.filter()、.closest()、.add()、.andSelf()。
DOM遍历:.parent()、.parents()、.parentsUntil()、.next()、.prev()、.nextAll()、.prevAll()、.nextUnit()、.prevUnit()、.siblings()、.children()、.contents()。
DOM插入:jQuery.before()、jQuery.after()、jQuery.replaceWith()、.append()、.prepent()、.before()、.after()、.replaceWith()。
定义方法.push( elems, name, selector ),它接受3个参数:
参数elems:将放入新jQuery对象的元素数组(或类数组对象)。
参数name:产生元素数组elems的jQuery方法名。
参数selector:传给jQuery方法的参数,用于修正原型属性.selector。
方法.end()结束当前链条中最近的筛选操作,并将匹配元素还原为之前的状态
代码如下:
end: function() { return this.prevObject || this.constructor(null); },
返回前一个jQuery对象,如果属性prevObect不存在,则构建一个空的jQuery对象返回。方法.pushStack()用于入栈,方法.end()用于出栈
静态属性和方法
相关源码如下:
代码如下:
388 jQuery.extend({ 389 noConflict: function( deep ) {}, 402 isReady: false, 406 readyWait: 1, 409 holdReady: function( hold ) {}, 418 ready: function( wait ) {}, 444 bindReady: function() {}, 492 isFunction: function( obj ) {}, 496 isArray: Array.isArray || function( obj ) {}, 501 isWindow: function( obj ) {}, 505 isNumeric: function( obj ) {}, 509 type: function( obj ) {}, 515 isPlainObject: function( obj ) {}, 544 isEmptyObject: function( obj ) {}, 551 error: function( msg ) {}, 555 parseJSON: function( data ) {}, 581 parseXML: function( data ) {}, 601 noop: function() {}, 606 globalEval: function( data ) {}, 619 camelCase: function( string ) {}, 623 nodeName: function( elem, name ) {}, 628 each: function( object, callback, args ) {}, 669 trim: trim ? function( text ) {} : function( text ) {}, 684 makeArray: function( array, results ) {}, 702 inArray: function( elem, array, i ) {}, 724 merge: function( first, second ) {}, 744 grep: function( elems, callback, inv ) {}, 761 map: function( elems, callback, arg ) {}, 794 guid: 1, 798 proxy: function( fn, context ) {}, 825 access: function( elems, key, value, exec, fn, pass ) {}, 852 now: function() {}, 858 uaMatch: function( ua ) {}, 870 sub: function() {}, 891 browser: {} 892 });
未完待续、、、今天就先到这里了,下次补齐。别急哈小伙伴们
위 내용은 Jquery의 전체 아키텍처에 대한 자세한 예의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











jQuery에서 PUT 요청 방법을 사용하는 방법은 무엇입니까? jQuery에서 PUT 요청을 보내는 방법은 다른 유형의 요청을 보내는 것과 유사하지만 몇 가지 세부 사항과 매개 변수 설정에 주의해야 합니다. PUT 요청은 일반적으로 데이터베이스의 데이터 업데이트 또는 서버의 파일 업데이트와 같은 리소스를 업데이트하는 데 사용됩니다. 다음은 jQuery에서 PUT 요청 메소드를 사용하는 구체적인 코드 예제입니다. 먼저 jQuery 라이브러리 파일을 포함했는지 확인한 다음 $.ajax({u를 통해 PUT 요청을 보낼 수 있습니다.

제목: DreamWeaver CMS의 보조 디렉터리를 열 수 없는 이유와 해결 방법 분석 Dreamweaver CMS(DedeCMS)는 다양한 웹 사이트 구축에 널리 사용되는 강력한 오픈 소스 콘텐츠 관리 시스템입니다. 그러나 때로는 웹사이트를 구축하는 과정에서 보조 디렉토리를 열 수 없는 상황이 발생할 수 있으며, 이로 인해 웹사이트의 정상적인 작동에 문제가 발생할 수 있습니다. 이 기사에서는 보조 디렉터리를 열 수 없는 가능한 이유를 분석하고 이 문제를 해결하기 위한 구체적인 코드 예제를 제공합니다. 1. 예상 원인 분석: 의사 정적 규칙 구성 문제: 사용 중

제목: jQuery 팁: 페이지에 있는 모든 태그의 텍스트를 빠르게 수정하세요. 웹 개발에서는 페이지의 요소를 수정하고 조작해야 하는 경우가 많습니다. jQuery를 사용할 때 페이지에 있는 모든 태그의 텍스트 내용을 한 번에 수정해야 하는 경우가 있는데, 이는 시간과 에너지를 절약할 수 있습니다. 다음은 jQuery를 사용하여 페이지의 모든 태그 텍스트를 빠르게 수정하는 방법을 소개하고 구체적인 코드 예제를 제공합니다. 먼저 jQuery 라이브러리 파일을 도입하고 다음 코드가 페이지에 도입되었는지 확인해야 합니다. <

제목: jQuery를 사용하여 모든 태그의 텍스트 내용을 수정합니다. jQuery는 DOM 작업을 처리하는 데 널리 사용되는 인기 있는 JavaScript 라이브러리입니다. 웹 개발을 하다 보면 페이지에 있는 링크 태그(태그)의 텍스트 내용을 수정해야 하는 경우가 종종 있습니다. 이 기사에서는 jQuery를 사용하여 이 목표를 달성하는 방법을 설명하고 구체적인 코드 예제를 제공합니다. 먼저 페이지에 jQuery 라이브러리를 도입해야 합니다. HTML 파일에 다음 코드를 추가합니다.

jQuery 요소에 특정 속성이 있는지 어떻게 알 수 있나요? jQuery를 사용하여 DOM 요소를 조작할 때 요소에 특정 속성이 있는지 확인해야 하는 상황이 자주 발생합니다. 이 경우 jQuery에서 제공하는 메소드를 사용하여 이 기능을 쉽게 구현할 수 있습니다. 다음은 jQuery 요소에 특정 속성이 있는지 확인하기 위해 일반적으로 사용되는 두 가지 방법을 특정 코드 예제와 함께 소개합니다. 방법 1: attr() 메서드와 typeof 연산자를 // 사용하여 요소에 특정 속성이 있는지 확인

jQuery는 웹 페이지에서 DOM 조작 및 이벤트 처리를 처리하는 데 널리 사용되는 인기 있는 JavaScript 라이브러리입니다. jQuery에서 eq() 메서드는 지정된 인덱스 위치에서 요소를 선택하는 데 사용됩니다. 구체적인 사용 및 적용 시나리오는 다음과 같습니다. jQuery에서 eq() 메서드는 지정된 인덱스 위치에 있는 요소를 선택합니다. 인덱스 위치는 0부터 계산되기 시작합니다. 즉, 첫 번째 요소의 인덱스는 0이고 두 번째 요소의 인덱스는 1입니다. eq() 메소드의 구문은 다음과 같습니다: $("s

제목: Tencent의 주요 프로그래밍 언어는 Go: 심층 분석 중국 최고의 기술 회사로서 Tencent는 프로그래밍 언어 선택에 있어 항상 많은 관심을 받아 왔습니다. 최근 몇 년 동안 일부 사람들은 Tencent가 주로 Go를 주요 프로그래밍 언어로 채택했다고 믿고 있습니다. 이 기사에서는 Tencent의 주요 프로그래밍 언어가 Go인지에 대한 심층 분석을 수행하고 이러한 관점을 뒷받침하는 구체적인 코드 예제를 제공합니다. 1. Tencent에 Go 언어 적용 Go는 Google에서 개발한 오픈 소스 프로그래밍 언어로 효율성, 동시성 및 단순성으로 인해 많은 개발자에게 사랑을 받고 있습니다.

jQuery는 웹 개발에 널리 사용되는 인기 있는 JavaScript 라이브러리입니다. 웹 개발 중에 JavaScript를 통해 테이블에 새 행을 동적으로 추가해야 하는 경우가 많습니다. 이 기사에서는 jQuery를 사용하여 테이블에 새 행을 추가하는 방법을 소개하고 특정 코드 예제를 제공합니다. 먼저 HTML 페이지에 jQuery 라이브러리를 도입해야 합니다. jQuery 라이브러리는 다음 코드를 통해 태그에 도입될 수 있습니다.
