2. 명시적으로 this 바인딩하는 방법

 1) call 메서드

  -call 메서드는 호출 주체인 함수를 즉시 실행하도록 명령

var func = function(){...};
func.call();

  -위와 같이 사용하여 call의 주체인 함수 func를 즉시 실행하도록 명령

  -call 메서드에서 첫번째 매개변수를 객체 형태로 주면 그 객체가 함수의 this 객체가 됨

 

var func = function (a,b,c) {
  console.log(this, a, b, c);
};

func(1, 2, 3);                        // Window{...} 1 2 3
func.call({x: 1}, 4, 5, 6);      // {x: 1} 4 5 6

  -func(1, 2, 3)은 첫번째 매개변수를 객체 형태로 지정하지 않았으므로 암묵적으로 원래 함수 호출 시 할당되는 this(전역 객체) 할당

  -func.call({x: 1}, 4, 5, 6)은 첫번째 매개변수를 객체 형태로 지정하여 명시적으로 func 함수의 this 객체를 지정해줌

 

  -객체의 메서드 또한 call 메서드 없이 호출하면 this 객체 = 메서드를 호출한 객체이지만 call 메서드를 통해 특정 객체를 this로 지정할 수 있음

var obj = {
  a: 1,
  method: function (x, y) {
    console.log(this.a, x, y);
  }
};

obj.method(2, 3);                      // 1 2 3
obj.method.call({a: 4}, 5, 6};    // 4 5 6

  -call 없이 this.a를 호출하면 method를 호출한 객체 obj가 this로 할당되어 obj내에서 선언한 a가 this.a로 출력됨

  -call에서 {a: 4}를 지정하면 method의 this 객체를 명시적으로 {a: 4}로 지정하여 this.a에서 {a: 4}의 a값인 4가 출력됨

 

 

 2) apply 메서드

  -apply 메서드는 call 메서드와 기능적으로 동일하며 함수에 전달할 인자를 배열의 형태로 전달한다는 것만 다름

var func = function(a, b, c) {
  console.log(this, a, b, c);
};
func.apply({x: 1}, [4, 5, 6]);            // {x: 1} 4 5 6

var obj = {
  a: 1,
  method: function(x, y) {
    console.log(this.a, x, y);
  }
};
obj.method.apply({a: 4}, [5, 6]);    // 4 5 6

 

 

 3) call / apply 메서드의 활용(1): 유사배열객체에 배열 메서드 적용

  -유사배열객체: 배열의 키 값이 0 또는 양의 정수인 프로퍼티가 존재 & length의 프로퍼티 값이 0 또는 양수인 객체(배열의 구조와 유사)

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
Array.prototype.push.call(obj, 'd');                   // 배열 메서드인 push를 통해 프로퍼티 3에 'd' 추가, length는 자동으로 1 추가
console.log(obj);                                   // {0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4}

var arr = Array.prototype.slice.call(obj);             // 배열 메서드인 slice 메서드를 통해 유사배열객체의 얕은 복사본 반환
console.log(arr);                                   // ['a', 'b', 'c', 'd']

  -slice 메서드는 시작 인덱스값과 마지막 인덱스값을 매개변수로 받아 시작값부터 마지막 바로 전의 값까지의 배열 요소를 추출하는 메서드

  -slice 메서드에 매개변수를 아무것도 주지 않으면 그냥 원본 배열의 얕은 복사본 반환

  -위 예시에서 call 메서드를 통해 원본 유사배열객체의 얕은 복사를 수행하고, slice 메서드가 배열 메서드이기 때문에 배열로 반환

 

  -배열 형태의 객체 외에 arguments, nodeList 등도 유사배열객체로서 작동

// arguments객체도 유사배열객체이므로 slice 메서드를 사용하여 배열로 전환 가능
function a() {
  var argv = Array.prototype.slice.call(arguments);
  argv.forEach(function(arg) { // argv의 인자들을 각각 순서대로 함수의 인자로 넣음
    console.log(arg);
  });
}
a(1, 2, 3) // 1 2 3


// querySelectAll, getElementByClassName 등의 Node 선택자를 선택한 결과인 nodeList도 유사배열객체로 slice 메서드를 통해 배열로 전환 가능
document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>';
var nodeList = document.querySelectAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function (node) {
    console.log(node);
});

 

※ 주의: 문자열로 인덱스와 length프로퍼티를 지니므로 유사배열객체처럼 사용할 수 있지만, 문자열의 length프로퍼티가 읽기 전용임

  -원본 문자열에 변경을 가하는 메서드(push, pop, shift, unshift, splice 등)은 에러

  -concat처럼 대상이 반드시 배열이어야 하는 경우는 제대로 된 결과 X

// 문자열 선언
var str = 'abc def';

// 문자열에 push 사용 불가능
Array.prototype.push.call(str, ', pushed string');
// 일반적인 배열이면 push를 통해 'abc def'뒤에 ', pushed string'이 추가될 것이지만 문자열은 원본에 변경을 가하는 메서드에 에러 출력

// 문자열에 concat 사용 시 제대로 된 결과 X
Array.prototype.concat.call(str, 'string');
// [ String{"abc def"}, "string" ]

// 문자열에 every와 some 메서드 사용
Array.prototype.every.call(str, function(char) {return char !== ' ' ;});    // false
// 문자열의 모든 문자에 대해 전부 ' '가 아니면 true, 하나라도 ' '이면 false

Array.prototype.some.call(str, function(char) {return char === ' ' ;});  // true
// 문자열의 모든 문자에 대해 하나라도 ' '이면 true, 전부 ' '가 아니면 false

// 문자열에 map 메서드 사용
var newArr = Array.prototype.map.call(str, function(char) {return char + '!';});
// 각 문자에 !를 더하여 배열의 요소로 하나씩 추가

// 문자열에 reduce 메서드 사용
var newStr = Array.prototype.reduce.apply(str, [
  function(string, char, i) {return string + char + i;},
  ''
]);
console.log(newStr);  // "a0b1c2 3d4e5f6"
// reduce 메서드의 매개변수는 앞에서부터 (callback 함수(function)의 반환값을 누적, 배열의 현재 요소, 인덱스, 호출할 배열)
// 처음 시행 시 callback 함수의 반환값은 없으므로 공백에서 시작하여 배열의 첫요소 부터 불러와 인덱스(0부터 시작하는 수)를 문자 뒤에 추가하고 반환

 

  -ES6부터는 유사배열객체, 순회가능한 모든 종류의 데이터타입을 배열로 전환하는 Array.from 메서드 도입

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
var arr = Array.from(obj);
console.log(arr);          // ['a', 'b', 'c']

 

 

 4) call / appy 메서드의 활용(2): 생성자 내부에서 나른 생성자 호출

  -생성자 내부에 다른 생성자와 공통된 내용이 있으면 call / apply를 이용해 다른 생성자를 호출하면 반복을 줄일 수 있음

// Student, Employee 생성자 내부 함수에서 Person 생성자 함수를 호출해 인스턴스의 속정을 정의

function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}

function Student(name, gender, school) {
  Person.call(this, name, gender); // name과 gender를 반복적으로 속성으로 정의하지 않고 Person으로부터 호출하면 편리
  this.school = school;                 // Person에는 없고 Student에만 있는 속성, school만 추가하면 됨
}

function Employee(name, gender, company) {
  Person.apply(this, [name, gender]);
  this.company = company;
}
var student1 = new Student('학생1', 'female', 'oo대');
var employee1 = new Employee('직원1', 'male', 'oo');

 

 

 5) apply 메서드의 활용: 여러 인수를 묶어 하나의 배열로 전달

