— javascript — 4 min read
1var countFactory = (function() {2 var staticCount = 0;3 4 return function() {5 var localCount = 0;6 7 return {8 increase: function() {9 return {10 static: ++staticCount,11 local: ++localCount12 };13 },14 decrease: function() {15 return {16 static: --staticCount,17 local: --localCount18 };19 }20 };21 };22})();2324var counter = countFactory();25var counter2 = countFactory();2627console.log(counter.increase());28console.log(counter.increase());29console.log(counter2.decrease());30console.log(counter.increase());
1<!DOCTYPE html>2<html>3<head>4 <meta charset="utf-8">5 <meta name="viewport" content="width=device-width">6 <title>클로저 예제</title>7</head>8<body>9 <button id="btnToggle">Toggle Pending</button>10 <div id="divPending">Pending</div>11 <script>12 (function() {13 var pendingInterval = false;14 var div = document.getElementById("divPending");15 var btn = document.getElementById("btnToggle");16 17 function startPending() {18 if (div.innerHTML.length > 13) {19 div.innerHTML = "Pending";20 }21 div.innerHTML += ".";22 };23 24 btn.addEventListener("click", function() {25 if (!pendingInterval) {26 pendingInterval = setInterval(startPending, 500);27 } else {28 clearInterval(pendingInterval);29 pendingInterval = false;30 }31 });32 })();33 </script>34</body>35</html>
1<!DOCTYPE html>2<html>3<head>4 <meta charset="utf-8">5 <meta name="viewport" content="width=device-width">6 <title>클로저 예제</title>7</head>8<body>9 <div id="wrapper">10 <button data-cb="1">Add div</button>11 <button data-cb="2">Add img</button>12 <button data-cb="delete">Clear</button>13 Adding below... <br/>14 <div id="appendDiv"></div>15 </div>16 <script>17 (function() {18 var appendDiv = document.getElementById("appendDiv");19 document.getElementById("wrapper").addEventListener("click", append);20 21 function append(e) {22 var target = e.target || e.srcElement || event.srcElement;23 var callbackFunction = callback[target.getAttribute("data-cb")];24 appendDiv.appendChild(callbackFunction());25 };26 27 var callback = {28 "1": (function () {29 var div = document.createElement("div");30 div.innerHTML = "Adding new div";31 return function () {32 return div.cloneNode(true);33 };34 })(),35 "2": (function () {36 var img = document.createElement("img");37 img.src = "https://dummyimage.com/600x400/000/fff";38 return function () {39 return img.cloneNode(true);40 };41 })(),42 "delete": function() {43 appendDiv.innerHTML = "";44 return document.createTextNode("Cleared");45 }46 };47 })();48 </script>49</body>50</html>
Function
을 생성할 때 기본적으로 프로토타입 속성이 생성된다.this.constructor.prototype
으로 접근하지 않으면 프로토타입의 값은 수정되지 않고 현재 객체 내에 같은 이름의 속성이 설정되어 프로토타입의 설정값이 가려진다.new
로 객체를 생성할 때 프로토타입은 객체 간 공유되어 메모리 자원이 절약될 수 있다.new
는 생성자 기반 상속으로 사용되며, Object.create
는 객체 기반 상속으로 사용된다.class
와 extends
키워드가 정의되었지만 호환성 검토가 필요하다.new
와 생성자로 객체를 생성하는 것이 Object.create
로 생성하는 것보다 성능상 유리하다.1<!DOCTYPE html>2<html>3<head>4 <meta charset="utf-8">5 <meta name="viewport" content="width=device-width">6 <title>이벤트 캡처링, 버블링</title>7 <style>8 div {9 border: none;10 }11 .divOutside {12 width: 200px;13 height: 200px;14 background-color: lightgreen;15 }16 .divMiddle {17 width: 150px;18 height: 150px;19 background-color: lightblue;20 }21 .divInside {22 width: 100px;23 height: 100px;24 background-color: pink;25 position: relative;26 }27 .divFloat {28 position: absolute;29 left: 210px;30 height: 50px;31 width: 50px;32 background-color: lightgray;33 }34 .highlight {35 background-color: black;36 }37 </style>38</head>39<body>40 <div id="divBubblingOutside" class="divOutside">41 <div id="divBubblingMiddle" class="divMiddle">42 <div id="divBubblingInside" class="divInside">43 Bubbling44 <div id="divBubblingFloat" class="divFloat"></div>45 </div>46 </div>47 </div>48 <br/>49 <div id="divCapturingOutside" class="divOutside">50 <div id="divCapturingMiddle" class="divMiddle">51 <div id="divCapturingInside" class="divInside">52 Capturing53 <div id="divCapturingFloat" class="divFloat"></div>54 </div>55 </div>56 </div>57 <script>58 (function() {59 document.getElementById("divBubblingOutside")60 .addEventListener("click", function() {61 this.classList.toggle('highlight');62 alert("Outside Bubbling");63 this.classList.toggle("highlight");64 });65 document.getElementById("divBubblingMiddle")66 .addEventListener("click", function() {67 this.classList.toggle("highlight");68 alert("Middle Bubbling");69 this.classList.toggle("highlight");70 });71 document.getElementById("divBubblingInside")72 .addEventListener("click", function() {73 this.classList.toggle("highlight");74 alert("Inside Bubbling");75 this.classList.toggle("highlight"); 76 });77 document.getElementById("divBubblingFloat")78 .addEventListener("click", function() {79 this.classList.toggle("highlight");80 alert("Float Bubbling");81 this.classList.toggle("highlight"); 82 });83 document.getElementById("divCapturingOutside")84 .addEventListener("click", function() {85 this.classList.toggle('highlight');86 alert("Outside capturing");87 this.classList.toggle("highlight");88 }, true);89 document.getElementById("divCapturingMiddle")90 .addEventListener("click", function() {91 this.classList.toggle("highlight");92 alert("Middle capturing");93 this.classList.toggle("highlight");94 }, true);95 document.getElementById("divCapturingInside")96 .addEventListener("click", function() {97 this.classList.toggle("highlight");98 alert("Inside capturing");99 this.classList.toggle("highlight"); 100 }, true);101 document.getElementById("divCapturingFloat")102 .addEventListener("click", function() {103 this.classList.toggle("highlight");104 alert("Float capturing");105 this.classList.toggle("highlight"); 106 }, true);107 })();108 </script>109</body>110</html>
event.stopPropagation();
으로 이벤트 단계를 중단할 수 있다.1(function () {2 function wrap(func, wrapper){3 return function() {4 var args = [func].concat(Array.prototype.slice.call(arguments));5 return wrapper.apply(this, args);6 };7 }8 9 function existingFunction() {10 console.log("Existing function is called with arguments");11 console.log(arguments);12 }13 14 var wrappedFunction = wrap(existingFunction, function (func) {15 console.log("Wrapper function is called with arguments");16 console.log(arguments);17 func.apply(this, Array.prototype.slice.call(arguments, 1));18 });19 20 console.log("1. Calling existing function");21 existingFunction("existingFunction First argument", "existingFunction Second argument", "existingFunction Third argument");22 23 console.log("\n2. Calling wrapped function");24 wrappedFunction("wrappedFunction First argument", "wrappedFunction Second argument", "wrappedFunction Third argument");25})();
1(function() {2 var car = {3 beep: function beep() {4 console.log('beep');5 },6 brake: function brake() {7 console.log('stop');8 },9 accelerator: function accelerator() {10 console.log('go');11 }12 };13 14 function wrap(func, wrapper) {15 return function() {16 var args = [func].concat(Array.prototype.slice.call(arguments));17 return wrapper.apply(this, args);18 };19 }20 21 function wrapObject(obj, wrapper) {22 var prop;23 for(prop in obj) {24 if (obj.hasOwnProperty(prop) && typeof obj[prop] === 'function') {25 obj[prop] = wrap(obj[prop], wrapper);26 }27 }28 }29 30 wrapObject(car, function(func) {31 console.log(func.name + ' has been invoked');32 33 func.apply(this, Array.prototype.slice(arguments, 1));34 });35 36 car.accelerator();37 car.beep();38 car.brake();39})();
1<!DOCTYPE html>2<html>3<head>4 <meta charset="utf-8">5 <meta name="viewport" content="width=device-width">6 <title>데코레이터 패턴</title>7</head>8<body>9<form id="personalInformation">10 <label>First name: <input type="text" class="validate" data-validate-rules="required alphabet" name="firstName"></label><br/>11 <label>Last name: <input type="text" class="validate" data-validate-rules="required alphabet" name="lastName"></label><br/>12 <label>Age: <input type="text" class="validate" data-validate-rules="number" name="age"></label><br/>13 <label>Gender: 14 <select class="validate" data-validate-rules="required">15 <option>Male</option>16 <option>Female</option>17 </select>18 </label><br/>19 <input type="submit">20</form>21<script>22 (function() {23 var formPersonalInformation = document.getElementById("personalInformation"),24 validator = new Validator(formPersonalInformation);2526 function Validator(form) {27 this.validatingForm = form;28 form.addEventListener("submit", function() {29 if (!validator.validate(this)) {30 event.preventDefault();31 event.returnValue = false;32 return false;33 }34 alert("Success to validate");35 return true;36 });37 }3839 Validator.prototype.ruleSet = {};40 Validator.prototype.decorate = function(ruleName, ruleFunction) {41 this.ruleSet[ruleName] = ruleFunction;42 }43 Validator.prototype.validate = function(form) {44 var validatingForm = form || this.validatingForm,45 inputs = validatingForm.getElementsByClassName("validate"),46 length = inputs.length,47 i, j,48 input,49 checkRules,50 rule,51 ruleLength;5253 for(i = 0; i<length; i++) {54 input = inputs[i];55 if (input.dataset.validateRules) {56 checkRules = input.dataset.validateRules.split(" ");57 ruleLength = checkRules.length;58 for(j = 0; j<ruleLength; j++) {59 rule = checkRules[j];60 if (this.ruleSet.hasOwnProperty(rule)) {61 if (!this.ruleSet[rule].call(null, input)) {62 return false;63 }64 }65 }66 }67 }6869 return true;70 }71 validator.decorate("required", function (input) {72 if (!input.value) {73 alert(input.name + " is required");74 return false;75 }76 return true;77 });78 validator.decorate("alphabet", function (input) {79 var regex = /^[a-zA-Z\s]*$/;80 if (!regex.test(input.value)) {81 alert(input.name + " has to be only alphabets");82 return false;83 }84 return true;85 });86 validator.decorate("number", function (input) {87 var regex = /^[0-9]{1,}$/;88 if(!regex.test(input.value)) {89 alert(input.name + " has to be only numbers");90 return false;91 }92 return true;93 });94 })();95</script>96</body>97</html>
1<!DOCTYPE html>2<html>3<head>4 <meta charset="utf-8">5 <meta name="viewport" content="width=device-width">6 <title>Self-defining function 패턴</title>7 <style>8 #commentWrapper {9 width: 200px;10 }11 .comment {12 width: 150px;13 display: inline-block;14 }15 .name {16 width: 40px;17 display: inline-block;18 }19 </style>20</head>21<body>22 <div id="commentWrapper">23 <div>24 <div class="comment">Comment</div>25 <div class="name">Name</div>26 </div>27 </div>28 <form id="formComment">29 <label>Comment: <input type="text" id="comment"></label>30 <label>Name: <input type="text" id="name"></label>31 <input type="submit">32 </form>33<script>34 (function() {35 var addComment = function() {36 var divCommentWrapper = document.getElementById("commentWrapper"),37 divCommentRow = document.createElement("div"),38 divComment = document.createElement("div"),39 divName = document.createElement("div"),40 inputComment = document.getElementById("comment"),41 inputName = document.getElementById("name");4243 divComment.className = "comment";44 divName.className = "name";45 divCommentRow.appendChild(divComment);46 divCommentRow.appendChild(divName);4748 addComment = function() {49 divComment.innerHTML = inputComment.value;50 divName.innerHTML = inputName.value;5152 inputComment.value = "";53 inputName.value = "";5455 divCommentWrapper.appendChild(divCommentRow.cloneNode(true));56 };57 addComment();58 }5960 document.getElementById("formComment").addEventListener("submit", function() {61 addComment();62 event.returnValue = false;63 return false;64 });65 })();66</script>67</body>68</html>
new
를 빼 먹고 생성자를 호출할 경우를 대비해서 생성자를 그냥 함수로 호출할 때 스스로 new
를 붙여서 다시 호출하는 패턴.1(function () {2 function sum(x, y) {3 return x + y;4 }5 6 var makeAdder = function (x) {7 return function (y) {8 return sum(x, y);9 };10 };11 12 var adderFour = makeAdder(4);13 console.log(adderFour(1));14 console.log(adderFour(5));15})();
1(function () {2 Function.prototype.curry = function() {3 if (arguments.length < 1) {4 return this;5 }6 7 var _this = this,8 args = Array.prototype.slice.apply(arguments);9 10 return function() {11 return _this.apply(this, args.concat(Array.prototype.slice.apply(arguments)));12 }13 }14 15 function unitConvert(fromUnit, toUnit, factor, input) {16 return input + ' ' + fromUnit + ' = ' + (input * factor).toFixed(2) + ' ' + toUnit;17 }18 19 var cm2inch = unitConvert.curry('cm', 'inch', 0.393701),20 metersquare2pyoung = unitConvert.curry('m^2', 'pyoung', 0.3025),21 kg2lb = unitConvert.curry('kg', 'lb', 2.204623),22 kmph2mph = unitConvert.curry('km/h', 'mph', 0.621371);23 24 console.log(cm2inch(10));25 console.log(metersquare2pyoung(30));26 console.log(kg2lb(50));27 console.log(kmph2mph(100));28})();
setInterval()
은 백그라운드에서도 실행되지만 requestAnimationFrame()
은 화면에 repaint 가 일어날 때 호출되므로 백그라운드에서 호출되지 않고 대기한다.absolute
나 fixed
로 설정하는 것이 좋다.position:relative
안에 position:absolute
를 이용하면 페이지 중간에도 position:absolute
를 넣을 수 있다.1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <meta http-equiv="X-UA-Compatible" content="ie=edge">7 <title>position:relative 와 position:absolute</title>8 <style>9 #bannerWrapper {10 overflow: hidden;11 height: 400px;12 width: 265px;13 position: relative;14 }1516 #bannerImg {17 position: absolute;18 }1920 #bannerImg.mouseover {21 top: -400px;22 }23 </style>24</head>25<body>26 <div id="bannerWrapper">27 <img id="bannerImg" src="http://i.imgur.com/3DpJ0ou.png"/>28 </div>29 <script>30 (function() {31 var imgBannerImg = document.getElementById('bannerImg');32 imgBannerImg.addEventListener('mouseover', function() {33 this.classList.add('mouseover');34 });35 imgBannerImg.addEventListener('mouseout', function() {36 this.classList.remove('mouseover');37 });38 })();39 </script>40</body>41</html>
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <meta http-equiv="X-UA-Compatible" content="ie=edge">7 <title>DOM reflow를 최소화한 공지사항 목록</title>8 <style>9 #noticeWrapper {10 overflow: hidden;11 height: 20px;12 width: 200px;13 position: relative;14 border: 1px solid black;15 }1617 #notice {18 position: absolute;19 margin: 0;20 padding: 0;21 top: 0;22 }2324 .noticeSubject {25 height: 20px;26 width: 200px;27 list-style: none; 28 }2930 .noReflow {31 width: 200px;32 height: 100px;33 border: 1px solid black;34 background-color: lightgray;35 }36 </style>37</head>38<body>39 <div class="noReflow">40 I'll nor reflow41 </div>42 <div id="noticeWrapper">43 <ul id="notice">44 <li class="noticeSubject">Link to the first article</li>45 <li class="noticeSubject">Link to the second article</li>46 <li class="noticeSubject">Link to the third article</li>47 <li class="noticeSubject">Link to the last article</li>48 </ul>49 </div>50 <div class="noReflow">51 Neither me52 </div>53 <script>54 (function() {55 var ulNotice = document.getElementById('notice'),56 currentNoticeTop = 0,57 currentIndex = 0,58 maxIndex = ulNotice.getElementsByClassName('noticeSubject').length - 1,59 currentRollingUp = true,60 subjectHeight = 20,61 velocityPerSecond = 20,62 previousFrame = null;6364 setTimeout(rollNextNotice, 0);6566 function rollNextNotice() {67 requestAnimationFrame(rollNotice);68 }6970 function rollNotice(time) {71 var diff = (previousFrame !== null ? time - previousFrame : 0);72 previousFrame = time;73 currentNoticeTop += (diff / 1000) * velocityPerSecond;7475 if (currentNoticeTop * velocityPerSecond >= currentIndex * -subjectHeight * velocityPerSecond) {76 if (currentIndex === maxIndex || currentIndex === 0) {77 currentRollingUp = !currentRollingUp;78 velocityPerSecond = -velocityPerSecond;79 }8081 currentNoticeTop = currentIndex * -subjectHeight;82 currentIndex += (currentRollingUp ? -1 : 1);83 previousFrame = null;84 setTimeout(rollNextNotice, 1000);85 } else {86 requestAnimationFrame(rollNotice);87 }88 ulNotice.style.top = currentNoticeTop + 'px';89 }90 })();91 </script>92</body>93</html>
DocumentFragment
객체에 추가해두고 마지막에 실제 DOM에 추가하는 것이 좋다.display:none
으로 Element 를 아예 백그라운드로 뺀 다음 변경 내용을 반영한 후 다시 display:block
을 하면 좋다.offsetWidth
와 clientHeight
에 접근하거나 getComputedStyle()
에 접근할 때 reflow 가 일어날 수 있다.1var element = document.getElementById('myElement');2var classElements = document.getElementsByClassName('myClass');3var tagElements = document.getElementsByTagName('div');4var cssSelector = document.querySelector('div#cssSelector');5var cssSelectors = document.querySelectorAll('div.myClass');
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <meta http-equiv="X-UA-Compatible" content="ie=edge">7 <title>메모이제이션 패턴을 이용한 DOM 탐색 최적화 예</title>8</head>9<body>10 <form id="myForm">11 <input type="text" name="firstName" id="firstName"/>12 <input type="text" name="middleName" id="middleName"/>13 <input type="text" name="lastName" id="lastName"/>14 <input type="submit" value="Submit"/>15 </form>16 <script>17 (function() {18 var memo = {};19 document.getElementById('myForm').onsubmit = function() {20 var inputFirstName = getMemo('firstName'),21 inputMiddleName,22 inputLastName;2324 if (inputFirstName.value === '') {25 alert('First name is mandatory');26 inputFirstName.focus();27 return false;28 }2930 inputLastName = getMemo('lastName');3132 if (inputLastName.value === '') {33 alert('Last name is mandatory');34 inputLastName.focus();35 return false;36 }3738 inputMiddleName = getMemo('middleName');3940 alert('Hello, ' + inputFirstName.value + ' ' + inputMiddleName.value + ' ' + inputLastName.value);41 }4243 function getMemo(id) {44 memo[id] = memo[id] || document.getElementById(id);45 46 return memo[id];47 }48 })();49 </script>50</body>51</html>
dedicated worker
는 worker를 생성한 스크립트에서만 호출과 접근을 할 수 있고, shared worker
는 여러 개의 스크립트에서 접근할 수 있다. shared worker
는 console.log()
등의 함수들이 브라우저 개발자 도구를 통해서 확인할 수 없으므로 디버깅하기 어렵다.1<!-- index.html -->2<!DOCTYPE html>3<html lang="en">4<head>5 <meta charset="UTF-8">6 <meta name="viewport" content="width=device-width, initial-scale=1.0">7 <meta http-equiv="X-UA-Compatible" content="ie=edge">8 <title>웹 워커 사용 예</title>9</head>10<body>11 <select id="doWorker">12 <option>-- SELECT --</option>13 <option>doLargeLoop</option>14 </select>15 <script>16 (function() {17 var worker = new Worker('./worker.js'),18 selectWorker = document.getElementById('doWorker');1920 selectWorker.addEventListener('change', function() {21 console.log('main thread: sending message - ' + this.value);22 worker.postMessage(this.value);23 });2425 worker.onmessage = function (msg) {26 console.log('main thread: ' + msg.data);27 }28 })();29 </script>30</body>31</html>
1// worker.js2(function () {3 var messages = {4 'doLargeLoop': function() {5 var i, sum = 0,6 start, end;7 console.log('worker thread: starting large loop');89 start = Date.now();10 for (i = 0; i < 10000000000; i++) {11 sum += i;12 }1314 end = Date.now();15 postMessage(`Elapsed time (${((end - start) / 1000).toFixed(2)} sec, sum=${sum}`);16 }17 };1819 onmessage = function (msg) {20 if (messages.hasOwnProperty(msg.data)) {21 messages[msg.data]();22 }23 }24})();
<img>
태그의 크기가 변하면 이미지가 로드될때마다 DOM reflow 가 일어나서 전체적인 웹페이지의 로딩 속도가 느려진다.