## Một số khái niệm
### Khai báo `var`, `let`, `const`
- `const` được sử dụng để khai báo 1 hằng số, và giá trị của nó không thay đổi trong suốt chương trình.
- `let` khai báo biến chỉ có thể truy cập được trong block bao quanh nó được xác định bằng cặp `{}`.
- `var` khai báo biến có thể truy cập ở phạm vi hàm số hoặc bên ngoài hàm số, toàn cục.
Ví dụ về trường hợp khai báo `let`:
```javascript
function checkScope() {
let i = 'function scope';
if (true) {
let i = 'block scope';
console.log('Block scope i is: ', i); // Block scope i is: block scope
}
console.log('Function scope i is: ', i); // Function scope i is: function scope
return i;
}
```
Biến `i` được khai báo `let` ở lần đầu, được sử dụng trong phạm vi toàn cục, còn biến `i` được khai báo với từ khóa `let` trong câu lệnh `if` chỉ sử dụng trong phạm vi câu lệnh, biến i được cập nhật trong câu lệnh if sẽ *không được lưu trữ* vào biến i toàn cục khai báo lần đầu trên
### Sử dụng hàm mũi tên thay cho hàm ẩn danh
JS thường không cần đặt tên cho hàm, đặc biệt là khi truyền một hàm làm đối số cho hàm khác. Nên hàm nội tuyến ra đời
```javascript
const myFunc = () => "value"; // trả về giá trị value
const doubler = (item) => item * 2;
doubler(4); // trả về 8
```
### Kiểm tra một kí tự/mẫu có tồn tại trong string hay không
```javascript
'string'.match(/regex/); // trả về 'string'
/regex/.test('string'); // trả về true/false
```
Note:
- `i` không phân biệt chữ hoa chữ thường, `g` tìm kiếm toàn bộ string
- `\w` = `[A-Za-z0-9_]`
- `\d` = `[0-9]`
- `\s` = khoảng trắng
- dấu cộng `+` để tìm kiếm một hoặc nhiều ký tự và dấu hoa thị `*` để tìm kiếm không hoặc nhiều ký tự.
### OOP Trong Javascript
#### Object là gì?
- Object là một tập hợp các property
- Property là một cặp *key-value* thể hiện tên và giá trị
- Cách tạo 1 object:
```javascript
// literal
const dog = { }
// constructor
const cat = new Object();
// static method
const horse = Object.create({ })
```
#### `Class` và `Constructor` function trong JS
Muốn khởi tạo 1 thực thể (instance object), ta sẽ bắt đầu với việc xây dựng 1 bản khuôn mẫu (constructor) và sau đó sử dụng từ khóa `new`.
Bản khuôn mẫu đó, có thể là 1 `constructor` function (cách cũ) hoặc 1 `class` (ES6).
- Sử dụng constructor function:
```javascript
// Tạo các thuộc tính (property)
function Dog(name, color, type) {
this.name = name;
this.color = color;
this.type = type
}
// Khởi tạo 1 instance object
let milu = new Dog('Milu','Black','Becgie');
```
- Sử dụng Class:
```javascript
class Dog {
// Tạo các thuộc tính (property)
constructor(name, color, type) {
this.name = name;
this.color = color;
this.type = type
}
}
let milu = new Dog('Milu', 'Black', 'Becgie');
```
Khi dùng `new` để tạo 1 instance object thì xảy ra lần lượt những thứ sau:
- Tạo một Object mới, trống rỗng
- Thực hiện constructor function, với giá trị this được gán là chính Object rỗng vừa tạo
- Kết thúc hàm, trả về Object mới vừa tạo
#### Prototype trong JS
- **Prototype** là cơ chế mà các object trong javascript **kế thừa** các tính năng từ một object khác. Tất cả các object trong javascript đều có một prototype, và các object này kế thừa các thuộc tính (properties) cũng như phương thức (methods) từ prototype của mình.
- Trong Javascript, một hàm (function) cũng được coi là 1 object. Và hàm có một thuộc tính gọi là thuộc tính prototype, **bản thân thuộc tính prototype này mang giá trị là 1 object**.
- **Để thực hiện kế thừa trong JS**, bạn chỉ cần tạo 1 hàm khởi tạo. Sau đó thêm các thuộc tính và phương thức vào thuộc tính prototype của hàm khởi tạo này. Các instance tạo ra bởi hàm khởi tạo này sẽ **chứa tất cả** các thuộc tính và phương thức được định nghĩa ở trên.
```javascript
//Tạo ra 1 hàm khởi tạo cơ sở
function Animal(_age){
this.age = _age;
}
//Có thể thêm thuộc tính vào thuộc tính prototype của hàm khởi tạo
Animal.prototype.showAge = function(){
console.log( this.age );
};
//Tạo ra 1 hàm khởi tạo con (sẽ dùng để kế thừa hàm cơ sở)
function Dog(_color){
this.color = _color;
}
//Thực hiện kế thừa, gán hàm khởi tạo của Animal cho prototype của Dog
Dog.prototype = new Animal();
Dog.prototype.showColor = function(){
console.log( this.color );
};
//Kiểm tra sự kế thừa
var poc = new Dog('yellow');
poc.age = 3;
poc.showAge(); //3
poc.showColor(); //yellow
```
#### Một số phương thức thường dùng trong object
`hasOwnProperty()` or `in` kiểm tra thuộc tính tồn tại hay không
`arr.push()` thêm vào cuối mảng
`concat()` hợp nhất cách mảng mà không làm thay đổi mảng ban đầu
`arr.unshift()` thêm vào đầu mảng
`arr.pop()` xóa phần tử cuối mảng
`arr.shift()` xóa phần tử đầu mảng
`arr.splice(0,2)` xóa 2 phần tử đầu
`arr.slice(start,stop)` sao chép phần tử mảng
`arr.indexOf()` kiểm tra phần tửu có tồn tại hay không, sau đó trả về vị trí phần tử, nếu không có sẽ trả về -1
`arr.split()` tách 1 chuỗi thành 1 mảng với các phần tử là các chữ
`Object.keys()` trả về tất cả thuộc tính
`[...arr]` copy mảng
#### `map()`, `filter()` method
- `map(callbackFn)` trả về một mảng mới, không làm thay đổi mảng cũ
>A new array with each element being the result of the callback function.
- `filter(callbackFn)` trả về một mảng mới, mà các phần tử trả về đã đáp ứng được điều kiện
>A shallow copy of the given array containing just the elements that pass the test. If no elements pass the test, an empty array is returned.
- `callbackFn`: Một hàm để thực thi cho *từng* phần tử trong mảng. Giá trị trả về của nó được thêm dưới dạng một phần tử trong mảng mới, và có các đối số sau :
- `element` : Phần tử hiện tại đang được xử lý trong mảng.
- `index` : Chỉ mục của phần tử hiện tại đang được xử lý trong mảng.
- `array`: Mảng map() đã được gọi.
#### Từ khóa `this`
Tất cả các hàm của JavaScript đều có thuộc tính, giống như mọi đối tượng khác. Và khi một hàm được thực thi, nó sẽ có thuộc tính `this` - *một biến với giá trị là đối tượng* đã gọi hàm sử dụng this.
>In non–strict mode, `this` is always a reference to an object. In strict mode, it can be any value
Đối với 1 hàm thông thường, giá trị của `this` là đối tượng mà hàm được truy cập. VD: nếu hàm gọi có form `obj.f()` thì `this` đề cập đến `obj`.
```javascript
var person = {
firstName: "Saa",
lastName: "lee",
// Bởi vì từ khóa "this" được sử dụng trong phương thức showFullName
// và phương thức này được định nghĩa trong đối tượng person
// nên "this" sẽ có giá trị của đối tượng person bởi
// vì đối tượng này sẽ gọi showFullName()
showFullName: function() {
console.log(this.firstName + " " + this.lastName);
}
}
person.showFullName(); // Saa lee
```
## Thực hành Projects FCC
### 1. Palindrome Checker
#### Đề