// apply 메서드를 적용하지 않으면
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];             // numbers 배열의 첫번째 값(10)을 max와 min 변수의 초깃값으로 선정
numbers.forEach(function(number) {   // numbers 배열의 각 값을 하나씩 초깃값과 비교하고, 더 큰지 작은지에 따라 max와 min 값을 바꿔가며 전체 배열 중 max와 min 탐색
  if (number > max ) {
    max = number;
  }
  if (number < min ) {
    min = number;
  }
});
console.log(max, min);
// apply 메서드를 적용하면
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers); // Math.max 메서드에 apply 메서드 적용
var min = Math.min.apply(null, numbers); // Math.min 메서드에 apply 메서드 적용
console.log(max, min);
// ES6에서는 펼치기 연산자를 적용하여 apply 메서드보다도 간편하게 작성
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min);

 

 

 6) bind 메서드

  -call과 비슷하지만 함수를 즉시 호출하지 않고 넘겨받은 this와 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드

  -다시 새로운 함수 호출 시 인수를 넘기면 기존 bund 메서드 호출 시 전달했던 인수 뒤에 추가되는 식으로 등록됨

  -bind 메서드는 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적

// bind 메서드로 호출할 함수
var func = function (a, b, c, d) {
  console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // Window{ ... } 1 2 3 4
// call이나 apply나 bind 메서드 없이 함수를 호출할 때 this는 전역객체임

// bind 메서드로 this 객체 선언(call이나 apply와 같은 기능)
var bindFunc1 = func.bind({x: 1});
bindFunc1(5, 6, 7, 8); // {x: 1} 5 6 7 8

// bind 메서드로 인자를 일부 넘겨주고 이후에 함수를 호출할 때 추가 인자 넘겨주기
var bindFunc2 = func.bind({x: 1}, 4, 5); // this 객체의 선언과 인자 4개 중 앞의 두 개만 미리 선언
bindFunc2(6, 7); // {x: 1} 4 5 6 7
// 나머지 뒤의 두 개의 인자와 함께 함수 호출
bincFunc2(8, 9); // {x: 1} 4 5 8 9
// 함수의 인자를 바꿨을 때, 마지막 두 개의 인자만 값이 바뀐 것을 알 수 있음

 

  -bind 메서드를 적용해서 만든 함수는 name 프로퍼티에 'bound(bind의 수동태)'라는 접두어가 붙음

  -call이나 apply 메서드를 적용한 함수보다 추적이 편리

var func = function (a, b, c, d) {
  console.log(this, a, b, c, d);
};

var bindFunc = func.bind({x: 1}, 4, 5);

console.log(func.name)           // func
console.log(bindFunc.name)    // bound func

 

  -상위 컨텍스트의 this를 내부 함수나 콜백 함수에 전달

// call 메서드 사용
var obj = {
    outer: function() {
          console.log(this);
        var innerFunc = function() {
            console.log(this);
        };
        innerFunc.call(this); // call 함수를 통해 이 줄을 실행할 때의 객체, obj 객체를 innerFunc 실행 시의 this 객체로 전달
    }
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체
// bind 메서드 사용
var obj = {
    outer: function() {
          console.log(this);
        var innerFunc = function() {
            console.log(this);
        }.bind(this);
        innerFunc();
    }
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체
// 콜백 함수에 bind 메서드를 통해 this 객체 전달
var obj = {
  logThis: function() {
    console.log(this);
  },
  
  // 따로 this 객체를 지정해주지 않은 콜백함수는 일반 함수와 마찬가지로 전역객체를 this 객체로 가짐
  logThisLater1:function() {
    setTimeout(this.logThis, 500);
  },
  
  // bind 메서드를 통해 this 객체를 지정하여 콜백함수로 넘겨줄 수 있음
  // 바로 직전 객체(obj)를 this 객체로 넘겨줌
  logThisLater2: function() {
    setTimeout(this.logThis.bind(this), 1000);
  }
};
obj.logThisLater1();  // Window{ ... }
obj.logThisLater2();  // obj {logThis: f, ...}

 

 

 7) 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외, 함수 내부에 this가 없고 접근하면 가장 가까운 this에 접근

var obj = {
  outer: function () {
    console.log(this);
    var innerFunc = () => {
      console.log(this);
    };
    innerFunc();
  }
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체

 

 

 8) 콜백함수 내에서 this를 별도의 인자로 받기

var report = {
  // sum과 count 두 개의 프로퍼티에 초깃값 0을 부여하며 선언
  sum: 0,
  count: 0,
  
  // 합계를 구하는 메서드
  add: function () {
    var args = Array.prototype.slice.call(arguments);  // 인자로 받은 arguments를 배열 형태로 바꿔서
    args.forEach(function(entry) {                             // 각각 차례대로 함수에 넣음
      this.sum += entry;                                          // 함수 내에서는 초깃값이 0인 sum에 인자들을 더하며 반환하는 과정을 반복
      ++this.count;                                                 // 반복할 때마다 개수도 하나씩 늘려 총 몇개의 인자를 받았는지 계산
    }, this);                                                           // forEach 내부의 콜백함수의 this는 두번째 인자로 전달해준 add메서드의 this(report 객체)가 바인딩됨
  },                                                                      // 따라서, report.sum에 인자가 더해지고, report.count가 1씩 늘어나 저장됨

  average: function () {
    return this.sum / this.count; // average 메서드의 this 또한 report객체이며 this.sum, this.count는 각각 report.sum, report.count이며 add 메서드에서 더해져 저장된 값들임
  }
};

report.add(60, 85, 95);
console.log(report.sum, report.count, report.averge());  // 240 3 80

 

  -forEach 외에 thisArg를 인자로 받는 메서드

Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
Array.prototype.some(callback[, thisArg])
Array.prototype.every(callback[, thisArg])
Array.prototype.find(callback[, thisArg])
Array.prototype.findIndex(callback[, thisArg])
Array.prototype.flatMap(callback[, thisArg])
Array.prototype.from(arrayLike[, callback[, thisArg]])
Set.prototype.forEach(callback[, thisArg])
Map.prototype.forEach(callback[, thisArg])

1. 아래 사이트를 통한 로그인 페이지 및 회원가입 페이지 구현

 

화면 오픈소스 사이트, codepen

1. https://codepen.io 접속 2. Search Codepen... 에 각종 테마 검색 3. 원하는 테마 선택 후 코드 확인 및 아래의 Comments 확인 4. Comments 클릭 후 가장 아래에 License 확인하기 Copyright (c) 2022 by Aigars Silkalns (https:

data-science-study.tistory.com

 

 -로그인 페이지

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>로그인</title>
        <style>
            ul {list-style:none;}
        </style>
        <script src="/js/home/login.js" defer></script>
        <link rel="stylesheet" href="/css/home/login.css">
    </head>
    <body>
        <div class="login-page">
            <div class="form">
              <form class="login-form">
                <input id="id" type="text" placeholder="아이디"/>
                <input id="pw" type="password" placeholder="비밀번호"/>
                <p id="button">login</p>
                <p class="message">Not registered? <a href="/register">Create an account</a></p>
              </form>
            </div>
          </div>
    </body>
</html>

<!-- Copyright (c) 2022 by Aigars Silkalns (https://codepen.io/colorlib/pen/rxddKy) -->

 

 -회원가입 페이지

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>회원가입</title>
        <style>
            ul {list-style:none;}
        </style>
        <script src="/js/home/register.js" defer></script>
        <link rel="stylesheet" href="/css/home/login.css">
    </head>
    <body>
        <div class="login-page">
            <div class="form">
              <form class="login-form">
                <input id="id" type="text" placeholder="아이디"/>
                <input id="name" type="text" placeholder="이름"/>
                <input id="pw" type="password" placeholder="비밀번호"/>
                <input id="confirm-pw" type="password" placeholder="비밀번호확인"/>
                <p id="button">SIGN UP</p>
                <p class="message">Already registered? <a href="/login">login</a></p>
              </form>
            </div>
          </div>
    </body>
</html>

<!-- Copyright (c) 2022 by Aigars Silkalns (https://codepen.io/colorlib/pen/rxddKy) -->

 

 

2. 회원가입 요청 에 대한 동작을 위해 register.js 파일 생성(login.js 파일과 동일하게 public > js > home 폴더에서 관리)

"use strict";

const id = document.querySelector("#id"),
      name = document.querySelector("#name"),
      pw = document.querySelector("#pw"),
      confirmPw = document.querySelector("#cofirm-pw"),
      registerBtn = document.querySelector("#registerBtn");

registerBtn.addEventListener("click", register);

function register() {
    const req = {
        id: id.value,
        name: name.value,
        pw: pw.value,
        confirmPw: confirmPw.value,
    };

    fetch("/register", {   // register 경로에서
        method: "POST",    // http 메소드 중 POST라는 메소드로 데이터 전달
        headers: {         // 전달하는 데이터가 json 형식이라는 것을 명시
            "Content-Type": "application/json",
        },
        body: JSON.stringify(req) // JSON 객체는 문자열로 감싸져서 출력됨
                                  // 그냥 req는 {id: "id", pw: "pw"}형태, JSON.stringify(req)는 {"id": "id", "pw": "pw"} 형태
    })
        .then((res) => res.json())
        .then((res) => {
            if (res.success) {          // res.success가 true이면
                location.href = "/login"     // "/login" 경로로 이동
            } else{
                alert(res.msg);         // res.success가 false이면 res에 있는 msg를 경고창으로 띄움
            }
        })
        .catch((err) => {   // 회원가입 중 에러 발생 시 console에 에러 표시
            console.error(new Error("회원가입 중 에러 발생")); // 
        });
}

 -기본적으로 login.js의 코드를 복사하여 사용

 

3. User.js와 UserStorage.js파일에도 회원가입 관련 기능 구현

// UserStorage.js

// class UserStorage{}안에 static save 변수 추가
static save(userInfo) {             // User.js에서 client(=this.body)를 인자로 받아옴
  const users = this.#users;        // UserStorage 클래스 내의 #users 객체를 받아와
  users.id.push(userInfo.id);       // 객체 안의 각 리스트에 id, name, pw을 push하여 저장
  users.name.push(userInfo.name);
  users.pw.push(userInfo.pw);
  return { success: true };         // 전부 제대로 저장이 되었다면 success: true 반환
}
// User.js

// class User{} 안에 register() 추가
register() {
  const client = this.body;
  const response = UserStorage.save(client); //constructor에서 넘겨준 body를 그대로 저장하고 UserStorage의 save함수로 넘겨줘 입력한 정보를 저장
  return response; // UserStorage의 save 함수를 실행하여 반환된 success: true값을 똑같이 반환해줌
                   // 반환한 값에 따라 success: true이면 register.js에서 '/login'경로로 이동하라는 명령을 실행
}

 

 

4. Controller인 home.ctrl.js파일에도 User.js파일에서 register함수 처리에 대한 컨트롤을 위해 process에 register 추가

// home.ctrl.js

// 원래 login()만 있던 process 객체에 register 추가
const process = {
    login: (req, res) => {
        const user = new User(req.body);
        const response = user.login();
        return res.json(response);
    },
    register: (req, res) => {
        const user = new User(req.body);
        const response = user.register();
        return res.json(response);
    },
};

 

 

5. index.js에도 /register경로에서의 데이터를 post로 받기 위해 코드 추가

router.get('/register', ctrl.output.register);    // '/register'경로로 들어가면 출력되는 화면 연결
router.post('/register', ctrl.process.register);  // 'register'경로에서 입력되는 데이터 post로 받기

1. 상황에 따라 달라지는 this

·this는 실행 컨텍스트가 생성될 때 결정됨

  -실행 컨텍스트는 함수가 호출될 때 생성되므로 this는 함수를 호출할 때 결정됨

 

 1) 전역 공간의 this

  -브라우저 환경일 때: this === window

 

  -Node.js 환경일 때: this === global

  -전역 변수를 선언하면 전역 변수를 전역 객체의 프로퍼티로 할당함

   →a를 1로 선언

  →선언한 a를 출력하면 1이 출력됨

  →this.a(객체 this의 프로퍼티 a)를 출력하면 마찬가지로 1이 출력됨

  →window.a(브라우저 환경에서 window === this이므로 this.a와 같음)

 

  →결과적으로 var a = 1;과 window.a = 1은 똑같이 전역 변수 a를 선언한 효과를 가짐

  →하지만, 삭제 명령에서 전역변수로의 선언과 전역 객체의 포로퍼티로의 선언은 다른 결과를 가짐

  →전역 변수로 선언한 a, b는 delete 명령어로 전역객체에서 삭제하는 명령이 false로 출력되며 다시 출력해도 정상적으로 출력됨

  →전역 객체의 프로퍼티로 선언한 c, d는 delete 명령어로 전역객체에서 삭제하는 명령이 true로 출력되며 다시 출력하면 이미 삭제된 상태이므로 에러가 남

 

 

 2) 메서드로서 호출할 때 메서드 내부의 this

  -함수: 함수는 그 자체로 독립적인 기능을 수행

  -메서드: 메서드 자신을 호출한 대상 객체에 관한 동작 수행

  -자바스크립트에서는 상황별로 this에 다른 값을 부여하며 이를 구별

// 함수
var func = function (x) {
  console.log(this, x);
};
func(1);

// 결과: Window{ ... } 1
// this에 전역객체 Window가 할당됨


// 메소드
var obj = {
  method: func // 객체 obj안에 method로 위에서 정의한 함수 func를 할당
};
obj.method(2);

// 결과: {method: f} 2
// this에 obj가 할당됨

 

  -함수는 함수 자체의 이름으로 호출 / 메서드는 어떤 객체의 메서드이므로 객체이름.메서드 또는 객체이름['메서드']로 호출

// 메서드로 함수 정의까지 한번에 하는 방법
var obj = {
  method: function(x) {console.log(this, x);}
};

obj.method(1);      // {method: f} 1
obj['method'](2);   // {method: f} 2

 

  -메서드 내부에서의 this: 메서드를 호출하는 주체는 메서드를 담고 있는 객체이므로 this === 객체가 됨

  -객체 내부에 객체를 생성하여 그 내부에 메서드를 정의할 경우, this === 마지막 객체(메서드 바로 윗단계의 객체)가 됨

var obj = {
  methodA: function() {console.log(this);},
  inner: {
    methodB: function() {console.log(this);}
  }
};

obj.methodA();         // {methodA: f, inner: {...}} => this에 객체 obj가 출력됨
obj.inner.methodB(); // {methodB: f} => this에 객체 obj.inner가 출력됨

 

 

 3) 함수로서 호출할 때 함수 내부의 this

  -this에는 호출한 주체에 대한 정보가 담기는 데 함수는 개발자가 직접 코드를 통해 호출하므로 호출 주체가 명확하지 않아 this에는 전역객체가 담김

  -메서드의 내부함수의 this

var obj1 = {
  outer: function(){
  
    console.log(this); // (2)
    
    var innerFunc = function(){ // (3)
      console.log(this); // (4)
    }
    
    innerFunc(); // (4)
    
    var obj2 = { // (5)
      innerMethod: innerFunc
    };
    
    obj2.innerMethod(); // (6)
    
  }
};

obj1.outer();

   (1) obj1이 선언된 뒤 obj1.outer()를 호출

   (2) outer 메서드는 obj1 객체 내부에서 정의된 메서드이므로 이때의 this는 메서드 바로 윗단계의 객체, obj1이 할당됨

   (3) innerFunc는 outer 메서드 내부에서 정의된 함수

   (4) innerFunc()를 통해 호출하면 메서드가 아닌 함수로써 호출한 것이므로 호출 주체가 명확하지 않아 this에 전역 객체(Window)가 할당됨

   (5) var obj2라는 객체를 하나 더 생성한 뒤 메서드로서 innerFunc를 정의

   (6) obj2.innerMethod()와 같이 메서드로써 호출하면 this에 메서드 호출의 객체 obj2가 할당됨

 

  -함수로 호출되었는지(this에전역객체 Window 할당) 메서드로 호출되었는지(this에 메서드의 객체 할당) 확인하면 this에 어떤 값이 할당되는 지 알 수 있음

 

  -메서드 내부 함수에서 this 우회하는 방법

  →변수를 통한 우회

var obj = {
  outer: function(){
    var self = this;
    var innerFunc = function(){
      console.log(self); // obj
    };
    innerFunc();
  }
};
obj.outer();

  →obj.outer에서 this는 outer메서드를 호출한 객체 obj이므로 innerFunc로 들어가기전 임의의 변수(self)에 obj로 할당된 this를 저장하면

        함수인 innerFunc에 들어가서도 this를 obj로 쓸 수 있음

 

  →화살표 함수를 통한 우회

var obj = {
  outer: function(){
    var innerFunc = () => {
      console.log(this); // obj
    };
    innerFunc();
  }
};
obj.outer();

   →화살표 함수를 사용하면 실행 컨텍스트 생성 시 this 바인딩 과장이 빠져 상위 스코프의 this를 그대로 활용 가능

 

 

 4) 콜백함수 내부의 this

  -콜백함수: 함수 A의 제어권을 다른 함수(또는 메서드) B에게 넘겨주는 경우, 함수 A를 콜백함수라고 함

  -콜백함수도 함수이므로 기본적으로는 this에 전역객체 할당하지만 콜백함수에 별도의 대상을 지정할 수 있음

