2.3 - JavaScript Language¶
The JavaScript language was developed alongside HTML to enable the manipulation of web pages after they get downloaded to a client. JavaScript runs silently in the background to process user events and dynamically modify a web page. If JavaScript code has problems or produces errors it is designed to fail silently so that the average user has no clue something went wrong.
JavaScript and Java have nothing in common. They are totally different languages designed for different purposes.
The major distinctions of JavaScript are:
- It is an interpreted programming language; it is not compiled, though there are some just-in-time JavaScript compilers that increase its execution speed.
- Variables are dynamically typed. The type of data in a variable can change at any time. One moment a variable might contain a string, the next moment a number.
- JavaScript never “crashes”. If an error occurs, the current thread of execution is terminated, but this has no effect on the operation of the web browser.
- JavaScript is object-oriented and functions are actually objects!
- JavaScript is a multi-paradigm language that supports ideas from object-oriented programming, imperative programming, and functional programming.
The syntax of JavaScript is closely related to the C programming language. This JavaScript Quick Reference Card contains most of the language syntax you should need for doing WebGL programming. Please take some time to study this reference card. The legend for the color coding on the card is at the end of the document.
Context¶
Everything in JavaScript is an object. Objects are instances of a class.
Objects contain data and have
functions that can manipulate that data. Functions of an object
have a context in which they work. Their context is defined by the data they
can access and the current state of that data. The idea that every function
in JavaScript is executed from a “context” is central to understanding how
JavaScript works. Let’s walk through some examples. If a function is called
because a user clicked on a button, then the function is executed in the
context of the HTML button element. If a function is called from code loaded
from a JavaScript file, then it is executed in the context of the global
address space. If a function is called from inside an object, then its context
is that object. There is a keyword in JavaScript that always references the
context from which a function is called. The keyword is called this
. The
keyword this
in Java, C++ and other object-oriented programming languages
means something totally different. Do not get them confused.
This is so important that it need to be repeated. In JavaScript, the keyword
this
references the context from which a function is called. You will see
how to use this
in powerful ways as you get more experience with JavaScript.
Classes and Objects in JavaScript¶
Defining classes and objects in JavaScript can be confusing, especially if you expect JavaScript to behave like other languages you know. Since we will be developing all of our WebGL code as objects, we need to specifically discuss how JavaScript implements object oriented programming.
A class is defined in the same way a normal function is defined. The function
definition is basically the constructor of the class. If you call the function
in the normal way, it will act like a normal function. If you use the new
command in front of the function call you are creating an object of that class.
Hopefully an example will make this clearer. Here is a simple function definition.
function Example(a,b,c) {
// Do some stuff
}
You can call this function in the normal way and it will perform some processing, like this:
Example(alpha, beta, gamma);
Or, you can create an object from the definition like this:
var my_object = new Example(alpha, beta, gamma);
When you create an object, any data defined inside the function is retained inside the object and the data can be accessed and modified at a later time.
Public and Private Data in a Class¶
By default, variables declared inside a function that defines a class are private. In the following example, all of the data and functions are private.
function ExampleClass(a,b,c) {
// Private class variables
var s,t,u;
// Private functions
function _innerOne() {
// can manipulate s, t, and u
}
function _innerTwo() {
// can manipulate s, t, and u
}
}
This is an example of an immutable object in JavaScript. An object created
from this class can’t be modified because it has no public data or public functions.
To make variables or functions
public you add them to the object as properties. Properties of an object
are accessed using dotted notation, as in object.property
. Since
JavaScript is an interpreted and dynamic language, new properties can be
added to an object at any time. This can cause hard to find errors if you misspell
property names. Instead of manipulating an existing property, a misspelled
property name will add an new unwanted property to an object. So watch your spelling!
When an object is created by calling the new
command, the this
keyword is a reference to the new object (just like in Java and C++). Therefore,
you can prefix any variable or function with the this
keyword to
make them public. Below is an example class definition that includes
both private and public data and functions.
function ExampleClass(a,b,c) {
// Private class variables
var s,t,u;
// Public class variables (actually properties of the object)
this.m = value1;
this.n = value2;
// Public function
this.doSomething = function () {
// can manipulate all private and public data
// can call all private and public functions
}
// Private function
function _innerOne() {
// can manipulate all private and public data
// can call all private and public functions
}
}
If we make an object from this example class like this:
var my_object = new ExampleClass(alpha, beta, gamma);
Then the following statements are valid because they are accessing the public members of the object.
my_object.doSomething();
my_object.m = 5;
However, the following statements are invalid because they are attempting to use the private members of the object.
my_object._innerOne(); // would cause an error
my_object.s = 5; // would cause an error
But wait! The above example has a major flaw. The value of the keyword
this
changes with context. When the object is actually used,
the keyword this
will take on various other values besides a reference
to the object. This will cause the code to fail. The solution is to not use the keyword
this
for accessing public members. We will use this
in the constructor
to get a reference to the object, not use it in the member functions.
The example below shows how this works. When
the constructor is executed the keyword this
will be a reference to the new
object because of the new
command context. It is a relatively standard practice
to use a private variable called self
to store a reference to the new
object inside the object itself. Then the local private variable self
is used throughout the rest of the class definition.
function ExampleClass(a,b,c) {
var self = this; // store a local reference to the new object
// Private class variables
var s,t,u;
// Public class variables (actually properties of the object)
self.m = value1;
self.n = value2;
// Public function
self.doSomething = function () {
// can manipulate all private and public data using self.property
// can call all private and public functions using self.property
}
function _innerOne() {
// can manipulate all private and public data using self.property
// can call all private and public functions using self.property
}
}
You are encouraged to re-read the above description. Often the second reading makes more sense.
Some Examples¶
In the WebGL demo code below there are two examples of JavaScript class definitions. Do not attempt to understand the functionality of the code at this time, but rather exam the structure of the class definitions. As you study the examples, please note that:
- All code that is not inside a member function composes the constructor of the class. This code executes once when objects of the class are created.
- Using
strict mode
, which requires that all variables be defined before they are used, means that the constructor code is sometimes not contiguous. - Notice how the variable
self
is used to define and access the public members of an object. - The important thing to read in these examples is the comments!
/**
* learn_webgl_render_01.js, By Wayne Brown, Fall 2015
*
* Given
* - a model definition as defined in learn_webgl_model_01.js, and
* - specific shader programs: vShader01.vert, fShader01.frag
* Perform the following tasks:
* 1) Build appropriate Vertex Object Buffers (VOB's)
* 2) Create GPU VOB's for the data and copy the data into the buffers.
* 3) Render the VOB's
*/
/**
* The MIT License (MIT)
*
* Copyright (c) 2015 C. Wayne Brown
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
"use strict";
// Global definitions used in this code:
//var Float32Array, Uint16Array, parseInt, parseFloat, console;
//-------------------------------------------------------------------------
// Build, create, copy and render 3D objects specific to a particular
// model definition and particular WebGL shaders.
//-------------------------------------------------------------------------
var Scene_object_example_render = function (learn, vshaders_dictionary,
fshaders_dictionary, models) {
var self = this; // Store a local reference to the new object.
//-----------------------------------------------------------------------
// Public function to render the scene.
self.render = function () {
var j, model_names;
// Clear the entire canvas window background with the clear color
// out.display_info("Clearing the screen");
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Build individual transforms
matrix.setIdentity(transform);
matrix.rotate(rotate_x_matrix, self.angle_x, 1, 0, 0);
matrix.rotate(rotate_y_matrix, self.angle_y, 0, 1, 0);
// Combine the transforms into a single transformation
matrix.multiplySeries(transform, transform, rotate_x_matrix, rotate_y_matrix);
// Draw each model
model_names = Object.keys(model_VOBs);
for (j = 0; j < model_names.length; j += 1) {
model_VOBs[model_names[j]].render(gl, transform_location, transform);
}
};
//-----------------------------------------------------------------------
// Public function to delete and reclaim all rendering objects.
self.delete = function () {
var j, model_names;
// Clean up shader programs
gl.deleteShader(program.vShader);
gl.deleteShader(program.fShader);
gl.deleteProgram(program);
// Delete each model's VOB
model_names = Object.keys(model_VOBs);
for (j = 0; j < model_names.length; j += 1) {
model_VOBs[model_names[j]].delete(gl);
}
// Remove all event handlers
var id = '#' + canvas_id;
$( id ).unbind( "mousedown", events.mouse_drag_started );
$( id ).unbind( "mouseup", events.mouse_drag_ended );
$( id ).unbind( "mousemove", events.mouse_dragged );
events.removeAllEventHandlers();
// Disable any animation
self.animate_active = false;
};
//-----------------------------------------------------------------------
// Object constructor. One-time initialization of the scene.
// Private variables
var canvas_id = learn.canvas_id;
var out = learn.out;
var gl = null;
var program = null;
var model_VOBs = {};
var matrix = new Learn_webgl_matrix();
var transform = matrix.create();
var transform_location = 0;
var rotate_x_matrix = matrix.create();
var rotate_y_matrix = matrix.create();
// Public variables that will be changed by event handlers.
self.canvas = null;
self.angle_x = 0.0;
self.angle_y = 0.0;
self.animate_active = true;
// Get the rendering context for the canvas
self.canvas = learn.getCanvas(canvas_id);
if (self.canvas) {
gl = learn.getWebglContext(self.canvas);
}
if (!gl) {
return null;
}
// Set up the rendering program and set the state of webgl
program = learn.createProgram(gl, vshaders_dictionary["shader05"], fshaders_dictionary["shader05"]);
gl.useProgram(program);
// Enable hidden-surface removal
gl.enable(gl.DEPTH_TEST);
// Remember the location of the transformation variable in the shader program
transform_location = gl.getUniformLocation(program, "u_Transform");
// Create Vertex Object Buffers for the models
var j, key_list;
key_list = Object.keys(models);
for (j = 0; j < key_list.length; j += 1) {
model_VOBs[key_list[j]] = new Learn_webgl_vob_model_01(gl, program, models[key_list[j]], out);
}
// Set up callbacks for user and timer events
var events;
events = new Learn_webgl_events(self, controls);
events.animate();
var id = '#' + canvas_id;
$( id ).mousedown( events.mouse_drag_started );
$( id ).mouseup( events.mouse_drag_ended );
$( id ).mousemove( events.mouse_dragged );
};
/**
* learn_webgl_events_01.js, By Wayne Brown, Fall 2015
*
* These event handlers can modify the characteristics of a scene.
* These will be specific to a scene's models and the models' attributes.
*/
/**
* The MIT License (MIT)
*
* Copyright (c) 2015 C. Wayne Brown
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
"use strict";
//-------------------------------------------------------------------------
function Learn_webgl_events(scene, control_id_list) {
var self = this; // Store a local reference to the new object.
//-----------------------------------------------------------------------
self.mouse_drag_started = function (event) {
//console.log("started mouse drag event x,y = " + event.clientX + " " + event.clientY + " " + event.which);
start_of_mouse_drag = event;
event.preventDefault();
if (animate_is_on) {
scene.animate_active = false;
}
};
//-----------------------------------------------------------------------
self.mouse_drag_ended = function (event) {
//console.log("ended mouse drag event x,y = " + event.clientX + " " + event.clientY + " " + event.which);
start_of_mouse_drag = null;
event.preventDefault();
if (animate_is_on) {
scene.animate_active = true;
self.animate();
}
};
//-----------------------------------------------------------------------
self.mouse_dragged = function (event) {
var delta_x, delta_y;
//console.log("drag event x,y = " + event.clientX + " " + event.clientY + " " + event.which);
if (start_of_mouse_drag) {
delta_x = event.clientX - start_of_mouse_drag.clientX;
delta_y = -(event.clientY - start_of_mouse_drag.clientY);
//console.log("moved: " + delta_x + " " + delta_y);
scene.angle_x += delta_y;
scene.angle_y -= delta_x;
scene.render();
start_of_mouse_drag = event;
event.preventDefault();
}
};
//-----------------------------------------------------------------------
self.key_event = function (event) {
var bounds, keycode;
bounds = canvas.getBoundingClientRect();
console.log("bounds = " + bounds.left + " " + bounds.right + " " + bounds.top + " " + bounds.bottom);
console.log("target = " + event.target);
if (event.clientX >= bounds.left &&
event.clientX <= bounds.right &&
event.clientY >= bounds.top &&
event.clientY <= bounds.bottom) {
keycode = (event.keyCode ? event.keyCode : event.which);
console.log(keycode + " keyboard event in canvas");
}
event.preventDefault();
return false;
};
//------------------------------------------------------------------------------
self.html_control_event = function (event) {
var control;
control = $(event.target);
if (control) {
switch (control.attr('id')) {
case "my_pause":
if (control.is(":checked")) {
animate_is_on = true;
scene.animate_active = true;
self.animate();
} else {
animate_is_on = false;
scene.animate_active = false;
}
}
}
};
//------------------------------------------------------------------------------
self.animate = function () {
var now, elapsed_time;
if (scene.animate_active) {
now = Date.now();
elapsed_time = now - previous_time;
if (elapsed_time >= frame_rate) {
scene.angle_x -= 0.5;
scene.angle_y += 1;
scene.render();
previous_time = now;
}
requestAnimationFrame(self.animate);
}
};
//------------------------------------------------------------------------------
self.removeAllEventHandlers = function () {
var j;
for (j = 0; j < control_id_list.length; j += 1) {
var control = $('#' + control_id_list);
if (control) {
control.unbind("click", self.html_control_event);
}
}
};
//------------------------------------------------------------------------------
// Constructor code for the class.
// Private variables
var out = scene.out; // Debugging and output goes here.
var canvas = scene.canvas;
// Remember the current state of events
var start_of_mouse_drag = null;
var previous_time = Date.now();
var animate_is_on = scene.animate_active;
// Control the rate at which animations refresh
var frame_rate = 16; // 16 milliseconds = 1/60 sec
// Add an onclick callback to each HTML control
var j;
for (j = 0; j < control_id_list.length; j += 1) {
var control = $('#' + control_id_list);
if (control) {
control.click( self.html_control_event );
}
}
}
Coding Standard¶
Before leaving this discussion of JavaScript, please review the coding standard we will be using.
Always include
"use strict";
to force the declaration of variables.Avoid global variables whenever possible.
Use JSLint to check for errors. (The Pycharm IDE will do this for you.)
Use two-space indentation.
Use shorthand for conditional statements where appropriate:
var results = (test === 5) ? alert(1) : alert(2);
The closing brace should be on the same indent as the original statement:
function func() { return { "name": "Batman" }; }
Naming conventions
Constructors start with a capital letter.
Methods/functions start with a small letter.
Methods/functions should use camel case.
thisIsAnExample
Variables should always use an underscore between words.
this_is_an_example
When appropriate, include the variable type in the name.
value_list
Element ID’s and class names should always use an underscore between words.
Private methods should use a leading underscore to separate them from public methods.
Abbreviations should not be used in names.
Plurals should not be used when assigning names.
Comments should be used within reason.
Use YUIDoc to document functions.
/** * Reverse a string * * @param {String} input_string String to reverse * @return {String} The reversed string */ function reverseString(input_string) { // ... return output_string; };
Glossary¶
- JavaScript
- JavaScript is a high-level, dynamic, untyped, and interpreted programming language that is used to manipulate the HTML and CSS code of a web page after the code has been downloaded to a client.
- class
- A construct that holds data and functions that can manipulate that data.
- object
- An instance of a class. For example, a class might hold data and functions related to an automobile. Multiple instances of the class can be created to store specific data about individual automobiles.
- object property
- A specific piece of data stored in an object.
- JavaScript
this
keyword - A builtin variable in JavaScript that always references the context in which a function is executing.