103
JavaScript Best Practices @GregWeng NCU 2015/10/02

JavaScript Best Pratices

Embed Size (px)

Citation preview

Page 1: JavaScript Best Pratices

JavaScript Best Practices@GregWeng

NCU 2015/10/02

Page 2: JavaScript Best Pratices

About me@GregWeng

Page 3: JavaScript Best Pratices

I'm

Working for Mozilla as a Software Engineer; 2yr+

Responsible for FirefoxOS screen locker (LockScreen)

ECMAScript experience: 6yr+

Free Software and Open Source are important to me

And I'm also a language enthusiast

Like to research programming language themselves

Haskell, Ruby, JavaScript

Java, PHP, Erlang, Python, C/C++

Page 4: JavaScript Best Pratices

Find me at

Mail: [email protected]

IRC: snowmantw

Twitter: @GregWeng

Page 5: JavaScript Best Pratices

No I don't use Facebook and LINE

Page 6: JavaScript Best Pratices

The LessonSome best practices from our daily work

Page 7: JavaScript Best Pratices

In this lesson you shall learn how to...

Bind this in Event handlers in the proper way

Deal with object properties w/ constructor & prototype

Extend instantiable object properly

Manage asynchronous flows with Promise

Write your first test case for a pure function

Page 8: JavaScript Best Pratices

In this lesson you may also try to...

Avoid using closure when it shouldn't appear

Implement functions with closure when it's necessary

Write meaningful comments in JSDoc format

Make your code more expressive with map/forEach

Page 9: JavaScript Best Pratices

About quizzes and the homework

We will refactor an example from scratch

Quizzes help you to think and learn things

They require you to add comments on several PR pages

Or, raise your hand

If you fail at homework they may save you, too

Homework will be marked strictly according to the public marking criteria

That means, you either get passed or failed, no other adjustments except the quizzes

Page 10: JavaScript Best Pratices

Since I suppose you know this class is not JavaScript tutorial

Page 11: JavaScript Best Pratices

Questionnaire

Before the class begins, I would like to ask you if you know:

How to write constructor + prototype (instantiable object)

How to extend an object to add new methods

How to receive and dispatch events

What the 'this' may refer to?

What's a 'closure'?

What's the better way to control asynchronous flow

Page 12: JavaScript Best Pratices

Let's start from the embarrassing past...

Page 15: JavaScript Best Pratices

Some anti-patterns

You put all states in local variables

And then bind events with anonymous callbacks

And then encapsulate them as a 1K+ lines closure

And then try to create a "singleton" via a function that will be invoked automatically when bootstrapping

And then make it as a Window Manager as Metacity of Gnome or KWin of KDE

And then you shoot yourself in the foot

Page 16: JavaScript Best Pratices

Alarm!

Page 17: JavaScript Best Pratices

Beware of these terms

You put all states in local variables

And then bind events with anonymous callbacks

And then encapsulate them as a 1K+ lines closure

And then try to create a "singleton" via a function that will be invoked automatically when bootstrapping

And then make it as a Window Manager as Metacity of Gnome or KWin of KDE

And then you shoot yourself in the foot

class, private, flags, global objects, temporary variables

Page 18: JavaScript Best Pratices

Usually, bad things happen when they appear

Page 20: JavaScript Best Pratices

"The real badass Todo list"

"You want to write a Todo list,

and you feel it is a simple quest"

Page 21: JavaScript Best Pratices

"The real badass Todo list"

"So you think, you only need to write several functions"

Page 22: JavaScript Best Pratices

"The real badass Todo list"

"So you think, you only need to write several functions"

I have three little functions,

one is to draw the list,

one is to fetch the data,

and the last one is to save the data

Page 25: JavaScript Best Pratices

Things become worse when the project grows up

Page 26: JavaScript Best Pratices

Issues

What if TodoItem and TodoInput are written by different people?

And what will happen if they both write a function called "draw"?

Page 27: JavaScript Best Pratices

Name collisions

Page 28: JavaScript Best Pratices

Since we put all things globally

window

drawList fetchData saveData ...

Page 29: JavaScript Best Pratices

Names and states may collide

window

drawList fetchData saveData ...

window

drawInput fetchData saveData ...

Working for the List

Working for the Input

Page 30: JavaScript Best Pratices

En.capsulation

Page 31: JavaScript Best Pratices

Encapsulation

"Why don't we solve collisions with closure, which is the most natural mechanism to do that in JavaScript?"

Page 32: JavaScript Best Pratices

