diff --git a/README.ko-KR.md b/README.ko-KR.md new file mode 100644 index 0000000..24cbd9f --- /dev/null +++ b/README.ko-KR.md @@ -0,0 +1,612 @@ +## You Don't Need jQuery + +오늘날 프론트엔드 개발 환경은 급격히 진화하고 있고, 모던 브라우저들은 이미 충분히 많은 DOM/BOM API들을 구현했습니다. 우리는 jQuery를 DOM 처리나 이벤트를 위해 처음부터 배울 필요가 없습니다. React, Angular, Vue같은 프론트엔드 라이브러리들이 주도권을 차지하는 동안 DOM을 바로 처리하는 것은 안티패턴이 되었고, jQuery의 중요성은 줄어들었습니다. 이 프로젝트는 대부분의 jQuery 메소드의 대안을 IE 10+ 이상을 지원하는 네이티브 구현으로 소개합니다. + +## 목차 + +1. [Query Selector](#query-selector) +1. [CSS & Style](#css-style) +1. [DOM 조작](#dom-manipulation) +1. [Ajax](#ajax) +1. [이벤트](#events) +1. [유틸리티](#utilities) +1. [브라우저 지원](#browser-support) + +## Query Selector + +평범한 class, id, attribute같은 selecotor는 `document.querySelector`나 `document.querySelectorAll`으로 대체할 수 있습니다. +* `document.querySelector`는 처음 매칭된 엘리먼트를 반환합니다. +* `document.querySelectorAll`는 모든 매칭된 엘리먼트를 NodeList로 반환합니다. `[].slice.call`을 사용해서 Array로 변환할 수 있습니다. +* 만약 매칭된 엘리멘트가 없으면 jQuery는 `[]` 를 반환하지만 DOM API는 `null`을 반환합니다. Null Pointer Exception에 주의하세요. + +> 안내: `document.querySelector`와 `document.querySelectorAll`는 꽤 **느립니다**, `getElementById`나 `document.getElementsByClassName`, `document.getElementsByTagName`를 사용하면 퍼포먼스가 향상을 기대할 수 있습니다. + +- [1.1](#1.1) class로 찾기 + + ```js + // jQuery + $('.css'); + + // Native + document.querySelectorAll('.css'); + ``` + +- [1.2](#1.2) id로 찾기 + + ```js + // jQuery + $('#id'); + + // Native + document.querySelector('#id'); + ``` + +- [1.3](#1.3) 속성(attribute)으로 찾기 + + ```js + // jQuery + $('a[target=_blank]'); + + // Native + document.querySelectorAll('a[target=_blank]'); + ``` + +- [1.4](#1.4) 여러가지 찾기 + + + 노드들 찾기 + + ```js + // jQuery + $el.find('li'); + + // Native + el.querySelectorAll('li'); + ``` + + + body 찾기 + + ```js + // jQuery + $('body'); + + // Native + document.body; + ``` + + + 속성 가져오기 + + ```js + // jQuery + $el.attr('foo'); + + // Native + e.getAttribute('foo'); + ``` + + + data 속성 가져오기 + + ```js + // jQuery + $el.data('foo'); + + // Native + // getAttribute를 사용하기 + el.getAttribute('data-foo'); + // IE 11+ 이상만 지원할 것이라면 dataset을 사용할 수도 있음 + el.dataset['foo']; + ``` + +- [1.5](#1.5) 형제/이전/다음 엘리먼트 찾기 + + + 형제 엘리먼트 + + ```js + // jQuery + $el.siblings(); + + // Native + [].filter.call(el.parentNode.children, function(child) { + return child !== el; + }); + ``` + + + 이전 엘리먼트 + + ```js + // jQuery + $el.prev(); + + // Native + el.previousElementSibling; + + ``` + + + 다음 엘리먼트 + + ```js + // next + $el.next(); + el.nextElementSibling; + ``` + +- [1.6](#1.6) Closest + + 현재 엘리먼트부터 document로 이동하면서 주어진 셀렉터와 일치하는 가장 가까운 엘리먼트를 반환합니다. + + ```js + // jQuery + $el.closest(queryString); + + // Native + function closest(el, selector) { + const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; + + while (el) { + if (matchesSelector.call(el, selector)) { + return el; + } else { + el = el.parentElement; + } + } + return null; + } + ``` + +- [1.7](#1.7) Parents Until + + 주어진 셀렉터에 매칭되는 엘리먼트를 찾기까지 부모 태그들을 위로 올라가며 탐색하여 저장해두었다가 DOM 노드 또는 jQuery object로 반환합니다. + + ```js + // jQuery + $el.parentsUntil(selector, filter); + + // Native + function parentsUntil(el, selector, filter) { + const result = []; + const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; + + // match start from parent + el = el.parentElement; + while (el && !matchesSelector.call(el, selector)) { + if (!filter) { + result.push(el); + } else { + if (matchesSelector.call(el, filter)) { + result.push(el); + } + } + el = el.parentElement; + } + return result; + } + ``` + +- [1.8](#1.8) Form + + + Input/Textarea + + ```js + // jQuery + $('#my-input').val(); + + // Native + document.querySelector('#my-input').value; + ``` + + + e.currentTarget이 `.radio`의 몇번째인지 구하기 + + ```js + // jQuery + $(e.currentTarget).index('.radio'); + + // Native + [].indexOf.call(document.querySelectAll('.radio'), e.currentTarget); + ``` + +- [1.9](#1.9) Iframe Contents + + `$('iframe').contents()`는 iframe에 한정해서 `contentDocument`를 반환합니다. + + + Iframe contents + + ```js + // jQuery + $iframe.contents(); + + // Native + iframe.contentDocument; + ``` + + + Iframe에서 찾기 + + ```js + // jQuery + $iframe.contents().find('.css'); + + // Native + iframe.contentDocument.querySelectorAll('.css'); + ``` + +**[⬆ 목차로 돌아가기](#table-of-contents)** + +## CSS & Style + +- [2.1](#2.1) CSS + + + style값 얻기 + + ```js + // jQuery + $el.css("color"); + + // Native + // NOTE: 알려진 버그로, style값이 'auto'이면 'auto'를 반환합니다. + const win = el.ownerDocument.defaultView; + // null은 가상 스타일은 반환하지 않음을 의미합니다. + win.getComputedStyle(el, null).color; + ``` + + + style값 설정하기 + + ```js + // jQuery + $el.css({ color: "#ff0011" }); + + // Native + el.style.color = '#ff0011'; + ``` + + + Style값들을 동시에 얻거나 설정하기 + + 만약 한번에 여러 style값을 바꾸고 싶다면 oui-dom-utils 패키지의 [setStyles](https://github.com/oneuijs/oui-dom-utils/blob/master/src/index.js#L194)를 사용해보세요. + + + + class 추가하기 + + ```js + // jQuery + $el.addClass(className); + + // Native + el.classList.add(className); + ``` + + + class 제거하기 + + ```js + // jQuery + $el.removeClass(className); + + // Native + el.classList.remove(className); + ``` + + + class를 포함하고 있는지 검사하기 + + ```js + // jQuery + $el.hasClass(className); + + // Native + el.classList.contains(className); + ``` + + + class 토글하기 + + ```js + // jQuery + $el.toggleClass(className); + + // Native + el.classList.toggle(className); + ``` + +- [2.2](#2.2) 폭과 높이 + + 폭과 높이는 이론상 동일합니다. 높이로 예를 들겠습니다. + + + Window의 높이 + + ```js + // window 높이 + $(window).height(); + // jQuery처럼 스크롤바를 제외하기 + window.document.documentElement.clientHeight; + // 스크롤바 포함 + window.innerHeight; + ``` + + + 문서 높이 + + ```js + // jQuery + $(document).height(); + + // Native + document.documentElement.scrollHeight; + ``` + + + Element 높이 + + ```js + // jQuery + $el.height(); + + // Native + function getHeight(el) { + const styles = this.getComputedStyles(el); + const height = el.offsetHeight; + const borderTopWidth = parseFloat(styles.borderTopWidth); + const borderBottomWidth = parseFloat(styles.borderBottomWidth); + const paddingTop = parseFloat(styles.paddingTop); + const paddingBottom = parseFloat(styles.paddingBottom); + return height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom; + } + // 정수로 정확하게(`border-box`일 때 이 값은 `height`이고, `content-box`일 때, 이 값은 `height + padding + border`) + el.clientHeight; + // 실수로 정확하게(`border-box`일 때 이 값은 `height`이고, `content-box`일 때, 이 값은 `height + padding + border`) + el.getBoundingClientRect().height; + ``` + +- [2.3](#2.3) Position & Offset + + + Position + + ```js + // jQuery + $el.position(); + + // Native + { left: el.offsetLeft, top: el.offsetTop } + ``` + + + Offset + + ```js + // jQuery + $el.offset(); + + // Native + function getOffset (el) { + const box = el.getBoundingClientRect(); + + return { + top: box.top + window.pageYOffset - document.documentElement.clientTop, + left: box.left + window.pageXOffset - document.documentElement.clientLeft + } + } + ``` + +- [2.4](#2.4) Scroll Top + + ```js + // jQuery + $(window).scrollTop(); + + // Native + (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop; + ``` + +**[⬆ 목차로 돌아가기](#table-of-contents)** + +## DOM 조작 + +- [3.1](#3.1) 제거 + ```js + // jQuery + $el.remove(); + + // Native + el.parentNode.removeChild(el); + ``` + +- [3.2](#3.2) Text + + + text 가져오기 + + ```js + // jQuery + $el.text(); + + // Native + el.textContent; + ``` + + + text 설정하기 + + ```js + // jQuery + $el.text(string); + + // Native + el.textContent = string; + ``` + +- [3.3](#3.3) HTML + + + HTML 가져오기 + + ```js + // jQuery + $el.html(); + + // Native + el.innerHTML; + ``` + + + HTML 설정하기 + + ```js + // jQuery + $el.html(htmlString); + + // Native + el.innerHTML = htmlString; + ``` + +- [3.4](#3.4) 해당 엘리먼트의 자식들 뒤에 넣기(Append) + + 부모 엘리먼트의 마지막 자식으로 엘리먼트를 추가합니다. + + ```js + // jQuery + $el.append("
hello
"); + + // Native + let newEl = document.createElement('div'); + newEl.setAttribute('id', 'container'); + newEl.innerHTML = 'hello'; + el.appendChild(newEl); + ``` + +- [3.5](#3.5) 해당 엘리먼트의 자식들 앞에 넣기(Prepend) + + ```js + // jQuery + $el.prepend("
hello
"); + + // Native + let newEl = document.createElement('div'); + newEl.setAttribute('id', 'container'); + newEl.innerHTML = 'hello'; + el.insertBefore(newEl, el.firstChild); + ``` + +- [3.6](#3.6) 해당 엘리먼트 앞에 넣기(insertBefore) + + 새 노드를 선택한 엘리먼트 앞에 넣습니다. + + ```js + // jQuery + $newEl.insertBefore(queryString); + + // Native + newEl.insertBefore(document.querySelector(queryString)); + ``` + +- [3.7](#3.7) 해당 엘리먼트 뒤에 넣기(insertAfter) + + 새 노드를 선택한 엘리먼트 뒤에 넣습니다. + + ```js + // jQuery + $newEl.insertAfter(queryString); + + // Native + function insertAfter(newEl, queryString) { + const parent = document.querySelector(queryString).parentNode; + + if (parent.lastChild === newEl) { + parent.appendChild(newEl); + } else { + parent.insertBefore(newEl, parent.nextSibling); + } + }, + ``` + +**[⬆ 목차로 돌아가기](#table-of-contents)** + +## Ajax + +[fetch](https://github.com/camsong/fetch-ie8)나 [fetch-jsonp](https://github.com/camsong/fetch-jsonp)로 교체하세요. + +**[⬆ 목차로 돌아가기](#table-of-contents)** + +## 이벤트 + +namespace와 delegation을 포함해서 완전히 갈아 엎길 원하시면 https://github.com/oneuijs/oui-dom-events 를 고려해보세요. + +- [5.1](#5.1) 이벤트 Bind 걸기 + + ```js + // jQuery + $el.on(eventName, eventHandler); + + // Native + el.addEventListener(eventName, eventHandler); + ``` + +- [5.2](#5.2) 이벤트 Bind 풀기 + + ```js + // jQuery + $el.off(eventName, eventHandler); + + // Native + el.removeEventListener(eventName, eventHandler); + ``` + +- [5.3](#5.3) 이벤트 발생시키기(Trigger) + + ```js + // jQuery + $(el).trigger('custom-event', {key1: 'data'}); + + // Native + if (window.CustomEvent) { + const event = new CustomEvent('custom-event', {detail: {key1: 'data'}}); + } else { + const event = document.createEvent('CustomEvent'); + event.initCustomEvent('custom-event', true, true, {key1: 'data'}); + } + + el.dispatchEvent(event); + ``` + +**[⬆ 목차로 돌아가기](#table-of-contents)** + +## 유틸리티 + +- [6.1](#6.1) 배열인지 검사(isArray) + + ```js + // jQuery + $.isArray(range); + + // Native + Array.isArray(range); + ``` + +- [6.2](#6.2) 앞뒤 공백 지우기(Trim) + + ```js + // jQuery + $.trim(string); + + // Native + String.trim(string); + ``` + +- [6.3](#6.3) Object Assign + + 사용하려면 object.assign polyfill을 사용하세요. https://github.com/ljharb/object.assign + + ```js + // jQuery + $.extend({}, defaultOpts, opts); + + // Native + Object.assign({}, defaultOpts, opts); + ``` + +- [6.4](#6.4) Contains + + ```js + // jQuery + $.contains(el, child); + + // Native + el !== child && el.contains(child); + ``` + +**[⬆ 목차로 돌아가기](#table-of-contents)** + +## 브라우저 지원 + +![Chrome](https://raw.github.com/alrra/browser-logos/master/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/firefox/firefox_48x48.png) | ![IE](https://raw.github.com/alrra/browser-logos/master/internet-explorer/internet-explorer_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/opera/opera_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/safari/safari_48x48.png) +--- | --- | --- | --- | --- | +Latest ✔ | Latest ✔ | 10+ ✔ | Latest ✔ | 6.1+ ✔ | + +# License + +MIT diff --git a/README.md b/README.md index e296dd9..203b97f 100644 --- a/README.md +++ b/README.md @@ -604,6 +604,7 @@ For a complete replacement with namespace and delegation, refer to https://githu ## Translation +* [한국어](./README.ko-KR.md) * [简体中文](./README.zh-CN.md) ## Browser Support