// 01. 300ms(0.3초) 뒤에 console에 this를 출력
setTimeout(function() {console.log(this);}, 300};
// 일반 함수와 마찬가지로 this에 전역객체가 할당되고 0.3초뒤에 전역 객체가 출력됨

// 02
[1, 2, 3, 4, 5].forEach(function (x) {
  console.log(this, x);
});
// 전역객체 + 리스트의 숫자가 각각 한번씩 출력(전역객체가 총 5번 출력되고 끝에 숫자는 리스트내 숫자가 바뀌며 출력됨)

// 03. 브라우저 마지막에 '클릭'이 쓰인 버튼을 추가하고, 버튼에 'a'라는 id 부여
document.body.innerHTML += '<button id = "a".클릭</button>';
document.body.querySelector('#a')
    .addEventListener('click', function(e) {
      console.log(this, e);
    });
// 클릭을 할 때마다 이벤트 정보를 콜백함수의 첫번째 인자로 삼아 함수를 실행
// addEventListener 메서드는 콜백 함수 호출 시 자신의 this를 상속하도록 정의되어 addEventListener의 .앞부분은 this가 됨
// 따라서 console에 앞서 지정한 엘리먼트와 클릭 이벤트에 대한 정보가 출력됨

 

 

 5) 생성자 함수 내부의 this

  -생성자 함수: 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수

  -한 클래스에서 각 인스턴스마다 공통적으로 존재하는 특성들을 생성자를 통해 정의하여 구체적인 인스턴스를 만들기 위한 틀로써 작용

