Modern JavaScript Application

on Server-Side

Do you think
you know JavaScript?

JavaJavaScript 的关系

雷锋雷峰塔 的关系

就像

History of JavaScript

ES6/ES2015 rules the world

... and ES2016 is coming

JavaScript may be not the language you have known 

prototype-based 
object-oriented programming

let anObject = {
  name: 'DEFAULT',
  color,
  display: function() {
    console.log(`${name} is ${color}`);
  }
};

anObject.color = 'RED';
onObject.display();




// function getConfig(key, options)

let port = getConfig('port', { default: 80 });

object

prototype chain

function User (theName, theEmail) {
    this.name = theName;
    this.email = theEmail;
    this.quizScores = [];
    this.currentScore = 0;
}
​
User.prototype = {
    constructor: User,
    saveScore:function (theScoreToAdd)  {
        this.quizScores.push(theScoreToAdd)
    },
    showNameAndScores:function ()  {
        var scores = this.quizScores.length > 0 ? this.quizScores.join(",") : "No Scores Yet";
        return this.name + " Scores: " + scores;
    },
    changeEmail:function (newEmail)  {
        this.email = newEmail;
        return "New Email Saved: " + this.email;
    }
}

encapsulation

class User {

    constructor(theName, theEmail) {
        this.name = theName;
        this.email = theEmail;
        this.quizScores = [];
        this.currentScore = 0;
    }
​
    saveScore(theScoreToAdd)  {
        this.quizScores.push(theScoreToAdd)
    }

    showNameAndScores()  {
        const scores = this.quizScores.length > 0 ? this.quizScores.join(",") : "No Scores Yet";
        return this.name + " Scores: " + scores;
    }

    changeEmail(newEmail)  {
        this.email = newEmail;
        return "New Email Saved: " + this.email;
    }
}

class

Object.create = function (o) {
    // creates a temporary constructor F()​
    function F() {
    }
    F.prototype = o;
    return new F();
}

​var cars = {
    type:"sedan",
    wheels:4​
};
​
​// We want to inherit from the cars object, so we do:​
​var toyota = Object.create (cars); // now toyota inherits the properties from cars​
console.log(toyota.type); // sedan

inheritance

class Toyota extends Car {

    constructor() {
        super();
        // ...
    }
    
}

extends

function is first class citizen

  • A function is an instance of the Object type

  • A function can have properties and has a link back to its constructor method

  • You can store the function in a variable

  • You can pass the function as a parameter to another function

  • You can return the function from a function

let myFunction = function() {
    //...function body here 
};

$('form').on('submit',myFunction);

assigning functions

passing functions

returning functions

function add(a) {
    return function(b) {
        return a + b;
    }   
}

let add5 = add(5);

add5(5); // 10
add5(8); // 13

partial application

function (x) {
  // ...
}

// arrow function
x => {
  // ...
}



// 普通函数写法
let result = values.sort(function(a, b) {
  return a - b;
});

// 箭头函数写法
let result = values.sort((a, b) => a - b);

arrow functions

JavaScript

from

browser

to

server-side 

node.js

Google Chrome V8 engine

const http = require('http');

const server = http.createServer( (req, res) => {
  res.writeHead(200);
  res.end('Hello Http');
});
server.listen(8080);

single thread

non-blocking I/O

asynchronous

fs.readFile('/foo.txt', function(err, data) {
  // If an error occurred, handle it (throw, propagate, etc)
  if(err) {
      if(err.fileNotFound) {
         console.log('File not found');
         return;
     }   
     if(err.noPermission) {
         console.log('No Permission');
         return;
     }
     console.log('Unknown Error');
     return;
  }
  // Otherwise, log the file contents
  console.log(data);
});

console.log('Loading...');

callback pattern

error first callback