Closure

It's about capture

Page 34: JavaScript Best Pratices

Encapsulation by closures

window

drawList fetchData saveData ...

window

drawInput fetchData saveData ...

The "List" closure

The "Input" closure

(insulated)

Page 36: JavaScript Best Pratices

It seems a successful solution to the issues...

Now we have two isolated parts of the program

People can work on that individually

There are no more tangled names and states

To those things like "singletons", closure looks a good solution to disallow others create the instance twice

Page 37: JavaScript Best Pratices

It seems a successful solution to the issues...

Now we have two isolated parts of the program

People can work on that individually

There are no more tangled names and states

To those things like "singletons", closure looks a good solution to disallow others create the instance twice

Really?

Page 38: JavaScript Best Pratices

Always think about these when you are coding

How do I test the function or instance?

Are these duplicated code necessary?

Could people reuse my code later on?

Is my solution the most expressive one?

Did I express the intention clearly?

Page 40: JavaScript Best Pratices

Don't be intrigued by closure in such cases

You don't have any references for unit tests

If you really have some re-usable data or functions across different parts, closures can't use them

That means, it's uneviable to write lots of duplicated functions do the almost same thing

Page 41: JavaScript Best Pratices

Don't be intrigued by closure in such cases

You don't have any references for unit tests

If you really have some re-usable data or functions across different parts, closures can't use them

That means, it's uneviable to write lots of duplicated functions do the almost same thing

However...

Page 43: JavaScript Best Pratices

"Managers"

Page 44: JavaScript Best Pratices

"Managers"

"You put all states in an object, and listen to all events and hope everything will be fine"

TodoListManager

drawList fetchData saveData ...onItemAdded

user event

used by

The only "public" interface

Page 45: JavaScript Best Pratices

Public interfaces of JavaScript program components

JavaScript components usually use handlers as public interfaces among the user, server and other components

like to draw or to fire new events

private helper methods...

event handlers

server gateway

local data keeper

component usernative events

serversync

componentscustom events

Page 46: JavaScript Best Pratices

So now we have

Managers that can keep queried elements and local data as their states

TodoListManager

drawList fetchData saveData ...onItemAdded

user event

used by

The only "public" interface

Page 47: JavaScript Best Pratices

And we're finally able to test our code

Unlike closure, we now have some references the test can manipulate

Page 48: JavaScript Best Pratices

About unit test

Right now, you don't need to figure out how it works

Just follow the structure and copy it

Page 50: JavaScript Best Pratices

However, to test them could be painful soon

Page 51: JavaScript Best Pratices

The globally "singleton" instance mess up tests

Consider what will happen when we load the files to test such global object pattern

unit test for 'saveData'

save some dummy data

TodoListManager

do some tests

_listTodoItem (null)

_listTodoItem' (dummy)unit test for 'drawList'

do some tests

(wrong)

Page 53: JavaScript Best Pratices

The globally "singleton" instance mess up tests

With such patterns, we need to clear the dummy data and other states made by previous tests

unit test for 'saveData'

save some dummy data

TodoListManager

do some tests

_listTodoItem (null)

_listTodoItem' (dummy)

unit test for 'drawList'

do some tests

(correct)

_listTodoItem' (null)

*reset it*

Page 54: JavaScript Best Pratices

The globally "singleton" instance mess up tests

With such patterns, we need to clear the dummy data and other states made by previous tests

And you barely know how to reset them properly if the component is 1K+ lines long, and written by lots of people

In Gaia, this has happened countless times

And worse, people sometimes wrote tests rely on that: they do some changes at the first test, and then use the contaminated data to perform the next test

Page 55: JavaScript Best Pratices

"What's the matter?

I don't write any test"

Page 56: JavaScript Best Pratices

Test is good to you because...

There are lots of commits may change your code

Page 57: JavaScript Best Pratices

Test is good to you because...

There are lots of commits may change your code

People will always screw you and your code if you don't test them

Page 58: JavaScript Best Pratices

Test is good to you because...

There are lots of commits may change your code

People will always screw you and your code if you don't test them

We call these issues regressions

Page 59: JavaScript Best Pratices

Regressions

Page 60: JavaScript Best Pratices

To test your code and kill possible regressions

Instantiable Objects

Page 61: JavaScript Best Pratices

Instantiable objects

TodoListManager

(constructor) start saveData ...onItemAddednew

Page 62: JavaScript Best Pratices

Test with instantiable objects

Create new instance when new test is performing

