What's wrong with an iterative (with counter) hashed salted KDF? It seems like the brain-dead simplest KDF would have a key-stretching structure that is not substantially different from $\text{hash}(S | P | S | P |\dots|S|P)$, where $S$ is a salt and $P$ is a password. Other protocols generally require lots of hashes-of-hashes-of-hashes; this one builds up one big string to hash. This naive version has some obvious problems with timing (longer passwords require longer to verify), but it also might be easier to parallelize because the structure is perfectly repetitive, and it might "fall into a fixed point" of the hash function's compression-function, whereby further repetitions do not increase security.

The obvious thing to do, then, involves pre-hashing the password and incorporating a counter. Just to make this question 100% unambiguous, one implementation of this idea in Node.js would be:

function createKDF(work_factor, output_length, hash_algorithm) {
    var crypto = require('crypto'), hash_length, output_blocks;
    function hash(string) {
        var context = crypto.createHash(hash_algorithm);
        context.update(string, 'utf8');
        return context.digest();
    }
    hash_length = hash("").length;
    // needed to support arbitrary output lengths
    output_blocks = Math.ceil(output_length / hash_length);
    function output_transform(bin_data) {
        var buff = new Buffer(hash_length * output_blocks), i, bstring;
        bstring = bin_data.toString('hex');
        for (i = 0; i &lt; output_blocks; i += 1) {
            hash(i + ':' + bstring).copy(buff, i * hash_length);
        }
        return buff.slice(0, output_length).toString('hex');
    }
    return {
        createSalt: function () {
            return crypto.randomBytes(hash_length - 4).toString('hex');
        },
        kdf: function (pass, salt) {
            var hpass = hash('nodejs-passwords-0:' + work_factor + ':' + output_length + ':' + pass),
                salt_buff = new Buffer(salt, 'hex'),
                buff = new Buffer(2 * hash_length),
                hash_state = crypto.createHash(hash_algorithm),
                round;
            assert(salt_buff.length + 4 == hash_length);
            // buffer: [32-bit counter][(N - 32)-bit fixed salt][N-bit hpass]:
            salt.copy(buff, 4);
            hpass.copy(buff, hash_length);
            for (round = 0; round &lt; work_factor; round += 1) {
                buff.writeUInt32LE(round, 0);
                hash_state.update(buff);
            }
            return output_transform(hash_state.digest());
        }
    };
}


The reason I ask is that this approach seems simple enough that, if it is secure, the common advice of "don't roll your own crypto" is somewhat wrong: there are a variety of implementations of $$\text{key} = h_1(0|S|h_2(P)|1|S|h_2(P)|2|S|h_2(P)\dots)$$which you can easily program to create a password-storage system which suits your particular application, if something like PBKDF2 or bcrypt or scrypt is not available in your programming environment.

(Of course Node.js has PBKDF2, so the above implementation is more to clarify the question of "what's wrong with this general approach?" rather than it is to be useful to a JS programmer.)
TL;DR: You put less of a burden an any attacker trying to brute-force this.

And please note: Implementing PBKDF2 shouldn't be much harder than implementing your approach.

Now let's head over to the explanation why "your" scheme is really bad for password-hashing.

The scheme you propose is that each try cost you exactly two hash-function evaluations. One for the first hashing of the password and another for the evaluation of the outer hash.

From a theoretical point of view this is bad because you don't require a certain amount calls to a given function that has a "lower bound" on speed. Memory operations XOR and similar don't count usually. So you have only two evaluations and can only tweak something that doesn't really make a difference.

The last point is that "your" scheme ignores the way how hash-functions work. Hash-functions tend to get faster (measured in bytes / cycle) the more data is fed into the function. So your scheme requires one large invocation with small workload per byte, hence you'd have to feed in much more data (10-1000x) to reach the same workload. PBKDF2 needs less because hash-functions are inefficient for small amounts of data.

To concern your question what would be the disadvantages against PBKDF2 and co.
PBKDF2 is standardized ("your" scheme isn't) and widely accepted for usage (although generally considered the worst option).
bcrypt is generally considered a good choice as it puts a burden on the minimal memory ("you" don't) and is hard to compute on GPUs ("your" scheme seems very GPU-friendly, like PBKDF2)
scrypt is generally considered a very good choice as it puts even more of a burden on the memory side and protects quite good against FPGAs and ASICs ("your" scheme doesn't).