How to optimize bitwise get/set/clear of ranges of bits on 8-bit integers in JavaScript?
For any of the following suggestions, please do profile them to test the performance difference. Performance is likely to vary across multiple browser implementations of the JavaScript interpretation engine.
Rather than a loop for getNumBits, try Math.floor(Math.log(n)/LN_2) - or maybe trunc instead of floor - where LN_2 is precomputed as Math.log(2).
Try to avoid single-letter variables. Your functions are very impenetrable and will be neither legible nor maintainable by anyone else, or by you in a few weeks. In other words, you should not be a human minifier.
Rewrite your code to assume that the index is zero-based starting from the least-significant bit and going left. This will greatly simplify your code and will obviate calls to getNumBits.

Example implementations:
const LN_2 = Math.log(2);

function getNumBits(n) {
    return Math.trunc(Math.log(n) / LN_2) + 1;
}

function getBitRange(n, startIndex, size) {
    return (n &gt;&gt; startIndex) &amp; ((1 &lt;&lt; size) - 1);
} 

function setBitRange(n, startIndex, size, value) {
    const mask = (1 &lt;&lt; size) - 1;
    return (
        n &amp; ~(mask &lt;&lt; startIndex)
    ) | ((value &amp; mask) &lt;&lt; startIndex);
}

function clearBitRange(n, startIndex, size) {
    const mask = (1 &lt;&lt; size) - 1;
    return n &amp; ~(mask &lt;&lt; startIndex);
}

A comment about getNumBits and the alternate implementation offered by @potato. That one is indeed faster than calls to log; a local test of mine using Node and 10,000,000 iterations indicates by a factor of about 16. But that needs to be taken with a grain of salt.
If you're (for some reason) doing a massive amount of client-side data processing and needing to call this function many thousands of times, it might be justified (with HEAVY commenting) to use the bit-twiddle method. In all other cases, I don't recommend using something that's so obscure and difficult-to-understand.