2023년 7월 11일 화요일

매우 유용한 자바 스크립트 트릭

1. JS 파일을 동적으로 로드

일부 특수 시나리오, 특히 일부 라이브러리 및 프레임워크 개발에서 JS 파일을 동적으로 로드하고 실행하는 경우가 있습니다. 다음은 Promise를 사용한 간단한 캡슐화입니다.

function loadJS(files, done) {
// Get the head tag
const head = document. getElementsByTagName('head')[0];
Promise.all(files.map(file => {
return new Promise(resolve => {
// create script tag and add to head
const s = document.createElement('script');
s.type = "text/javascript";
s.async = true;
s.src = file;
// Listen to the load event, resolve if the loading is complete
s. addEventListener('load', (e) => resolve(), false);
head.appendChild(s);
});
})).then(done); // everything is done, execute the user's callback event
}
loadJS(["test1.js", "test2.js"], () => {
// user's callback logic
});There are two core points in the code above. One is to use Promise to process asynchronous logic, but to use script tags to load and execute js.


2. 템플릿 엔진 구현

다음 예제에서는 동적 템플릿 렌더링 엔진을 구현하는 데 코드를 거의 사용하지 않습니다. 일반 동적 변수의 대체를 지원할 뿐만 아니라 for 루프, if 판단 등을 포함한 동적 JS 구문 논리도 지원합니다.

// This is a dynamic template that contains js code
var template =
'My avorite sports:' +
'<%if(this.showSports) {%>' +
'<% for(var index in this.sports) { %>' +
'<a><%this.sports[index]%></a>' +
'<%}%>' +
'<%} else {%>' +
'<p>none</p>' +
'<%}%>';
// This is the function string we're going to concatenate
const code = `with(obj) {
var r=[];
r.push("My avorite sports:");
if(this. showSports) {
for(var index in this. sports) {
r. push("<a>");
r.push(this.sports[index]);
r. push("</a>");
}
} else {
r.push("<span>none</span>");
}
return r.join("");
}`

// dynamically rendered data
const options = {
sports: ["swimming", "basketball", "football"],
showSports: true
}
// Build a feasible function and pass in parameters to change the direction of this when the function is executed
result = new Function("obj", code).apply(options, [options]);
console. log(result);


3. reduce를 사용하여 데이터 구조 변환

때때로 프론트엔드는 프론트엔드의 비즈니스 로직에 적응하기 위해 백엔드의 데이터를 변환하거나 구성 요소의 데이터 형식을 변환한 다음 처리를 위해 백엔드로 전달해야 하며 reduce는 매우 강력한 도구입니다.

const arr = [
{ classId: "1", name: "Jack", age: 16 },
{ classId: "1", name: "Jon", age: 15 },
{ classId: "2", name: "Jenny", age: 16 },
{ classId: "3", name: "Jim", age: 15 },
{ classId: "2", name: "Zoe", age: 16 }
];
groupArrayByKey(arr, "classId");
function groupArrayByKey(arr = [], key) {
return arr.reduce((t, v) => (!t[v[key]] && (t[v[key]] = []), t[v[key]].push(v), t), {})
}


4. 기본값 추가

메서드에서 사용자가 매개 변수를 전달해야 하는 경우가 있습니다. 일반적으로 두 가지 방법으로 대처할 수 있습니다. 사용자가 전달하지 않으면 일반적으로 기본값을 제공하거나 사용자가 매개 변수를 전달해야하며 전달되지 않으면 오류가 발생합니다.

function double() {
return value *2
}
// If not passed, give a default value of 0
function double(value = 0) {
return value * 2
}
// The user must pass a parameter, and an error will be thrown if no parameter is passed
const required = () => {
throw new Error("This function requires one parameter.")
}
function double(value = required()) {
return value * 2
}
double(3) // 6
double() // throw Error

listen 메소드는 NodeJS 네이티브 http 서비스를 생성하고 포트를 수신하고, 서비스의 콜백 함수에 컨텍스트를 생성한 후 사용자가 등록한 콜백 함수를 호출하고 생성된 컨텍스트를 전달하는 데 사용됩니다. 이전에 createContext와 handleRequest의 구현을 살펴보자.