var Cat = function(name, age) {
  this.bark = '야옹';
  this.name = name;
  this.age = age;
};

var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);

/* 결과
Cat {bark: '야옹', name: '초코', age: 7}
Cat {bark: '야옹', name: '나비', age: 5}
*/

   →생성자 함수 Cat에 이름과 나이는 변수로 받아 적용하고 bark는 '야옹'으로 통일

   →new 명령어와 함께 이름과 나이를 인자로 주면 새로운 Cat객체가 생성

   →이때 생성자 함수 내부의 this는 새로 생성되는 인스턴스 자신이 됨(choco, nabi)

   →따라서, choco를 생성할 때 넘겨준 인자 '초코'와 7은 각각 name과 age에 대입되어 choco.name, choco.age가 됨

  →console에 choco를 출력하게 되면 choco.bark('야옹'), choco.name('초코'), choco.age(7)이 각각 잘 대입되어 출력됨 

1. https://codepen.io 접속

 

2. Search Codepen... 에 각종 테마 검색

 

3. 원하는 테마 선택 후 코드 확인 및 아래의 Comments 확인

 

4. Comments 클릭 후 가장 아래에 License 확인하기

Copyright (c) 2022 by Aigars Silkalns (https://codepen.io/colorlib/pen/rxddKy)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 - 저작권 표시를 포함하면 사용해도 된다는 내용

 

5. html, css 코드 복사

// login.html

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>로그인</title>
        <style>
            ul {list-style:none;}
        </style>
        <!-- css 코드 복사한 문서 연결 -->
        <link rel="stylesheet" href="/login.css">
    </head>
    <body>
    <!-- body 안에 html 코드 복사 -->
        <div class="login-page">
          <div class="form">
            <form class="register-form">
              <input type="text" placeholder="name"/>
              <input type="password" placeholder="password"/>
              <input type="text" placeholder="email address"/>
              <button>create</button>
              <p class="message">Already registered? <a href="#">Sign In</a></p>
            </form>
            <form class="login-form">
              <input type="text" placeholder="username"/>
              <input type="password" placeholder="password"/>
              <button>login</button>
              <p class="message">Not registered? <a href="#">Create an account</a></p>
            </form>
          </div>
        </div>
    <!-- body 안에 html 코드 복사 -->
    </body>
</html>

<!-- Comments에 있던 저작권 표시도 복사 -->
<!-- Copyright (c) 2022 by Aigars Silkalns (https://codepen.io/colorlib/pen/rxddKy) -->
// login.css

@import url(https://fonts.googleapis.com/css?family=Roboto:300);

.login-page {
  width: 360px;
  padding: 8% 0 0;
  margin: auto;
}
.form {
  position: relative;
  z-index: 1;
  background: #FFFFFF;
  max-width: 360px;
  margin: 0 auto 100px;
  padding: 45px;
  text-align: center;
  box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.form input {
  font-family: "Roboto", sans-serif;
  outline: 0;
  background: #f2f2f2;
  width: 100%;
  border: 0;
  margin: 0 0 15px;
  padding: 15px;
  box-sizing: border-box;
  font-size: 14px;
}
.form button {
  font-family: "Roboto", sans-serif;
  text-transform: uppercase;
  outline: 0;
  background: #4CAF50;
  width: 100%;
  border: 0;
  padding: 15px;
  color: #FFFFFF;
  font-size: 14px;
  -webkit-transition: all 0.3 ease;
  transition: all 0.3 ease;
  cursor: pointer;
}
.form button:hover,.form button:active,.form button:focus {
  background: #43A047;
}
.form .message {
  margin: 15px 0 0;
  color: #b3b3b3;
  font-size: 12px;
}
.form .message a {
  color: #4CAF50;
  text-decoration: none;
}
.form .register-form {
  display: none;
}
.container {
  position: relative;
  z-index: 1;
  max-width: 300px;
  margin: 0 auto;
}
.container:before, .container:after {
  content: "";
  display: block;
  clear: both;
}
.container .info {
  margin: 50px auto;
  text-align: center;
}
.container .info h1 {
  margin: 0 0 15px;
  padding: 0;
  font-size: 36px;
  font-weight: 300;
  color: #1a1a1a;
}
.container .info span {
  color: #4d4d4d;
  font-size: 12px;
}
.container .info span a {
  color: #000000;
  text-decoration: none;
}
.container .info span .fa {
  color: #EF3B3A;
}
body {
  background: #76b852; /* fallback for old browsers */
  background: rgb(141,194,111);
  background: linear-gradient(90deg, rgba(141,194,111,1) 0%, rgba(118,184,82,1) 50%);
  font-family: "Roboto", sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;      
}

 

6. 결과화면

 

※ Codepen 이외에 bootstrap, Material-UI, Semantic-UI 등을 설치하여 React 기반의 UI 라이브러리 이용 가능

1. 실행 컨텍스트

 1) 실행 컨텍스트: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체

  -선언된 변수, 함수 등을 실행 컨텍스트에 저장해두었다가 호출하면 나옴

 

 2) 스택(stack): 우물 같은 구조로 가장 처음 넣은 데이터는 데이터가 쌓일 수록 밑에 깔리게 되어 가장 마지막에 나오고

                              가장 마지막에 넣은 데이터가 가장 처음 나오는 구조

 3) 큐(queue): 양쪽이 모두 열린 파이프 구조로 한쪽으로 입력되어 다른 쪽으로 출력, 가장 먼저 들어간 데이터가 가장 먼저 나옴

 

 4) 예시