#### Solution
Ý tưởng của mình ban đầu là sẽ lọc hết các kí tự đặc biệt ra và chỉ lấy chữ và số, nên ở bước đầu này mình sẽ dùng phương thức `.match()` để tìm ký tự trong chuỗi mà mình cần tìm
```javascript
a= str.toLowerCase().match(/[a-z0-9]/g)
```
Sau đó mình sẽ tạo ra 1 mảng rỗng để lữu trữ các phần tử của mảng mà `.match` trả về, nhưng các phần tử sẽ đảo ngược lại bằng cách sử dụng vòng lặp `for`
```javascript
const b=[];
for(let i=0; i<a.length; i++){
b[i]=a[a.length-1-i];
}
```
Cuối cùng để so sánh 2 mảng thì mình sẽ tạo ra 1 biến `cnt` để đếm số phần tử trùng nhau trong 2 mảng, nếu `cnt` bằng độ dài mảng thì sẽ trả về true, khác thì false
```javascript
let cnt=0;
if(cnt==a.length) return true;
return false;
```
**FINAL CODE**
```javascript
function palindrome(str) {
const a= str.toLowerCase().match(/[a-z0-9]/g);
const b=[];
if(a!=null){
for(let i=0; i<a.length; i++){
b[i]=a[a.length-1-i];
}
let cnt=0;
for(let i=0; i<a.length; i++){
if(a[i]==b[i]) cnt++;
}
if(cnt==a.length) return true;
return false;
}
else {
return true;
}
}
```
hmmm cách trên có hơi thủ công nên là mình có lên ggsearch solution khác, thì có cách ngắn hơn, đó là
Thay vì dùng vòng lặp `for` để đảo ngược các phần tử thì ta nên sử dụng phương thức `.reverse()`
Thay vì so sánh từng phần tử của 2 mảng thì ta nên so sánh 2 chuỗi qua toán tử `===` , vậy nên ta sử dụng `.join()` để ghép các phẩn tử trong mảng lại thành 1 chuỗi
```javascript
function palindrome(str) {
const alphanumericOnly = str
.toLowerCase()
.match(/[a-z0-9]/g);
return alphanumericOnly.join('') ===
alphanumericOnly.reverse().join('')
}
```
### 2. Roman Numeral Converter
#### Đề
Chuyển đổi 1 số sang số la mã
>Số la mã được xây dựng dựa trên các ký tự nhất định. Các số la mã được tạo nên từ 7 chữ số cơ bản (I, V, X, L, C, D, M) và 6 nhóm chữ số đặc biệt (IV, IX, XL, XC, CD, CM), cùng với 1 số quy tắc kết hợp thì ta có thể ra 1 số la mã
#### Solution
Ở đây, đầu tiên mình sẽ tạo ra 1 đối tượng `obj` chứa chữ số làm thuộc tính và lưu trữ số tương đương La Mã của chúng làm giá trị của các thuộc tính đó
hmm với bài này mình đã nghĩ là sẽ gọi hàm đệ quy để gọi lại chính hàm đó.
VD : `23 = 10 + 10 + 3`
1. **`23`** ứng với trường hợp `10<num && num<40`, mình sẽ `return` giá trị trả về là
`X` + `convertToRoman(23-10)`
2. hàm `convertToRoman(23-10)` được gọi lại với đối số mới là **`13`**, vẫn ứng với trường hợp `10<num && num<40`, tương tự như trên sẽ return lại về giá trị `X` + `convertToRoman(13-10)`
3. hàm `convertToRoman(13-10)` được gọi với đối số **`3`**, ứng với trường hợp `num<4`, sẽ return giá trị `III`
4. sau khi hết thúc hàm sẽ trả về giá trị **`XXIII`**
**FINAL CODE**
```javascript!
const obj = {
1: "I",
4: "IV",
5: "V",
9: "IX",
10: "X",
40: "XL",
50: "L",
90: "XC",
100: "C",
400: "CD",
500: "D",
900: "CM",
1000: "M"
};
function convertToRoman(num) {
if( num==1 || num==4 || num==5 || num==9 || num==10 || num==40 || num==50 || num==90 || num==100 || num==400 || num==500 || num==900 || num==1000 ) {
return obj[num];
}
else if(num<4) {
const a=[];
for(let i=0; i<num; i++){
a[i]=obj[1];
}
return a.join('');
}
else if(5<num && num<9) {
return obj[5]+convertToRoman(num-5);
}
else if(10<num && num<40) {
return obj[10]+convertToRoman(num-10);
}
else if(40<num && num<50){
return obj[40]+convertToRoman(num-40);
}
else if(50<num && num<90){
return obj[50]+convertToRoman(num-50);
}
else if(90<num && num<100){
return obj[90]+convertToRoman(num-90);
}
else if(100<num && num<400){
return obj[100]+convertToRoman(num-100);
}
else if(400<num && num<500){
return obj[400]+convertToRoman(num-400);
}
else if(500<num && num<900){
return obj[500]+convertToRoman(num-500);
}
else if(900<num && num<1000){
return obj[900]+convertToRoman(num-900);
}
else if(1000<num && num<4000){
return obj[1000]+convertToRoman(num-1000);
}
}
console.log(convertToRoman(46));
```
hmm cách trên vẫn rất thủ công nên mình lại đi tìm solution, và thấy 1 cách hay và ngắn hơn
- Đầu tiên cũng tạo ra 1 đối tượng chức các thuộc tính và giá trị là các số la mã tương đương
- Sau đó tạo ra 1 chuỗi rỗng để lưu trữ các chữ số la mã sau khi chuyển đổi
```javascript
let romanizedNum='';
```
- Tạo 1 mảng chứa các thuộc tính (1,4,5...) và đảo ngược lại bắt đầu từ thuộc tính lớn nhất(1000)
```javascript
const arabicNum = Object.keys(numerals).reverse();
```
>Phương thức `Object.keys()` trả về tên thuộc tính của một đối tượng nhất định
>Phương thức .reverse() ghi lại kết quả theo thứ tự đảo ngược, từ đơn vị cuối cùng (đơn vị lớn nhất) đến đơn vị đầu tiên (đơn vị nhỏ nhất).
- Sử dụng `forEach()` để lặp qua lần lượt các thuộc tính(key) của đối tượng, bắt đầu từ thuộc tính lớn nhất (1000). Sử dụng vòng lặp `while` để kiểm tra xem `key<=num` hay không. Nếu có thì thêm chữ số la mã đó vào `romanizedNum`, sau đó `num -= key` và tiếp tục vòng lặp while cho đến thuộc tính cuối cùng (1)
```javascript
arabicNum.forEach(key => {
while(key <= num) {
romanizedNum += numerals[key];
num -= key;
}
```
Final code
```javascript
function convertToRoman(num) {
const numerals = {
1: 'I',
4: 'IV',
5: 'V',
9: 'IX',
10: 'X',
40: 'XL',
50: 'L',
90: 'XC',
100: 'C',
400: 'CD',
500: 'D',
900: 'CM',
1000: 'M'
};
let romanizedNum = '';
const arabicNum = Object.keys(numerals).reverse();
arabicNum.forEach(key => {
while(key <= num) {
romanizedNum += numerals[key];
num -= key;
}
});
return romanizedNum;
}
```
### 3.Caesars Cipher
#### Đề