5. 함수는 한 번만 실행됩니다.

경우에 따라 특정 함수를 한 번만 실행할 수 있거나 바인딩된 메서드를 한 번만 실행할 수 있는 특별한 시나리오가 있습니다.

export function once (fn) {
// Use the closure to determine whether the function has been executed
let called = false
return function () {
if (! called) {
called = true
fn. apply(this, arguments)
}
}
}


6. Realize Curring

JavaScript에서 커리는 여러 인수를 취하는 함수를 하나의 인수 만 취하는 일련의 함수로 변환하는 프로세스입니다. 이를 통해 함수를 보다 유연하게 사용할 수 있고, 코드 중복을 줄이며, 코드 가독성을 높일 수 있습니다.

function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
function add(x, y) {
return x + y;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)); // output 3
console.log(curriedAdd(1, 2)); // output 3

currying을 통해 유효성 검사, 캐싱 등과 같은 몇 가지 일반적인 기능을 모듈화할 수 있습니다. 이렇게 하면 코드의 유지 관리 용이성과 가독성이 향상되고 오류 가능성이 줄어듭니다.


7. 싱글톤 모드 구현

JavaScript의 싱글톤 모드는 일반적으로 사용되는 디자인 모드입니다. 클래스에 인스턴스가 하나만 있는지 확인하고 인스턴스에 대한 전역 액세스 지점을 제공할 수 있습니다. JS에는 쇼핑 카트, 캐시 개체, 전역 상태 관리 등과 같은 광범위한 응용 시나리오가 있습니다.

let cache;
class A {
// ...
}
function getInstance() {
if (cache) return cache;
return cache = new A();
}
const x = getInstance();
const y = getInstance();
console.log(x === y); // true


8. CommonJs 사양 구현

CommonJS 사양의 핵심 아이디어는 각 파일을 모듈로 취급하고, 각 모듈에는 자체 범위가 있으며, 그 안의 변수, 함수 및 객체는 비공개이며 외부에서 액세스할 수 없습니다. 모듈의 데이터에 액세스하려면 export and require를 수행해야 합니다.

// id: full file name
const path = require('path');
const fs = require('fs');
function Module(id){
// Used to uniquely identify the module
this.id = id;
// Properties and methods used to export modules
this.exports = {};
}
function myRequire(filePath) {
// Directly call the static method of Module to load the file
return Module._load(filePath);
}
Module._cache = {};
Module._load = function(filePath) {
// First address the absolute path of the file through the filePath passed in by the user
// Because in CommnJS, the unique identifier of the module is the absolute path of the file
const realPath = Module._resoleveFilename(filePath);
// Cache priority, if it exists in the cache, it will directly return the exports property of the module
let cacheModule = Module._cache[realPath];
if(cacheModule) return cacheModule. exports;
// If it is loaded for the first time, a new module is required, and the parameter is the absolute path of the file
let module = new Module(realPath);
// Call the load method of the module to compile the module
module.load(realPath);
return module. exports;
}
// The node file is not discussed yet
Module._extensions = {
// Process the js file
".js": handleJS,
// process the json file
".json": handleJSON
}
function handleJSON(module) {
// If it is a json file, read it directly with fs.readFileSync,
// Then use JSON.parse to convert and return directly
const json = fs.readFileSync(module.id, 'utf-8')
module.exports = JSON.parse(json)
}
function handleJS(module) {
const js = fs. readFileSync(module. id, 'utf-8')
let fn = new Function('exports', 'myRequire', 'module', '__filename', '__dirname', js)
let exports = module. exports;
// The assembled function can be executed directly
fn.call(exports, exports, myRequire, module, module.id, path.dirname(module.id))
}
Module._resolveFilename = function (filePath) {
// Splice the absolute path, and then search it, if it exists, it will return
let absPath = path. resolve(__dirname, filePath);
let exists = fs.existsSync(absPath);
if (exists) return absPath;
// If it does not exist, try splicing .js, .json, .node in sequence
let keys = Object.keys(Module._extensions);
for (let i = 0; i < keys. length; i++) {
let currentPath = absPath + keys[i];
if (fs.existsSync(currentPath)) return currentPath;
}
};
Module.prototype.load = function(realPath) {
// Get the file extension and hand it over to the corresponding method for processing
let extname = path.extname(realPath)
Module._extensions[extname](this)
}

