— 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 가 일어나서 전체적인 웹페이지의 로딩 속도가 느려진다.