fs.readdir(source, function(err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function(filename, fileIndex) {
      gm(source + filename).size(function(err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          aspect = (values.width / values.height)
          widths.forEach(function(width, widthIndex) {
            height = Math.round(width / aspect)
            this.resize(width, height).write(filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

callback hell

async function save(data) {
  try {
    await insert(data);
  } catch (err) {
    if (err.code === '23505') {
      await update(data);
    } else {
      throw err;
    }
  }
}

function saveTermAndCondition (data, callback) {
  save(data)
    .then(() => callback(null))
    .catch(err => callback(err));
};

async / await

the power of Promise

JavaScript

not for large-scale project

???

// world.js

const msg = 'Hello World'

function show(msg) {
  console.log(msg);
}

export function hello() {
  show(msg);
}

the module system

// main.js

import * as world from './world';

world.hello();
  • Modularization
    each module has a clear responsibility

  • Encapsulation (OO isn't the only way)
    make a distinction between their "public" surface area and their "private" implementation details

  • Re-use

import + export

npm : open source modules

a dynamically-typed language

no static type binding at compile time

let x = 'string';
x = 100;
x = (a, b) => a + b;

const WARN = 3;
logger.log(WARN, 'error', e);

logger.log('debug', 'some message', anObject);

statically typed JavaScript

class Point {
    x: number;
    y: number;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }

    getDist(): number { 
        return Math.sqrt(this.x * this.x + this.y * this.y); 
    }
}

let p = new Point(3,4);
let dist = p.getDst();

code refactoring

const DEFAULT_FILE = 'ConfigData';
const configs = new Map<string, string>();

/**
 * @param {String} name - the name of configuration file
 * @param {Function} onReload - [optional] callback on file is reloaded
 */
function load(name: string, onReload?: () => void): void {
  // ...
}

interface option {
  default: any,
}

/**
 * Get a property or sub tree of properties from configure file.
 * @param {String} property
 * @param {Object} options - [optional] The only available option is 'default'
 * @return {Object} property value
 */
function get(property:string, options?: option): any {
  // ...
}

type definitions

Gulp: automatic workflow

build, test, release etc.

gulp.task('default', ['test', 'lint']);

gulp.task('clean', () => {
  return del(['lib', 'build', 'reports']);
});

gulp.task('format', () => {
  return gulp.src(tsSourceCode, { base: "./" })
    .pipe(tsfmt({options: {IndentSize: 2}}))
    .pipe(gulp.dest('.'));
});

gulp.task('build', ['clean', 'format'], () => {
  return gulp.src(tsCompileContext)
    .pipe(sourcemaps.init())
    .pipe(typescript(tsconfig));
    .pipe(sourcemaps.write());
    .pipe(gulp.dest('./build'));
});

gulp.task('run-test', ['pre-test'], () => {
  return gulp.src(['build/test/**/*.js'])
    .pipe(mocha())
    .pipe(istanbul.writeReports({
      reporters: ['json'],
      dir: 'reports/coverage'
    }));
});

gulpfile

[elizlia.CN00062139] ➤ gulp
[12:12:32] Using gulpfile C:\leoliang\ScmView\ses-nodejs-common\gulpfile.js
[12:12:32] Starting 'clean'...
[12:12:32] Starting 'format'...
[12:12:32] Starting 'tsconfig:files'...
[12:12:32] Finished 'tsconfig:files' after 169 ms
[12:12:32] Finished 'format' after 209 ms
[12:12:32] Starting 'lint'...
[12:12:32] [gulp-tslint] error (no-unused-variable) C:\leoliang\ScmView\ses-nodejs-common\src\index.ts[3, 13]: unused variable: 'applog'
[12:12:32] [gulp-tslint] error (no-var-requires) C:\leoliang\ScmView\ses-nodejs-common\test\applog.ts[1, 16]: require statement not part of an import statement
[12:12:32] [gulp-tslint] Failed to lint: 2 errors.
[12:12:32] Finished 'lint' after 703 ms
[12:12:34] Finished 'clean' after 2.02 s
[12:12:34] Starting 'build'...
[12:12:36] Finished 'build' after 2.6 s
[12:12:36] Starting 'pre-test'...
[12:12:36] Finished 'pre-test' after 61 ms
[12:12:36] Starting 'run-test'...


  applog
    init
      √ should throw exception if module is not inited
    reload
      - should auto reload config file on change
  ......

  12 passing (19ms)
  2 pending

[12:12:36] Finished 'run-test' after 204 ms
[12:12:36] Starting 'remap-istanbul'...
------------|----------|----------|----------|----------|----------------|
File        |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
------------|----------|----------|----------|----------|----------------|
 src\       |    91.67 |    73.33 |       90 |    91.57 |                |
  applog.ts |    84.21 |    58.33 |    83.33 |    84.21 |... 32,35,61,67 |
  config.ts |    97.62 |    83.33 |      100 |    97.62 |             84 |
  index.ts  |      100 |      100 |      100 |      100 |                |
------------|----------|----------|----------|----------|----------------|
All files   |    91.67 |    73.33 |       90 |    91.57 |                |
------------|----------|----------|----------|----------|----------------|


=============================== Coverage summary ===============================
Statements   : 91.67% ( 77/84 )
Branches     : 73.33% ( 22/30 )
Functions    : 90% ( 9/10 )
Lines        : 91.57% ( 76/83 )
================================================================================
[12:12:37] Finished 'remap-istanbul' after 193 ms
[12:12:37] Starting 'test'...
[12:12:37] Finished 'test' after 23 μs
[12:12:37] Starting 'default'...
[12:12:37] Finished 'default' after 11 μs

lint

Type Doc 

documentation generator for TypeScript

describe('config', () => {
  before(() => config.from(path.join(__dirname, 'fixtures/')));

  describe('source', () => {

    it('should read config from default config file', () => {
      expect(config.get('p1')).to.equal('from default');
    });

    it('should throw Error when config file not exist', () => {
      expect(() => {
        config.get('nowhere', 'something');
      }).to.throw(Error);
    });
  });

  describe('data', () => {

    it('should read property from tree leaf', () => {
      expect(config.get('system.x.port')).to.equal('1000');
    });

    it('should read property tree', () => {
      expect(config.get('system')).to.have.all.keys(['x', 'y']);
    });

    it('should throw Error when read non-exist property', () => {
      expect(() => {
        config.get('non-exist');
      }).to.throw(Error);
    });
  });

  describe('reload', () => {
    it('should auto reload config file on change');
    // TODO
  });
});

BDD test

[12:24:39] Starting 'run-test'...

  config
    source
      √ should read config from default config file
      √ should read config from additional config file
      √ should throw Error when config file not exist
    data
      √ should read property from tree leaf
      √ should read property tree
      √ should throw Error when read non-exist property
      √ should return default when read non-exist property
      √ should return null value
    reload
      - should auto reload config file on change

  8 passing (18ms)
  1 pending

[12:24:39] Finished 'run-test' after 216 ms
------------|----------|----------|----------|----------|----------------|
File        |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
------------|----------|----------|----------|----------|----------------|
 src\       |    91.67 |    73.33 |       90 |    91.57 |                |
  applog.ts |    84.21 |    58.33 |    83.33 |    84.21 |... 32,35,61,67 |
  config.ts |    97.62 |    83.33 |      100 |    97.62 |             84 |
  index.ts  |      100 |      100 |      100 |      100 |                |
------------|----------|----------|----------|----------|----------------|
All files   |    91.67 |    73.33 |       90 |    91.57 |                |
------------|----------|----------|----------|----------|----------------|


=============================== Coverage summary ===============================
Statements   : 91.67% ( 77/84 )
Branches     : 73.33% ( 22/30 )
Functions    : 90% ( 9/10 )
Lines        : 91.57% ( 76/83 )
================================================================================

test code coverage

HTML report is available

IDE

Visual Studio Code

https://code.visualstudio.com/

learning resources

JavaScript 的语言规范变化了很多,市面上的书和网上中文资料很多内容都是过时的,需要判别

Modern JavaScript Development on Server Side

By Leo Liang

Modern JavaScript Development on Server Side

  • 1,107