*項目地址:awesome-coding-javascript.javascript
在平常開發中爲了提升開發效率,咱們通常是直接調用封裝好的函數方法。可是做爲工程師僅知道這些方法如何調用確定是不夠的,還須要瞭解這些方法背後的實現原理,以便以後更好地運用這些方法,並能夠基於此改進或者寫出本身以爲不錯的方法。java
這方面社區裏也有不少關於原理介紹和代碼實現的文章,能夠從中學到不少。參照着文章裏的描述,本身也能夠實現這些方法,用 console.log
打印一下輸出結果檢查看有沒有問題,可是這樣寫起來比較零碎分散不方便代碼邏輯的持續改進。node
例如:每次要驗證代碼邏輯的時候,都須要把代碼拷到控制檯或者一個文件裏去運行;測試的數據須要臨時構造或者也須要一個一個複製過去;測試用例都須要本身手動執行,不夠自動化;正是因爲測試不方便,因此寫的測試用例也比較粗略;有代碼修改時要保證邏輯正確,須要從新手動執行因此測試用例,繁瑣重複;沒有數據來評估代碼質量。git
如何解決?其實能夠從開源的庫和插件中找到答案,那就是爲這些實現代碼加上單元測試,並保證代碼覆蓋率。github
加上單元測試後的優點有:算法
保證代碼覆蓋率的意義有:編程
因此加上單元測試和保證代碼覆蓋率以後,能解決掉最開始提到的問題,讓咱們即方便又有保障地持續構建咱們的源碼庫。接下來就是列出一些咱們認爲須要實現的源碼庫。數組
它包括 JavaScript 原生和經常使用方法的代碼實現、經常使用庫和插件的代碼實現以及使用 JavaScript 實現的數據結構和經常使用算法。因爲涉及的內容比較多,不可能短期內所有集齊,這裏先把大致的框架搭起來,以後再持續地構建本身的源碼庫。promise
爲了讓你們有個直觀的影響,這裏貼出一些比較有表明性的代碼實現和單元測試,供你們參考。完整的代碼展現請見 Repo : awesome-coding-javascript。bash
實現代碼:
export default function (...args) { // 使用 Symbol 建立一個全局惟一的函數名 const func = Symbol('func'); let context = args.shift(); // 非嚴格模式下,傳 null 或者 undeinfed,context 等於 window 對象 if (context == null) { context = window; } else { context = Object(context); } // 賦予函數屬性 context[func] = this; // 函數執行 const result = context[func](...args); // 刪除臨時的函數屬性 Reflect.deleteProperty(context, func); return result; } 複製代碼
單元測試用例:
import call from './call'; describe('Function.prototype.call', () => { Function.prototype._call = call; it('change the direction of this', () => { const foo = { value: 1, }; function bar() { return this.value; } // 和原生的 call 操做進行比較驗證 expect(bar._call(foo)).toBe(bar.call(foo)); }); it('change the direction of this, use in constructor', () => { function Product(name, price) { this.name = name; this.price = price; } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } function Food2(name, price) { Product._call(this, name, price); this.category = 'food'; } // 和原生的 call 操做進行比較驗證 expect(new Food2('cheese', 5).name).toBe(new Food('cheese', 5).name); }); it('when \'this\' argument is null or undefined', () => { window.value = 2; function bar() { return this.value; } // 這是非嚴格模式下的結果,嚴格模式下會報錯 expect(bar._call(null)).toBe(bar.call(null)); expect(bar._call(undefined)).toBe(bar.call(undefined)); }); it('when \'this\' is other primitive value', () => { function bar() { return this.length; } // 和原生的 call 操做進行比較驗證 expect(bar._call('123')).toBe(bar.call('123')); }); }); 複製代碼
執行單元測試用例:
PASS src/javascript/call/call.test.js Function.prototype.call ✓ change the direction of this (4ms) ✓ change the direction of this, use in constructor (1ms) ✓ when 'this' argument is null or undefined (1ms) ✓ when 'this' is other primitive value 複製代碼
實現代碼:
export default function (...bindArgs) { // 函數自身 const self = this; // 傳進來的 this 對象 let context = bindArgs.shift(); // 非嚴格模式下,傳 null 或者 undeinfed,context 等於 window 對象 if (context == null) { context = window; } else { context = Object(context); } // 返回的新函數 const fBound = function (...args) { // 當返回的新函數做爲構造函數使用時,以前指定的 this 對象會失效,此時 this 是指向實例對象 if (this instanceof fBound) { context = this; } // 函數運行並返回結果 return self.apply(context, [...bindArgs, ...args]); }; // 修改返回函數的原型對象,實例對象就能夠從原函數的原型對象上繼承屬性和方法 fBound.prototype = Object.create(self.prototype); return fBound; } 複製代碼
單元測試用例:
import bind from './bind'; describe('Function.prototype.bind', () => { Function.prototype._bind = bind; it('change the direction of this, return a function', () => { const foo = { value: 1, }; function bar(age1, age2) { age1 = age1 || 0; age2 = age2 || 0; return this.value + age1 + age2; } const newBar = bar._bind(foo, 3); expect(typeof newBar).toBe('function'); // 和原生 bind 操做進行比較驗證 expect(newBar(2)).toBe((bar.bind(foo, 3))(2)); }); it('when return function as a constructor, \'this\' points to the instance object', () => { const foo = { value: 1 }; function bar(name, age) { this.name = name; this.age = age; } bar.prototype.friend = 'kevin'; const bindFoo = bar.bind(foo); // 原生 bind 操做生成的實例對象 const bindFoo2 = bar._bind(foo); bindFoo2.prototype.address = 1; // 修改返回函數的原型對象 // 驗證返回的函數做爲構造函數,實例對象會從原函數的原型對象上繼承屬性 expect(new bindFoo2().friend).toBe(new bindFoo().friend); // 驗證返回的函數做爲構造函數,以前綁定的 this 對象會失效,this 會指向實例對象 expect(new bindFoo2().value).toBe(undefined); expect(new bindFoo2().value).toBe(new bindFoo().value); // 驗證修改返回函數的原型對象,不會引發原始函數 bar 原型對象的修改 expect(bar.prototype.address).toBe(undefined); }); it('when rebind \'this\', cannot change the direction of this', () => { const foo = { value: 1, }; function bar(age1, age2) { age1 = age1 || 0; age2 = age2 || 0; return this.value + age1 + age2; } const bindFoo = bar.bind(foo); // 原生 bind 操做生成的實例對象 const bindFoo2 = bar._bind(foo); // 對返回的函數用 call 或者 apply 從新綁定 this 對象時,this 對象不會發生改變 expect(bindFoo2.call({ value: 2 })).toBe(1); expect(bindFoo2.call({ value: 2 })).toBe(bindFoo.call({ value: 2 })); }); it('when \'this\' argument is null or undefined', () => { window.value = 2; function bar(age1, age2) { age1 = age1 || 0; age2 = age2 || 0; return this.value + age1 + age2; } // 這是非嚴格模式下的結果,嚴格模式下會報錯 expect(bar._bind(null, 3)(1)).toBe(6); expect(bar._bind(undefined, 3)(1)).toBe(6); }); }); 複製代碼
執行單元測試用例:
PASS src/javascript/bind/bind.test.js Function.prototype.bind ✓ change the direction of this, return a function (3ms) ✓ when return function as a constructor, 'this' points to the instance object (1ms) ✓ when rebind 'this', cannot change the direction of this (1ms) ✓ when 'this' argument is null or undefined 複製代碼
實現代碼:
import { isArrowFunction } from './../../shared/is'; export function objectFactory(Factory, ...args) { if (typeof Factory !== 'function') { throw new Error('need be a function argument'); } if (isArrowFunction(Factory)) { throw new Error('arrow function is not allowed'); } const instance = Object.create(Factory.prototype); // 建立實例對象 const result = Factory.apply(instance, args); // 執行構造函數 return result instanceof Object ? result : instance; } 複製代碼
單元測試用例:
import { objectFactory } from './new'; describe('new', () => { it('take a function as an argument', () => { const Factory = 123; function excute() { objectFactory(Factory); } expect(excute).toThrowError('need be a function argument'); }); it('cannot be an arrow function', () => { const Factory = (name, age) => { this.name = name; this.age = age; return 233; }; function excute() { objectFactory(Factory); } expect(excute).toThrowError('arrow function is not allowed'); }); it('create a instance', () => { function Factory(name, age) { this.name = name; this.age = age; } Factory.prototype.getName = function () { return this.name; }; const f = objectFactory(Factory, 'jack', 12); const nf = new Factory('jack', 12); // 原生的 new 操做生成的實例對象 expect(f.name).toBe(nf.name); expect(f.age).toBe(nf.age); expect(f.getName()).toBe(nf.getName()); }); it('if return a primitive value, return the newly instance', () => { const Factory = function (name, age) { this.name = name; this.age = age; return 233; }; Factory.prototype.getName = function () { return this.name; }; const f = objectFactory(Factory, 'jack', 12); const nf = new Factory('jack', 12); // 原生的 new 操做生成的實例對象 expect(f.name).toBe(nf.name); expect(f.age).toBe(nf.age); expect(f.getName()).toBe(nf.getName()); }); it('if return a object, return the object', () => { const Factory = function (name, age) { this.name = name; this.age = age; return { name: 'john', }; }; Factory.prototype.getName = function () { return this.name; }; const f = objectFactory(Factory, 'jack', 12); const nf = new Factory('jack', 12); // 原生的 new 操做生成的實例對象 expect(f.name).toBe(nf.name); expect(f.age).toBe(nf.age); expect(f.getName).toBe(nf.getName); }); }); 複製代碼
執行單元測試用例:
PASS src/javascript/new/new.test.js new ✓ take a function as an argument (5ms) ✓ cannot be an arrow function (1ms) ✓ create a instance (1ms) ✓ if return a primitive value, return the newly instance (1ms) ✓ if return a object, return the object (1ms) 複製代碼
實現代碼:
export default function throttle(func, interval) { let startTime = new Date(); return function (...args) { const curTime = new Date(); // 大於間隔時才執行函數的邏輯 if (curTime - startTime > interval) { // 重設開始時間 startTime = curTime; // 執行函數 func.apply(this, args); } }; } 複製代碼
單元測試用例:
import throttle from './throttle'; import sleep from './../../shared/sleep'; describe('throttle', () => { it('when less than interval, will throttle', async () => { let count = 0; const addCount = () => { count += 1; }; const timer = setInterval(throttle(addCount, 400), 200); /** * 400 -> 1 * 800 -> 2 */ setTimeout(() => { clearInterval(timer); }, 1000); await sleep(1500); expect(count).toBe(2); }); it('when greate than interval, normally call', async () => { let count = 0; const addCount = () => { count += 1; }; const timer = setInterval(throttle(addCount, 200), 300); /** * 300 -> 1 * 600 -> 2 * 900 -> 3 */ setTimeout(() => { clearInterval(timer); }, 1000); await sleep(1500); expect(count).toBe(3); }); }); 複製代碼
執行單元測試用例:
PASS src/javascript/throttle/throttle.test.js
throttle
✓ when less than interval, will throttle (1506ms)
✓ when greate than interval, normally call (1505ms)
複製代碼
實現代碼:
export default class Promise { constructor(fn) { // 記錄 promise 的狀態,根據狀態來操做 callbacks 隊列 this.state = 'pending'; // 記錄 resolve 或 reject 時的值 this.value = null; // 裏面維護了一個隊列,是 resolve 時要執行的代碼 this.callbacks = []; fn(this.resolve, this.reject); } static resolve = val => { return new Promise(function (resolve) { resolve(val); }); } static reject = reason => { return new Promise(function (resolve, reject) { reject(reason); }); } static stop = () => { return new Promise(function () {}); } // 註冊 onFulfilled 和 onRejected 函數 then = (onFulfilled, onRejected) => { return new Promise((resolve, reject) => { this.handle({ onFulfilled: onFulfilled || null, onRejected: onRejected || null, resolve, reject, }); }); } catch = onRejected => { return this.then(null, onRejected); } finally = onFinally => { onFinally = typeof onFinally === 'function' ? onFinally : function noop() {}; return this.then( val => Promise.resolve(onFinally()).then(() => val), reason => Promise.resolve(onFinally()).then(() => { throw reason; }) ); } // 更改狀態,調用執行 callbacks 的方法 resolve = val => { // 狀態只能更改一次 if (this.state !== 'pending') { return; } // resolve 的值是 Promise 的狀況 if (val && (typeof val === 'function' || typeof val === 'object')) { const then = val.then; if (typeof then === 'function') { then.call(val, this.resolve, this.reject); return; } } this.state = 'fulfilled'; this.value = val; this.execute(); } reject = reason => { // 狀態只能更改一次 if (this.state !== 'pending') { return; } this.state = 'rejected'; this.value = reason; this.execute(); } // 執行 callbacks execute = () => { setTimeout(() => { this.callbacks.forEach(callback => { this.handle(callback); }); }, 0); } // 負責處理單個 callback handle = callback => { if (this.state === 'pending') { this.callbacks.push(callback); return; } if (this.state === 'fulfilled') { // 若是 then 中沒有傳遞任何東西 if (!callback.onFulfilled) { // 直接執行下一個 promise callback.resolve(this.value); return; } try { // 執行當前的 promise const ret = callback.onFulfilled(this.value); // 執行下一個 promise callback.resolve(ret); } catch (err) { callback.reject(err); } return; } if (this.state === 'rejected') { // 若是沒有指定 callback.onRejected if (!callback.onRejected) { callback.reject(this.value); return; } try { const ret = callback.onRejected(this.value); callback.resolve(ret); } catch (err) { callback.reject(err); } return; } } } 複製代碼
單元測試用例:
import Promise from './promise'; import sleep from './../../shared/sleep'; // ref: https://github.com/promises-aplus/promises-tests describe('Promise', () => { const time = 500; it('take a function as an argument', () => { expect(() => { new Promise(1); }).toThrowError('is not a function'); }); it('return a promise instace, exposes the public API', () => { const promise = new Promise(function (resolve, reject) { +new Date() % 2 === 0 ? resolve() : reject(); }); expect(promise).toHaveProperty('then'); expect(promise).toHaveProperty('catch'); expect(promise).toHaveProperty('finally'); }); it('promise.then, onFulfilled', done => { const promise = new Promise(function (resolve) { setTimeout(function () { resolve(time); }, time); }); promise.then(ms => { expect(ms).toBe(time); done(); }); }); it('promise.then, onRejected', done => { const promise = new Promise(function (resolve, reject) { setTimeout(function () { reject(time); }, time); }); promise.then(() => { // onFulfilled }, reason => { // onRejected expect(reason).toBe(time); done(); }); }); it('promise.catch', done => { const promise = new Promise(function (resolve, reject) { setTimeout(function () { reject(time); }, time); }); promise.then(() => { // onFulfilled }).catch(reason => { expect(reason).toBe(time); done(); }); }); it('promise.finally', done => { const promise = new Promise(function (resolve, reject) { setTimeout(function () { reject(time); }, time); }); promise.then(() => { // onFulfilled }).catch(() => { // onRejected }).finally(() => { // Finally expect(true).toBe(true); done(); }); }); it('promise chain call, onFulfilled', done => { new Promise(function (resolve) { setTimeout(function () { resolve(time); }, time); }).then(ms => { return new Promise(function (resolve) { setTimeout(function () { resolve(ms + time); }, time); }); }).then(total => { expect(total).toBe(time * 2); done(); }); }); it('promise chain call, onRejected', done => { new Promise(function (resolve, reject) { setTimeout(function () { reject(time); }, time); }).then(ms => { return new Promise(function (resolve) { setTimeout(function () { resolve(ms + time); }, time); }); }).then(total => { return new Promise(function (resolve) { setTimeout(function () { resolve(total + time); }, time); }); }).catch(reason => { expect(reason).toBe(time); done(); }); }); it('can only change status once, cannot from fulfilled to rejected', async () => { let result = ''; const promise = new Promise(function (resolve, reject) { setTimeout(function () { resolve(time); setTimeout(function () { // 設定以後再 reject 一次 reject(time); }, 0); }, time); }); promise.then(() => { result += '=fulfilled='; }).catch(() => { result += '=rejected='; }); await sleep(2000); // 不等於 'fulfilled rejected' expect(result).not.toBe('=fulfilled==rejected='); // 等於 'fulfilled' expect(result).toBe('=fulfilled='); }); it('can only change status once, cannot from rejected to fulfilled', async () => { let result = ''; const promise = new Promise(function (resolve, reject) { setTimeout(function () { reject(time); setTimeout(function () { // 設定以後再 resolve 一次 resolve(time); }, 0); }, time); }); promise.then(() => { result += '=fulfilled='; }).catch(() => { result += '=rejected='; }); await sleep(2000); // 不等於 'fulfilled rejected' expect(result).not.toBe('=rejected==fulfilled='); // 等於 'rejected' expect(result).toBe('=rejected='); }); it('Promise.resolve', done => { Promise.resolve(1).then(num => { expect(num).toBe(1); done(); }); }); it('Promise.reject', done => { Promise.reject(1).catch(num => { expect(num).toBe(1); done(); }); }); }); 複製代碼
執行單元測試用例:
PASS src/javascript/promise/promise.test.js (8.328s) Promise ✓ take a function as an argument (13ms) ✓ return a promise instace, exposes the public API (2ms) ✓ promise.then, onFulfilled (507ms) ✓ promise.then, onRejected (505ms) ✓ promise.catch (506ms) ✓ promise.finally (509ms) ✓ chain call, onFulfilled (1007ms) ✓ chain call, onRejected (505ms) ✓ can only change status once, cannot from fulfilled to rejected (2002ms) ✓ can only change status once, cannot from rejected to fulfilled (2002ms) ✓ Promise.resolve ✓ Promise.reject (1ms) 複製代碼
實現代碼:
export function asyncGenerator(fn) { return function () { const gen = fn.apply(this, arguments); return new Promise(function(resolve, reject) { function step(result) { try { // 執行結束 if (result.done) { return resolve(result.value); } // 繼續往下執行 Promise.resolve(result.value) .then(val => step(gen.next(val))) .catch(err => reject(err)); } catch(e) { return reject(e); } } // 遞歸地一步一步執行 gen.next() step(gen.next()); }); }; } 複製代碼
單元測試用例:
import { asyncGenerator } from './async'; describe('async generator', () => { it('auto excute generator', done => { const asyncFunc = asyncGenerator(function*() { const a = yield new Promise(resolve => { setTimeout(() => { resolve('a'); }, 1000); }); const b = yield Promise.resolve('b'); const c = yield 'c'; const d = yield Promise.resolve('d'); return [a, b, c, d]; }); asyncFunc().then(res => { expect(res).toEqual(['a', 'b', 'c', 'd']); done(); }); }); it('auto excute generator, when get reject', done => { const errorMsg = 'error'; const asyncFunc = asyncGenerator(function*() { const s = yield 's'; yield Promise.reject(errorMsg); return s; }); asyncFunc() .catch(res => { expect(res).toBe(errorMsg); done(); }); }); }); 複製代碼
執行單元測試用例:
PASS src/javascript/async/async.test.js
async generator
✓ auto excute generator (1012ms)
複製代碼
實現代碼:
export default class EventEmitter { constructor() { this.listeners = {}; } on = (event, listener) => { this.bindEvent(event, listener, false); } once = (event, listener) => { this.bindEvent(event, listener, true); } emit = (event, ...args) => { if (!this.hasBind(event)) { console.warn(`this event: ${event} don't has bind listener.`); return; } const { listeners, isOnce } = this.listeners[event]; listeners.forEach(listener => listener.call(this, ...args)); if (isOnce) { this.off(event); } } off = (event, listener) => { if (!this.hasBind(event)) { console.warn(`this event: ${event} don't exist.`); return; } // remove all listener if (!listener) { delete this.listeners[event]; return; } // remove specific listener const listeners = this.listeners[event].listeners; listeners.forEach(listener => { const index = listeners.indexOf(listener); if (index !== -1) { listeners.splice(index, 1); } }); } hasBind = event => { return this.listeners[event] && this.listeners[event].listeners && this.listeners[event].listeners.length; } bindEvent = (event, listener, isOnce = false) => { if (!event || !listener) { return; } this.listeners[event] = this.listeners[event] || { isOnce: false, listeners: [] }; this.listeners[event].isOnce = isOnce; if (isOnce) { this.listeners[event].listeners = [listener]; } else { this.listeners[event].listeners.push(listener); } } } 複製代碼
單元測試用例:
import EventEmitter from './event-emitter'; describe('EventEmitter', () => { let emitter; beforeEach(() => { emitter = new EventEmitter(); }); it('exposes the public API', () => { expect(emitter).toHaveProperty('on'); expect(emitter).toHaveProperty('emit'); expect(emitter).toHaveProperty('once'); expect(emitter).toHaveProperty('off'); }); it('emitter.on', () => { const foo = jest.fn(); const bar = jest.fn(); emitter.on('foo', foo); expect(emitter.listeners['foo'].listeners).toEqual([foo]); emitter.on('foo', bar); expect(emitter.listeners['foo'].listeners).toEqual([foo, bar]); }); it('emitter.once', () => { const foo = jest.fn(); const bar = jest.fn(); emitter.once('foo', foo); expect(emitter.listeners['foo'].listeners).toEqual([foo]); emitter.once('foo', bar); expect(emitter.listeners['foo'].listeners).toEqual([bar]); }); it('emitter.emit', () => { // emitter.on const foo = jest.fn(); emitter.on('foo', foo); emitter.emit('foo', 'x'); expect(foo).toHaveBeenNthCalledWith(1, 'x'); emitter.emit('foo', 'x'); expect(foo).toHaveBeenCalledTimes(2); // emitter.once const bar = jest.fn(); emitter.once('bar', bar); emitter.emit('bar', 'x'); expect(bar).toHaveBeenNthCalledWith(1, 'x'); emitter.emit('bar', 'x'); expect(bar).toHaveBeenCalledTimes(1); }); it('emitter.off, remove all listener', () => { const foo = jest.fn(); emitter.on('foo', foo); emitter.emit('foo', 'x'); emitter.off('foo'); emitter.emit('foo', 'x'); expect(foo).toHaveBeenCalledTimes(1); }); it('emitter.off, remove specific listener', () => { const foo = jest.fn(); const bar = jest.fn(); emitter.on('foo', foo); emitter.on('foo', bar); emitter.emit('foo', 'x'); expect(foo).toHaveBeenCalledTimes(1); expect(bar).toHaveBeenCalledTimes(1); emitter.off('foo', foo); emitter.emit('foo', 'x'); expect(foo).toHaveBeenCalledTimes(1); expect(bar).toHaveBeenCalledTimes(2); }); }); 複製代碼
執行單元測試用例:
PASS src/javascript/event-emitter/event-emitter.test.js
EventEmitter
✓ exposes the public API (4ms)
✓ emitter.on (2ms)
✓ emitter.once (1ms)
✓ emitter.emit (14ms)
✓ emitter.off, remove all listener (2ms)
✓ emitter.off, remove specific listener (1ms)
複製代碼
實現代碼:
/** * 堆(Heap)是計算機科學中的一種特別的樹狀數據結構,如果知足一下特性,便可成爲堆:給定堆中任意節點 P 和 C, * 若 P 是 C 的母節點,那麼 P 的值會小於等於(或大於等於)C 的值。若母節點的值恆小於等於子節點的值,此堆成爲最小堆。 * 若母節點恆小於等於子節點的值,此堆成爲最小堆(min heap) * 若母節點恆大於等於子節點的值,此堆成爲堆大堆(max heap) * 在堆中最頂端的那一個節點,稱做根節點(root node),根節點自己沒有母節點(parent node) * * 在隊列中,調度程序反覆提取隊列中第一個做業並運行,由於實際狀況中某些事件較短的任務將等待很長事件才能結束。 * 或者某些不短小,但具備重要性的做業,一樣應當具備優先權。堆即爲解決此類問題設計的一種數據結構。 * 也就是優先隊列:一種特殊的隊列,隊列中元素出棧的順序是按照元素的優先權大小,而不是元素入隊的前後順序。 * * 堆的特性: * - 堆是一棵徹底二叉樹。即除了最底層,其餘層的節點都被元素填滿,且最底層儘量地從左到右填入。 * - 任意節點小於(或大於)它的全部子節點。 * - 能夠用數組來存儲二叉堆。 */ /** * 實現堆(最大堆、最小堆) */ export default class Heap { constructor(type, nums) { this.type = type || 'max'; // 默認爲最大堆 this.items = []; if (Array.isArray(nums) && nums.length) { nums.forEach(n => this.add(n)); } } isMaxHeap = () => { return this.type === 'max'; } isMinHeap = () => { return this.type === 'min'; } size = () => { return this.items.length; } getParentIndex = i => { return Math.floor((i - 1) / 2); } getLeftChildIndex = i => { return i * 2 + 1; } getRightChildIndex = i => { return i * 2 + 2; } swap = (i, j) => { const temp = this.items[i]; this.items[i] = this.items[j]; this.items[j] = temp; } // 向堆底插入元素 add = el => { this.items.push(el); this.siftUP(this.size() - 1); } siftUP = index => { // 遞歸終止條件 if (index <= 0) { return; } // 找到父元素的索引和值 const item = this.items[index]; const parentIndex = this.getParentIndex(index); const parent = this.items[parentIndex]; // 若是是最大堆 if (this.isMaxHeap()) { // 若是母節點的值小於子節點,則該節點須要上浮,即須要交換位置 if (item > parent) { this.swap(index, parentIndex); // 再遞歸對 parent 作上浮操做 this.siftUP(parentIndex); } } else if (this.isMinHeap()) { // 若是母節點的值大於子節點,則該節點須要上浮,即須要交換位置 if (item < parent) { this.swap(index, parentIndex); // 再遞歸對 parent 作上浮操做 this.siftUP(parentIndex); } } } // 取出棧頂元素,並從新構建徹底二叉樹 extract = () => { // 獲取堆頂元素 const item = this.items.shift(); // 若是當前堆的元素個數小於 2 時,能夠直接返回,不須要從新構建徹底二叉樹 if (this.size() < 2) { return item; } // 如今分離成了兩個徹底二叉樹,須要從新構建成一顆徹底二叉樹 // 獲取最後一個元素,並把它放到堆頂 this.items.unshift(this.items.pop()); // 進行 siftDown 操做,從新構建一顆徹底二叉樹 this.siftDown(0); // 返回堆頂元素 return item; } siftDown = index => { const leftChildIndex = this.getLeftChildIndex(index); // 當沒有左子樹(也就沒有右子樹)時,遞歸終止 if (leftChildIndex >= this.size()) { return; } const leftChild = this.items[leftChildIndex]; const rightChildIndex = this.getRightChildIndex(index); const rightChild = this.items[rightChildIndex]; let nextIndex = leftChildIndex; if (this.isMaxHeap()) { // 找到左右子樹的最大值 if (typeof rightChild !== undefined && rightChild > leftChild) { nextIndex = rightChildIndex; } } else if (this.isMinHeap()) { // 找到左右子樹的最小值 if (typeof rightChild !== undefined && rightChild < leftChild) { nextIndex = rightChildIndex; } } const parent = this.items[index]; const next = this.items[nextIndex]; if (this.isMaxHeap()) { // 若是左右子樹的最大值大於母節點的值,則母節點須要下沉,即須要交換位置 if (next > parent) { this.swap(index, nextIndex); // 再遞歸對母節點進行下沉 this.siftDown(nextIndex); } } else if (this.isMinHeap()) { // 若是左右子樹的最小值小於母節點的值,則母節點須要下沉,即須要交換位置 if (next < parent) { this.swap(index, nextIndex); // 再遞歸對母節點進行下沉 this.siftDown(nextIndex); } } } toString = connector => { return this.items.join(connector); } } 複製代碼
單元測試用例:
import Heap from './heap'; describe('Heap', () => { let heap; beforeEach(() => { heap = new Heap(); }); it('exposes the public API', () => { expect(heap).toHaveProperty('add'); expect(heap).toHaveProperty('extract'); expect(heap).toHaveProperty('toString'); }); }); describe('Max Heap', () => { let heap; beforeEach(() => { heap = new Heap(); }); it('maxHeap.add', () => { // 亂序加入 heap.add(3); heap.add(1); heap.add(5); heap.add(2); heap.add(4); heap.add(6); expect(heap.toString()).toBe('6,4,5,1,2,3'); }); it('maxHeap.extract', () => { // 亂序加入 heap.add(1); heap.add(4); heap.add(2); heap.add(5); heap.add(3); heap.add(6); expect(heap.extract()).toBe(6); expect(heap.toString()).toBe('5,4,2,1,3'); }); }); describe('Min Heap', () => { let heap; beforeEach(() => { heap = new Heap('min'); }); it('minHeap.add', () => { // 亂序加入 heap.add(3); heap.add(1); heap.add(5); heap.add(2); heap.add(4); heap.add(6); expect(heap.toString()).toBe('1,2,5,3,4,6'); }); it('minHeap.extract', () => { // 亂序加入 heap.add(1); heap.add(4); heap.add(2); heap.add(5); heap.add(3); heap.add(6); expect(heap.extract()).toBe(1); expect(heap.toString()).toBe('2,3,6,5,4'); }); }); 複製代碼
執行單元測試用例:
PASS src/dsa/heap/heap.test.js Heap ✓ exposes the public API (4ms) Max Heap ✓ maxHeap.add (1ms) ✓ maxHeap.extract (1ms) Min Heap ✓ minHeap.add ✓ minHeap.extract (1ms) 複製代碼
實現代碼:
/** * 輸入一個鏈表,輸出該鏈表中倒數第 k 個結點。 */ export default function findKthToTail(head, k) { if (!head || !k) { return null; } // 雙指針 let first = head; let second = head; let index = 1; while (first.next) { first = first.next; // 當 first 和 second 相距 k 時,second 開始逐漸前進 if (index >= k) { second = second.next; } index++; } // 循環結束後, k 若是大於 index 則說明倒數第 k 個節點不存在 if (k > index) { return null; } return second; } 複製代碼
單元測試用例:
import { LinkedList } from './../linked-list/linkedList'; import findKthToTail from './findKthToTail'; describe('find Kth to tail', () => { it('when k is less or equal than linked list size', () => { const sourceArr = [1, 2, 3, 4, 5]; const linkedList = LinkedList.from(sourceArr); expect(findKthToTail(linkedList.head, 3).val).toEqual(3); expect(findKthToTail(linkedList.head, 5).val).toEqual(1); }); it('when k is greate than linked list size', () => { const sourceArr = [1, 2, 3, 4, 5]; const linkedList = LinkedList.from(sourceArr); expect(findKthToTail(linkedList.head, 6)).toEqual(null); }); }); 複製代碼
執行單元測試用例:
PASS src/dsa/doublePointer/findKthToTail.test.js
find Kth to tail
✓ when k is less or equal than linked list size (4ms)
✓ when k is greate than linked list size (9ms)
複製代碼
能夠看出來,寫單測並不複雜,測試框架的接口簡單易用,寫單測用例也比較容易。目前的代碼覆蓋率在 90% 以上。