Diese Übersetzung ist in Arbeit.

In diesem Abschnitt werden wir uns mit den typischen cross-browser Problemen von Javascript befassen und lernen, wie man diese löst. Wir lernen die Browser-Entwicklungstools kennen und wie man sogenannte Polyfills und Bibliotheken benutzt um Probleme zu verhindern, und wie man moderne JavaScript Features in älteren Browsern zum Laufen bekommt.

Vorraussetzungen: Basiswissen von HTML, CSS, und JavaScript; Ein Gefühl für die wichtigsten principles of cross browser testing.
Ziel: Die Fähigkeit verbreitete JavaScript cross-browser Probleme zu diagnostizieren. Wissen über die richtigen Tools und Techniken um diese Probleme zu beheben.

Das Problem mit JavaScript

In der Vergangenheit gab es viele Probleme mit der cross-browser Kompabilität von JavaScript: In den 1990er Jahren gab es eigentlich nur zwei Browser, Internet Explorer und Netscape, und sie hatten ihre Skriptsprache in verschiedenen "Flavors" implementiert (Netscape hatte JavaScript, IE hatte JScript und optional auch VBScript). Und obwohl zumindest JavaScript und JScript größtenteils kompatibel waren (beide basierten auf der ECMAScript Spezifikation) wurden viele Dinge doch auf verschiedene inkompatible und widersprüchliche Arten implementiert, was den Entwicklern zu dieser Zeit den letzten Nerv raubte.

Derartige Kompatibilitätsprobleme hielten sich bis in die frühen 2000er, weil veraltete Browser immer noch im Einsatz waren und unterstützt werden mussten. Aus diesem Grund wurden unter anderem Bibliotheken wie jQuery erschaffen - um die unterschiedlichen Implementierungen der verschiedenen Browser wegzuabstrahieren (als Beispiel der Code für How to make an HTTP request), so dass Entwickler nur eine einzelne Zeile Code schreiben mussten (siehe dazu jQuery.ajax()). Die Bibliothek (jQuery oder ähnliches) kümmert sich um die Kompabilitätsproblematik, so dass der Entwickler es nicht tun muss.

Die Situation hat sich seit dem deutlich verbessert: Moderne Browser leisten gute Arbeit wenn es darum geht klassische JavaScript Features zu unterstützen. Der Bedarf an oben genannten Bibliotheken ist deutlich geschrumpft da ältere Browser oft nicht mehr unterstützt werden müssen - auch wenn sie immer noch existieren, wohlbemerkt!

Heutzutage entstehen cross-browser Probleme von JavaScript hauptsächlich dann:

  • Wenn schlecht geschriebener "Browser Sniffing" Code, "Feature Detection" Code, oder die Benutzung von Vendor-Prefixes einen Browser daran hindert Code auszuführen, den er ansonsten problemlos ausführen könnte.
  • Wenn Entwickle neue und/oder experimentelle JavaScript Features benutzen (zum Beispiel ECMAScript 6 / ECMAScript Next Features, moderne Web APIs...) und sich herausstellt, dass diese Features in älteren Browsern nicht funktionieren.

Wir werden uns jetzt mit diesen und weiteren Problemen befassen.

Fixing general JavaScript problems