#### Solution
Với bài này, ý tưởng của mình là sẽ tạo 2 mảng, 1 mảng bảng chữ cái theo đúng thứ tự, mảng còn lại sẽ là các chữ cái tương ứng với mảng trên nhưng đã cộng(trừ) 13 đơn vị
```javascript
const arr1="ABCDEFGHIJKLMNOPQRSTUVWXYZ".split('');
const arr2="NOPQRSTUVWXYZABCDEFGHIJKLM".split('');
```
Dựa vào 2 mảng trên, để chuyển đổi chữ cái sang mã Caesar thì mình sẽ xét chỉ mục của từng chữ cái của chuỗi ở mảng `arr1`, sau đó sẽ dùng chính chỉ mục đó để truy cập vào mảng `arr2` để lấy chữ cái tương ứng
Mình sẽ dùng **`.split()`** để tách chuỗi thành mảng, sau đó dùng **`.map()`** để duyệt qua từng phần tử của mảng, chuyển đổi từng phần tử riêng lẻ sau đó sẽ trả về mảng mới, rồi lại dùng **`.join()`** để ghép các phần tử của mảng lại thành 1 chuỗi
**FINAL CODE**
```javascript
function rot13(str) {
const arr1="ABCDEFGHIJKLMNOPQRSTUVWXYZ".split('');
const arr2="NOPQRSTUVWXYZABCDEFGHIJKLM".split('');
const newStr= str.split('')
.map(x => {
if(arr1.indexOf(x)!=-1) return arr2[arr1.indexOf(x)];
return x;
})
.join('');
return newStr;
}
console.log(rot13("SERR PBQR PNZC")) // FREE CODE CAMP
```
hmm cách trên mình thấy cũng khá ngắn gọn, dễ hiểu rùi (chắc là do bài cũng dễ chút), nhưng mình vẫn đi tìm solution khác xem sao.
có 1 solution dựa vào bảng mã ASCII, từ A-Z có giá trị là từ 65-90 trong bảng mã. Như vầy:
- ta sẽ tìm giá trị của chữ cái bằng **`.charCodeAt()`**
- rồi kiểm tra giá trị (charCode) đó :
- nếu `charCode >= 65 && charCode <= 77` thì giá trị sẽ được công thêm 13
- nếu `charCode > 77 && charCode <= 90` thì giá trị sẽ trừ đi 13
Final code
```javascript
function rot13(str) {
let charCode = 0;
let strArr = [];
strArr = str.split(""); // split the string into array
for (let i = 0; i < strArr.length; i++) {
charCode = strArr[i].charCodeAt();
if (charCode >= 65 && charCode <= 90) {
if (charCode > 77) {
charCode = charCode - 13;
}
else {
charCode = charCode + 13;
}
strArr.splice(i,1,String.fromCharCode(charCode));
}
}
return strArr.join('');
}
rot13("SERR PBQR PNZC");
```
### 4. Telephone Number Validator
#### Đề