//------------------------------전역 컨텍스트 생성
var a = 1;

function outer() { //----------outer 컨텍스트 생성
  function inner() { //--------inner 컨텍스트 생성
    console.log(a); //---------(1) undefined
    var a = 3; // inner 함수가 끝남과 동시에 사라짐
  }
  inner();
  console.log(a); //----------(2) 1
}

outer();

console.log(a); //-----------(3) 1

  -저장

   (1) 전역 컨텍스트가 생성되어 스택 가장 아래에 저장되고, 위에서부터 차례대로 실행

   (2) outer 함수가 호출되며 전역 컨텍스트 위에 저장되고, outer 함수의 코드 실행

   (3) outer 함수 내의 inner 함수가 호출되며 ouer 함수 컨텍스트 위에 저장되고, inner 함수의 코드 실행

   (4) inner 함수의 마지막 코드가 실행되면 다시 outer 함수로 돌아와 나머지 코드 실행

   (5) outer 함수의 마지막 코드가 실행되면 다시 전역으로 돌아와 나머지 코드 실행

   (6) 전역 컨텍스트의 마지막 코드가 실행되면 전역 컨텍스트까지 제거되고 콜 스택에는 아무것도 남지 않음

 

 

  -컨텍스트가 콜 스택에 있을 때만 컨텍스트 내부의 환경변수들이 현재 실행하는 코드에 관여할 수 있음

  →inner 함수 내부에서 선언한 var a = 3은 inner 함수가 콜 스택에서 나감과 동시에 사라짐

  →전역에서 선언한 var a = 1은 전역 컨텍스트가 사라지는 마지막까지 남아서 console.log(a)를 실행할 때 적용됨

 

 

2. VariableEnvironment / LexicalEnvironment

 1) 둘 다 environmentRecord와 outerEnvironmentReference로 구성되며 VariableEnvironment는 최초 실행 시의 스냅샷을 유지한다는 점이 다름

 

 2) environmentRecord: 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨

  -컨텍스트를 구성하는 함수의 매개변수 식별자, 함수 자체, var로 선언된 식별자 등이 식별자에 해당

  -컨텍스트 내부 전체를 처음부터 끝까지 순서대로 훑어 수집

 

 3) 호이스팅: 변수 수집 과정을 이해하기 쉬운 방법으로 대체한 가상의 개념(자바스크립트가 식별자들을 컨텍스트의 최상단으로 끌어올린 후 실제 코드 실행)

  -예시 (1): 호이스팅은 다음과 같은 원리로 작동되지만 실제로 자바스크립트 실행과정에서 다음과 같이 코드가 바뀌지는 않음

// 01. 원본 코드

function a(x) {
  console.log(x);
  var x;
  console.log(x);
  var x = 2;
  console.log(x);
}

a(1);
// 02. 호이스팅(1) 매개변수를 함수 내에서 하나의 변수를 선언한 것과 같다고 가정

function a(){
  var x = 1;
  console.log(x);
  var x;
  console.log(x);
  var x = 2;
  console.log(x);
}

a();
// 03. 호이스팅(2) 식별자를 함수 가장 위로 끌어올린 뒤 나머지 코드 실행

function a() {
  var x;
  var x;
  var x;
  
  x = 1;
  console.log(x);  // 1
  console.log(x);  // 1
  x = 2;
  console.log(x);  // 2
}

a(1);

  -01에서 함수 a에 매개변수로 준 1는 함수 a 내부에서 var x = 1로 선언된 것과 같음

  -02에서 매개변수를 없애고 함수 a 내부에서 var x = 1을 선언하는 것으로 변경

  -03에서 var x를 선언한 모든 부분을 함수의 가장 위로 끌어올리고(호이스팅 하고) 나머지 코드 실행

  -03에서 끌어올린 var x선언 중 가장 처음을 제외한 나머지 두 부분은 중복되므로 무시됨

    →그 후, x = 1로 변수 x에 1이 할당되어 다음 두 줄에서 1이 출력

    →x = 2로 x에 2가 재할당되어 마지막 줄에서 2가 출력

 

  -예시(2): 컨텍스트 내에서 선언된 함수 자체도 하나의 식별자로 보고 호이스팅 하므로 다음과 같은 예시의 결과가 나옴

// 01. 원본 코드

function a() {
  console.log(b);
  var b = 'bbb';
  console.log(b);
  function b() {
    console.log(b);
  }
}

a();
// 02. 변수 식별자롸 함수 호이스팅

function a() {
  var b;
  function b() { };
  
  console.log(b);
  b = 'bbb';
  console.log(b);
  console.log(b);
}

a();
// 03. 함수 선언문을 함수 표현식으로 변경

function a() {
  var b;
  var b = function b() { }; // 호이스팅된 함수의 선언은 함수명으로 선언한 변수(b)에 함수를 할당한 것과 동일
  
  console.log(b);  // function b
  b = 'bbb';
  console.log(b);  // 'bbb'
  console.log(b);  // 'bbb'
}

