Make an array of length 256 where each index's value is the index, then shuffle it. For this step, Perlin has a canonical array which he calls the permutation table, accessible here.
Generate an array of 256 random unit vectors. The indices in the permutation table are actually a key to look up vectors from this vectors array.
Given a point in 2D P = (x, y), find the integer grid that it belongs to, whose top left corner index is (xi, yi) (presuming we are using the HTML canvas coordinate system where the y-axis points down). Then the indices for the other three corners are easily calculated: (xi + 1, yi), (xi, yi + 1), and (xi + 1, yi + 1).
For each corner, find its corresponding unit vector. Note that the same corner always maps to the same random unit vector no matter which grid you calculate from. Perlin's formula for doing this is vectorIndex = permutation[permutation[xi] + yi], then cornerVector = randomVectorArray[vectorIndex].
For each corner, make a vector from the corner to the point P. Then, take the dot product of this vector with that corner's unit vector. Now we have four dot products, one for each corner.
Interpolate the four dot products into one. First, interpolate the two on the left side using f(a, b, t) where t = dy = y - yi, then the two on the right. Then interpolate the left and right with f(a, b, t) again where t = dx = x - xi. Here if we use simple linear interpolation we can get hard edges, so Perlin proposed a smooth function where t = 6*t^5 - 15*t^4 + 10*t^3.
This gives the final value, which is always within the range [-1, 1]. To transform it to [0, 1], simply do v = (v+1)/2.
Perlin published another paper in 2002 to improve the Perlin noise algorithm. The core of the paper is that instead of using random vectors in step 4, he randomly chooses between 4 vectors (from center to each edge of the rect). I implemented this variation with PerlinNoise.sampleImproved(), and it's toggle-able in the first visualization diagram on this page. I notice more dark areas and diagonal patterns with this version.