As a guest speaker in NCU, I gave a talk about some best practices of JavaScript programming to college students. It covers basic JavaScript elements and some common pitfalls while dealing with asynchronous programming.
3. 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++
4. Find me at
Mail: gweng@mozilla.com
IRC: snowmantw
Twitter: @GregWeng
7. 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
8. 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
9. 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
10. Since I suppose you know this
class is not JavaScript tutorial
11. 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
15. 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
17. 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
20. "The real badass Todo list"
"You want to write a Todo list,
and you feel it is a simple quest"
21. "The real badass Todo list"
"So you think, you only need to write several functions"
22. "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
23. "The real badass Todo list"
"So you think, you only need to write several functions"
http://bit.ly/1LeY5lv
28. Since we put all things globally
window
drawList fetchData saveData ...
29. Names and states may collide
window
drawList fetchData saveData ...
window
drawInput fetchData saveData ...
Working for the List
Working for the Input
36. 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
37. 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?
38. 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?
40. 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
41. 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...
44. "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
45. 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
server
sync
componentscustom events
46. 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
47. And we're finally able to test our code
Unlike closure, we now have some references the test can
manipulate
48. About unit test
Right now, you don't need to figure out how it works
Just follow the structure and copy it
51. 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)
53. 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*
54. 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
56. Test is good to you because...
There are lots of commits may change your code
57. 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
58. 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
62. 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)
63. The pattern
Design a constructor + prototype and then create instances
(constructor)
function
TodoListManager
TodoListManager.prototype
function start()
{...}
function drawList()
{...}
...
68. 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
69. 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
70. 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)
71. 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() {}
72. 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
79. this
the owner
var SomeObject = {
foo: 3,
bar() {
console.log('Foo: ', this.foo);
}
}
SomeObject.bar();
// Will print "Foo:3"
80. this
the 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'
81. this
the 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'
82. You need to bind the 'this' if you use that
It is important since every time you put a callback...
83. 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...
84. 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...
93. 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
94. 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
95. 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))
}
}
96. 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;
}
98. Solve things in advance but native JavaScript ways
"class" (not ES6 class)
Don't try to copy them
Use "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 bad
Stop it and re-think the
whole case again
flags, temporary variables
singleton
99. 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
100. 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
101. 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)
102. 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)