a();

  -02에서 함수 a 내부의 모든 식별자 호이스팅(변수 식별자(var b), 함수 자체(function b()))

  -03에서 호이스팅된 함수 선언문을 선언한 변수(b)에 함수를 할당하는 것으로 변경

    →그러면 나머지 코드 실행 전에 변수 b에는 b함수가 할당되어 있고 첫 번째 코드 console.log(b)를 실행할 때는 함수 b가 출력

    →두 번째 코드에서 b = 'bbb'로 새로운 문자열이 할당되었으므로 원래 할당되어있던 함수는 삭제되고 b는 'bbb'가 됨

    →세 번째 코드부터는 b = 'bbb'로 할당되어 있으므로 'bbb' 출력

 

  ※ 함수 선언문과 함수 표현식

function a() {}  // 함수 선언문(함수명 a = 변수명)
a();                  // 실행 가능

var b = function () {}  // 익명 함수 표현식(변수명 b = 함수명)
b();                           // 실행 가능

var c = function d () {}  // 기명 함수 표현식(변수명 c != 함수명 d)
c();                              // 실행 가능
d();                             // 실행 불가능
// 함수 선언문과 함수 표현식의 호이스팅 방식 차이

console.log(sum(1, 2));
console.log(multiply(3, 4));

// 함수 선언문
function sum(a, b) {
  return a+b;
}

// 함수 표현식
var multiply(a,b) {
  return a*b;
}
// 함수 선언문은 전체를 호이스팅
var sum = function sum(a, b) {
  return a+b;
}

// 함수 표현식은 변수 선언부분만 호이스팅
var multiply

console.log(sum(1, 2));       // 3, 함수 선언문 전체가 호이스팅 되어 sum 함수가 선언된 상태이므로 정상적으로 sum 함수의 결과값이 반환
console.log(mutiply(3, 4));  // mutiply is not a function, 변수의 선언부분만 호이스팅되어 multiply가 함수가 아닌 선언되지 않은 변수의 상태이므로
                                        //아래에서 선언되는 multiply함수의 결과값을 윗부분인 이 곳에서 반환할 수 없음

multiply = function(a, b) {
  return a*b;
}

 

 

 4) 스코프: 식별자에 대한 유효범위, 선언한 변수가 적용되는 범위

 5) 스코프 체인: 식별자의 유효범위를 안에서부터 바깥으로 검색해나가는 것

  -각 실행 컨텍스트가 활성화 될 때, LexiccalEnvironment(이하 L.E)가 생성되며, L.E 안에는 environmentRecord(이하 e), outerEnvironmentReference(이하 o)가 생성

  -outerEnvironmentReference는 현재 컨텍스트 바깥의 컨텍스트에 접근할 수 있도록 바깥의 컨텍스트를 저장

  -코드가 실행될 때 참조할 식별자는 현재 활성화 되어 있는 컨텍스트 내의 L.E에 먼저 접근

    →현재 활성화 컨텍스트의 L.E 내부에서 e를 먼저 확인하여 참조할 식별자가 있는지 확인

    →e에서 확인되지 않으면 o를 통해 바로 한 단계 위의 컨텍스트의 L.E 내부의 e를 확인

    →한 단계 위의 L.E의 e에도 참조할 식별자가 없다면 o를 통해 다시 그 위의 컨텍스트에 접근

    →위 과정 반복하며 전역 컨텍스트까지 참조할 식별자를 찾을 수 없으면 undefined

 

  -예시

var a = 1;

function outer() {
  function inner() {
    console.log(a);
    var a = 3;
  }
  inner();
  console.log(a);
}

outer();

console.log(a);

   (1) 코드 시작과 함께 전역 컨텍스트 생성

        →전역 컨텍스트 L.E  -  e  -  { a = 1, outer(함수) }

 

   (2) outer 함수 호출로 전역 컨텍스트 비활성화, outer 실행 컨텍스트 활성화

        →outer 컨텍스트 L.E  -  e  -  { inner(함수) }

                                                      -  o  -  { GLOBAL L.E }

 

   (3) inner 함수 호출로 outer 컨텍스트 비활성화, inner 실행 컨텍스트 활성화

        →inner 컨텍스트 L.E  -  e  -  { a=3 }

                                                   -  o  -  { outer L.E }

        →inner 함수 내에서 호이스팅에 의해 var a;가 위로 올라가고 → console.log(a)에서는 a=3이 선언되기 전이므로 undefined 출력

        →inner 함수내에서 a = 3 선언

        →inner 함수의 마지막 코드 실행과 함께 inner 컨텍스트가 사라짐

    ※ 가장 마지막에 var a = 3;이 선언되어 있지 않다면 호이스팅되는 a도 없어서 외부 컨텍스트에서 a를 찾았겠지만 밑에서 a가 선언되어 undefined가 출력되어 버림

          (변수 은닉화: inner 변수 내부에 a 변수 선언으로 전역 공간의 변수 a에 접근할 수 없어짐)

 

   (4) outer 함수의 나머지 부분, console.log(a) 실행 → outer 컨텍스트의 L.E에는 식별자 a가 없으므로

          → o를 통해 GLOBAL L.E로 가서 → GLOBAL L.E의 e에 있는 식별자 a = 1을 받아와 1출력

 

   (5) outer 함수의 마지막 코드 실행과 함께 outer 컨텍스트가 사라짐

 

   (6) 전역 컨텍스트의 나머지 부분, console.log(a) 실행 → 전역 컨텍스트의 L.E의 e에 있는 식별자 a = 1을 받아와 1 출력

 

 6) 전역변수와 지역변수

  -위 예시에서 전역 스코프에서 선언한 a와 outer가 전역변수

  -outer 함수 내부에서 선언한 inner와 inner 함수 내부에서 선언한 a는 지역변수

  -지역변수는 함수 내부에서만 쓸 수 있으므로 전역 공간에서 접근 불가능 → 코드가 망가질 염려가 없음

  -전역변수의 사용을 최소화하는 것이 좋음

·UserStorage 파일을 만들어 사용자 정보를 따로 관리

1) src 폴더 아래에 models 폴더 생성 > models 폴더 안에 UserStorage.js 파일 생성

// UserStorage.js

"use strict";

class UserStorage{

  // 사용자 정보를 객체로 보관
  // static(정적 변수)로 선언하여 controller에서 클래스를 인스턴스화하지 않고 바로 users변수에 접근가능하도록 함
  // #users처럼 변수 앞에 #을 붙여 public 변수에서 private 변수로 선언하여 외부 파일에서 함부로 불러올 수 없도록 함
  static #users = {
    id: ["사용자1", "사용자2", "사용자3"],
    pw: ["1234", "3456", "5678"],
    name: ["이름1", "이름2", "이름3"]
  };


  // users 변수를 private로 선언하여 불러올 수 없게 한 것을 로그인 확인을 위하여 불러와야 할 때 불러올 수 있도록 함
  static getUsers(...fields) {
    const users = this.#users;                                                 // 위의 users 객체를 this를 통해 복사
    const newUsers = fields.reduce((newUsers, field) => {       // users 객체의 각 변수에 대해
      if (users.hasOwnProperty(field)) {                                   // users 객체에 존재하는 변수에 대해
        newUsers[field] = users[field];                                     // newUsers 객체에 똑같은 변수를 복사
      }
      return newUsers;
    }, {})										  // users를 복사한 newUsers를 객체 형태로 반환
    return newUsers;
  }
  
  
  // 로그인 페이지에서 입력받은 id에 따라 알맞은 pw와 name 반환하기
  ststic getUserInfo(id) {							    // id를 인자로 넘겨 받아(로그인 페이지에서 입력한 id)
    const users = this.#users;
    const idx = users.id.indexOf(id);					    // 인자로 넘겨받은 id의 users 객체 중 id 변수에서의 인덱스를 받아오기 
    const usersKeys = Object.keys(users);                                // users 객체의 key(변수 이름) 가져오기
    const userInfo = userKeys.reduce((newUser, info) => {        // 각 변수의 이름에 대해
      newUser[info] = users[info][idx];                                     // 각 변수의 이름별 인덱스(id의 인덱스, pw의 인덱스 등)을 받음
      return newUsers;
    }, {});                                   // 로그인 페이지에서 입력받은 id를  users 객체의 id 변수에서 찾아 같은 인덱스의 pw, 같은 인덱스의 name을 찾아 객체 형태로 반환
    return userInfo;
  }
}