Wie schon in dem previous article über HTML/CSS erwähnt, solltest du erst einmal sicherstellen, dass dein Code überhaupt so funktioniert wie er soll, und dich dann erst mit der cross-browser Problematik befassen. Wenn du mit den Grundlagen vom Troubleshooting JavaScript noch nicht vertraut sein solltest, dann lies erst diesen Artikel bevor du hier weiter machst. Hier folgt eine Auflistung von typischen Javascript Problemen, die man im Kopf behalten sollte:
 

  • Grundsätzliche Probleme mit Syntax oder Logik (siehe auch: Troubleshooting JavaScript).
  • Die "Scope", in der Variablen definiert werden, ist wichtig, weil es sonst Konflikte gibt zwischen Elementen, die an verschiedenen Stellen im Code deklariert wurden (siehe auch: Function scope and conflicts).
  • Das Keyword this stiftet of Verwirrung, da es unklar sein kann, auf welche "Scope" es sich eigentlich bezieht und ob es den Wert hat, den man erwartet. Eine Einleitung zu dem Thema bietet der Artikel What is "this"? Beispiele wie this one zeigen eine verbreitete Technik, bei der die this scope einer seperaten Variable zugewiesen wird. Damit wird sichergestellt, dass die richtige this Scope gemeint ist wenn diese Variable dann z.B. in einer eingebetteten Funktionen aufgerufen wird.
  • Funktionen innerhalb von Schleifen. In dem Code Beispiel bad-for-loop.html (source code) gibt es einen For-Loop mit 10 Iterationen. Bei jeder Iteration wird ein Paragraph erzeugt, zu dem ein onclick Handler gehört. Wenn so ein Paragraph geklickt wird soll ein Alert mit der Zahl angezeigt werden, die der Nummer der Iteration entspricht. Unerwarteterweise wird jedoch jedes Mal die Zahl 11 angezeigt. Das liegt daran, dass For-Loops erst einmal alle Iterationen durchlaufen bevor eine verschachtelte Funktion aufgerufen wird. Damit es so funktioniert, wie es eigentlich sollte, müsste man eine Funktion deklarieren die den Handler seperat hinzufügt: Diese Funktion wird dann bei jeder Iteration mit den aktuellen Werten von  para und i aufgerufen. Hier findest du eine Version die funktioniert: good-for-loop.html (source code).
  • Making sure asynchronous operations have returned before trying to use the values they return. For example, this Ajax example checks to make sure the request is complete and the response has been returned before trying to use the response for anything. This kind of operation has been made easier to handle by the introduction to Promises to the JavaScript language.

NoteBuggy JavaScript Code: The 10 Most Common Mistakes JavaScript Developers Make has some nice discussions of these common mistakes and more.

Linters

As with HTML and CSS, you can ensure better quality, less error-prone JavaScript code using a linter, which points out errors and can also flag up warnings about bad practices, etc., and be customized to be stricter or more relaxed in their error/warning reporting. The JavaScript/ECMAScript linters we'd recommend are JSHint and ESLint; these can be used in a variety of ways, some of which we'll detail below.

Online

The JSHint homepage provides an online linter, which allows you to enter your JavaScript code on the left and provides an output on the right, including metrics, warnings, and errors.

Code editor plugins

It is not very convenient to have to copy and paste your code over to a web page to check its validity several times. What you really want is a linter that will fit into your standard workflow with the minimum of hassle. Many code editors have linter plugins, for example Github's Atom code editor has a JSHint plugin available.