unit test for 'saveData'

save some dummy data

TodoListManager

do some tests

_listTodoItem (null)

_listTodoItem' (dummy)

unit test for 'drawList'

do some tests

TodoListManager

_listTodoItem (null)

Page 63: JavaScript Best Pratices

The pattern

Design a constructor + prototype and then create instances

(constructor)

function TodoListManager

TodoListManager.prototype

function start() {...}

function drawList() {...}

...

Page 64: JavaScript Best Pratices

Constructor

(constructor)

function TodoListManager

Is just a function

Page 65: JavaScript Best Pratices

Constructor

(constructor)

function TodoListManager

It should set up all data members and do nothing

(constructor)

function TodoListManager() { this._wrapper = document.query... this._listTodoItems = [] ...}

Page 66: JavaScript Best Pratices

!Caveat!

Page 67: JavaScript Best Pratices

Don't do anything with effects inside the constructor

Page 68: JavaScript Best Pratices

Constructor with effects

(constructor)

function TodoListManager

We prefer to hold the instance before use it

(constructor)

function TodoListManager() { this._wrapper = document.query... this._listTodoItems = [] this.fetchData(...);}

(create the instance & test it)

var instance = new TodoListManager();// It will do fetching immediately,// which is BAD

instance.drawList(...)// Although we only want to test this

Page 69: JavaScript Best Pratices

Constructor

(constructor)

function TodoListManager

Put all initial effects in another function

TodoListManager.prototype

function start() { this.fetchData(...)}

...

(create the instance & test it)

var instance = new TodoListManager();// It will do noththing now

instance.drawList(...)// We can test this without deal with// the effect we don't need

Page 70: JavaScript Best Pratices

It means the shareable methods and data among instances

Prototype

(constructor)

function TodoListManager(){ this.foo = [];}

TodoListManager.prototype

function start() { this.fetchData(...)}

foo: [ ]

start() {}

foo: [ ]

start() {}

foo: [ ]

start() {}

foo: [ ]

start() {}

foo: [ ]

start() {}

(new instances)

Page 71: JavaScript Best Pratices

So it's better to put data ONLY IN SETUP FUNCTIONS

Prototype

(constructor)

function TodoListManager(){}

TodoListManager.prototype

function start() { this.fetchData(...)}

start() {} start() {} start() {} start() {}

(wrong!)

foo: [ ]

foo: [ ]

start() {}

Page 72: JavaScript Best Pratices

Every test can now modify the instance by their own needs

For tests

(constructor)

function TodoListManager(){ this.foo = [];}

TodoListManager.prototype

function start() { this.fetchData(...)}

foo: [1]

start() {}

foo: [ ]

start() {}

foo: [3,2]

start() {}

foo: null

start() {}

foo: {}

start() {}

(new instances)test #1 test #2 test #3 test #4

Page 74: JavaScript Best Pratices

"this"

Page 75: JavaScript Best Pratices

"this"the necessary evil to be instantiable

Page 76: JavaScript Best Pratices

Trolls you when you are not familiar with it

Page 78: JavaScript Best Pratices

this the ownerthe windowthe bound one

Page 79: JavaScript Best Pratices

thisthe owner

var SomeObject = { foo: 3, bar() { console.log('Foo: ', this.foo); }}

SomeObject.bar();// Will print "Foo:3"

Page 80: JavaScript Best Pratices

thisthe window

var SomeObject = { foo: 3, bar() { console.log('Foo: ', this.foo); }}

var bar = SomeObject.bar;// Will get reference to bar

bar();// Will print "Foo:undefined"// without binding and owner,// the 'this' will become 'window'

Page 81: JavaScript Best Pratices

thisthe bound one

var SomeObject = { foo: 3, bar: (function() { console.log('Foo: ', this.foo); }).bind(SomeObject)}

var bar = SomeObject.bar;// Will get reference to bar

bar();//// since it's bound, the this will// always be 'SomeObject'

Page 82: JavaScript Best Pratices

You need to bind the 'this' if you use that

It is important since every time you put a callback...

Page 83: JavaScript Best Pratices

Event handlers should follow the EventListener interface to make a centralized dispatcher with the bound 'this'

It is important since every time you put a callback...

Page 84: JavaScript Best Pratices

Event handlers should follow the EventListener interface to make a centralized dispatcher with the bound 'this'

It is important since every time you put a callback...

Page 86: JavaScript Best Pratices

Using Promise

Page 87: JavaScript Best Pratices