module.exports = UserStorage; // 외부 파일에서도 UserStorage 클래스를 사용할 수 있도록 exports

 

 

2) UserStorage.js 파일을 만든 models 폴더에 User.js 파일을 생성하여 로그인 모델 구현

// User.js

"use strict";

const UserStorage = require("UserStorage");    // 사용자의 정보를 담고 사용자의 id와 pw를 비교해줄 UserStorage 가져오기

class User {
  constructor(body) {       // 생성자 생성(body의 초기값 설정)
    this.body = body;
  }
  
  login() {                     // 로그인 모델 구현
    const body = this.body                                                                  // this.body가 반복적으로 나오므로 짧게 body로 사용하기 위해 선언
    const { id, pw } = UserStorage.getUserInfo(body.id);                       // UserStorage.js 파일에서 getUserInfo 함수에 인자로 id를 주고 그 id에 해당하는 id와 pw를 객체 형태로 받기
    if (id) {                                                                                         // 그 id가 사용자 정보에 존재한다면
      if (id === body.id && pw === body.pw) {                                    // 그리고 그 id가 로그인 페이지에서 받은 id와 일치하고 pw가 로그인 페이지에서 받은 pw와 일치한다면
        return {success: true};                                                             // {success: true}를 객체로 return
      }
      return {success: false, msg: "비밀번호가 틀렸습니다."};                  // 만약 id는 존재하지만 pw가 틀렸다면 success는 false로, 메세지와 함께 반환 
    }
    return {success: false, msg: "존재하지 않는 아이디입니다."};             // 만약 id가 존재조차하지 않는다면 success는 false로, 메세지와 함께 반환
  }
}

module.exports = User;

 

 

3) Controller(home.ctrl.js)파일에서 로그인 프로세스를 새로 만든 User 클래스를 사용해 바꿔주기

// home.ctrl.js

// 기존 const process에서 아래의 코드로 변경
const process = {
  login: (req, res) => {
    const user = new User(req.body);     // 로그인 페이지에서 로그인 요청과 함께 받은 body(id와 pw)를 User 클래스에 초기값으로 전달
    const response = user.login();          // User클래스의 login 함수 실행하여 id와 pw 일치여부 확인 및 반응
    consoel.log(response);                    // id와 pw에 따른 반응결과를 console에 출력(success 여부 및 메세지)
    return res.json(response);              // json 형태로 반환(페이지에 경고창으로 띄우기 등)
  },
};

 

 

4) 결과

 -입력한 id가 사용자 정보에 없을 때

 

 -입력한 id는 사용자 정보에 있지만 pw가 일치하지 않을 때

 

 -id와 pw가 일치할 때(login.js 파일에서 로그인 성공 시(res.success가 ture일 시) "/"경로로 이동하도록 지정해 놓았으므로 로그인 성공 시 "/" 경로로 이동

5. 불변 객체

 1) 불변 객체

  -참조형 데이터에서 데이터의 객체 자체를 변경할 때는 변경할 객체를 새로운 빈 공간의 주소에 할당하므로 기존의 데이터는 바뀌지 않음

  -따라서 내부 변수를 변경할 때마다 새로운 객체를 만들어 재할당하는 것으로 규칙을 정하거나 자동으로 새로운 객체를 만드는 도구를 활용하면 불변성 확보 가능

 

 2) 불변 객체가 필요한 경우

  -아래의 경우 사용자의 이름이 바뀌는 것을 감지해야 하지만 이름이 바뀌는 순간 초기값의 이름도 바뀌므로 초기값과 바뀐 값의 이름을 비교해도 항상 같다고 나옴

// 가장 초기 이름과 성별 정보 입력
var user = {
  name: 'old_name',
  gender: 'male'
};

// 초기 정보와 새로운 이름을 인자로 받아 초기 정보에서 이름 값을 바꾸는 함수
var changeName = function(user, newName){
  var newUser = user;
  newUser.name = newName;
  return newUser;
};

// 함수 실행(user 객체의 name을 'old_name'에서 'new_name'으로 변경)
var user2 = changeName(user, 'new_name');

// 초기 객체인 user와 이름을 바꾼 user2를 비교하여 다른 점이 있다면 아래 메세지 출력
// 함수를 통해 이름을 바꿔 user와 user2의 이름이 다르다면 아래의 메세지가 출력될 것
if(user !== user2){
  console.log('유저 정보가 변경되었습니다.');
}
// 하지만 객체 내부의 변수를 하나 바꾸면 기존의 객체의 값까지 바꿔버리므로 user의 name도 new_name이 되어버려 메세지 출력X

console.log(user.name, user2.name);   // new_name new_name
console.log(user === user2);              // true

 

  -따라서 다음과 같이 함수에서 객체 전부를 가져온 뒤 바꾸고 싶은 내부변수(name)만 바꾸고 return하도록 해야함

var changeName = function(user, newName){
  return {
    name: newName,
    gender: user.gender
  };
};

 

  -또한 예시의 객체는 내부 변수가 두 개뿐이지만 여러 개라 매번 전부 써줄 수 없을 경우 for문을 통해 범용 함수 사용

var copyObject = function(target) {
  var result = {};
  for (var prop in target) {          // 인자로 받은 target 객체 내의 모든 내부 변수 각각에 대해
    result[prop] = target[prop];   // result에 똑같은 내부 변수 생성
  }
  return result;
};

 

  -위 함수를 통해 user 객체를 user2로 복사하고 user2의 내부 변수를 바꾸면 user의 내부 변수는 그대로, user2의 내부 변수만 변경할 수 있음

var user = {
  name: 'old_name',
  gender: 'male'
}

var user2 = copyObject(user) // user 객체의 모든 내부 변수 복사
user2.name = 'new_name'      // user2에 복사된 변수 중 name 변수 변경, 기존 초기 객체 user에는 변화X

if (user !== user2){
  console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name);  // old_name new_name
console.log(user === user2);             // false

 

  -매번 copyObject와 같은 형식의 함수를 작성하는 것 대신 immutable.js, baobab.js 등의 라이브러리 사용해도 됨

// immutable.js 사용 예시

// immutable 라이브러리 설치
const Immutable = require('immutable')

// user를 immutable 라이브러리의 Map함수를 통해 정의
const user = Immutable.Map({name: 'old_name', gender: 'male'});

// user2는 user 객체에서 name만 'new_name'으로 바꾼 객체로 정의
const user2 = user1.set('name', 'new_name')