위의 내용은 CommonJs 사양의 간단한 구현입니다. 코어는 범위의 격리를 해결하고 Myrequire 메서드를 제공하여 메서드와 속성을 로드합니다.


9. 재귀적으로 개체 속성 가져오기

가장 널리 사용되는 디자인 패턴을 선택한다면 관찰자 패턴을 선택합니다. 내가 만난 가장 알고리즘적인 사고를 선택한다면 그것은 재귀 일 것입니다. 재귀는 원래 문제를 동일한 구조를 가진 구조로 나눕니다. 하위 문제를 해결 한 다음 이러한 하위 문제를 차례로 해결하고 하위 문제의 결과를 결합하여 최종적으로 원래 문제에 대한 답을 얻습니다.

const user = {
info: {
name: "Jacky",
address: { home: "MLB", company: "AI" },
},
};
// obj is the object to get the property, path is the path, and fallback is the default value
function get(obj, path, fallback) {
const parts = path. split(".");
const key = parts. shift();
if (typeof obj[key] !== "undefined") {
return parts. length > 0 ?
get(obj[key], parts. join("."), fallback) :
obj[key];
}
// return fallback if key not found
return fallback;
}
console.log(get(user, "info.name")); // Jacky
console.log(get(user, "info.address.home")); // MLB
console.log(get(user, "info.address.company")); // AI
console.log(get(user, "info.address.abc", "fallback")); // fallback


10. 임의의 문자열 생성

const randomString = () => Math.random().toString(36).slice(2)

randomString() // gi1qtdego0b
randomString() // f3qixv40mot
randomString() // eeelv1pm3ja


11. HTML 특수 문자 이스케이프

const escape = (str) => str.replace(/[&<>"']/g, (m) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[m]))

escape('<div class="medium">Hi Medium.</div>') 
// &lt;div class=&quot;medium&quot;&gt;Hi Medium.&lt;/div&gt


12. 문자열을 camelCase로 변환

const toCamelCase = (str) => str.trim().replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''));

toCamelCase('background-color'); // backgroundColor
toCamelCase('-webkit-scrollbar-thumb'); // WebkitScrollbarThumb
toCamelCase('_hello_world'); // HelloWorld
toCamelCase('hello_world'); // helloWorld


13. 두 숫자 사이의 임의의 정수 구하기

const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min)

random(1, 50) // 25
random(1, 50) // 34


14. 인수의 평균 값 구하기

const average = (...args) => args.reduce((a, b) => a + b) / args.length;

average(1, 2, 3, 4, 5);   // 3


15. 임의의 헥사 색상 생성

const randomColor = () => `#${Math.random().toString(16).slice(2, 8).padEnd(6, '0')}`

randomColor() // #9dae4f
randomColor() // #6ef10e


16. 다크 모드 감지

const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches


17. Get the mouse selection

const getSelectedText = () => window.getSelection().toString()
getSelectedText()


18. Convert rgba to hexadecimal OR hexadecimal to rgba

const rgbaToHex = (r, g, b) => "#" + [r, g, b].map(num => parseInt(num).toString(16).padStart(2, '0')).join('')
rgbaToHex(0, 0 ,0) // #000000
rgbaToHex(255, 0, 127) //#ff007f
const hexToRgba = hex => {
const [r, g, b] = hex.match(/\w\w/g).map(val => parseInt(val, 16))
return `rgba(${r}, ${g}, ${b}, 1)`;
}
hexToRgba('#000000') // rgba(0, 0, 0, 1)
hexToRgba('#ff007f') // rgba(255, 0, 127, 1)


19. Generate a random string of specified length

const generateRandomString = length => [...Array(length)].map(() => Math.random().toString(36)[2]).join('')
generateRandomString(12) // cysw0gfljoyx
generateRandomString(12) // uoqaugnm8r4s


20. Clear all cookies

const clearCookies = document.cookie.split(';').forEach(cookie => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`))


21. Determine if the current tab is active or not

const checkTabInView = () => !document.hidden


22. Check if an element is focused

const isFocus = (ele) => ele === document.activeElement