or face the Callback Hell

I know, I know, it's actually from the Abyss, but the chaos fits the case better better than the order

Page 88: JavaScript Best Pratices

TheHell

Page 89: JavaScript Best Pratices

If you think it's tolerable, think about this

Page 90: JavaScript Best Pratices
Page 91: JavaScript Best Pratices

That's why Promise is so important

Page 93: JavaScript Best Pratices

new Promise(function(resolve, reject) { // Do some async things. // Then resolve it after done. resolve();

// Or reject it due to the error. reject();}).then(function() { // This function only executes after the // previous step. return [1,2,3];}).then(function() { // If return another Promise, // the flow will stop here and only // continue when the promise is resolved return new Promise(function(r, c) { setTimeout(r, 1000); });}).then(function() { // This will not execute within the second}).catch(function(error) { // !IMPORTANT! // Remember to catch Promise error in a // function like this.});

The Flow

Page 94: JavaScript Best Pratices

Promise + Event Handler

|dispatchEvent| as the public interface to send message

|handleEvent| as the public interface to receive message

|Promise| to concat all steps of event handling

TodoListManager

drawList fetchData saveData ...onItemAdded

user event

organized by Promises

The only "public" interface

Page 95: JavaScript Best Pratices

Promise + Event Handler

|dispatchEvent| as the public interface to send message

|handleEvent| as the public interface to receive message

|Promise| to concat all steps of event handling

handleEvent(event) { switch(event.type) { case 'todo-item-created': var data = event.detail; this.saveData(data) .then(this.tidyUp.bind(this)) .then(...) .then(...) ... .catch(this.onError.bind(this)) }}

Page 96: JavaScript Best Pratices

Promise + Event Handler

Every asynchronous method should return a Promise to allow we concating it, so we can eliminate all the callbacks

saveData() { var promise = new Promise(function() { ... }; return promise; }

fetchData() { var promise = new Promise(function() { ... }; return promise; }

Page 97: JavaScript Best Pratices

Conclusion

Page 98: JavaScript Best Pratices

Solve things in advance but native JavaScript ways

"class" (not ES6 class)Don't try to copy themUse "instantiable objects"prototype + constructor

private

encapsulate

closure Use them very carefully and wiselyanonymous functions

callback ** PROMISE ** (or beyond)

local variables As immutable as possible

global object Usually it means that you're doing something really badStop it and re-think the whole case again

flags, temporary variables

singleton

Page 99: JavaScript Best Pratices

Homework

1. Fork the Homework repo here

2. Fix all the issues in the code to refactor it

3. After that, make a git branch, commit, and then push it

4. Fire a Pull Request and set review to TAs and me

Remember: you can always ask any question at anytime

Don't struggle with any part you don't really understand

Page 100: JavaScript Best Pratices

Homework: How to get points

You need at least address all the Tier 1 issues to get 4 points

You will get the extra 3 points from addressing Tier 2 issues

However, you will NOT get any points from the Tier 2,if you fail to address ANY single issue of Tier 1

You need at least the 4 points from Tier 1 to pass this class

The extra points will benefit you in the following lessons

You can add or remove some code from the example. However, make sure every change you make is with clear intentions

Page 101: JavaScript Best Pratices

Homework: Requirements (Tier 1)

Bind this in Event handlers

Deal with object properties w/ constructor & prototype

Deal with asynchronous flows with Promise

Write your first test case for a pure function (as the last test of |test-list.js| file shows; for example, to test if |Math.sin| works well as your assumption)

Page 102: JavaScript Best Pratices

Homework: Requirements (Tier 2)

Implement functions with closure when it's necessary

Avoid using closure when it shouldn't appear (follow what we taught in the class)

Write meaningful comments in JSDoc format(reference: https://en.wikipedia.org/wiki/JSDoc#Example)

Page 103: JavaScript Best Pratices

Update: people who already commented on PRs

Quiz#1rockwyc992 : 吳易璋CaeserNieh : 聶順成rockwyc992 : 吳易璋Quiz#2jason1122g : 邱義傑bdsword : 蔣彥亭c910335 : 陳達仁Davisanity : 林唐正amm040341 : 蘇聖雅rueian : 黃瑞安w181496 : 黃詩凱Peter654q :莊侑穎bdsword : 蔣彥亭Quiz#3rueian : 黃瑞安c910335 : 陳達仁CrashedBboy : 吳承霖Sharknevercries : 李政遠jason1122g : 邱義傑

Good work, buddies.