console.info(user.get('name');       // old_name, user2를 바꿨어도 user는 바뀌지 않아 기존 값이 출력
console.info(user2.get('name');     // new name, user2는 바뀐 값으로 출력
console.info(user === user2);      // false, user와 user2가 서로 달라짐

 

 

 3) 얕은 복사와 깊은 복사

  -얕은 복사는 바로 아래 단계의 값만 복사(중첩된 객체가 있을 시(객체 안의 객체), 내부의 객체의 내부 변수는 주소값만 복사되어 원본과 사본이 연결되어 있음)

  -깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사

  -예시

// user 객체 내에 urls 객체가 중첩되어 존재
var user = {
  name: 'old_name',
  urls: {
    portfolio: 'http://github.com/abc',
    blog: 'http://blog.com',
    facebook: 'http://facebook.com/abc',
  }
};

// 새로운 변수 user2에 위에서 사용한 copyObject 함수를 사용하여 얕은 복사 시행
var user2 = copyObject(user)

// name은 중첩되지 않고 객체 내에 바로 아래 단계의 변수이므로 바귄 객체에 대해 다른 주소가 할당되어 기존값은 바뀌지 않음
user2.name = 'new_name';
console.log(user.name === user2.name);   // false

// urls 내부의 변수는 name변수보다 한 단계 아래의 변수로, 기존 값과 주소를 공유하게 되어 원본과 사본 둘 중 하나가 바뀌면 같이 바뀜
user2.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);   // true

user2.urls.blog = '';
console.log(users.urls.blog === user2.urls.blog);             // true

 

  -따라서 다음과 같이 중첩된 객체 urls에 대해서도 copyObject 함수를 적용하여 깊은 복사를 시행해주어야 함

var user2 = copyObject(user);
user2.urls = copyObject(user.urls);

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);     // false

user.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog);                 // false

 

  -범용적으로 사용할 수 있는 깊은 복사를 수행하는 함수

var copyObjectDeep = function(target){
  var result = {};
  if(typeof target === 'object' && target !== null){  // target이 null이 아닌 객체인지 확인
    for(var prop in target){                                      // target이 객체라면 target 내부의 변수를 result에 각각 복사하여 모든 객체내의 내부변수를 복사하도록 명령
      result[prop] = copyObjectDeep(target[prop]);   // copyObjectDeep 함수를 재귀적으로 호출하여 객체 내부에 객체가 있다면 마지막 객체 내부까지 들어가서 모든 객체의 내부 변수를 탐색
    }
  }else{
    result = target;   // 객체 내부의 객체가 아닌 내부 변수로 존재하는 것은 그대로 result에 복사
  }
  return result;
};

 

  -JSON을 활용한 깊은 복사: 객체를 JSON 형식의 문자열로 전환했다가 다시 JSON 객체로 바꾸기

var copyObjectViaJSON = function(target){
  return JSON.parse(JSON.stringify(target));
};

    ※주의할 점: JSON으로 변경할 수 없는 변수(__proto__나 geteer/setter 등)은 무시됨

 

 

6. undefined와 null

 1) undefined: 사용자가 어떤 값을 지정할 것이라고 예상되는 상황에 실제로 값을 지정하지 않을 때 Javascript가 자동적으로 부여

  -값을 대입하지 않은 변수(데이터 영역의 메모리 주소를 지정하지 않은 식별자)에 접근할 때

var a;
a // undefined

 

  -객체 내부에 존재하지 않는 변수에 접근할 때

var a={
  b: 1,
  c: 2
}

a.d //undefined

 

  -return문이 없거나 호출되지 않는 함수 실행할 때

var a = function(b, c){
  return b+c
};

var b = function(b, c){
  b+c;
};

a(1, 1) // 2
b(1, 1) // undefined

 

  -undefined를 명시적으로 부여한 경우: undefined는 비어있을을 의미하는 값으로 실재하며 빈 공간을 확보하고 인덱스가 이름으로 지정됨

                                                                                  (forEach 등으로 순회의 대상이 됨)

  -undefined를 Javascript에서 자체적으로 부여한 경우: undefined는 키값(인덱스)가 존재하지 않음 (forEach 등으로 순회할 때 무시됨)

 

 2) null: 비어있음을 명시적으로 나타냄

var n = null;

console.log(typeof n);                  // object, null은 object 형식
console.log(n == undefined);         // true, null은 undefined와 동일하게 '비어있음'을 의미
console.log(n == null);                 // true
console.log(n === undefined);     // false, null과 undefined는 의미는 같지만 형식이 다름
console.log(n === null);              // true

  -'비어있음'을 명시적으로 나타내고 싶다면 undefined보다는 null을 사용하는 것이 좋음

     (undefined를 명시하는 것은 Javascript가 자체적으로 부여하는 undefined와 헷갈릴 수 있음)

 

[Node.js] fetch를 이용해 프런트의 정보를 서버로 보내기

·로그인 페이지에서 입력한 아이디와 비밀번호 받아오기 1) login.js파일에서 로그인 버튼을 눌렀을 때 동작하는 함수에 fetch 기능 추가하기 // login.js function login() { const req = { id: id.value, pw: pw.value,

data-science-study.tistory.com

·이전 글에서 home.ctrl.js 파일에서 login 프로세스가 있을 때 단순히 console.log에 띄우는 작업만 하던 것을 id와 pw를 비교하는 과정으로 변경

 

1) 임의의 id와 pw리스트 지정하고 프런트에서 받은 id, pw와 비교

// home.ctrl.js

// 원래는 controller에 id와 pw 리스트가 있으면 절대 안되지만 연습용으로 controller에 작성
const users = { // 각 사용자의 id와 pw를 리스트로 만들어서 같은 번지에 부여
    id: ["사용자1", "사용자2", "사용자3"],
    pw: ["1234", "3456", "5678"]
};

// login process 작성
const process = {
  login: (req, res) => {
    const id = req.body.id,                         // login.js에서 fetch 안에서 body로 받은 id와 pw를 각각 id와 pw 변수로 받기
           pw = req. body.pw
    if (users.id.includes(id)) {                     // 만약 위에서 임의로 작성한 id 리스트에 프런트에서 받은 id가 존재한다면
      const idx = users.id.indexOf(id);         // 그 id의 인덱스를 idx 변수로 받기
      if (users.pw[idx] === pw){               // 그리고 pw 리스트의 같은 인덱스에 있는 pw와 프런트에서 받은 pw 비교하여 같으면
        return res.json({                            // json 형태로 success: true 전달
          success: true,
        });
      }
    }
    return res.json({                               // 만약 위에서 실패하여 success: true를 return하지 못했다면
      success: false,                                // success: false와 msg: "로그인에 실패하셨습니다."를 json 형식으로 return
      msg: "로그인에 실패하셨습니다.",
    });
  },
};

 

2) login.js에서 success가 true인지 false인지에 따라 응답할 행동 지정

// login.js

function login() {
    const req = {
        id: id.value,
        pw: pw.value,
    };

    fetch("/login", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify(req)
    })
    
//////////////////// 여기까지는 동일 ///////////////////////
 
        .then((res) => res.json())
        .then((res) => {
            if (res.success) {                  // res.success가 true이면
                location.href = "/"            // "/" 경로로 이동
            } else{
                alert(res.msg);                // res.success가 false이면 res에 있는 msg를 경고창으로 띄움
            }
        })
        .catch((err) => {                    // 로그인 중 에러 발생 시 console에 에러 표시
            console.error(new Error("로그인 중 에러 발생"));
        });
}

 

3) 확인

  -로그인 실패 시

 -로그인 실패 경고창

 

 

 -로그인 성공 시

 -코드에서 지정한대로 루트 경로로 이동

+ Recent posts