Understanding audio downsampling code (JavaScript) and adapting to a different ratio
The vector of coefficients that you gave implement a finite impulse response (FIR) filter with a cutoff of 8 kHz (for an input rate of 48 kHz). As you surmised, these coefficients specify weights that are applied to a sliding window of input samples to generate each output sample; this is the basic operating principle of an FIR filter.

An illustrative way to look at the filter's behavior is via its frequency response. This indicates what frequencies are passed through the filter unchanged and which are attenuated. Generally, if you want to downsample a signal, you first need to bandlimit it so that any frequency content above the post-downsampling Nyquist rate are sufficiently attenuated, to avoid aliasing as much as needed for a particular application.

jojek provided the frequency response in a comment above, which I will shamelessly reproduce here:



Some observations:


This shows a lowpass characteristic, with a band of low frequencies passed through the filter with little to no attenuation, followed by a transition region where the frequency response falls off. It then enters a stopband where all frequencies are significantly more attenuated than in the passband. There are no hard-and-fast objective terms that can be used in all cases to identify exactly where these regions start and end, but you can get an idea by looking at the plot.
This is not a particularly good filter in terms of stopband attenuation (it only attenuates frequencies above the desired band by ~20 dB at minimum), but better specifications would require more coefficients (and therefore more computations per output sample). 


Since the filter only attenuates the stopband content by ~20 dB, this data will alias into the signal that you are left with after downsampling. Any content that was in the stopband region in the original signal will be aliased down to some other frequency in the downsampled signal, with its power level decreased by ~20 dB (or more, depending on exactly where in the stopband it is; notice that there are some narrow regions, often called nulls or zeros of the filter, where the filter generates very high attenuation).

This is an equiripple filter, in that the ripples in the stopband have an equal peak height (at just under -20 dB). Based on some quick playing around with Matlab's Filter Design and Analysis tool, it looks like this was designed for a passband from 0-7 kHz and a stopband starting at 9 kHz, with equal weights to the passband and stopband approximation error (these are parameters for the Remez exchange algorithm used to design equiripple filters).


If you wanted to enhance or duplicate this design, you would need to use a digital filter design procedure. As far as implementation goes, these range from pretty simple (e.g. the window method) to quite complex (Remez exchange as I described before). 

However, there are a lot of tools already out there to do this for you, both in paid tools like MATLAB and in free open-source tools like SciPy. Here's an example of how you could use SciPy to design a filter for the 4 kHz output rate that you mentioned (you'll also need matplotlib installed to plot the result):

from scipy import signal
import numpy as np

# Sample rate of the input signal (Hz).
fs = 48e3
# Desired number of coefficients in the lowpass filter.
N = 21
# Passband edge (Hz).
fpass = 3e3
# Stopband edge (Hz).
fstop = 5e3
# Generate the desired filter coefficients. The arguments can be interpreted as follows:
#   - the desired length of the filter
#   - list of band edges; this describes a passband from 0 Hz to `fpass` Hz
#     and a stopband from `fstop` Hz to `fs/2` Hz
#   - a response of 1 in the passband is desired (0 dB attenuation), and
#     a response of 0 in the stopband is desired
#   - since we passed frequencies in Hz to the filter design routine, it needs
#     to know the value of the sample rate
h = signal.remez(N, [0, fpass, fstop, fs/2], [1, 0], Hz = fs)

# Normalize the filter to 0 dB gain at zero frequency (which is just equal to
# the sum of the filter coefficients).
h = h / np.sum(h)

# Calculate the frequency response of the filter, then calculate its magnitude
# response, measured in dB.
freq, response = signal.freqz(h)
ampl = 20*np.log10(np.abs(response))

import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(freq/(2*np.pi)*fs, ampl, 'b-')
plt.title('Magnitude response of designed filter')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Magnitude (dB)')
plt.grid()
plt.show()


Note that digital filters don't have any inherent knowledge of their sample rate. Instead, their behavior is normalized to whatever sample rate you push through the filter. For example, if you took your original filter and passed data into it at 24 kHz instead of 48 kHz, it would have a cutoff commensurate with a 4 kHz output rate instead of the 8 kHz of the original.

With that said, I'm not saying that this is a great filter design. There is noticeable ripple in the passband and the stopband attenuation is relatively poor. There are a lot of parameters that you can adjust with this method, and there are lots of other filter design techniques as well; this should get you a start. As far as a text goes, I would recommend Understanding DSP by Lyons; it's a good introductory book.