🕓

Programming 연습 문제

타입
연습 문제
공부 노트
Property
Property 1

재귀 → 꼬리물기 재귀 → for문 예제

// sum 함수를 재귀 -> 꼬리물기 재귀 -> for로 변경 const recusrive = (v) => v>1 ? v + recusrive(v-1) : 1; console.log("재귀 함수", recusrive(10)) const _sum = (v, acc) => v>1 ? _sum(v-1, acc+ v) : acc + 1; const sum = v => _sum(v,0); console.log("꼬리물기 재귀 함수", sum(10)) const v = 10; let acc = 0; for(let i = v; i>1; i--) acc+= i; acc+= 1; console.log("acc", acc)
JavaScript
복사

arr 안의 배열의 합 계산

클로저를 이용한 은닉 및 내결함성의 제거 - 내결함성으로 살펴보는 안정성과 신뢰성의 상관관계 - 안전성이 높다 => Context에러가 발생할 확률이 높다. - 신뢰성이 높다 => 자주 떨어진다.
const recursive = (arr) => { if(!Array.isArray(arr)) throw 'invalid parameter'; if(arr.some((el)=>typeof el !== 'number')) throw 'invalid elements' const _recursive = (arr, i, acc) => i < arr.length ? _recursive(arr, i+1, acc + arr[i]) : acc return _recursive(arr, 0 ,0) } recursive([1,2,3,4])
JavaScript
복사
validator의 분리 - 같은 기능 => 코드의 수정이유가 같을까? => 응집성과 결합도 => listValidator - 유지 보수를 위해서, 수정하는 이유가 다르다면 분리 시켜주어야 함.
const validator = { //인터페이스의 일치 // 함수의 호출형식과 리턴형식을 일치시키면 가능 datas: [ (arr)=> !Array.isArray(arr), (arr) => arr.some((el)=>typeof el !== 'number') ], // 여기서 데이터와 기능을 합치므로써, every를 써야 하는 것을 강제할 수 있다. validate: function(arr) { return this.datas.some(data => data(arr)) } } const recursive = (arr) => { if(validator.validate(arr)) throw "invalid error" const _recursive = (arr, i, acc) => i < arr.length ? _recursive(arr, i+1, acc + arr[i]) : acc return _recursive(arr, 0 ,0) } recursive([1,2,3,4])
JavaScript
복사

연산과 메모리는 교환가능하다.

// 라이프사이클 : 영구적 , arraySum을 매번 만들지 않음 const arraySum = (()=> { const elementSum = (arr, i, acc) => { if(arr.length === i) return acc; return elementSum(arr, i + 1, acc + arr[i]); }; const _arraySum = (arr) => elementSum(arr, 0, 0); return _arraySum })() const arraySum = (arr=> { const elementSum = (arr, i, acc) => { if(arr.length === i) return acc; return elementSum(arr, i+ 1, acc + arr[i]); }; return elementSum(arr, 0, 0); }) // :arraySum이 호출될때 생성되서 종료될때 종료. arraySum을 매번 만듬 // for문은 컨텍스트(스코프)를 공유하는 함수라는 것을 보여주는 예제 // 하지만 재귀함수는 스코프를 공유하지 않은 사이드이펙트가 없는 함수.
JavaScript
복사
기계적인 for문 변경
const err = msg => { throw msg } // 아래에서 i >-1 로 한다면 acc를 더하는 것을 중복 제거 가능함. 2 / 1:18:15 const _tailRecursive = (array, i, acc) => i > 0 ? _tailRecursive(array, i - 1, array[i] + acc) : (array[0] ?? err("invalid element index 0")) + acc; const tailRecursive = (array) => _tailRecursiveSum(array, array.length - 1, 0) // ⬇ 아래와 같이 변경 const iterateSum = (array) => { let acc = 0; let i = array.length - 1; const f = () => acc = array[i] + acc; for (; i > 0; i = i - 1) f(); acc += (array[0] ?? err("invalid element index")) return acc } iterateSum([1,2,3])
JavaScript
복사
정리
1. 오류와 실패의 관계 - 오류는 중간요소의 내 결함성때문에 실패로 이어지지 않을 수 있따: 오류가 최대한 빨리 실패로 이어지게 짜라. 컨텍스트 에러가 더 무서우니까 -> 신뢰성, 안정성(컨텍스트에러발생이 올라감) 2. 코드의 분리 또는 정리 :: 변화율 ( 변화율이 같은 애들끼리 코드를 모아라) 변화율의 원인 -> 수정되는 이유 3. 자바스크립트 인터페이스란 함수의 이름 인자 반환값의 형식이 일치하는 경우 4. 인터페이스를 일치시키면 컬렉션으로 묶을 수 있따 -> 일종의 일반화 -> 서로 다른 형테인 경우 인터페이스를 일치시켜 일반화를 한다. 5. 데이터와 데이터를 이용한 알고리즘이 이원화되면 관리가 불가능 -> 데이터를 소유한 쪽에서 데이터를 사용하는 알고리즘을 제공한다. 6. 꼬리물기 최적화함수를 루프로 고칠때 기계적으로 고친다는 의미 7. 결국 루프는 클로저에만 의존하는 함수를 반복시키고, 재귀함수는 인자에만 의존하는 함수를 반복시킨다. 8. 반복되는 코드를 제거하기 위해 집착하라. 9. 변수는 스코프와 라이프사이클을 같는다. 메모리와 연산은 상호 교환할 수 있으며 특히 라이프사이클이 관여함.
JavaScript
복사

중첩된 배열 JSON.stringify

문자열 스토리지로 사용한 방법
// 문자열 스토리지로 사용한 방법 const question = [1,2,[3], [4,[5,[6]],7]] const stringify = (arr, acc, stack, i) => { if(arr.length > i) { const element = arr[i] if(Array.isArray(element)) { stack.push([arr, i + 1]) return stringify(element, acc + '[', stack, 0) } else { return stringify(arr, acc + element + ',', stack, i + 1) } } else { if(stack.length) { const [prevArr, prevI] = stack.pop(); return stringify(prevArr, acc.slice(0, acc.length-1) + '],' , stack, prevI) } else { return acc.slice(0, acc.length-1) + ']' } } // 첫번째 acc 는 [로 시작한다. // [1,2, [3]] // 배열이 전부 돌지 않았을 때 // 엘리먼트가 배열이 아니라면 "엘리먼트 + ," 문자열을 하고 다음 인덱스를 탐구한다. ex) [ 1, | [ 1, 2, // 엘리먼트가 배열이라면 "기존 배열과, 인덱스를 스택에 저장하고 꺽쇠를 연 후에 다음 인덱스를 탐구한다." [ 1, 2, [ 3, // 배열이 전부 돌았을때 // 스택이 남아 있다면, 마지막 콤마를 제거후에 꺽쇠를 닫고 콤마를 추가한다. 그 후에 스택을 하나 꺼내 그 배열로 그 배열의 다음 인덱스를 탐구한다. [ 1, 2, [3] // 스택이 남아있지 않을때 마지막 문자열을 제거하고 acc + 닫는 꺽쇠로 마무리한다. } assert.ok(stringify(question, '[', [], 0) == JSON.stringify(question)) JSON.stringify(question) stringify(question, '[', [], 0)
JavaScript
복사
배열을 스토리지로 사용한 방법
// 배열을 스토리지로 사용한 방법 const question = [1,2,[3], [4,[5,[6]],7],8] function arrayToString(acc) { let accStr = ""; for (const v of acc) accStr += "," + v; accStr = "[" + accStr.substr(1) + "]"; return accStr; } const recursive = (arr, acc, i, stack) => { if (i < arr.length) { const element = arr[i]; if (Array.isArray(element)) { stack.push([arr, acc, i + 1]) return recursive(arr[i], [], 0, stack) } else { acc.push(arr[i]); return recursive(arr, acc, i + 1, stack); } } else { const accStr = arrayToString(acc); const prev = stack.pop(); if (prev) { const [prevArr, prevAcc, prevIndex] = prev; prevAcc.push(accStr); return recursive(prevArr, prevAcc, prevIndex, stack); } else { return accStr; } } } // 요소가 있을 시에 // 요소가 배열이라면 // 스택에 현재 배열, 현재 acc, 다음 인덱스를 적재한다. // 현재 요소를 배열, acc를 초기화, 인덱스를 초기화하여 다음 recursive를 한다. // 요소가 배열이 아니라면 // acc에 요소를 적재한다. // 이후에 현재 배열에 대해 다음 인덱스를 recursive한다. // 요소가 더이상 없을 시에 // 요소들을 ["," + 요소] 의 문자열로 바꾼다. 이 때 첫번째 ,는 제거한다. // 이전 스택이 있다면 // 이전 acc에 현재 acc를 문자열로 치환한 값을 적재한다. => acc는 문자열을 요소로 담는데, 이 때 이전 값을 추가해 준거임 // 이전 스택에 대한 recursive를 진행한다. // 이전 스택이 없다면 // 현재 accStr를 반환한다. recursive(question, [], 0, [])
JavaScript
복사
// 다른 뷰의 적용 function arrayToHTML(acc) { let accStr = ""; for (const v of acc) { if(typeof v !== 'number') { accStr += v.toString() continue } accStr += "<li>" + v.toString() + "</li>"; } accStr = "<ul>" + accStr.substr() + "</ul>"; return accStr; }
JavaScript
복사
라우팅 테이블과 링크드리스트를 이용한 방법 - 1
const question = [1,2,[3], [4,[5,[6]],7],8] const arrayToString = (acc) => { let accStr = "" for (v of acc) accStr += "," + v accStr = `[${accStr.substr(1)}]` return accStr } // OCP // IOC // Divide And Conquer const routingTable = { object: (element, arr, acc, prev, i) => { return [element, [], [arr, i + 1, acc, prev], 0] }, number: (element, arr, acc, prev, i) => { acc.push(element) return [arr, acc, prev, i + 1] }, route : function(element, arr, acc, prev, i) { return this[typeof element](element, arr, acc, prev, i) } } const stringify = (arr, acc, prev, i) => { if(arr.length > i) { const result = routingTable.route(arr[i] , arr, acc, prev, i) return stringify(...result) } else { let accStr = arrayToString(acc) if(prev) { const [prevArr, prevI, prevAcc, prevPrev] = prev; prevAcc.push(accStr) return stringify(prevArr, prevAcc , prevPrev, prevI) } else { return accStr } } } assert.ok(stringify(question, [], null, 0) == JSON.stringify(question)) JSON.stringify(question) stringify(question, [], null, 0)
JavaScript
복사
라우팅 테이블과 링크드리스트를 이용한 방법 - 2
const question = [1,2,[3], [4,[5,[6]],7],8] const arrayToString = finalNode => { let accStr ='' const arr =[]; let curr = finalNode; do { arr.unshift(curr.value); } while(curr = curr.prev); for(const v of arr) accStr += ',' + v; return "[" + accStr.substr(1) + "]"; } // OCP // IOC // Divide And Conquer const routingTable = { object: (element, arr, acc, prev, i) => { arr return [element, null, [arr, acc, prev, i + 1], 0] }, number: (element, arr, acc, prev, i) => { acc return [arr, {prev: acc, value: element }, prev, i + 1] }, route : function(element, arr, acc, prev, i) { return this[typeof element](element, arr, acc, prev, i) } } const stringify = (arr, acc, prev, i) => { if(i < arr.length) { const [resultArr, resultAcc, resultPrev, resultI] = routingTable.route(arr[i] , arr, acc, prev, i) return stringify(resultArr, resultAcc, resultPrev, resultI) } else { const accStr = arrayToString(acc) if(prev) { const [prevArr, prevAcc, prevPrev, prevI] = prev; return stringify(prevArr, {prev: prevAcc, value: accStr} , prevPrev, prevI) } else { return accStr } } } assert.ok(stringify(question, null, null, 0) == JSON.stringify(question)) JSON.stringify(question) stringify(question, null, null, 0)
JavaScript
복사
반복문으로의 기계적인 번역
const loopStringify = () => { let arr = question, acc = [], prev = null, i = 0 while(true) { if(arr.length > i) { const [resultArr, resultAcc, resultPrev, resultI] = routingTable.route(arr[i] , arr, acc, prev, i) arr = resultArr; acc = resultAcc; prev = resultPrev; i = resultI; } else { let accStr = arrayToString(acc) if(prev) { const [prevArr, prevI, prevAcc, prevPrev] = prev; arr = prevArr i = prevI acc = prevAcc prev = prevPrev prevAcc.push(accStr) return stringify(prevArr, prevAcc , prevPrev, prevI) } else { return accStr } } } } assert.ok(loopStringify() == JSON.stringify(question)) JSON.stringify(question) loopStringify()
JavaScript
복사

Object JSON Stringify

Coroutine Object Entries
const objectEntries = function *(obj) { for(const k in obj) { if(obj.hasOwnProperty(k)) yield [k, obj[k]]; } } var box = objectEntries({a:3, b:5}); box.next(); // {value: [a, 3], done: false} box.next(); // {value: [b, 5], done: false} box.next(); // {value: undefined, done: true} box.next(); // {value: undefined, done: true}
JavaScript
복사
iterabl Object
// iterable const iterable = { [Symbol.iterator]() { const arr = [1,2,3,4]; let cursor = 0; //iterator return { next() { // done 은 항상 false -> true가 진행되어야 함 return { done: cursor >= arr.length , value: cursor < arr.length ? arr[cursor++] : undefined } } } } } const test = iterable[Symbol.iterator]() console.log(test.next()) console.log(test.next()) console.log(test.next()) console.log(test.next()) console.log(test.next()) console.log([...iterable]) const [a,b, ...rest] = iterable
JavaScript
복사
Iterable 객체와 지연호출
// 아래 함수는 7(filter) + 4(map) = 11번의 연산을 함 const arr = [1,2,3,4,5,6,7]; const result = arr.filter((v)=>v%2).map((v)=>v*2); // 아래 함수는 7(done false) + 1(done true) = 8번의 연산을 함 const filter = (iter, block) => ({ next() { let {done, value} = iter.next(); while(!done) { if(block(value)) return { done: false, value}; ({done, value} = iter.next()); } return {done}; } }); const map = (iter, block) => ({ next(){ const {done, value} = iter.next(); if(!done) return {done, value:block(value)}; else return {done}; } }); const iter = iter => ({ [Symbol.iterator](){return iter } }) const result = [...iter(map(filter([1,2,3,4,5][Symbol.iterator](), v=> v%2), v=>v*2))]
JavaScript
복사
Lazy Evaluation
const numbers = function* () { let i = 1 while (true) { yield i++ } } class Lazy { constructor(iterable, callback) { this.iterable = iterable this.callback = callback } filter(callback) { return new LazyFilter(this, callback) } map(callback) { return new LazyMap(this, callback) } next() { return this.iterable.next() } take(n) { const values = [] for (let i=0; i<n; i++) { values.push(this.next().value) } return values } } class LazyFilter extends Lazy { next() { while (true) { const item = this.iterable.next() if (this.callback(item.value)) { return item } } } } class LazyMap extends Lazy { next() { const item = this.iterable.next() const mappedValue = this.callback(item.value) return { value: mappedValue, done: item.done } } } let result = new Lazy(numbers()).map(num=>num*3).take(4).reduce((a,v) => a + v) console.log('result = ' + result) result = new Lazy(numbers()).filter(n=>n%2==0).take(4).reduce((a,v) => a + v) console.log('result = ' + result) result = new Lazy(numbers()).filter(n=>n%2==0).map(num=>num**2).take(4).reduce((a,v) => a + v) console.log('result = ' + result) result = new Lazy(numbers()).map(num=>num**2).filter(n=>n%2==0).take(4).reduce((a,v) => a + v) console.log('result = ' + result)
JavaScript
복사
Get-Lazy ( lazy란 함수의 본질을 이용하는 것 )
function lazy( target, propertyKey, descriptor, ) { const getter = descriptor.get if (!getter) { return } descriptor.get = function() { const value = getter.apply(this, arguments) Object.defineProperty(this, propertyKey, { value }) return value } } class Person { constructor(firstName, lastName) { this.firstName = firstName; this._lastName = lastName; } count = 0 @lazy get lastName() { return this._lastName + this.count++; } } const p = new Person('John', 'Doe'); p.lastName p.lastName p.lastName p.lastName p.lastName
JavaScript
복사
Object Entiries Stringify By Generator
const objEntries = function*(obj){ for(const k in obj) if(obj.hasOwnProperty(k)) yield [k, obj[k]]; }; // generateor는 Iterable Object를 생성한다. const toString = v => v +""; const join = acc => { if(!acc) return "{}"; else { let target = acc, result = ""; do{ result = `,"${target.k}":${toString(target.v)}` + result; }while(target = target.prev); return "{" + result.substr(1) + "}"; } } const recursive = (iter, acc) => { const {done, value:[k, v] = []} = iter.next(); return done ? join(acc) : recursive(iter, {prev:acc, k, v}); } const stringify = obj => recursive(objEntries(obj), null); stringify({a:5, b:3, c:8})
JavaScript
복사
JSON stringify
const objEntries = function*(obj){ // 코루틴 값이 오브젝트인 경우 이터레이터로 변환하기 for(const k in obj) if(obj.hasOwnProperty(k)) yield [k, obj[k]]; }; const convert = v => "" + v; const accuToString = (isObject, acc) => { const [START, END] = isObject ? "{}": "[]"; let result = ""; if(acc && acc.value) { let curr = acc; do { result = "," + (isObject ? `"${curr.value[0]}":${convert(curr.value[1])}` : convert(curr.value)) + result }while(curr = curr.prev); result = result.substr(1); } return START + result + END; }; // iter - 현재 iterator // isObject - 현재 iterator가 Object인지 어쩌구 인지 // accu - 이전 linkedList const _stringify = (iter, isObject, accu, prev) => { const {done, value} = iter.next(); // object라면 value : [1,2,3], array라면 요소가 들어옴 if(!done) { const v = isObject ? value[1] : value; switch(true) { case Array.isArray(v): return _stringify(v[Symbol.iterator](), false, null, {target:iter, isObject, k:isObject ? value[0]: "", accu, prev}); case v && typeof v == "object": return _stringify(objEntries(v), true, null, {target:iter, isObject, accu, k:isObject ? value[0]: "", prev}); default: return _stringify(iter, isObject, { prev: accu, value }, prev); } } else { let accuStr = accuToString(isObject, accu); if(prev) { return _stringify(prev.target, prev.isObject, {prev: prev.accu, value: prev.isObject? [prev.k, accuStr]: accuStr}, prev.prev); } else { return accuStr; } } } const stringify = v => _stringify(Array.isArray(v) ? v[Symbol.iterator](): objEntries(v),!Array.isArray(v), null, null) JSON.stringify([3])
JavaScript
복사
Array Json Parser
if(1) { const q = "[[[[[[[]]]]]]]" // 1. 열린 괄호가 있다. => 새로운 배열을 시작한다. // 2. 닫힌 괄호가 있다. => 현재 배열을 끝낸다. 이 때 이전 배열이 있다면 이전 배열의 요소가 된다. // 3. 요소 혹은 요소, 가 있다 => 현재 배열에 요소를 추가한다. const parse = (str, curr, prev) => { str = str.trim() if(str.length <= 0) return curr[0] switch(true) { case str[0] === '[': return parse(str.substr(1), [], [curr, prev]) case str[0] === ']': prev[0].push(curr) return parse(str.substr(1), prev[0], prev[1]) default: // * = {0,} // + = {1,} // ? = {0,1} const value = /^\s*([0-9]+)\s*,?/.exec(str) if(!value) { throw 'error' } curr.push(parseInt(value[1])) return parse(str.substr(value[0].length), curr, prev) } } parse(q,[],null) JSON.parse(q) } // 정규식 // 문자에 대한 식 ( 문자열에 대한 식 x) // 문자란 : => 단일 문자 1개 // 그룹 -> 그룹진 문자를 하나의 문자로 볼 수 있다. // 그룹은 괄호()를 이용하여 만든다. (abc) 가 하나 // 결합 -> 결합 연산자는 일반적으로 생략한다 // 예를들어 abcde는 a + b + c + d + e // 결합은 연속적이여야한다. // 선택 -> |를 이용하여 만든다. // 예를들어 ab|bb 라면 // ab 혹은 bb 이다. ( 왜냐하면 결합연산자가 선택연산자보다 우선순위가 높기 때문에 ) // 우선순위는 다음과 같다. // 그룹 > 결합 ? 선택 // a(b|c|d)b 라고 한다면 // a + (b|c|d) + b 로 해석한다. // 이는 선택그룹 연산자 클래스로 치환 가능하다. 이 때 선택그룹 연산자 클래스의 ^는 No를 의미 // a[bcd]b => 이것은 문자 세개의 식 // 정규식은 탐욕 연산자다 => 최대한 많은 값을 일치시켜려고 한다. // 때문에 ? 를 한다는 것은 최소 일치를 하고자 하는것이다. // 문자의 위치도 문자다. // ^ 줄의 시작이라는 문자. // $ 줄의 끝이라는 문자. // \b 단어의 끝이라는 문자. // ex) abc\b 라면 abcafbfabS(x) abc abc (o
JavaScript
복사
JSON.stringify toJSON
class A { toJSON() { return 'hello new world' } } JSON.stringify(new A())
JavaScript
복사
JSON number Parse
const rNum = /^\s*([0-9]+)\s*[,]?/; const rKey = /^\s*"((?:[^"]|\\")+)"\s*:\s*/ const parse = (str, acc, key, stack) => { let v = str.trim(); if(!v.length) return acc switch(v[0]) { case'[': case'{': stack.push([acc, key]) return parse(str.substr(1), v[0] == '[' ? [] : {}, null, stack) case']': case'}': if(!stack.length) throw 'invliad json error' const [prevAcc, prevKey] = stack.pop() || [] if(!prevAcc) return acc else { if(prevAcc instanceof Array) { prevAcc.push(acc) }else { prevAcc[prevKey] = acc } v = v.substr(1).trim(); return parse(v[0] == "," ? v.substr(1): v, prevAcc, null, stack); } default: if(acc instanceof Array) { const value = rNum.exec(v) value if(!value) throw 'invalid error' + v acc.push(parseFloat(value[1])) return parse(str.substr(value[0].length), acc, null, stack) } else { if(key == null) { const key = rKey.exec(v) if(!key) throw 'invalid key' + v return parse(v.substr(key[0].length), acc, key[1], stack) }else { const value = rNum.exec(v) if(!value) throw 'invalid key error' + v acc[key] = parseFloat(value[1]) return parse(v.substr(value[0].length), acc, null, stack) } } } } parse(`{"a":[1,2,[3,4],5,{"a":2}],"b":{"a":123, "b":456}}`, null, null, []);
JavaScript
복사
내 답
const rNum = /\s*([0-9]+),*\s*/ const rKey = /^\s*"((?:[^"]|\\")+)"\s*:\s*/ const parse = (str, acc, key, stack) => { const v = str.trim(); if(!v.length) return acc // 더 이상 문자열이 없다. // 종료한다. switch(v[0]) { case '{': case'[': { stack.push([acc, key]) return parse(v.substr(1), v[0] === '[' ? [] : {}, null, stack) } case '}': case ']': { const [prevAcc, prevKey] = stack.pop(); if(!prevAcc) return acc if(Array.isArray(prevAcc)) { prevAcc.push(acc) } else { prevAcc[prevKey] = acc } const vv = v.substr(1) return parse(vv[0] === ',' ? vv.substr(1) : vv, prevAcc, null, stack) } default: { if(Array.isArray(acc)) { const num = rNum.exec(v) acc.push(parseFloat(num[1])) return parse(str.substr(num[0].length), acc, null, stack) } else { if(!key) { const k = rKey.exec(v) if(!k) throw `${v} error` return parse(str.substr(k[0].length), acc, k[1], stack) } else { const num = rNum.exec(v) acc[key] = parseFloat(num[1]) return parse(str.substr(num[0].length), acc, null, stack) } } } } // 가장 최신 문자열 하나를 가져온다. // { 혹은 [ 이다. // { 이다. // 새로운 객체로 acc를 시작한다. 이전 acc는 stack에 담아둔다. // [ 이다. // 새로운 배열로 acc를 시작한다. 이전 acc는 stack에 담아둔다. // } 혹은 ] 이다. // 배열을 끝내고 현재 acc를 이전 acc에 추가한다. // 이전 acc가 객체이다. // 이전키와 이전값을 acc로 추가한다. // 이전 acc가 배열이다. // 이전 acc에 acc를 추가한다. // 그 외이다. // 현재 acc가 Object이다. // 키다. // 키를 넘겨주면서 새로운 콜을 한다. // 키가 아니다. // 현재 키와 값으로 acc에 요소를 추가한다. // 현재 acc가 배열이다. // acc에 현재 요소를 추가한다. } // parse(`{"d":123,"e":456}`, null, null, []); parse(`{"a":[1,2,[3,4],5,{"a":2}],"b":{"a":123, "b":456}}`, null, null, []);
JavaScript
복사