JavaScript
History
- Originally designed as a language to run in the browser to
- Add nice UI effects
- Reduce the need for page reloads (e.g., by performing form validation locally).
- 1995: Netscape creates LiveScript (quickly changed to JavaScript)
- Microsoft introduces VBScript and then Jscript
- 1996: Netscape proposes ECMAScript specification as a standard
- ECMA: European Computer Manufacturers Association
- https://www.ecma-international.org
- 1997: First version of ECMA-262
- 2015: ES6 – later called ES2015
- Introduced many of the “modern” features (classes, imports, () => notation, etc.)
- New version every year since
- Current version is 10th Edition (also called ES2019)
- Has capability to modify the web page (i.e., the DOM), thereby giving web apps a more native feel. (More on that later.)
Basics
- Similar to Java/C/C++
- I will focus on the key features – especially those that are different from most programming languages.
- JavaScript is Dynamically Typed (as opposed to Java/C/C++ which are statically typed)
- What does it mean for a language to be dynamically typed?
- Variable types aren’t declared.
- What are the pros and cons?
- Pro: Can leverage polymorphism without elaborate inheritance/interface structures.
- Con: Compiler can’t identify when you’ve tried to call a method that doesn’t exist.
- See
dynamicTyping.jsin the course sample code. - Pro: This lack of typing makes functional programming less cumbersome because we don’t have to jump through hoops to specify the type of the “lambda” parameter.
- Con: Again, mistakes aren’t caught until runtime.
- What does it mean for a language to be dynamically typed?
Key features / differences
- Template strings
- Uses “Java-style” comments (both
//and/* ... */) - Integers and floating point are the same type. (Try
typeof(1)andtypeof(3.14159)); - All objects behave like associative arrays.
==vs=====converts both parameters to the same type before comparison (called coercion)===does not coerce parameters. If the parameters are different types, the result isfalse.- Use
===unless you have a good reason to use==. - When you use
==add a comment explaining why. - See
keyFeatures.js
nullandundefinedare different.null == undefinedistrue: Both convert tofalsenull === undefinedisfalse: They are different types.- Be sure to check for both
nullandundefined.
letvsvar- Variables declared using
lethave block scope (are local to the block) - Variables declared using
varare visible throughout the function (are not local to the block.) - Use
letunless you have a good reason to usevar. - When you use
var, and a comment explaining why.
- Variables declared using
- Some methods are asynchronous. (See
asynchronous.js) - Use a linter (e.g.,
JSHint)
Express
npm init(accept defaults)npm install express --savenpm install ejs --save(skip if you prefer to use Pug)-
Create
index.jswith the following content:const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => res.send('Hello World!')) app.listen(port, () => console.log(`Example app listening on port ${port}!`)) - Launch
node index.js- visit
localhost:3000
- visit
- Create a directory named
views- By default, templates go here.
- Convention vs. configuration.
- If you want to change it:
app.set('views', './your_preferred_location')
-
Create a file in
viewsnamedhello.ejs(orhello.pug, if you prefer)<p>Hello, there <%= name %></p> <p>Not a bad first page.</p> - Add
app.get('/', (req, res) => res.render('hello.ejs', { name: "Bob" }));toindex.js- You can also set a default template engine:
app.set('view engine', 'ejs') - Doing so allows you to leave off the
.ejs
- You can also set a default template engine:
-
Launch the server and visit the root page
- Add a route
- print the
resobject. - print just
res.query - Use
res.queryset a variable in the template
- print the
- Show multiplication table and processing of POST
MVC (Model, View, Controller)
- This is a general software development design pattern. It’s not just for web apps
- Model
- Represents the data / objects
- Access the database
- Provides the business logic
- View
- Renders the web page
- Controller:
- Ties model and view together
- Basic workflow
- Receives input / action (e.g., HTTP request)
- Calls appropriate model (runs the correct business logic)
- Uses the results of the model’s actions to update view accordingly.
- Look at
Toy.js- Primarily a container for a DB record
- class methods represent DB accesses
- The
module.exports =line is a module system called CommonJS- CommonJS was designed by Node to prevent namespace pollution (in other words, to avoid name collisions when importing multiple modules)
module.exportsis an object contains the exported elements of a module.- Sometimes it is the single exported object
- (In the case of
express, the module returns a function that creates the server object.)
- (In the case of
- Sometimes it is an object that maps names to the several “public” elements of a module.
- Sometimes it is the single exported object
- Node provides a
requiremethod to load the code and return theexportsobject- The
Toymodule only returns one thing: TheToyclass.
- The
- The most recent versions of JavaScript use new
importandexportstatements.- Node supports these statements on an “experimental” basis.
- The routing in
index.jsmaps each web request (combination of method and path) to a method on a controller. - That controller method
- Accesses the model
- Renders a view
- Step through the following
- Index
- Show
- Create
- Update
- Point out the use of partials.
- Point out the use of the Array “find” method.
Convention over configuration
- By convention, Express uses pug.
- If you choose to use pug, you don’t need to type anything.
- If you choose to go against convention (e.g., use EJS), you need to add a line of configuration.
-
Similarly, by convention, the EJS templates go in a folder named
views; but you can explicitly configure it. - Notice that the set of routes in
index.jsfollows a patternGET /modelName=>modelNameController#indexGET /modelName/:id=>modelNameController#show- etc.
- If we agree to use this pattern for all models, we can easily write a function to automatically generate these routes.
- We could also have this routing function automatically render a conventionally-named view.
- We could even have a program write an outline of our model, controller, and all of our views.
- Rails (and many other frameworks) do all of this.
- They have steep learning curves; but, allow you to be very expressive/efficient once you become comfortable.
Testing / Jest
- Discuss the addition of
isValid()to the Model. - Add Jest to the project https://jestjs.io
npm install jest --save- edit the “test” line of
packages.jsonto say"test": "jest" - Add a file
test/Toy.test.jsand add some test to it. - run `npm run test
- Notice the use of “!this.name” to check for a missing name (including an empty string.)
- Notice that
!this.pricedoesn’t work for checking missing values, because it will also disallow0.0 - Explain
parseFloat(this.price) != this.price - Demonstrate
.only - Demonstrate
toBeTruthyandtoBeFalsy - Be sure to watch tests fail. Watch what happens if we
- Make a mistake in
createand - Make a mistake in the test (by using a previous description).
- Make a mistake in
- Review the
describe/itsyntax inToy.test.js- Run
npm run test test/Toy.test.js - Notice how the tests read as a sentence and describe the expected behavior (hence the
describeblock)
- Run
- To run tests by name
- run
npm run test -- -t '#create' - The
--causes the-t '#create'to be passed to jest instead of consumed by npm. - We need single quotes around
#createbecause#has special meaning to the shell.
- run
Mocking
- Look at
ToyController.test.js- To verify the behavior or this controller, we primarily need to verify how it interacts with the
requestandresponseobjects - Setting up these objects (and avoiding any pitfalls) would be time consuming and difficult, so we use mock objects.
- We “mock” the response object for use in the
#indexmethod by simply creating an object with arendermethod. - This mocked render method, doesn’t actually do anything, it just keeps track of how it is used (how many times it is called, what parameters are passed, etc.)
- This tracking is done by
jest.fn - explain the test
renders the index view - explain the
#showtests - explain the
#newtests- Explain how
Toyand its constructor are mocked
- Explain how
- To verify the behavior or this controller, we primarily need to verify how it interacts with the
Databases / Asynchronous programming / Promises
- You can use the
sqlitepackage to access a simple, file-based SQL database. - Challenge: DB access is asynchronous.
- Examine
sqliteWithCallbacks/whyCallbacks.js - examine
SqliteToyDB.js- Notice that
allandfindneed to take a callback method. - They should really take a second callback for errors.
- Notice that
- This example isn’t too bad; but, if we had a sequence of database accesses, you would need nested callbacks, which can get confusing and hard to maintain.
- Promises provide a means of organizing chains of callbacks.
- See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises for an example of the “pyramid of doom”
- Examine
sqliteWithPromises - Note: At this point, the difference is small.
- Async / Await
- We can use new keywords
asyncandawaitto avoid callbacks at all. - If a method returns a promise, then you can just say
awaitto instead obtain theresolvevalue. - Any method that uses
awaitmust be declared asasync. asyncmethods implicitly return promises, so you may need toawaitan async method.- Show updated MVC
- Point out that when updating, we need to use
function()instead of()=>to have access tothis.
- Point out that when updating, we need to use
- We can use new keywords