To install it:

  1. Install Atom (if you haven't got an up-to-date version already installed) — download it from the Atom page linked above.
  2. Go to Atom's Preferences... dialog (e.g. by Choosing Atom > Preferences... on Mac, or File > Preferences... on Windows/Linux) and choose the Install option in the left-hand menu.
  3. In the Search packages text field, type "jslint" and press Enter/Return to search for linting-related packages.
  4. You should see a package called lint at the top of the list. Install this first (using the Install button), as other linters rely on it to work. After that, install the linter-jshint plugin.
  5. After the packages have finished installing, try loading up a JavaScript file: you'll see any issues highlighted with green (for warnings) and red (for errors) circles next to the line numbers, and a separate panel at the bottom provides line numbers, error messages, and sometimes suggested values or other fixes.

Other popular editors have similar linting packages available. For example, see the "Plugins for text editors and IDEs"  section of the JSHint install page.

Other uses

There are other ways to use such linters; you can read about them on the JSHint and ESLint install pages.

It is worth mentioning command line uses — you can install these tools as command line utilities (available via the CLI — command line interface) using npm (Node Package Manager — you'll have to install NodeJS first). For example, the following command installs JSHint:

npm install -g jshint

You can then point these tools at JavaScript files you want to lint, for example:

You can also use these tools with a task runner/build tool such as Gulp or Webpack to automatically lint your JavaScript during development. (see Using a task runner to automate testing tools in a later article.) See ESLint integrations for ESLint options; JSHint is supported out of the box by Grunt, and also has other integrations available, e.g. JSHint loader for Webpack.

Note: ESLint takes a bit more setup and configuration than JSHint, but it is more powerful too.

Browser developer tools

Browser developer tools have many useful features for helping to debug JavaScript. For a start, the JavaScript console will report errors in your code.

Make a local copy of our broken-ajax.html example (see the source code also). If you look at the console, you'll see the following output:

The error message reads "TypeError: jsonObj is null", and the line number signified is line 37. If we look at the source code, the relevant code section is this:

function populateHeader(jsonObj) {
  var myH1 = document.createElement('h1');
  myH1.textContent = jsonObj['squadName'];
  header.appendChild(myH1);

  ...

So the code falls over as soon as we try to access jsonObj (which as you might expect, is supposed to be a JSON object). This is supposed to be fetched from an external .json file using the following XMLHttpRequest call:

var requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json';
var request = new XMLHttpRequest();
request.open('GET', requestURL);
request.send();

var superHeroes = request.response;
populateHeader(superHeroes);
showHeroes(superHeroes);

But this fails.

The Console API

You may already know what is wrong with this code, but let's explore it some more to show how you could investigate this. For a start, there is a Console API that allows JavaScript code to interact with the browser's JavaScript console. It has a number of features available, but the main one you'll use often is console.log(), which prints a custom message to the console.

Try inserting the following line just below line 31 (bolded above):

console.log('Response value: ' + superHeroes);

Refresh the page in the browser, and you will get an output in the console like so:

The console.log() output shows that the superHeroes object doesn't appear to contain anything, although note that the error message has now changed, to "TypeError: heroes is undefined". This shows that the error is intermittent, giving further evidence that this is some kind of asynchronous error. Let's fix the current error and move on — remove the console.log() line, and update this code block:

var superHeroes = request.response;
populateHeader(superHeroes);
showHeroes(superHeroes);

to the following:

request.onload = function() {
  var superHeroes = request.response;
  populateHeader(superHeroes);
  showHeroes(superHeroes);
}

This solves the asynchronous issue, by ensuring that the functions are not run and passed the superHeroes object until the response has finished loading and is available.

So to summarize, anytime something is not working and a value does not appear to be what it is meant to be at some point in your code, you can use console.log() to print it out and see what is happening.

Using the JavaScript debugger

We have solved one problem, but we are still stuck with the error message "TypeError: heroes is undefined", reported on line 51. Let's investigate this now, using a more sophisticated feature of browser developer tools: the JavaScript debugger as it is called in Firefox.

Note: Similar tools are available in other browsers; the Sources tab in Chrome, Debugger in Safari (see Safari Web Development Tools), etc.

In Firefox, the Debugger tab looks as follows:

  • On the left, you can select the script you want to debug (in this case we have only one).
  • The center panel shows the code in the selected script.
  • The right-hand panel shows useful details pertaining to the current environment — Breakpoints, Callstack and currently active Scopes.

The main feature of such tools is the ability to add breakpoints to code — these are points where the execution of the code stops, and at that point you can examine the environment in its current state and see what is going on.

Let's get to work. First of all, we know that the error is being thrown at line 51. Click on line number 51 in the center panel to add a breakpoint to it (you'll see a blue arrow appear over the top of it). Now refresh the page (Cmd/Ctrl + R) — the browser will pause execution of the code at line 51. At this point, the right-hand side will update to show some very useful information.

  • Under Breakpoints, you'll see the details of the break-point you have set.
  • Under Call Stack, you'll see two entries — this is basically a list of the series of functions that were invoked to cause the current function to be invoked. At the top, we have showHeroes() the function we are currently in, and below we have request.onload, which stores the event handler function containing the call to showHeroes().
  • Under Scopes, you'll see the currently active scope for the function we are looking at. We only have two — showHeroes, and Window (the global scope). Each scope can be expanded to show the values of variables inside the scope at the point that execution of the code was stopped.

We can find out some very useful information in here.

  1. Expand the showHeroes scope — you can see from this that the heroes variable is undefined, indicating that accessing the members property of jsonObj (first line of the function) didn't work.
  2. You can also see that the jsonObj variable is storing a text string, not a JSON object.
  3. Exploring further down the call stack, click request.onload in the Call Stack section. The view will update to show the request.onload function in the center panel, and its scopes in the Scopes section.
  4. Now if you expand the request.onload scope, you'll see that the superHeroes variable is a text string too, not an object. This settles it — our XMLHttpRequest call is returning the JSON as text, not JSON.

Note: We'd like you to try fixing this problem yourself. To give you a clue, you can either tell the XMLHttpRequest object explicitly to return JSON format, or convert the returned text to JSON after the response arrives. If you get stuck, consult our fixed-ajax.html example.

Note: The debugger tab has many other useful features that we've not discussed here, for example conditional breakpoints and watch expressions. For a lot more information, see the Debugger page.

Performance issues

As your apps get more complex and you start to use more JavaScript, you may start to run into performance problems, especially when viewing apps on slower devices. Performance is a big topic, and we don't have time to cover it in detail here. Some quick tips are as follows:

  • To avoid loading more JavaScript than you need, bundle your scripts into a single file using a solution like Browserify. In general, reducing the number of HTTP requests is very good for performance.
  • Make your files even smaller by minifying them before you load them onto your production server. Minifying squashes all the code together onto a huge single line, making it take up far less file size. It is ugly, but you don't need to read it when it is finished! This is best done using a minification tool like Uglify (there's also an online version — see JSCompress.com)
  • When using APIs, make sure you turn off the API features when they are not being used; some API calls can be really expensive on processing power. For example, when showing a video stream, make sure it is turned off when you can't see it. When tracking a device's location using repeated Geolocation calls, make sure you turn it off when the user stops using it.
  • Animations can be really costly for performance. A lot of JavaScript libraries provide animation capabilities programmed by JavaScript, but it is much more cost effective to do the animations via native browser features like CSS Animations (or the nascent Web Animations API) than JavaScript. Read Brian Birtles' Animating like you just don’t care with Element.animate for some really useful theory on why animation is expensive, tips on how to improve animation performance, and information on the Web Animations API.

Note: Addy Osmani's Writing Fast, Memory-Efficient JavaScript contains a lot of detail and some excellent tips for boosting JavaScript performance.

Cross-browser JavaScript problems

In diesem Abschnitt schauen wir uns einige weit verbreitete cross-browser Probleme von JavaScript an.

In this section, we'll look at some of the more common cross-browser JavaScript problems. We'll break this down into:

  • Using modern core JavaScript features
  • Using modern Web API features
  • Using bad browser sniffing code
  • Performance problems

Using modern JavaScript/API features

In dem previous article haben wir uns angeschaut, wie die Sprachen HTML und CSS mit Fehlern und unbekannten Features umgehen. JavaScript ist da im Vegleich zu HTML und CSS viel weniger tolerant. Wenn die JavaScript Engine auf Fehler oder unbekannte Syntax stößt, dann wird dies in den meisten Fällen auch als Fehler ausgegeben.

Viele der modernen JavaScript Sprach-Features, die in neueren Spec Versionen (ECMAScript 6 / ECMAScript Next) definiert wurden, funktionieren in älteren Browsern schlicht nicht. Manche dieser Features sind einfach nur sogenannter "Syntax Zucker" (also eine bequemere, einfachere Art Dinge zu implementieren, die man auch bereits vorher mit existierender Syntax implementieren konnte), aber manche eröffnen auch komplett neue Möglichkeiten

Zum Beispiel:

  • Promises sind ein tolles neues Feature mit dem man asynchrone Operationen ausführen kann und dabei sicher stellt, dass eine Operation auch tatsächlich abgeschlossen ist wenn Code ausgeführt wird, der auf den Ergebnissen dieser Operation beruht. Die Fetch API (eine modernere Version des XMLHTTPRequest) beispielsweise benutzt Promises um Resourcen über ein Netzwerk abzurufen und sicherzustellen, dass die Antwort des Servers bereits eingetroffen ist bevor diese  weiter verwendet wird (zum Beispiel um ein Bild in einem img Element. darzustellen. They are not supported in IE at all but are supported across all modern browsers.
  • Arrow functions provide a shorter, more convenient syntax for writing anonymous functions, which also has other advantages (see Arrow functions). For a quick example, see arrow-function.html (see the source code also). Arrow functions are supported across all modern browsers, except for IE and Safari.
  • Declaring strict mode at the top of your JavaScript code causes it to be parsed with a stricter set of rules, meaning that more warnings and errors will be thrown, and some things will be disallowed that would otherwise be acceptable. It is arguably a good idea to use strict mode, as it makes for better, more efficient code, however it has limited/patchy support across browsers (see Strict mode in browsers).
  • Typed arrays allow JavaScript code to access and manipulate raw binary data, which is necessary as browser APIs for example start to manipulate streams of raw video and audio data. These are available in IE10 and above, and all modern browsers.

There are also many new APIs appearing in recent browsers, which don't work in older browsers, for example:

  • IndexedDB API, Web Storage API, and others for storing website data on the client-side.
  • Web Workers API for running JavaScript in a separate thread, helping to improve performance.
  • WebGL API for real 3D graphics.
  • Web Audio API for advanced audio manipulation.
  • WebRTC API for multi-person, real-time video/audio connectivity (e.g. video conferencing).
  • WebVR API for engineering virtual reality experiences in the browser (e.g. controlling a 3D view with data input from VR Hardware)

There are a few strategies for handling incompatibilities between browsers relating to feature support; let's explore the most common ones.

Note: These strategies do not exist in separate silos — you can, of course combine them as needed. For example, you could use feature detection to determine whether a feature is supported; if it isn't, you could then run code to load a polyfill or a library to handle the lack of support.

Feature Detection

Die Grundidee von Feature Detection ist, dass man erst einen Test ausführt ob ein bestimmtes Feature im Browser unterstützt wird und dann Code ausführt, der dem User eine akzeptable Experience bietet, egal ob sein Browser das Feature unterstützt oder nicht. Am besten kann man das z.B. anhand der Geolocation API erklären: Mit diesem Feature kann man die Lokalisierungsdaten des Gerätes, auf dem der Web-Browser läuft, auslesen. An dieses Feature kommt man über einen einzigen entry point, die geolocation property des globalen Navigator Objekts. Um zu testen, ob ein Browser das Feature Geolocation unterstützt kannst du diesen Code ausführen:

if("geolocation" in navigator) {
  navigator.geolocation.getCurrentPosition(function(position) {
    // show the location on a map, perhaps using the Google Maps API
  });
} else {
  // Give the user a choice of static maps instead perhaps
}

Genauso könnte man auch für ein bestimmtes CSS Feature einen Test schreiben. Man kann beispielsweise prüfen ob element.style.property existiert (z.b. paragraph.style.transform !== undefined).  Höchstwahrscheinlich ist es aber besser gleich eine richtige Bibliothek für Feature Detektion zu nutzen anstatt jedes mal deinen eigenen Code dafür zu schreiben. Die Bibliothek namens "Modernizr" ist für Feature Detection die beste Wahl.

Und noch etwas wichtiges zum Schluss: Feature detection ist nicht das selbe wie Browser Sniffing (also wenn man den tatsächlichen Browser ausfindig macht mit dem der User die Seite besucht) - letzteres ist unter allen Umständen zu vermeiden. Für mehr Details kannst du diesen Artikel lesen: Using bad browser sniffing code.

Note: Some features are known to be undetectable — see Modernizr's list of Undetectables.

Note: Feature detection will be covered in a lot more detail in its own dedicated article, later in the module.

Libraries

JavaScript libraries are essentially third party units of code that you can attach to your page, providing you with a wealth of ready-made functionality that can be used straight away, saving you a lot of time in the process. A lot of JavaScript libraries probably came into existence because their developer was writing a set of common utility functions to save them time when writing future projects, and decided to release them into the wild because other people might find them useful too.

JavaScript libraries tend to come in a few main varieties (some libraries will serve more than one of these purposes):

  • Utility libraries: Provide a bunch of functions to make mundane tasks easier and less boring to manage. jQuery for example provides its own fully-featured selectors and DOM manipuation libraries, to allow CSS-selector type selecting of elements in JavaScript and easier DOM building. It is not so important now we have modern features like Document.querySelector()/Document.querySelectorAll()/Node methods available across browsers, but it can still be useful when older browsers need supporting.
  • Convenience libraries: Make difficult things easier to do. For example, the WebGL API is really complex and challenging to use when you write it directly, so the Three.js library (and others) is built on top of WebGL and provides a much easier API for creating common 3D objects, lighting, textures, etc. The Service Worker API is also very complex to use, so code libraries have started appearing to make common Service Worker uses-cases much easier to implement (see the Service Worker Cookbook for several useful code samples).
  • Effects libraries: These libraries are designed to allow you to easily add special effects to your websites. This was more useful back when DHTML was a popular buzzword, and implementing effect involved a lot of complex JavaScript, but these days browsers have a lot of built in CSS3 features and APIs to implementing effects more easily. See JavaScripting.com/animation for a list of libraries.
  • UI libraries: Provide methods for implementing complex UI features that would otherwise be challenging to implement and get working cross browser, for example jQuery UI and Foundation. These tend to be used as the basis of an entire site layout; it is often difficult to drop them in just for one UI feature.
  • Normalization libraries: Give you a simple syntax that allows you to easily complete a task without having to worry about cross browser differences. The library will manipulate appropriate APIs in the background so the functionality will work whatever the browser (in theory). For example, LocalForage is a library for client-side data storage, which provides a simple syntax for storing and retrieving data. In the background, it uses the best API the browser has available for storing the data, whether that is IndexedDB, Web Storage, or even WebSQL (which is now deprecated, but is still supported in some older versions of Safari/IE). As another example, jQuery

When choosing a library to use, make sure that it works across the set of browsers you want to support, and test your implementation thoroughly. Also make sure that the library is popular and well-supported, and isn't likely to just become obsolete next week. Talk to other developers to find out what they recommend, see how much activity and how many contributors the library has on Github (or wherever else it is stored), etc.

Note: JavaScripting.com gives you a good idea of just how many JavaScript libraries there are available, and can be useful for finding libraries for specific purposes.

Library usage at a basic level tends to consist of downloading the library's files (JavaScript, possibly some CSS or other dependencies too) and attaching them to your page (e.g. via a <script> element), although there are normally many other usage options for such libraries, like installing them as Bower components, or including them as dependencies via the Webpack module bundler. You will have to read the libraries' individual install pages for more information.

Note: You will also come across JavaScript frameworks in your travels around the Web, like Ember and Angular. Whereas libraries are often usable for solving individual problems and dropping into existing sites, frameworks tend to be more along the lines of complete solutions for developing complex web applications.

Polyfills

Auf den ersten Blick ähneln Polyfills Bibliotheken in der Hinsicht, dass sie auch aus 3rd party JavaScript bestehen, die du in dein Projekt integrieren kannst. Aber der Unterschied ist, dass Bibliotheken dazu da sind, existierende Funktionalitäten zu verbessern und deren Benutzung zu erleichtern. Polyfills dagegen bieten Funktionalität,die sonst gar nicht vorhanden wäre, weil der Browser es gar nicht unterstützt. Zum Beispiel kann man mit dem Polyfill für es6-promise Promises in Browsern nutzen, die sie gar nicht von Haus aus unterstützen.

Modernizr's list of HTML5 Cross Browser Polyfills is a useful place to find polyfills for different purposes. Again, you should research them before you use them — make sure they work and are maintained.

Let's work through an exercise — in this example we will use a Fetch polyfill to provide support for the Fetch API in older browsers; however we also need to use the es6-promise polyfill, as Fetch makes heavy use of promises, and browsers that don't support them will still be in trouble.

  1. To get started, make a local copy of our fetch-polyfill.html example and our nice image of some flowers in a new directory. We are going to write code to fetch the flowers image and display it in the page.
  2. Next, save copies of the Fetch polyfill and the es6-promises polyfill in the same directory as the HTML.
  3. Apply the polyfill scripts to the page using the following code — place these above the existing <script> element so they will be available on the page already when we start trying to use Fetch:
    <script src="es6-promise.js"></script>
    <script src="fetch.js"></script>
  4. Inside the original <script>, add the following code:
  5. var myImage = document.querySelector('.my-image');
    
    fetch('flowers.jpg').then(function(response) {
      response.blob().then(function(myBlob) {
        var objectURL = URL.createObjectURL(myBlob);
        myImage.src = objectURL;
      });
    });
  6. Now if you load it in a browser that doesn't support Fetch (Safari and IE are obvious candidates), you should still see the flower image appear — cool! 

Note: You can find our finished version at fetch-polyfill-finished.html (see also the source code).

Note: Again, there are many different ways to make use of the different polyfills you will encounter — consult each polyfill's individual documentation.

One thing you might be thinking is "why should we always load the polyfill code, even if we don't need it?" This is a good point — as your sites get more complex and you start to use more libraries, polyfills, etc., you can start to load a lot of extra code, which can start to affect performance, especially on less-powerful devices. It makes sense to only load files as needed.

Doing this requires some extra setup in your JavaScript. You need some kind of a feature detection test that detects whether the browser supports the feature we are trying to use:

if (browserSupportsAllFeatures()) {
  main();
} else {
  loadScript('polyfills.js', main);
}

function main(err) {
  // actual app code goes in here
}

So first we run a conditional that checks whether the function browserSupportsAllFeatures() returns true. If it does, we run the main() function, which will contain all our app's code. browserSupportsAllFeatures() looks like this:

function browserSupportsAllFeatures() {
  return window.Promise && window.fetch;
}

Here we are testing whether the Promise object and fetch() function exist in the browser. If both do, the function returns true. If the function returns false, then we run the code inside the second part of the conditional  — this runs a function called loadScript(), which loads the polyfills into the page, then runs main() after the loading has finished. loadScript() looks like this:

function loadScript(src, done) {
  var js = document.createElement('script');
  js.src = src;
  js.onload = function() {
    done();
  };
  js.onerror = function() {
    done(new Error('Failed to load script ' + src));
  };
  document.head.appendChild(js);
}

This function creates a new <script> element, then sets its src attribute to the path we specified as the first argument ('polyfills.js' when we called it in the code above). When it has loaded, we run the function we specified as the second argument (main()). If an error occurs in the loading of the script, we still call the function, but with a custom error that we can retrieve to help debug a problem if it occurs.

Note that polyfills.js is basically the two polyfills we are using put together into one file. We did this manually, but there are cleverer solutions that will automatically generate bundles for you — see Browserify (see Getting started with Browserify for a basic tutorial). It is a good idea to bundle JS files into one like this — reducing the number of HTTP requests you need to make improves the performance of your site.

You can see this code in action in fetch-polyfill-only-when-needed.html (see the source code also). We'd like to make it clear that we can't take credit for this code — it was originally written by Philip Walton.  Check out his article Loading Polyfills Only When Needed for the original code, plus a lot of useful explanation around the wider subject).

Note: There are some 3rd party options to consider, for example Polyfill.io — this is a meta-polyfill library that will look at each browser's capabilities and apply polyfills as needed, depending on what APIs and JS features you are using in your code.

JavaScript transpiling

Eine weitere Methode, die sehr üblich ist wenn man moderne JavaScript Features (aus ECMAScript 6/ECMAScript 2015) benutzen will, ist das Umwandeln des geschriebenen Codes in eine Version, die auch in älteren Browsern funktioniert. Dieser Prozess heißt "Transpiling".

Note: "Transpiling" ist anders als "Compiling". Beim Compiling wird Code, der in einer höheren Programmiersprache (z.B. C) geschrieben ist auf ein niedrigeres Level übersetzt, so dass ein Computer sie ausführen kann. Beim Transpiling wird ein Stück Code in ein anderes Stück Code übersetzt, dessen Syntax vom Abstraktionsgrad her nicht anders ist als die Syntax des Originalcodes. Es handelt sich eher um die Übersetzung zwischen verschiedenen "Flavors" von JavaScript.

Wir haben uns ja bereits mit Arrow Functions befasst (siehe arrow-function.html für die Live-Version und den source code). Die folgende Zeile Code funktioniert nur in neueren Browsern:

() => { ... }

Wir könnten diesen Code transpilieren, so dass dabei eine "altmodische" anonyme Funktion herauskommt. Dieser Code würde dann auch in älteren Browsern funktionieren.

function() { ... }

Das beste Tool um JavaScript zu transpilieren ist momentan Babel. Babel bietet die Möglichkeit viele geeignete Features zu transpilieren. Für andere Features, die nicht so einfach transpiliert werden können bietet Babel sogenannte Polyfills zum Download.

Wenn du Babel einfach mal ausprobieren möchtest, kannst du dir die online version anschauen, bei der du auf der linken Seite Code eingeben kannst und auf der rechten Seite die transpilierte Version angezeigt bekommst.

Note: Man kann Babel auf ganz verschiedene Arten benutzen (Task runner, Automatisierungstools, usw.) wie du auf der setup page sehen kannst.

Using bad browser sniffing code

All browsers have a user-agent string, which identifies what the browser is (version, name, OS, etc.) In the bad only days when pretty much everyone used Netscape or Internet Explorer, developers used to use so-called browser sniffing code to detect which browser the user was using, and give them appropriate code to work on that browser.

The code used to look something like this (although this is a simplified example):

var ua = navigator.userAgent;

if(ua.indexOf('Firefox') !== -1) {
  // run Firefox-specific code
} else if(ua.indexOf('Chrome') !== -1) {
  // run Chrome-specific code
}

The idea was fairly good — detect what browser is viewing the site, and run code as appropriate to make sure the browser will be able to use your site OK.

Note: Try opening up your JavaScript console now and running navigator.userAgent, to see what you get returned.

However, as time went on, developers started to see major problems with this approach. For a start, the code was error prone. What if you knew a feature didn't work in say, Firefox 10 and below, and implemented code to detect this, and then Firefox 11 came out — which did support that feature? Firefox 11 probably wouldn't be supported because it's not Firefox 10. You'd have to change all your sniffing code regularly.

Many developers implemented bad browser sniffing code and didn't maintain it, and browsers start getting locked out of using websites containing features that they had since implemented. This became so common that browsers started to lie about what browser they were in their user-agent strings (or claim they were all browsers), to get around sniffing code. Browsers also implemented facilities to allow users to change what user-agent string the browser reported when queried with JavaScript. This all made browser sniffing even more error prone, and ultimately pointless. 

Note: You should read History of the browser user-agent string by Aaron Andersen for a useful and amusing take on this situation.

The lesson to be learned here is — NEVER use browser sniffing. The only really use case for browser sniffing code in the modern day is if you are implementing a fix for a bug in a very specific version of a particular browser. But even then, most bugs get fixed pretty quickly in browser vendor rapid release cycles. It won't come up very often. Feature detection is almost always a better option — if you detect whether a feature is supported, you won't need to change your code when new browser versions come out, and the tests are much more reliable.

If you come across browser sniffing when joining an existing project, look at whether it can be replaced with something more sensible. Browser sniffing causes all kind of interesting bugs, like Bug 1308462.

Handling JavaScript prefixes

In the previous article, we included quite a lot of discussion about handing CSS prefixes. Well, new JavaScript implementations sometimes use prefixes too, although JavaScript uses camel case rather than hyphenation like CSS. For example, if a prefix was being used on a new shint API object called Object:

  • Mozilla would use mozObject
  • Chrome/Opera/Safari would use webkitObject
  • Microsoft would use msObject

Here's an example, taken from our violent-theremin demo (see source code), which uses a combination of the Canvas API and the Web Audio API to create a fun (and noisy) drawing tool:

var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioCtx = new AudioContext();

In the case of the Web Audio API, the key entry points to using the API were supported in Chrome/Opera via webkit prefixed versions (they now support the unprefixed versions). The easy way to get around this situation is to create a new version of the objects that are prefixed in some browsers, and make it equal to the non-prefixed version, OR the prefixed version (OR any other prefixed versions that need consideration) — whichever one is supported by the browser currently viewing the site will be used.

Then we use that object to manipulate the API, rather than the original one. In this case we are creating a modified AudioContext constructor, then creating a new audio context instance to use for our Web Audio coding.

This pattern can be applied to just about any prefixed JavaScript feature. JavaScript libraries/polyfills also make use of this kind of code, to abstract browser differences away from the developer as much as possible.

Again, prefixed features were never supposed to be used in production websites — they are subject to change or removal without warning, and cause cross browser issues. If you insist on using prefixed features, make sure you use the right ones. You can look up what browsers require prefixes for different JavaScript/API features on MDN reference pages, and sites like caniuse.com. If you are unsure, you can also find out by doing some testing directly in browsers.

For example, try going into your browser's developer console and start typing

window.AudioContext

If this feature is supported in your browser, it will autocomplete.

Finding help

There are many other issues you'll encounter with JavaScript; the most important thing to know really is how to find answers online. Consult the HTML and CSS article's Finding help section for our best advice.

Summary

So that's JavaScript. Simple huh? Maybe not so simple, but this article should at least give you a start, and some ideas on how to tackle the JavaScript-related problems you will come across.

 

In this module

 

Schlagwörter des Dokuments und Mitwirkende

Mitwirkende an dieser Seite: mdnwebdocs-bot, Alexa
Zuletzt aktualisiert von: mdnwebdocs-bot,