#### Solution
hmm bài nì mình đã nghĩ là sẽ chia nhiều trường hợp để xét, nhưng mà không đi về đâu cả.... so mình đã lên ggsearch
tìm ra được giải pháp đó là sử dụng biểu thức chính quy (regex)
mình tìm được payload như này
```javascript
regex = /^1?\s?(\d{3}|\(\d{3}\))-?\s?\d{3}-?\s?\d{4}$/g
```
Ở đây:
- **/^** biểu thị sự bắt đầu của biểu thức chính quy
- **1?** cho thấy số bắt đầu có thể bằng 1. *Dấu hỏi chấm (?) biểu hiện 1 biến tùy chọn trong biểu thức chính quy.*
- **\s?** biểu thị rằng có thể có khoảng trắng sau số 1 ở trên
- **( \d{3} \| \\(\d{3}\\\) )** biểu thị 1 nhóm biểu thức chính quy với:
- **\d{3}** được sử sụng để khớp 3 chữ số liên tiếp
- **|** có nghĩa là hoặc
- **\(\d{3}\)** biểu thị 3 chữ số liên tiếp được đặt trong ngoặc tròn
- Dấu ngoặc xung quanh biểu thị sự bắt đầu và kết thúc của 1 nhóm
- **-?** biểu thị dấu - là tùy chọn , có thể có hoặc khong
- **\s?** tiếp đó là tùy chọn khoảng trắng
- **\d{3}** tùy chọn gạch nối và khoảng trắng trên sẽ khớp sau với 3 chữ số liên tiếp
- **-?\s?** biểu thị rằng tập số thứ 2 cũng có thể theo sau bởi dấu - hoặc khoảng trắng
- **\d4** biểu thị 4 chữ số liên tiếp
- **$/** biểu thị sự kết thúc của biểu thức chính quy
- `g` là flag _ toàn cầu : để regex khớp với tất cả các lần xuất hiện có thể xảy ra
**FINAL CODE**
```javascript
function telephoneCheck(str) {
let regex = /^1?\s?(\d{3}|\(\d{3}\))-?\s?\d{3}-?\s?\d{4}$/g
return regex.test(str);
}
telephoneCheck("555-555-5555");
```
### 5. Cash Register
#### Đề

#### Solution
Ý tưởng của mình là :
- mình sẽ tính số tiền cần trả lại `change = cash-price` , rồi sau đó sẽ lấy `change` trừ lần lượt số tiền hiện có từ mệnh giá lớn nhất
- sau đó xét trường hợp
- nếu `change != 0 ` tức là số tiền hiện có không trả đủ
- nếu `change == 0` thì sẽ có 2 trường hợp : một là trả đủ nhưng tiền trong ngăn sẽ hết, hai là trả đủ và tiền trong ngăn vẫn còn
**Note:**
Trong quá trình làm, khi mà mình tính số tiền trả lại `change` thì nó lại trả lại kết quả không chính xác
```javascript
let change= cash-price;
for(let i=0; i< (x/arrMoney[idx]); i++) {
if(change>=arrMoney[idx]) {
change -= arrMoney[idx];
}
}
console.log(change); // trả về 0.009999999999994869 chứ không phải là 0
```
rất là buồn sầu thì mình lại đi ggsearch, tìm được lý do là
>Sự khác biệt này được gọi là *representation error or roundoff error* . Nó xảy ra do JavaScript và nhiều ngôn ngữ khác sử dụng dạng nhị phân (cơ sở 2) để thể hiện các số thập phân (cơ sở 10). Và, hầu hết các phân số thập phân không thể được biểu diễn chính xác ở dạng nhị phân, do đó xảy ra sự khác biệt nhỏ. JavaScript làm tròn số dấu phẩy động thành 17 chữ số, đủ chính xác hoặc chính xác trong hầu hết các trường hợp. Trong các số nguyên JavaScript có độ chính xác lên tới 15 chữ số.
Để khắc phục thì mình đã nhân số tiền với 100
lúc này, sau khi nhân tiền với 100 , thì vấn đề nữa xảy ra

để khắc phục mình sử dụng hàm **Math.round()** để làm tròn kết quả thành 205
**FINAL CODE**
```javascript
const arr = [["PENNY",0.01], ["NICKEL",0.05], ["DIME",0.1], ["QUARTER",0.25], ["ONE",1], ["FIVE",5], ["TEN",10], ["TWENTY",20], ["ONE HUNDRED",100] ];
let arrMoney=arr.map(x => (x[1])*100).reverse();
function checkCashRegister(price, cash, cid) {
let change = (cash-price)*100;
let objReturn = {
status: "",
change: []
};
let newCid=cid.map(x => (Math.round(x[1]*100))).reverse();
let arrReturn= [...arr].reverse();
newCid.forEach((x,idx) => {
let changeReturn =0;
for(let i=0; i< (x/arrMoney[idx]); i++) {
if(change>=arrMoney[idx]) {
change -= arrMoney[idx];
changeReturn +=arrMoney[idx] ;
}
arrReturn[idx][1] = changeReturn/100;
}
})
let sumCid=0;
for(let i=0; i<cid.length; i++){
sumCid += cid[i][1];
}
let result=[];
for(let i=0; i<arrReturn.length; i++){
if(arrReturn[i][1] !== 0) result.push(arrReturn[i]);
}
if(change != 0){
objReturn.status = "INSUFFICIENT_FUNDS";
}
else{
if(sumCid == (cash-price) ) {
objReturn.status = "CLOSED";
objReturn.change = cid;
} else {
objReturn.status = "OPEN";
objReturn.change = result;
}
}
return objReturn;
}
console.log(checkCashRegister(3.26, 100, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]])
);
```