JavaScript

Content

 

Intro

Grammar

Objects

Functions

Inheritance

Arrays

 

 

Intro

Intro

  • Language of the browser
  • Thus - one of the most popular programming languages of the world
  • At the same time, there is a lot of controversy around JS
  • DOM
    • Awful API, JS is blamed
    • Poorly specified, inconsitent implementation
  • JS
    • Expressive language
    • Get things done without knowing all parts

Intro #2

  • JS based on some good and bad ideas
  • Good ideas
    • Functions, loose typing, dynamic objects, expressive object literal notation
  • Bad ideas
    • Programming model based on global variables
  • Functions are first class objects
  • First main stream lambda language
  • JS more in common with Lisp and Scheme than Java
  • JS ~ Lisp in C clothing

Intro #3

  • Most programming languages demand strong typing
    • Detect large class of errors at compile time
  • JS loosely typed language
    • Unable to detect type errors
  • Strong vs. loosely typing 
    • Strong typing still needs careful testing
    • Loose typing liberating
    • No complex class hierarchies
    • No fighting with the type system
  • Powerful object literal notation (inspired JSON)
  • Prototypal inheritance
    • Controversal feature

Grammar

Whitespace

  • Used to format characters or comments
  • Usually insignificant
  • Occassionally used to separate sequences of characters that would be combined into a single token
  • var myVar = 'Hi';
  • Space between var and myVar cannot be removed but other whitespace is optional
  • var myVar='Hi';
  • Comments
    • Block comments: /* */
    • Line-ending comments: //
  • Use Line-ending comments
    • /* may occur in regular expressions 
  • Write accurate comments, bad comments are worse than no comments

Names

  • Name is a letter optionally followed by one or more letters, digits, or underbars
  • Name cannot be a reserved word
    • abstract
    • boolean break byte
    • case catch char class const continue ...
  • Some words that cannot be used and are not reserved
    • undefined NaN Infinity
  • Names are used for statements, variables, parameters, property names, operators, and labels
  • Not permitted to name a variable or paramter with a reserved word
  • Not permitted as object property 

Numbers

  • JS has single number type
  • Internally represented as 64-bit floating point
  • double in Java
  • 1 and 1.0 are the same
  • All you have to know about a number is that it's a number
  • Large class of numeric type errors are avoided
  • Numbers may have exponent part
    • 100 ~ 1e2
  • Negative numbers by - prefix operator
  • NaN is a number value of result of an operation that cannot produce a normal result
  • NaN is not equal to any value except to itself
    • isNaN(number) may be used to detect NaN
  • Infinity all numbers greater 1.79769313486231570e+308

Strings

  • String literal may be packed in "abc" or 'abc'
  • Zero or more characters
  • \ (backslash) is escape character
  • JS characters are 16-bit wide
    • At beginning of JS Unicode was 16-bit character set
  • JS does not have a character type
  • Escape sequence allow for inserting characters into strings that are not normally permitted
  • 'A' === '\u0041'
  • String have a length property
    • 'JavaScript'.length is 10
  • Strings are immutable
  • Concatenating strings with + operator 
  • 'c' + 'a' + 't' === 'cat'

Statements

  • Compilation unit contains set of executable statements
  • Each <script> tag delivers a compilation unit, that is compiled and executed
  • JS has no linker
    • throws all compilation units in a global namespace
  • var statements
    • var myVar = 'test';
    • When used in functions, var statement defines function's local variables
  • switch, while, for, and do statements are allowed to have a label statement as prefix (interact with break statement)

 

Statements #2

  • Statements tend to be executed in order
    • from top to bottom
  • Alter sequence of execution with conditional statements
    • if and switch
    • Looping statements: while, for, do
    • Disruptive statements: break, return, throw
    • function invocation
  • Block is a set of statements wrapped in curly braces
  • Blocks in JavaScript do NOT create a new scope
  • Then block of if statement is executed if the expression is truthy
  • Falsy values: false, null, undefined, '', 0, NaN
  • All other values are truthy:
    • true, 'false', objects, ...

Expressions

  • literal values (strings, numbers)
  • variables
  • built-in values (true, false, null, undefined, ...)
  • invocation expression preceded by new
  • refinement expression preceded by delete
  • expression wrapped in parantheses
  • expression preceded by a prefix operator
  • expression followed by
    • Infix operator or another expression
    • Ternary operator (?:)
    • Invocation
    • Refinement

Operator Precedence

. [] ( )                         Refinement and invocation
delete new typeof + - !          Unary operators
* / %                            Multiplication, division, modulo
+ -                              Addition/concatenation, subtraction
>= <= > <                        Inequality
=== !==                          Equality
&&                               Logical and
||                               Logical or
?:                               Ternary

Objects

JavaScript Types

  • Simple types are numbers, strings, booleans (true, false), null and undefined
  • All other values are objects
  • Numbers, strings and booleans are object like
    • they have methods
    • but immutable
  • Objects in JavaScript are mutable keyed collections
  • Arrays, functions and regular expressions are objects too 

JavaScript Types #2

  • Object is a container of properties
  • Each property has a name and a value
  • Property name can be any string (including empty string)
  • Property value can be any value except for undefined
  • Objects in JavaScript are class-free
    • No constraints on the names or values of properties
  • Objects are useful for collecting and organizing data
  • Objects can contain other objects
  • JavaScript provides prototype feature
    • Allows an object to inherit the properties of another
    • Reduces object init time and memory consumptions

Object Literals

var empty_object = {};

var stooge = {
    "first-name": "Jerome",
    "last-name": "Howard"
};

var flight = {
    airline: "Oceanic",
    number: 815,
    departure: {
        IATA: "SYD",
        time: "2004-09-22 14:55",
        city: "Sydney"
    },
    arrival: {
        IATA: "LAX",
        time: "2004-09-23 10:42",
        city: "Los Angeles"
    }
};

Object Retrieval

stooge["first-name"] // "Joe"

stooge["middle-name"] // undefined
flight.status // undefined
stooge["FIRST-NAME"] // undefined

var middle = stooge["middle-name"] || "(none)";
var status = flight.status || "unknown";

flight.equipment // undefined
flight.equipment.model // throw "TypeError"
flight.equipment && flight.equipment.model // undefined

Object Update

stooge['first-name'] = 'Jerome';

// If the object does not already have that property name, the object is augmented:
stooge['middle-name'] = 'Lester';
stooge.nickname = 'Curly';

flight.equipment = {
    model: 'Boeing 777'
};

flight.status = 'overdue';

Object Reference

var x = stooge;
x.nickname = 'Curly';
var nick = stooge.nickname;
// nick is 'Curly' because x and stooge
// are references to the same object

var a = {}, b = {}, c = {};
// a, b, and c each refer to a
// different empty object

a = b = c = {};
// a, b, and c all refer to
// the same empty object

Prototype

  • Every object is linked to a prototype object from which it can inherit properties
  • All objects created  from object literals are linked to Object.prototype
    • Std object in JavaScript
  • When creating a new object, you may select the prototype

Prototype #2

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}

var another_stooge = Object.create(stooge);

another_stooge['first-name'] = 'Harry';
another_stooge['middle-name'] = 'Moses';
another_stooge.nickname = 'Moe';
  • When object is updated, the prototype is not touched
  • Prototype link has just effect on updating
  • If we try to retrieve property from an object, object lacks that property
    • Runtime tries to retrieve it from the prototype object
    • All up the prototype chain (delegation)
  • If property does not exist in prototype chain - undefined

Prototype #3

stooge.profession = 'actor';
another_stooge.profession // 'actor'
  • The prototype relationship is dynamic
  • If we add a new property to a prototype, that property will immediately be visible in all of the objects that are based on that prototype

Reflection

typeof flight.number      // 'number'
typeof flight.status      // 'string'
typeof flight.arrival     // 'object'
typeof flight.manifest    // 'undefined'

typeof flight.toString    // 'function'
typeof flight.constructor // 'function'
// typeof consideres the whole prototype chain

// hasOwnProperty checks own object
flight.hasOwnProperty('number') // true
flight.hasOwnProperty('constructor') // false

Delete

another_stooge.nickname // 'Moe'
// Remove nickname from another_stooge, revealing
// the nickname of the prototype.

delete another_stooge.nickname;
another_stooge.nickname // 'Curly'
  • Delete operator may used to remove a property from an object
  • It will remove a property from the object if it has one
  • Will not touch objects in the prototype linkage
  • Removing a property from an object may reveal prototype property

Functions

Function Objects

  • Best thing about JavaScript is the implementation of functions
  • Functions enclose a set of statements
  • Fundamental modular unit of JavaScript
  • Used for code reuse, information hiding and composition
  • Functions in JavaScript are objects
  • Objects are a collection of name/value pairs having a hidden link to a prototype object

Function Objects #2

  • Object produced from object literal are linked to Object.prototype
  • Function objects are linked to Function.prototype which itself is linked to Object.prototype
  • Since functions are objects they can be used like any other value
  • Can be stored in variables, objects and arrays
  • Functions can be passed as function argument, and functions may return functions
  • Functions can be invoked

Function Literal

// Create a variable called add and store a function
// in it that adds two numbers.
var add = function (a, b) {
    return a + b;
};
  • Function literal has 4 parts
  • function keyword
  • function name 
    • anonymous if not given
  • function parameters wrapped in parantheses
  • function body
    • set of statements wrapped in curly braces

Closure

  • function literal can appear everywhere where an expression is valid
  • functions can be defind inside functions
  • Inner function has access to parameters and variables of outer function
  • function object created by a function literal contains a link to the outer context (closure)
  • Powerful language property

Method Invocation

  • Suspends the execution of the current function, passing control and parameters to the new function
  • In addition to declared parameters every function receives two additional parameters: this and arguments
  • Value of this parameters determined by invocation pattern
  • Invocation operator is a pair of parantheses that follows any expression that produces a function value
  • Parantheses can contain zero or more expressions that produces a parameter value
  • No runtime error when number of arguments and number of parameters do not match 
    • If there are too few arguments, missing values will be undefined
    • If there are too much arguments, they will be ignored

Method Invocation Pattern

// Create myObject. It has a value and an increment
// method. The increment method takes an optional
// parameter. If the argument is not a number, then 1
// is used as the default.
var myObject = {
    value: 0,
    increment: function (inc) {
    this.value += typeof inc === 'number' ? inc : 1;
    }
};

myObject.increment( );
document.writeln(myObject.value); // 1
myObject.increment(2);
document.writeln(myObject.value); // 3
  • When a function is stored as a property of an object, we call it a method
  • When a method is invoked, this argument is bound to that object
  • this parameter can be used to access properties of the object

Function Invocation Pattern

var sum = add(3, 4); 

// Augment myObject with a double method.
myObject.double = function() {
    var that = this; // Workaround.
    var helper = function() {
        that.value = add(that.value, that.value);
    };
    helper(); // Invoke helper as a function.
};

// Invoke double as a method.
myObject.double();
document.writeln(myObject.getValue( )); // 6
  • When a function is not a property of an object, then it is invoked as a function
  • this parameter is bound to the global object! (design mistake)
  • Easy workaround

Constructor Invocation Pattern

  • JavaScript is a prototypal inheritance language
  • Objects can inherit properties directly from other objects
  • No classes!
  • Differnt to most of the current languages
  • Therefore prototypal nature is hidden with strange object-making syntax
  • If function is invoked with the new prefix, then a new object will be created with a hidden link to the value of the function's prototype member

Constructor Invocation Pattern #2

// Create a constructor function called Quo.
// It makes an object with a status property.
var Quo = function (string) {
    this.status = string;
};

// Give all instances of Quo a public method
// called get_status.
Quo.prototype.get_status = function ( ) {
    return this.status;
};

// Make an instance of Quo.
var myQuo = new Quo("confused");
document.writeln(myQuo.get_status( )); // confused
  • Functions that are intended to be used with the new prefix are called constructors
  • Variable names should be capitalized names

The Apply Invocation Pattern

// Make an array of 2 numbers and add them.
var array = [3, 4];
var sum = add.apply(null, array); // sum is 7

// Make an object with a status member.
var statusObject = {
status: 'A-OK'
};

// statusObject does not inherit from Quo.prototype,
// but we can invoke the get_status method on
// statusObject even though statusObject does not have
// a get_status method.
var status = Quo.prototype.get_status.apply(statusObject);
// status is 'A-OK'
  • Apply method lets us construct an array of arguments to use to invoke a function
  • Also allows to choose the value of the this argument
  • First argument is the this value
  • Second argument is an array of parameters

Arguments Parameter

var sum = function ( ) {
    var i, sum = 0;
    for (i = 0; i < arguments.length; i += 1) {
        sum += arguments[i];
    }
    return sum;
};

document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108
  • Bonus parameter that is available to functions when they are invoked
  • Gives access to all function parameters including excess arguments
  • Makes it possible to write functions that take an unspecified number of parameters

Return

  • When a function is invoked it begins execution with the first statement and ends on }
  • Return statements can be used to return earlier
  • Function always return a value, if not specified it will be unspecified
  • If the function was invoked with the new prefix and the return value is not an object it will return the value of this

Exceptions

var add = function (a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        }; 
    }
    return a + b;
}
  • Throw statement interrupts execution of the function
  • Exception object should contain a name and message property

Exceptions #2

// Make a try_it function that calls the new add function incorrectly.
var try_it = function () { 
    try {
        add("seven");
    } catch (e) {
        document.writeln(e.name + ': ' + e.message);
    }
}

try_it();
  • Exception object is delivered to the catch clause of a try statement
  • Try statement has a single catch block that will catch all exceptions
  • Inspect name property to determine type of exception

Augmenting Types

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};
  • JavaScript allows the basic types of the language to be augmented
  • When adding a method to Object.prototype it is available to all objects
  • Same works for functions, arrays, strings, numbers, regular expressions
  • Example: make a function available to all functions

Recursion

var hanoi = function (disc, src, aux, dst) {
    if (disc > 0) {
        hanoi(disc - 1, src, dst, aux);
        document.writeln('Move disc ' + disc +
            ' from ' + src + ' to ' + dst);
        hanoi(disc - 1, aux, src, dst);
    } 
};

hanoi(3, 'Src', 'Aux', 'Dst');
  • Recursive function calls itself
  • Towers of Hanoi
  • Tail recursion optimization
    • If a function returns the result of invoking itself, recursion is replaced by a loop
    • Speed up 

Scope

var foo = function () { 
    var a = 3, b = 5;
    var bar = function () { 
        var b = 7, c = 11;
        // At this point, a is 3, b is 7, and c is 11
        a += b + c;
        // At this point, a is 21, b is 7, and c is 11
    };

    // At this point, a is 3, b is 5, and c is not defined bar();
    // At this point, a is 21, b is 5
};
  • Scope controls visibility and lifetimes of variables and parameters
  • Important for developers
    • Reduces naming collisions
    • Provides automatic memory management

Scope #2

  • Most languages with C syntax have block scope
    • All variables in a block are not visible from outside of the block
    • Variables may be released when the execution of the block is finished
  • JavaScript does not have block scope
  • Function scope
    • Variables defined in a function are not visible outside of the function
    • Declare all variables at the top of a function

Closure

var myObject = function () { 
    var value = 0;
    return {
        increment: function (inc) {
            value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function () { 
            return value;
        } 
    };
}();
  • Inner functions get access to parameters and variables of the functions they are defined within (expect this and arguments)

Callbacks

// sync - client is blocked
request = prepare_the_request();
response = send_request_synchronously(request); 
display(response);

// async
request = prepare_the_request(); 
send_request_asynchronously(request, function (response) {
    display(response);
});

Cascade

getElement('myBoxDiv').
    move(350, 150).
    width(100).
    height(100).
    color('red').
    border('10px outset').
    padding('4px').
    appendText("Please stand by").
    on('mousedown', function (m) {
        this.startDrag(m, this.getNinth(m));
    }).
    on('mousemove', 'drag').
    on('mouseup', 'stopDrag'). 
    later(2000, function () {
        this.color('yellow').
        setHTML("What hath God wraught?").
        slide(400, 40, 200, 200);
    }).
    tip('This box is resizeable');
  • Some methods do not have return value
  • If we return this instead of undefined, cascade is enabled

Inheritance

Intro

  • Promotes code reuse
  • Introduces a type system
  • In classical languages
    • Objects are instances of classes
    • Class can inherit from another class
  • JavaScript prototypal style
    • Objects inherit directly from other objects
  • Many code reuse patterns

Pseudoclassical

var Mammal = function (name) {
    this.name = name;
};

Mammal.prototype.get_name = function () { 
    return this.name;
};

Mammal.prototype.says = function () { 
    return this.saying || '';
};

var myMammal = new Mammal('Herb the Mammal');
var name = myMammal.get_name(); // 'Herb the Mammal'
  • JavaScript hides prototypal nature 
  • Instead of objects inheriting directly from objects and unnecessary level of indirection is used
    • Objects are produced by constructors

Pseudoclassical #2

var Cat = function (name) {
        this.name = name;
        this.saying = 'meow';
};

// Replace Cat.prototype with a new instance of Mammal
Cat.prototype = new Mammal();

// Augment the new prototype with
// purr and get_name methods.
Cat.prototype.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
        if (s) {
            s += '-'; 
        }
        s += 'r'; 
    }
    return s; 
};

Cat.prototype.get_name = function () { 
    return this.says() + ' ' + this.name + ' ' + this.says();
};

var myCat = new Cat('Henrietta');
var says = myCat.says(); // 'meow'
var purr = myCat.purr(5); // 'r-r-r-r-r' 
var name = myCat.get_name(); // 'meow Henrietta meow'

Pseudoclassical #3

  • All properties are public!
  • Constructor has to be called with new keyword
  • Provides comfort to programmers who are unfamiliar with JavaScript

Object Specifiers

var myObject = maker(f, l, m, c, s);

var myObject = maker({
    first: f,
    last: l,
    state: s,
    city: c
});
  • Constructor with large number of parameters
  • Can be difficult to remeber the order of the arguments
  • Rewrite the constructor to accept a single object
  • Arguments can be listed in any order

Prototypal

var myMammal = {
    name : 'Herb the Mammal', get_name : function () {
        return this.name;
    },
    says : function () {
        return this.saying || '';
    } 
};


var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
    if (s) {
    s += '-';
    }
    s += 'r'; 
    }
    return s; 
};

myCat.get_name = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};

Prototypal #2

  • Prototypal inheritance is conceptually simpler than classical inheritance
  • New object can inherit the properties of an old object
  • Make a useful object
  • Construct similar objects via differential inheritance
  • Customizing a new object, specify the difference from the object on which it is based on

Functional

var mammal = function (spec) {
    var that = {};
    that.get_name = function () { 
        return spec.name;
    };
    
    that.says = function () { 
        return spec.saying || '';
    };
    return that;
};

var myMammal = mammal({name: 'Herb'});
  • With this inheritance patterns until now, we do not get privacy
  • All properties visible, no private variables, no private methods

Arrays

Intro

  • Linear allocation of memory
  • Integers are used to compute offsets
  • JavaScript does not have such an array
  • JavaScript provides an object that has some array-like characteristics
  • Converts array subscripts into strings that are used to make properties
  • Slower than real array
  • But more convenient to use

Array Literals

var empty = [];

var numbers = [
'zero', 'one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine'
];

empty[1] // undefined
numbers[1] // 'one'

empty.length // 0
numbers.length // 10
  • Provide convenient notation for creating new array values
  • Pair of square brackets surrounding zero or more values separated by commas 
  • Array literal can appear everywhere where an expression may appear
  • First value gets the property name '0'
  • Second value gets the property name '1'

Array Literals #2

var numbers_object = {
    '0': 'zero', '1': 'one', '2': 'two',
    '3': 'three', '4': 'four', '5': 'five',
    '6': 'six', '7': 'seven', '8': 'eight',
    '9': 'nine'
};
  • numbers array and numbers_object object produce similar results
  • 10 properties, containing same values
  • numbers inherits from Array.prototype
  • number_object inherits from Object.prototype
  • numbers inherits a larger set of useful methods
  • numbers gets the length property, numbers_object does not

Array Literals #2

var misc = [
    'string', 98.6, true, false, null, undefined,
    ['nested', 'array'], {object: true}, NaN,
    Infinity
];

misc.length // 10
  • In most languages all array elements must be the same type
  • In JavaScript an array may contain mixture of values

Length Property

var myArray = [];
myArray.length // 0
myArray[1000000] = true;
myArray.length // 1000001
  • Not upper bound for array size
  • If element is stored at a subscript greater than the current length, the length is increased
  • No array bound errors
  • Length property is the largest integer property name +1
  • Not necessarily the number of properties in the array
  • Length can be explicitly set
    • making it larger does not allocate more space
    • making it smaller deletes all elements with subscripts greater than the length

Length Property

numbers.length = 3;
// numbers is ['zero', 'one', 'two']

numbers[numbers.length] = 'shi';
// numbers is ['zero', 'one', 'two', 'shi']

numbers.push('go');
// numbers is ['zero', 'one', 'two', 'shi', 'go']
  • Use length to add new element at the end
  • Or use the push method

Delete 

// numbers is ['zero', 'one', 'two', 'shi', 'go']
delete numbers[2];
// numbers is ['zero', 'one', undefined, 'shi', 'go']

numbers.splice(2, 1);
// numbers is ['zero', 'one', 'shi', 'go']
  • Since JavaScript arrays are objects, delete operator can be used to remove elements from an array
  • Leaves a hole in the array, since elements to the left keep the names
  • Use splice method
    • First argument sets position
    • Second argument determines num of elements to delete
  • Splice takes time for big arrays

Enumeration

var i;
for (i = 0; i < myArray.length; i += 1) {
    document.writeln(myArray[i]);
}
  • Since JavaScript arrays are objects, for in statement may be used to iterate over all of the properties of an array
  • For in makes no guarantee about the order of the properties
  • Also problems with unexpected properties from prototype chain
  • Conventional for statement avoids these problems

Arrays vs. Objects

  • Common mistake in JavaScript is to use an object when an array is required or vice versa
  • Rule: When the property names are small sequentiel integers, use an array, otherwise use an object
  • JavaScript is confused about difference between arrays and objects
  • typeof operator reports 'object' for arrays
  • No good mechanism to distinguish between array and objects

Methods

Array.method('reduce', function (f, value) {
    var i;
    for (i = 0; i < this.length; i += 1) {
        value = f(this[i], value);
    }
    return value;
});
  • JavaScript provides a set of methods for acting on arrays
  • Functions stored in Array.prototype
  • Array.prototype and Obejct.prototype may be augmented
  • Reduce method takes a function and a starting value
  • For each element of the array, it calls the function with an element and the value, and computes a new value
  • E.g. pass in function that adds integers, result will be the sum

Method #2

Array.method('reduce', function (f, value) {
    var i;
    for (i = 0; i < this.length; i += 1) {
        value = f(this[i], value);
    }
    return value;
});


var data = [4, 8, 15, 16, 23, 42];

var add = function (a, b) {
    return a + b;
};

var sum = data.reduce(add, 0); // sum is 108

Dimensions

Array.dim = function (dimension, initial) {
    var a = [], i;
    for (i = 0; i < dimension; i += 1) {
        a[i] = initial;
    }
    return a;
};
// Make an array containing 10 zeros.
var myArray = Array.dim(10, 0);
  • JavaScript arrays are not initialized
  • If you ask for a new array with [], it will be empty
  • If you access a missing element, undefined will be returned
  • Some algorithms assume default value (0)

Dimensions #2

var matrix = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8]
];

matrix[2][1] // 7

Array.matrix = function (m, n, initial) {
    var a, i, j, mat = [];
    for (i = 0; i < m; i += 1) {
        a = [];
        for (j = 0; j < n; j += 1) {
            a[j] = initial;
        }
        mat[i] = a;
    }
    return mat;
};
  • Array of arrays
  • Write own init method

 

 

References

 

JavaScript: The Good Parts

Douglas Crockford

 

 

 

 

 

Thank you for your attention!

 

JavaScript

By dinony

JavaScript

JavaScript

  • 430