new, instanceof
- 空のオブジェクトの作成
- コンストラクタのプロトタイプの継承:オブジェクトのプロトタイプは、コンストラクタのプロトタイプオブジェクトを指します。
- コンストラクタを実行し、オブジェクトを this にバインドします。
- リターンオブジェクト
function customNew(constructor: Function, ...args: any[]) {
const obj = {};
obj.__proto__ = constructor.prototype
const res = constructor.apply(obj, args);
return ((typeof res === "object" || typeof res === "function") && res !== null) ? res : obj
}
互換性処理へのこだわり
function customInstanceof(instance: any, origin: any) {
if (instance === null || instance === undefined) {
return false
}
if (typeof instance !== 'object' && typeof instance !== "function") {
return false
}
while (instance) {
if (instance.__proto__ === origin.prototype) {
return true
} else {
instance = instance.__proto__
}
}
return false
}
bind呼び出し、適用
- this "といくつかのパラメータをバインドします。矢印関数は thisをバインドできません。
- を実行しない新しい関数を返します。
Function.prototype.customBind = function () {
const args = [...arguments];
const ctx = args[0];
let params = args.slice(1);
const that = this;
return function () {
params = [...params, ...arguments]
return that.apply(ctx, params)
}
}
// this "にバインドし、実行パラメータを渡す。
Function.prototype.customCall = function (ctx, ...args) {
// 型の互換性、nullからwindowへ | global, 関数またはそれ自身
if (ctx === null || ctx === undefined) {
ctx = globalThis
}
// 値の型が変換される。例えば、1 => Number{ 1 }
if (typeof ctx !== "object") {
ctx = new Object(ctx)
}
// 一意なキーを使用し、リネームによるパラメータの汚染を防ぐ。
const symbolKey = Symbol();
ctx[symbolKey] = this;
const res = ctx[symbolKey](...args);
delete ctx[symbolKey];
return res
}
// 呼び出しと同じ実装だが、引数が異なる
Function.prototype.customApply = function (ctx, args) {
if (ctx === null || ctx === undefined) {
ctx = globalThis
}
if (typeof ctx !== "object") {
ctx = new Object(ctx)
}
const symbolKey = Symbol();
ctx[symbolKey] = this;
const res = ctx[symbolKey](...args);
delete ctx[symbolKey];
return res
}
アンチフリッカー、スロットリング
アンチシェイク:いつ停止するか、いつ機能を実行するか、シナリオ:入力ボックス内のファジー検索、ボタンクリックなど。
function debounce(fn, delay = 100) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
スロットル:各期間、1つだけの実行、シナリオ:イベントが頻繁にトリガされ、トリガプロセスと関数を実行するには
// タイムスタンプを比較する
function throttle(fn, time = 100) {
let oldTime = null
return function (...args) {
const newTime = new Date().getTime()
if (newTime - time > oldTime) {
oldTime = newTime
fn.apply(this, args)
}
}
}
// レコードの遅延
function throttle(fn, time = 100) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, time)
}
}
}
getType
// データ型を取得する
function getType(val: any): string {
return Object.prototype.toString.call(val).split(" ")[1].replace("]", "").toLowerCase()
}
配列の平坦化
const arr = [1, 2, 3, [5, 7, 8, [9, 10]], 0, 4];
// 第一レベルをフラットにする:[1, 2, 3, 5, 7, 8, [9, 10], 0, 4]
function flatten(arr: any[]) {
let res = [];
for (let i = 0; i < arr.length; i++) {
res = res.concat(arr[i])
}
return res
}
// ディープフラット化:[1, 2, 3, 5, 7, 8, 9, 10, 0, 4]
function flatten(arr: any[]) {
let res: any[] = []
for (let i = 0; i < arr.length; i++) {
const v = arr[i];
res = res.concat(Array.isArray(v) ? flatten(v) : v)
}
return res
}
curry
コリアライゼーションを実施する場合、原則は主に引数の数を決めることです。
const fn = (a: number, b: number, c: number, d: number) => {
return a + b + c + d
}
function curry(fn: Function) {
const len = fn.length;
let params = [];
function calc(...args: any[]) {
params = [...params, ...args].slice(0, len);
if (params.length === len) {
return fn(...params)
}
return calc
}
return calc
}
const fn2 = curry(fn);
console.log(fn2(1)(2)(3)(4))
ディープコピー
Object.assignと浅いコピーである文字の展開
const obj: any = {
a: 1,
b: { x: 10 },
c: [2, { d: 100 }],
e: Symbol('foo'),
fn: () => { console.log("fn...") },
map: new Map([
['name', ' ],
['title', 'Author']
]),
set: new Set(["a", "b"])
}
obj.self = obj
obj.__proto__.f = 123
JSON.stringifyは、一般的なデータのコピーに使用できます。ただし、symbol、function:は無視され、map、set:はnull値を取得し、循環参照は扱えません。
console.log("JSON", JSON.parse(JSON.stringify(obj)))
通常のコピー・メソッドは、jsonと比較して、シンボルや関数だけでなく、マップやセット、ヌル値も扱うことができます!
export function cloneDeep(target: any) {
if (typeof target !== "object" || target === null || target === undefined) {
return target
}
const res: any = target instanceof Array ? [] : {};
for (const key in target) {
if (target.hasOwnProperty(key)) {
res[key] = cloneDeep(target[key])
}
}
return res
}
循環参照を解決する:トラバーサルプロセスの値を一時的に格納する必要があり、メモリリークを引き起こす可能性がない、この弱い参照のデータ構造をWeakMapを選択します。
export function cloneDeep(target: any, weakMap = new WeakMap()) {
if (typeof target !== "object" || target === null || target === undefined) {
return target
}
const map = weakMap.get(target)
if (map) {
return map
}
let res: any = {}
weakMap.set(target, res)
if (target instanceof Map) {
res = new Map()
target.forEach((v, k) => {
res.set(cloneDeep(k, weakMap), cloneDeep(v, weakMap))
})
} else if (target instanceof Set) {
res = new Set()
target.forEach(v => {
res.add(cloneDeep(v, weakMap))
})
} else if (target instanceof Array) {
res = target.map(v => cloneDeep(v, weakMap))
} else {
for (let i in target) {
res[i] = cloneDeep(target[i], weakMap)
}
}
return res
}
EventBus
// カスタム・イベント、パブリッシュ・サブスクライブ・デザイン・パターン
export default class EventBus {
private events: {
[key: string]: Array<{ fn: Function; isOnce: Boolean }>
};
constructor() {
this.events = {}
}
on(key: string, fn: Function, isOnce: Boolean = false) {
if (!this.events[key]) {
this.events[key] = []
}
this.events[key].push({ fn, isOnce })
}
// 一度だけトリガーされる。
once(key: string, fn: Function) {
this.on(key, fn, true)
}
emit(key: string, ...args: any[]) {
if (this.events[key]) {
this.events[key] = this.events[key].filter(i => {
i.fn(...args);
return !i.isOnce
})
}
}
// 関数のバインドを解除できる。
off(key: string, fn?: Function) {
if (this.events[key]) {
if (!fn) {
delete this.events[key]
} else {
this.events[key] = this.events[key].filter(i => i.fn !== fn)
}
}
}
}
ツリーから配列への変換
配列の要素は順番に並べられ、初めて相互に変換されます。
配列からツリーへ
const flattenArr = [
{ id: 1, name: '部署A', parentId: 0 }, // 0 トップノードを表し、親はない
{ id: 2, name: '部署B', parentId: 1 },
{ id: 3, name: '部門C', parentId: 1 },
{ id: 4, name: '部門D', parentId: 2 },
{ id: 5, name: '部署E', parentId: 2 },
{ id: 6, name: 'セクターF', parentId: 3 }
]
// 配列要素
interface ArrItem {
id: number
name: string
parentId: number
}
// ノード要素、ノードは階層的、parentIdは必要ない。
interface NodeItem {
id: number
name: string
children?: NodeItem[]
}
function fnArrToTree(arr: ArrItem[]): NodeItem | null {
const map: Map<number, NodeItem> = new Map()
let root: NodeItem | null = null
arr.forEach(item => {
const { id, name, parentId } = item
const curNode: NodeItem = { id, name }
// すべてのノードを保存し、親ノードを見つけやすくする
map.set(id, curNode)
const parentNode: NodeItem | undefined = map.get(parentId)
if (parentNode) {
if (!parentNode.children) {
parentNode.children = []
}
parentNode.children.push(curNode)
}
if (parentId === 0) {
root = curNode
}
})
return root
}
console.log(fnArrToTree(flattenArr))
ツリーから配列へ
変換の結果は上記と同じです。難点は、同じレベルのノードをトラバースする方法だけでなく、最終的な順序はABDECFではなく、ABCDEFであるような子ノードをトラバースするために、現在のノードの順序に従って、前者は優先順位のトラバースの幅であり、後者は優先順位の深さです。
const treeObj = {
id: 1,
name: '部署A',
children: [
{
id: 2,
name: '部署B',
children: [
{ id: 4, name: '部門D' },
{ id: 5, name: '部署E' }
]
},
{
id: 3,
name: '部門C',
children: [
{ id: 6, name: 'セクターF' }
]
}
]
}
interface ArrItem {
id: number
name: string
parentId: number
}
interface NodeItem {
id: number
name: string
children?: NodeItem[]
}
function fnTreeToArr(root: NodeItem): ArrItem[] {
// 幅優先探索にスタックを使う
const stack: NodeItem[] = [root]
const res: ArrItem[] = []
// ノードと親ノードの間のリンクを保持する。
const map: Map<NodeItem, NodeItem> = new Map()
while (stack.length) {
const cur: NodeItem | undefined = stack.shift()
if (cur) {
const { id, name } = cur
const parentNode = map.get(cur)
res.push({ id, name, parentId: parentNode ? parentNode.id : 0 })
if (cur.children) {
cur.children.forEach(i => {
map.set(i, cur)
stack.push(i)
});
}
}
}
return res
}
console.log(fnTreeToArr(treeObj))
lru
- get,setを実装。十分に高速であること 複雑さ O(1)
- オーバーフローの処理 , オーバーフローの実装 , 並べ替え可能な順序が必要
map
マップを使用すると、マップをトラバースする順序が挿入される順序になります。
- set: has/setの2つのAPIを呼び出し、複雑さはO(1)
- gethas/get/deleteのようなAPI、複雑さO
- オーバーフローを超える処理:mapのkeysメソッドの呼び出し、複雑さO(n)
export default class LRUMap {
private length: number
private mapData: Map<any, any> = new Map()
constructor(length: number) {
if (length < 1) {
throw new Error("invalid length")
}
this.length = length
}
// mapのセットは、整然と後方に挿入される。アクセスはkeys()を呼び出すことができる。
set(key: any, val: any) {
// 最新のキャッシュ:追加する前に既存のデータを削除する
if (this.mapData.has(key)) {
this.mapData.delete(key)
}
this.mapData.set(key, val)
// オーバーフローを処理する:削除するキーを見つけるためにマップイテレータを使用する。
if (this.mapData.size > this.length) {
// を削除する。
const delKey = this.mapData.keys().next().value
this.mapData.delete(delKey)
}
}
get(key: any): any {
if (!this.mapData.has(key)) {
return null
}
// 既存のデータをsetで、最初に削除し、後で挿入する
const val = this.mapData.get(key)
this.mapData.delete(key)
this.mapData.set(key, val)
return val
}
}
連鎖リスト
双方向に連結されたテーブル:順序を満たす、クエリに十分な速度
- set: next/prevを変更するだけでよく、複雑さはO(1)
- get: 各ノードを記録するキーを使用、オブジェクトクエリの複雑さ O(1)
- オーバーフローの処理: ヘッドからトラバースする必要があり、複雑さは最小O(1)、最大O(n)
export default class LRULink {
private limit: number = 0
private length: number = 0
private head: LinkNode | null = null
private tail: LinkNode | null = null
private link: { [key: string]: LinkNode } = {}
constructor(limit: number) {
if (limit < 1) {
throw new Error("invalid limit")
}
this.limit = limit
}
// 長さのオーバーフローを処理する
private tryClean() {
while (this.length > this.limit) {
if (!this.head || !this.head.next) {
throw new Error("head or head.next is undefined")
}
const tamp = this.head.next
delete this.head.next
delete this.head.prev
delete this.link[this.head.key]
// ヘッドリストとチェーンリストの長さを更新する
this.head = tamp
this.length--
}
}
set(key: string | number, value: any) {
const curNode = this.link[key]
// ノードが見つからない
if (!curNode) {
// 1.ノードの作成
const newNode: LinkNode = { key, value }
// 3.ハッシュテーブルに格納し、チェーンテーブルの長さを更新する。
this.link[key] = newNode
this.length++
// 2.末尾に移動する
this.moveToTail(newNode)
} else {
// ノードのチェック、値の更新、末尾への移動
curNode.value = value
this.moveToTail(curNode)
}
// 長さオーバーフローのケースを処理する
this.tryClean()
}
// ノードを末尾に移動:最新に設定する
private moveToTail(node: LinkNode) {
if (this.tail === node) {
return
}
// 1.カットノード.prevノード.nextとノードの関連
if (node.prev) {
if (node.next) {
node.prev.next = node.next
} else {
delete node.prev.next
}
}
if (node.next) {
if (node.prev) {
node.next.prev = node.prev
} else {
delete node.next.prev
}
}
// 2.ノードヘッドリンクを作成する
if (this.length === 1) {
this.head = node
} else if (this.head === node && node.next) {
// ノードが先頭ノードの場合、先頭ノードを次のノードに更新する
this.head = node.next
}
// 2.カットノード.prevノード.next
delete node.prev
delete node.next
// 3.ノード-テールリンクを作成する
if (this.tail) {
this.tail.next = node
node.prev = this.tail
}
this.tail = node
}
get(key: string | number): any {
const curNode = this.link[key]
if (!curNode) {
return null
}
// ノードが末尾ノードである場合、直接返す
if (this.tail === curNode) {
return curNode.value
}
// 現在点を末尾に移動する
this.moveToTail(curNode)
return curNode.value
}
}
変数の昇格
console.log("header.a", a) // function
var a = 0;
// 関数宣言
function a() {}
console.log("end.a", a) // 0
console.log("header.b", b) // undefined
var b = 0;
// 関数式
var b = function () {}
console.log("end.b", b) // function
型変換
条件判定のための基本的なデータ型変換
console.log(Boolean('')) // false
console.log(Boolean(-0)) // false
console.log(Boolean(-1)) // true
オブジェクト型変換
// 1.最初にvalueOf()を呼び出し、基本データ型を返すならそれを返す。
// 2.toString()を呼び出す、このような配列として、文字列に、文字列は、基本的なデータ型である、変換する
// 3.[Symbol.toPrimitive] 優先順位が最も高い
var num = 1
var arr = [1, 2, 3];
var obj = {
a: 1
}
var primitive = {
a: 1,
valueOf() {
return 2
},
toString() {
return "3"
},
[Symbol.toPrimitive]() {
return 2
}
}
console.log(num + arr) // 11,2,3
console.log(num + obj) // 1[object Object]
console.log(num + primitive) // 3
四則演算
パーティが文字列または数値でない場合は、文字列または数値に変換します。
true + true // 2
1 + [1, 2, 3] // 11,2,3
"a" + +"b" //aNaN +"b" = NaN
4 * [] // 0, [].valueOf = '', toString = 0
== ===
// ブール値の場合は、まずブール値を数値に変換する。
"1" == true // "1" == 1 true
// 片方はオブジェクト、もう片方はNumberかString、まずオブジェクトを基本データ型に変換する。
"1,2,3" == [1, 2, 3] // true
"[object Object]" == {} //true
+ 0.2 != 0.3 ?
コンピュータは2進数のストレージを使用し、2進数では0.1は無限ループの数字の束であり、多くの10進小数も同様に無限ループです。そして、jsは浮動小数点数標準を使用しているため、数値の一部が切り取られてしまいます。
これらの無限ループの数値は切り取られ、精度が失われます。その結果、0.1は0.1ではなく、0.1000000000000002になります。 同様に、0.2は0.00200000000002になります。
0.1 + 0.2 === 0.30000000000000004 // true
なぜ、console.log(0.1)で0.1を出力するのでしょうか? これは、再度内容を入力すると、2進数が10進数に変換され、10進数が文字列に変換されるからです。この処理もまた近似値で、例えば
console.log(0.100000000000000002) // 0.1
解答:切り捨て
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
クロージャ
閉鎖の定義:
- 関数内部の変数にアクセスできる関数
- bはクロージャです。
- クロージャとは、関数+関数内でアクセス可能な変数の「和」です。
クロージャのポイントは、関数の内部で変数に間接的にアクセスできることです。
// 非同期、あまり説明する必要はない
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, 0)
}
console.log("...");
// 各ループ、関数の実行は、別々のスコープを作成する。
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function timer() {
console.log(i)
}, 0)
})(i)
}
// 説明しすぎないこと、各ループは別々のブロックレベルのスコープを作る。
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, 0)
}
// タイマーの有効期限が切れると、最初の関数の正式なパラメータに渡される。
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j)
},i, i)
}
プロトタイプ
function F(name) {
this.name = name
}
var f = new F("f")
console.log("f", f)
console.log("インスタンスとコンストラクタの関係: ----------")
console.log(f.__proto__ === F.prototype) //true
console.log(f.constructor === F) //true
console.log(F.prototype.constructor === F) //true
// インスタンスは__proto__.constructorコンストラクタを指す
// プロトタイプ経由のコンストラクタ.constructor自分自身を指す
console.log("なぜfはvalueof, toStringを持つのか: ----------")
console.log(Object.getOwnPropertyNames(Object.prototype))
// valueoftoStringはObjectプロトタイプのメソッドで、fのプロトタイプはObjectのプロトタイプを指す。
console.log(f.__proto__.__proto__ === Object.prototype) //true
console.log(Function.prototype.__proto__ === Object
.prototype) // true
console.log("Fにバインドがある理由、呼び出し: ----------")
console.log(Object.getOwnPropertyNames(Function.prototype))
// bind関数、呼び出しは関数プロトタイプのメソッド、Fプロトタイプはオブジェクトプロトタイプを指す。
console.log(F.__proto__ === Function.prototype) //true
// 関数はFunctionのインスタンスということか??
console.log(Function.__proto__ === Function.prototype) // true
// 関数自体もそれ自身のインスタンスである...
console.log("他の組み込みオブジェクトはどこから来るのか: ----------")
// FunctionObject、Math、DateなどのコンストラクタもすべてFunctionのインスタンスなのか?結局のところ、これらはすべて組み込みコンストラクタである
console.log(Object.__proto__ === Function.prototype) // true
console.log(Array.__proto__ === Function.prototype) // true
console.log(Object.__proto__ === Function.prototype) // true
console.log(Math.__proto__ === Function.prototype) // true
console.log(Date.__proto__ === Function.prototype) // true
// すべてのjs組み込みオブジェクトはFunctionのインスタンスか?Mathだけは違うのか?
console.log("Function.prototypeそしてどこから: ----------")
console.log(Function.prototype.__proto__ === Object.prototype) //true
// FunctionFunctionのプロトタイプはObjectを指し、FunctionのプロトタイプオブジェクトはObjectのインスタンスである。つまり、jsのすべての根源はObjectなのだ。.prototype?
es6
varlet、const
var a = 0;
let b = 1;
const c = 2;
console.log("window.a", window.a) // 0
console.log("window.b", window.b) // undefined
console.log("window.c", window.c) // undefined
プロトタイプとカルス継承
目標の達成
function Parent(name) {
this.name = name
}
Parent.prototype.sayName = function () {
console.log("name: ", this.name)
}
// より一般的なのは、呼び出しを使って親コンストラクタを実行するが、インスタンスの thisは子を指す。
function Child(name) {
Parent.call(this, name)
}
- サブインスタンスはname属性を持ちます。
- 子は親クラスのsayNameメソッドを継承します。
- 子インスタンスのコンストラクタは親クラスを指し、親インスタンスには影響しません。
組み合わせ継承
上記は親クラスの属性のみを継承し、親クラスで実装されているsayNameメソッドは継承しません:
- 子コンストラクタを親に変更するインスタンス
- 子コンストラクタを親のプロトタイプに変更
// 子コンストラクタのプロトタイプは親クラスのインスタンスを指すので、インスタンスが自分でsayNameを見つけられない場合、親インスタンスのプロトタイプに行く。
Child.prototype = new Parent()
var child = new Child("child")
var child2 = new Parent("child2")
console.log("child", child)
console.log("child2", child2)
これは親クラスのメソッドを継承しますが、2つの問題があります:
- サブクラスがインスタンス化されると、プロトタイプは親インスタンスを指すので、name属性が追加されます。
- で、子インスタンスのコンストラクタは親クラスを指します。
Child.prototype = Parent.prototype
var child = new Child("child")
console.log("child", child)
プロトタイプにはもう親クラスの余分な属性はありませんが、サブクラスのコンストラクタはまだ親クラスを指しています。そこで、サブクラスのコンストラクタのポインティングを変更する必要があります。
Child.prototype.constructor = Child
しかしその場合、親クラスのインスタンスが指すコンストラクタに影響を与えます。
寄生的組み合わせ継承
子インスタンスと親インスタンスのコンストラクタのポインティングに影響を与えることなく、親のメソッドを継承することが可能です。Object.createを使用すると、新しいオブジェクトが作成され、渡された引数は新しいオブジェクトの__proto__として使用され、新しいオブジェクトのコンストラクタを子クラスにポインティングします。
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child
}
})
フルコード
function Parent(name) {
this.name = name
}
Parent.prototype.sayName = function () {
console.log("name: ", this.name)
}
function Child(name) {
Parent.call(this, name)
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child
}
})
var child = new Child("child")
var child2 = new Parent("child2")
console.log("child", child)
console.log("child2", child2)
class
class Person {
constructor(name) {
this.name = name
}
sayName() {
console.log("name: ", this.name)
}
}
let person = new Person("person")
console.log("person", person)
class Child extends Person {
constructor(name) {
super(name)
}
}
let child = new Child("child")
console.log("child", child)
モジュール性
補足
手書きコード
map
var arr = [{ a: 1}, { a: 2}, { a: 3}]
var a = arr.map(item => ({
a: item.a * 2
}))
console.log("a", a)
function _reduce(fn) {
var _this = this
return _this.reduce((a, b) => {
a.push(fn(b))
return a
}, [])
}
Array.prototype._reduce = _reduce
var b = arr._reduce(item => ({
a: item.a * 2
}))
console.log("b", b)
new関数がコンストラクタとして使用された場合、インスタンス化に渡された thisは無視されます。
var f = new newFn()
console.log(f.__proto__ === newFn.prototype) //false
console.log(f.__proto__ === sayName.prototype) // true
console.log(f instanceof newFn, f instanceof sayName) // true, true
最終的なインスタンスのプロトタイプは新しい関数ではなく、元の関数であり、f = new sayName()と等価であることがわかります。最後に、新しい関数と元の関数の両方がインスタンスのプロトタイプチェーンにあることがわかります。
- 引数を thisとして新しい関数を返すbind()の呼び出しは、クロージャ関数です。
- 新しい関数を呼び出すには、元の関数をメソッドとして呼び出し、最初のパラメータを指定します。
- 最初の引数以外を収集し、元の関数に渡します。
- 新しい関数がコンストラクタとして使用される場合、新しい関数と元の関数の両方がインスタンスのプロトタイプチェーンに存在しますが、インスタンスのプロトタイプは元の関数のプロトタイプオブジェクトを指します。
function _bind() {
var fn = this
var args = [...arguments]
var _this = args.shift()
function _newFn() {
let _args = [...args, ...arguments]
return fn.apply(this instanceof _newFn ? this : _this, _args)
}
function _fn() {
}
_fn.prototype = this.prototype
_newFn.prototype = new _fn()
return _newFn
}
Function.prototype._bind = _bind
ソート
- バブルソート
var arr = [6, 1, 40, 3, 9, 2, 50, 64, 0]
let _start = performance.now();
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
let tamp = arr[i]
arr[i] = arr[j]
arr[j] = tamp
}
}
}
let _end = performance.now()
console.log("arr", _end - _start, arr);
- 高速ソート
var arr2 = [6, 1, 40, 3, 9, 2, 50, 64, 0]
function test(arr) {
if (!arr.length) {
return arr
}
let left = []
let right = []
let middle = [arr.shift()]
arr.forEach(item => {
if (item > middle[0]) {
right.push(item)
} else {
left.push(item)
}
})
return test(left).concat(middle, test(right))
// return [...test(left), ...middle, ...test(right)]
}
let _start2 = performance.now();
let arr3 = test(arr2)
let _end2 = performance.now()
console.log("arr3", _end2 - _start2, arr3);
デザインパターン
ファクトリーパターン
インスタンス作成の複雑さを隠し、使用するインターフェイスを1つだけにすることで、ユーザーが単純明快に感じられるようにする効果です
class Person {
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
class Factory {
static create(name) {
return new Person(name)
}
}
Factory.create('kaixin').sayName()
シングルトンモード
グローバル・ステートなどでは、1つのオブジェクトしか必要としないことが多く、シングルトン・パターンを使うことができます。
class Person {
constructor(name) {
this.name = name
this.instance = null
}
static getInstance(name) {
return this.instance ? this.instance = new Person(name) : this.instance
}
}
let person1 = Person.getInstance()
let person2 = Person.getInstance()
console.log(person1 === person2);
ゴミ収集
- メモリ・リークの定義:プログラムが、使用されなくなったメモリを解放することができず、メモリの浪費を招き、プログラムの動作が遅くなったり、クラッシュしたりすること。
- ごみコレクタの原理:マークとクリア方法。ウィンドウなどのルートノードが占有するメモリは、ルートノードから、再帰的に子ノードをチェックし、すべての参照状態としてマークされ、再生成されません。マークされていないメモリブロックは、ごみメモリとみなされ、再生され、新しいリソースから再割り当てされます。
よくあるメモリーリーク
- xxx = window.xxx、解決策:var、let、constを使用するか、使用後にnullに設定します。
- タイマー、クロージャ
- 循環参照、古いカウント・アルゴリズムではメモリ・リーク、マーク除去は不可
var a=new Object;
var b=new Object;
a.r=b;
b.r=a;
- dom
var select = document.querySelector;
var treeRef = select('#tree');
var leafRef = select('#leaf'); //DOMツリーでは、leafRefはtreeFreの子ノードである。
select('body').removeChild(treeRef); //#treetreeRefが残っているため、リサイクルできない。