JavaScript - controlling the insertion point for [

2019-01-08 15:41发布

问题:

I would like to create a page that runs a 3rd party script that includes document.write after the DOM was already fully loaded.

My page is not XHTML. My problem is that the document.write is overwriting my own page. (which is what it does once the DOM was loaded).

I tried overriding the document.write function (in a way similiar to http://ejohn.org/blog/xhtml-documentwrite-and-adsense/) but that doesn't cover cases where the document.write contains partial tags.

An example that would break the above code is:

document.write("<"+"div");
document.write(">"+"Done here<"+"/");
document.write("div>");

Is there some way to modify the document.write insertion point through JavaScript? Does anyone have a better idea how to do this?

回答1:

If you're dealing with 3rd party scripts, simply replacing document.write to capture the output and stick it in the right place isn't good enough, since they could change the script and then your site would break.

writeCapture.js does what you need (full disclosure: I'm the author). It basically rewrites the script tags so that each one captures it's own document.write output and puts it in the correct place. The usage (using jQuery) would be something like:

$(document.body).writeCapture().append('<script type="text/javascript" src="http://3rdparty.com/foo.js"></script>');

Here I'm assuming that you want to append to the end of the body. All jQuery selectors and manipulation methods will work with the plugin, so you can inject it anywhere and however you want. It can also be used without jQuery, if that is a problem.



回答2:

It is possible to override the document.write method. So you can buffer the strings sent to document.write and output the buffer wherever you like. However changing a script from synchronous to asynchronous can cause errors if not handled correctly. Here's an example:

Simplified document.write replacement

(function() {
    // WARNING: This is just a simplified example
    // to illustrate a problem.
    // Do NOT use this code!

    var buffer = [];
    document.write = function(str) {
        // Every time document.write is called push
        // the data into buffer. document.write can
        // be called from anywhere, so we also need
        // a mechanism for multiple positions if
        // that's needed.
        buffer.push(str);
    };

    function flushBuffer() {
        // Join everything in the buffer to one string and put
        // inside the element we want the output.
        var output = buffer.join('');
        document.getElementById("ad-position-1").innerHTML = output;
    }

    // Inject the thid-party script dynamically and
    // call flushBuffer when the script is loaded
    // (and executed).
    var script = document.createElement("script");
    script.onload = flushBuffer;
    script.src = "http://someadserver.com/example.js";

})();

Content of http://someadserver.com/example.js

var flashAdObject = "<object>...</object>";
document.write("<div id='example'></div>");

// Since we buffer the data the getElementById will fail
var example = document.getElementById("example");
example.innerHTML = flashAdObject; // ReferenceError: example is not defined

I've documented the different problems I've encountered when writing and using my document.write replacement: https://github.com/gregersrygg/crapLoader/wiki/What-to-think-about-when-replacing-document.write

But the danger of using a document.write replacement are all the unknown problems that may arise. Some are not even possible to get around.

document.write("<scr"+"ipt src='http://someadserver.com/adLib.js'></scr"+"ipt>");
adLib.doSomething(); // ReferenceError: adLib is not defined

Luckily I haven't come across the above problem in the wild, but that doesn't guarantee it won't happen ;)

Still want to try it out? Try out crapLoader (mine) or writeCapture:

You should also check out friendly iframes. Basically it creates a same-domain iframe and loads everything there instead of in your document. Unfortunately I haven't found any good libraries for handling this yet.



回答3:

Original Answer before the edit:


Basically the problem of document.write is that it does not work in XHTML documents. The most broad solution then (as harsh as it may seem) is to not use XHTML/xml for your page. Due to IE+XHTML and the mimetype problem, Google Adsense breaking (may be a good thing :), and the general shift towards HTML5 I don't think it's as bad as it seems.

However if you'd really like to use XHTML for your page, then John's script that you linked to is the best you've got at this point. Just sniff for IE on the server. If the request is from IE, don't do anything (and don't serve the application/xhtml+xml mimetype!). Otherwise drop it into the <head> of your page and you're off to the races.

Re-reading your question, is there a specific problem you have with John's script? It is known to fail in Safari 2.0 but that's about it.



回答4:

You may be interested in the Javascript library I developed which allows to load 3rd party scripts using document.write after window.onload. Internally, the library overrides document.write, appending DOM elements dynamically, running any included scripts which may use document.write as well.

Unlike John Resig's solution (which was part of the inspiration for my own code), the module I developed supports partial writes such as the example you give with the div:

document.write("<"+"div");
document.write(">"+"Done here<"+"/");
document.write("div>");

My library will wait for the end of the script before parsing and rendering the markup. In the above example, it would run once with the full string "<div>Done here</div>" instead of 3 times with partial markup.

I have set up a demo, in which I load 3 Google Ads, an Amazon widget as well as Google Analytics dynamically.



回答5:

FWIW, I found postscribe to be the best option out there these days - it handles wrapping a pesky ad rendering module like a charm allowing our page to load without being blocked.



回答6:

In order to alter the content of the page after the DOM has rendered you need to either use a javascript library to append HTML or text at certain points (jQuery, mootools, prototype, ...) or just use the innerHTML property of each DOM element to alter/append text to it. This works crossbrowser and doesn't require any libraries.



回答7:

There are better ways to do this. 2 ways

1) Append

<html><head>
<script>
  window.onload = function () {
    var el = document.createTextNode('hello world');
    document.body.appendChild(el);
  }
</script></head><body></body></html>

2) InnerHTML

<html><head><script>
  window.onload = function () {
    document.body.innerHTML = 'hello world';
  }
</script></head><body></body></html>