Giter Club home page Giter Club logo

headwindz.run's Introduction

Hi there 👋

I'm headwindz, living in Hangzhou

headwindz.run's People

Contributors

headwindz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

headwindz.run's Issues

Polishing CSS

I have been using CSS for the last 4 years and never take systematic learning about it. Thanks to the course CSS - The Complete Guide (incl. Flexbox, Grid and Sass) I get a chance to polish my CSS skills and knowledge. In this post I am not going to give an introduction of CSS by long shot. I will list some points which I don't notice/know/understand before and think might be useful to you.

Margin collapsing

According to the spec:

In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.

Horizontal margins never collapse.

Demo

Vertical margin and padding does't have effect on inline element

Demo

Pseudo class vs Pseudo element

Pseudo class

Pseudo class defines the style of a special state of an element, represented as :class-name.

E.g. :first-child and :last-of-type

Use Case: Build a navigation bar which has n items separated by '|' while the last item doesn't have the separator.

Demo

Pseudo element

Pseudo element defines the style of a specific part of an element, represented as :element-name.

E.g. ::after and ::first-letter

When would you want to use pseudo element:

  • You don’t have control over the html but want to add content. For example, you want to slightly change a third-party component to adapt to your custom needs.
  • Html content without meaning. Just achieve some effect. E.g clear float.

Border radius can work without border.

<div></div>
div {
  width: 100px;
  height: 100px;
  background: #0ff;
  border: none;
  border-radius: 50%;
}

Demo

background-origin vs background-clip

  • background-origin: sets the background's origin, from the border start, inside the border, or inside the padding. The default is padding-box.

  • background-clip: sets whether an element's background extends underneath its border box, padding box, or content box. The default is border-box.

Demo

Negative margin can lead to stack

If two elements sit near each other, applying negative margin to one element can make them stack. With z-index, we can then achieve the same visual effect as position;

The z-index CSS property sets the z-order of a positioned element and its descendants or flex items
A positioned element is an element whose computed position value is either relative, absolute, fixed, or sticky. (In other words, it's anything except static)

Demo

Percentage unit

Percentage unit set to element is relative to the containing block. Containing block is subject to the position property of the element.

  • For fixed positioned element, the containing block is viewport.
  • For absolute positioned element with position absolute, the containing block is the closest positioned ancestor, including content and padding.
  • For static(default) and relative positioned element, the containing block is the closest block element, including only content.

Demo

BEM

BEM — Block Element Modifier is a methodology that helps you to create reusable components and code sharing in front-end development

To summarize, name the class as: .block__element—-modifier

For more details, checkout the introduction

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Iterators and iterables

Iterators bring the concept of iteration directly into the javascript language and provide a mechanism for customizing the behavior of for...of/spread operator loops.

Iteration -> Iterable -> iterator

Iterator Protocol

To follow this protocol, the object must implement a next method which returns an object with keys value and done:

  • value: the current item in iteration
  • done: a boolean that represents whether the iteration is complete or not

E.g.

function PersonQuery() {
  const mike = { first: 'mike', last: 'zheng'};
  const pane = { first: 'pane', last: 'huang'};
  const john = { first: 'john', last: 'wood'};

  let nextPerson = mike;
  this.next = () => {
    const currentPerson = nextPerson;
    switch(currentPerson) {
      case mike:
        nextPerson = pane;
        break;
      case pane:
        nextPerson = john;
        break;
      default:
        nextPerson = null;
    }

    const done = currentPerson === null;
    return {
      ...(done ? {} : { value: currentPerson }),
      done
    }
  }
}

const personIterator = new PersonQuery();
console.log(personIterator.next()); // { "value": { "first": "mike", "last": "zheng" }, "done": false }
console.log(personIterator.next()); // { "value": { "first": "pane", "last": "huang" }, "done": false }
console.log(personIterator.next()); // { "done": true }
console.log(personIterator.next()); // { "done": true }}

Iterable Protocol

An object becomes iterable if it has a method (own or inherited) whose key is Symbol.iterator. That method must return an iterator, an object that enumerates the items “inside” the iterable via its next method.

E.g

function PersonQuery() {

  const mike = { first: 'mike', last: 'zheng'};
  const pane = { first: 'pane', last: 'huang'};
  const john = { first: 'john', last: 'wood'};

  this[Symbol.iterator] = () => {
    let nextPerson = mike;
    const next = () => {
      const currentPerson = nextPerson;
      switch(currentPerson) {
        case mike:
          nextPerson = pane;
          break;
        case pane:
          nextPerson = john;
          break;
        default:
          nextPerson = null;
      }

      const done = currentPerson === null;
      return {
        ...(done ? {} : { value: currentPerson }),
        done
      }
    }
    // conform to iterator protocol
    return { next };
  }
}

for (const record of new PersonQuery()) {
  console.log(record)
}

Use Cases

Rest operator

Example

const [firstPerson, ...rest] = new PersonQuery();
console.log(firstPerson, rest);

Spread operator

Example

const numbers1 = [1, 2,3]
const combined = [...numbers1, ...new PersonQuery()];

for...of

Example

Generator

The Generator object is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol.

Example

function* PersonQuery() {
  yield { first: 'mike', last: 'zheng'};
  yield { first: 'pane', last: 'huang'};
  yield { first: 'john', last: 'wood'};
}

const generatorObject = PersonQuery();
// generatorObject has `next` and [Symbol.iterator] that comform to both iterator and iterable protocol
console.log(generatorObject);

// iterator protocol
const genObj1 = PersonQuery();
console.log(generatorObject.next());
console.log(generatorObject.next());
console.log(generatorObject.next());

// iterable protocol
const genObj2 = PersonQuery();
const iterator = genObj2[Symbol.iterator]()
console.log(iterator === genObj2);
for(let item of genObj2) {
  console.log(item);
}

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe

Make a simple custom EventEmitter

Thoughts

Recently I have been reading the book Async JavaScript about JS asynchronicity and JS event is one of the useful solutions to the problem. To get a deeper understanding of how events work, I create a custom EventEmitter which constains most of the working functionalities of Node EventEmitter. The source code is no more than 60 lines.

General ideas

The general idea is to have an object (this.handlers) to hold the mapping from event name(type: string) to its associated listeners/handlers(type: Array<Function>). When each event is triggerd, walk through the associated listeners/handlers and execute them.

class Emitter {
  constructor() {
    /**
     * keep mapping information。
     * e.g.
     *   {
     *      'event1': [fn1, fn2],
     *      'event2': [fn3, fn4, fn5]
     *   }
     */
    this.handlers = {};
  }
}

Some details about the methods

on - event binding

on(evt, handler) {
  this.handlers[evt] = this.handlers[evt] || [];
  let hdl = this.handlers[evt];
  hdl.push(handler);
  return this;
}

We don't check the duplicates when binding the handler for simplicity. That is to say, if you call on for the same function twice, then it will be called twice when the event is triggered. The method returns this to allow for method chaining。

off - event unbinding

removeListener(evt, handler) {
  this.handlers[evt] = this.handlers[evt] || [];
  let hdl = this.handlers[evt];
  let index = hdl.indexOf(handler);
  if(index >= 0) {
    hdl.splice(index, 1);
  }
  return this;
}

Note that here we compare the function reference with strict comparision when unbinding a function. Function in JavaScript is compared by their reference, same as how objects comparision works.

function f1() {
  console.log("hi");
}

function f2() {
  console.log("hi");
}

let f3 = f1;
console.log(f1 === f2); //false
console.log(f1 === f3); //true

once - binding, but only can be triggered once

once(evt, handler) {
  this.handlers[evt] = this.handlers[evt] || [];
  let hdl = this.handlers[evt];
  hdl.push(function f(...args){
    handler.apply(this, args);
    this.removeListener(evt, f);
  });
  return this;
}

It works similarly with on method. But we need to wrap the handler with another function such that we can remove the binding once the handler is executed to achieve triggered only once.

emit - trigger event

emit(evt, ...args) {
  this.handlers[evt] = this.handlers[evt] || [];
  let hdl = this.handlers[evt];
  hdl.forEach((it) => {
    it.apply(this, args);
  });
  return this;
}

When an event is triggered, find all its associated handlers(i.e. this.handlers[evt]) and execute them.

eventNames - get the list of registered events which has active(i.e. not-empty) handlers

eventNames() {
  return Object.keys(this.handlers).reduce((arr, evt) => {
    if(this.listenerCount(evt)) {
      arr.push(evt);
    }
    return arr;
  }, []);
}

Here we don't simply return all the keys of the this.handlers , as some events can be binded with some handlers and then remove them afterwards. In the case, the event name exists as a valid key in this.handlers but without active handlers. E.g.

let server = new Emitter();
let fn = function() {};
server.on("connection", fn);
server.removeListener("connection", fn);
server.handlers.connection; //[]

Therefore, we need to filter out the events with empty hanlders. Here we use Array.prototype.reduce to make the code a little bit cleaner. There are many situations where reduce can be useful, such as computing the sum of an array:

function sumWithForEach(arr) {
  // with forEach
  let sum = 0;
  arr.forEach(it => {
    sum += it;
  });
  return sum;
}

function sumWithReduce(arr) {
  // with reduce
  return arr.reduce((sum, current) => {
    return sum + current;
  });
}

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Fixed with absolute

A few concepts

  • offsetWidth: gives you the width the element takes up in pixels.
  • offsetHeight: gives you the height the element takes up in pixels.
  • clientWidth: gives you the size of the space inside the element, ignoring border width.
  • clientHeight: gives you the size of the space inside the element, ignoring border height.
  • pageXOffset: returns the number of pixels scrolled along the horizontal axis (left and right).
  • pageYOffset: returns the number of pixels the document is currently scrolled along the vertical axis, with a value of 0.0 indicating that the top edge of the Document is currently aligned with the top edge of the window's content area.
  • getBoundingClientRect: returns an object with top, bottom, left, and right properties, indicating the pixel positions of the sides of the element relative to the top left of the viewport. If you want them relative to the whole document, you must add the current scroll position, which you can find in the pageXOffset and pageYOffset bindings.
  • innerWidth: width (in pixels) of the browser window viewport including, if rendered, the vertical scrollbar.
  • innerHeight: height (in pixels) of the browser window viewport including, if rendered, the horizontal scrollbar.
  • scrollWidth: a measurement of the width of an element's content, including content not visible on the screen due to overflow.
  • scrollHeight: a measurement of the height of an element's content, including content not visible on the screen due to overflow.

Practice

Let's put together the above concepts to achieve fixed position effect with absolute position, as well as scrolling progress functionality.

// fixed.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Fixed</title>
    <style>
        body {
            margin: 0;
            position: relative;
            /* extend the body so that it can scroll to see the fixed effect */
            width: 2000px;
            height: 2000px;
        }
        #greet {
            width: 100px;
            height: 100px;
            line-height: 100px;
            padding: 5px;
            text-align: center;
            border: 2px solid green;
            position: absolute;
        }
        .progress {
            text-align: center;
            border: 1px solid black;
            /* fixed for progress as we only want to demonstrate the usage of scrollWidth and page offset for progress functionality */
            position: fixed;
            left: 50%;
            padding: 10px;
            /* center the progress info divs */
            transform: translateX(-50%);
        }
        #xprogress{
            top: 0;
        }
        #yprogress{
            top: 50px;
        }
    </style>
</head>
<body>
    <div id="greet"> Hello World </div>
    <div id="xprogress" class="progress">horizontal: 0</div>
    <div id="yprogress" class="progress">vertical: 0</div>
    <script>
        let node = document.querySelector('#greet');
        console.log(JSON.stringify({
            offsetWidth: node.offsetWidth, // 114 - with padding and border
            offsetHeight: node.offsetHeight, // 114 - with padding and border
            clientWidth: node.clientWidth, // 110 - with padding but without border
            clinetHeight: node.clientHeight, // 110 - with padding but without border
            innerWidth: innerWidth, // 1670, vary based on your setting
            innerHeight: innerHeight // 700, vary based on your setting
        }, null, '\t'));
        node.style.left = (innerWidth / 2 - node.offsetWidth / 2) + 'px';
        node.style.top = (innerHeight / 2 - node.offsetHeight / 2) + 'px';

        // for fixed div
        document.addEventListener('scroll', function() { // listen to scroll event, adjust the position based on the scroll infomation
            let rec = node.getBoundingClientRect();
            let {top, left, width, height} = rec; // top and left is based on viewport, need to take scroll offset into account
            console.log(JSON.stringify({
                width,
                height,
                top,
                left,
                pageYOffset,
                pageXOffset
            }, null, '\t'));
            node.style.left = (pageXOffset + innerWidth / 2 - width / 2) + 'px';
            node.style.top = (pageYOffset + innerHeight / 2 - height / 2) + 'px';
        });

        let yMax = document.body.scrollHeight - innerHeight;
        let xMax = document.body.scrollWidth - innerWidth;
        // for progress bar
        document.addEventListener('scroll', function () {
            let xProgress = document.querySelector('#xprogress');
            let yProgress = document.querySelector('#yprogress');

            xProgress.textContent = `horizontal: ${pageXOffset * 100/ xMax}%`;
            yProgress.textContent = `vertical: ${pageYOffset * 100 / yMax}%`;
        });
    </script>
</body>
</html>

Code Sample

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

The software developer's life manual

Reading notes from the book Soft Skills: The software developer's life manual. It's not about teaching you how to polish your technical skills but to provide a list of useful suggestions that can benefit your development life in various aspects.

Think about your goals

  • Every step you take without a clear direction is a wasted step. Don't randomly walk through life without a purpose for your career.
  • Try to split you big goals into smaller goals. The smaller goals keep you on track and motivated so that you keep heading in the direction of your bigger goals.

Climbing the corporate ladder

  • Taking responsibility: there is almost always some neglected area of business that you can find to contribute your talents to - you just have to dig to find it. One of the best places to search is in areas that no one else wants to get involved in. Another way to indrectly take on responsibility is to become a mentor for others on your team.
  • Becoming visible: All of your hard work can easily go to waste if you can't find a way to let your boss and upper management know what you're doing.
    • Keep daily/weekly report.
    • Offer to give presentations or trainings.
    • Speak up - do this at meetings.
    • Be seen - set up regular meetings with your boss.

Being professional

  • A professional is someone who takes their responsibilities and career seriously and is willing to make the tough choices that have to be made - often at their own expense - for the purpose of doing what they know is right. Set a high bar and stick to it, don't compromise for the shorcut.
  • A real professional has high-quality standards for all areas of his or her work, because a professional knows that 'how you do anything is how you do everything.
  • Find your gaps: anything you're doing repeatedly is worth a thorough examination to see if there's something you don't know that, if you did, might increase your efficiency.

Leaning - 10 steps

  • Get the big picture
  • Determine scope
  • Define success
  • Find Resource
  • Create a learning plan
  • Filter resources
  • Learn enough to get started
  • Play around
  • Learn enough to do something useful (e.g. apply in real projects)
  • TEACH !

Facing failure

  • Failure isn't the same thing as defeat. Failure is temporary, defeat is permanent. If you want to succeed, you have to learn how to swallow your pride and get out there and not be afraid to make a fool of yourself.
  • You could have all the skills in life that should make you successful, but if you lack one important skill, perseverance, it will all be worthless, because at the first sign of trouble, you'lll give up - and we all will face some amount of trouble in our lives.

Productivity

  • Working like a machine on a tight schedule every single day isn't something that can be maintained in the long run. Make sure that you have some time off, best to save it for family.
  • Pomodoro technique: the basic idea is that you plan out the work your're going to do for a day. Then you set a timer for 40 minutes and work on the first task you've planned. You work only on a single task at a time and give it your complete focus for the full 40 minutes. If you're are interrupted, there are various ways of handling the interruption, but generally you strive to not be interrupted at all. You never want to break focus.
  • It's important to develop a sense of self-accountability to be productive. Without accountability, you're always dependent on external motivations to get you to perform.
  • Get away from time wasters
    • Watching TV
    • Social media
    • Unnecessary meetings
    • Playing videogames(especially online games)
  • Breaking down things
    • One of the main reasons for procrastination which is the bane of productivity is problem admiration: being so busy admiring the size of a problem that you dont actually try and solve it.
    • When faced with large problems we tend to spend more time thinking about the problem than taking steps to solve the problem. It's important to make wise decisions and to think things through, but often you dont have all the information you'd like to have and you have to just go ahead and make a choice - TAKE ACTION.

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Five minutes to understand async and defer

Script tag

When we want to insert a script into a web page, the standard way is to use the script tag(i.e. <script>). The first impression that comes to people's mind about script tag is - BLOCKING .
The book High Performance Web Sites Rule 6 suggests to put scripts at the bottom of html body. The article will examine how putting scripts at varying positions affects performance and how async and defer attributes work for script tag.

First thing first, when a script is referenced in a html page, the browser does two things for you:

  • Retrieve/Load the script content, this is NON-BLOCKING!
  • Run the script content, this is BLOCKING !

Assume we have two scripts on the page:

//script1.js
let t1 = +new Date();
console.log("script1 is running at", t1);

console.log("script1 element", document.getElementById("load-experiment"));
while (+new Date() - t1 < 1000) {
  // delay 1 seconds
}
console.log("script1 finishes running at", +new Date());
//script2.js
let t2 = +new Date();
console.log("script2 is running at", t2);

console.log("script2 element", document.getElementById("load-experiment"));
while (+new Date() - t2 < 2000) {
  // delay 2 seconds
}
console.log("script2 finishes running at", +new Date());

Put script tags in head

<!--all-in-head.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
        <script src='./script1.js'></script>
        <script src='./script2.js'></script>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
    </body>
</html>

The console output:

script1 is running at 1496747869008
script1.js:4 script1 element null
script1.js:8 script1 finishes running at 1496747870008
script2.js:2 script2 is running at 1496747870009
script2.js:4 script2 element null
script2.js:8 script2 finishes running at 1496747872009

Conclusion:

  • When we open the html in the browser, we can notice the delay of page load. The page goes blank before it renders correctly. This is due to the fact that the running of the two scripts blocks the DOM rendering.
  • When scripts are running, they are not able to fetch the DOM element (i.e. document.getElementById('load-experiment') being null). This is because scripts are run before DOM is rendered.
  • Scripts themselves are blocking each other. They are run in the order specified in the html. Script2 is run after script1 finishes running.

Put script tags at the bottom of body

This is the suggestion from the Rule 6 of the book High Performance Web Sites.

<!--all-in-body.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
        <script src='./script1.js'></script>
        <script src='./script2.js'></script>
    </body>
</html>

The console output:

script1 is running at 1496751597679
script1.js:4 script1 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script1.js:8 script1 finishes running at 1496751598679
script2.js:2 script2 is running at 1496751598680
script2.js:4 script2 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script2.js:8 script2 finishes running at 1496751600680

Conclusion:

  • No page delay and blank page when opening the page in browser, as scripts are put/run after the DOM is ready.
  • Scripts can correctly fetch DOM elements.
  • Scripts themselves are blocking each other, as the same as the first example.

However, puting all scripts at the bottom of body sometimes doesn't fit in some specific real life cases. For example, if we aim to calculate the ATF[https://en.wikipedia.org/wiki/Above_the_fold] of a page, we can't simple wait for the DOM to render. We have to load and run some scripts beforehand and then fire the ATF marker when the ATF is ready, which is definitely before DOM is ready for web pages with scrolling content. Therefore, it seems reasonable to come out with the next solution.

Put scripts seperately in head and body based on requirements.

Put scripts that needs pre-running at head and others at the bottom of body. E.g.

<html>
    <head>
        <script src="headScripts.js"></scripts>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
        <script src="bodyScripts.js"></script>
    </body>
</html>

defer!

The main disadvantage of putting scripts at the bottom of body is that the scripts will only be retrieved/loaded after the DOM is rendered. As we said, retrieving/loading the script content is NON-BLOCKING while running the script content is the BLOCKING part. We can improve the web performance if we can retrieve/load the scripts while the DOM is rendering, rather than wait for the DOM to complete rendering. This works pretty well especially when the scripts are large. This is why defer is introduced. defer loads the scripts simultaneously but only runs the scripts after the DOM is ready.

<!--defer.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
        <script defer src='./script1.js'></script>
        <script defer src='./script2.js'></script>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
    </body>
</html>

The console output:

script1 is running at 1496760312686
script1.js:4 script1 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script1.js:8 script1 finishes running at 1496760313686
script2.js:2 script2 is running at 1496760313686
script2.js:4 script2 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script2.js:8 script2 finishes running at 1496760315686

Conclusion:

  • The result is the same as putting scripts at the bottom of body. We can conclude from the result that the scripts are run after the DOM is ready as we can indeed fetch the DOM elements.
  • Even the defered scripts follow the order rule specified in html, script1 is run after script2.

async!

Web pages often contain some add-on features which are strictly independently and NOT must-to-have, such as the comment and chat functionalities on some pages. As the features are independent, they don't have the running order restriction. In this case, we can use async

<!--async.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
        <script async src='./script1.js'></script>
        <script async src='./script2.js'></script>
    </body>
</html>

We can observe different console outputs when we refresh the page:

  • The running order of script1 and script2 varies
  • The result of fetching DOM element is inconsistent

As async scripts don't guarantee the running order, this is often the source of potential hidden bugs. Have a second thought before using async and mare sure these scripts are strictly independent.

Conclusion

The general rule to import script is:

<html>
    <head>
        <!--headScripts.js is the script that has to be loaded and run before DOM is ready-->
        <script src="headScripts.js"></scripts>
        <!--bodyScripts.js loads first and runs after DOM is ready-->
        <script defer src="bodyScripts.js"></script>
    </head>
    <body>
        <!--body content-->
        <h1 id='load-experiment'> hello world </h1>
        <!--independent scripts,nice-to-have -->
        <script async src="forumWidget.js"></script>
        <script async src="chatWidget.js"></script>
    </body>
</html>

Code Sample

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

You need to know curry

Functions are first-class citizen

Functions are first-class citizen in JavaScript, as the same as other types(e.g. number, array, object). They can be used as arguments, as well as return value from other function.

Take a simple example, if we aim to print out all the elements in an array, most people probably will do it this way:

function printWithLoop(arr) {
  for (let i = 0, len = arr.length; i < len; i++) {
    console.log(arr[i]);
  }
}

If you're a bit familar with higher-order function, you may be inclined to use Array.prototype.forEach:

function printWithIterator(arr) {
  (arr || []).forEach(it => {
    console.log(it);
  });
}

We can then simplify the code further with:

function simplePrint(arr) {
  (arr || []).forEach(console.log);
}

Have a second thought here, is the output from simplePrint exactly the same as printWithIterator? If not, can you explain what makes the difference?

Function overloading

Function overloading gives the ability for functions to behave differently based on the number or type of the arugments. E.g. Array.from.

  • When given a single argument, it simplely creates a new Array instance from an array-like or iterable object
let set = new Set(["foo", "bar", "foo"]);
console.log(Array.from(set)); //["foo", "bar"]
  • When given a second argument mapFn which is optioinal, it will apply mapFn to each element when creating the new array.
console.log(Array.from([1, 2, 3], x => x + x)); // [2, 4, 6]

Curry

Curry, also called partial application. Currying a function basically means that a function will absord some arguments and return another function for later invocation. The returning function can have access to the already absorded arguments through closure.

Parameters vs Arguments

First we need to understand two basic concepts in functions.

  • Parameters: the placeholders in function declarations. We can use function.length to get the number of parameters.
function A(a, b) {}
// a and b are parameters
console.log(A.length); //2
  • Arguments: the values passed to functions when functions are applied. We can use arguments to get the list of arguments.
function B(a, b) {
  console.log(arguments);
}

B(1, 2, 3); // 1,2,3

To conclude, parameters are what you expect while arguments are what you got.

Curry example

Assume we have a function to compute the sum of three numbers.

function sum(x, y, z) {
  console.log(x + y + z);
}

sum(1, 2, 3); //6

If we want to achieve the following result:

sum(1, 2, 3); //6
sum(1)(2, 3); //6
sum(1, 2)(3); //6

Have a deep look, what we want to achieve is that when the function sum receives the arguments it expects (i.e. three numbers), it will compute their sum and return the value. Otherwise, it will keep returning another function (e.g. sum(1) and sum(1,2) both return another function) which can be invoked with more numbers. This is Curry!

function curry(fn) {
  //Let's ignore the function context for simplicity
  return function f(...args) {
    /**
     * if the number of passed in arguments is more than what we expect
     * invoke the function and return the result
     */
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      //return another function which can access to the passed arguments through closure
      return function(...arr) {
        return f.apply(this, args.concat(arr));
      };
    }
  };
}

let sumWithCurry = curry(sum);
sumWithCurry(1, 2, 3); //6
sumWithCurry(1)(2, 3); //6
sumWithCurry(1, 2)(3); //6

Function.prototype.bind

Function.prototype.bind has two main functionalities:

  • binding the function context this
  • curry
function sayHi(greeting, ending) {
  console.log(`My name is ${this.name}, ${greeting}. ${ending}!`);
}

let fn = sayHi.bind({ name: "mike" }, "Love you"); // greeting is absorded
fn("Thanks!"); // My name is mike, Love you. Thanks!!

In development, we can use curry to write more elegant code:

function print(arr) {
  console.log(arr.join("|"));
}

let arr = [1, 2, 3];
setTimeout(function() {
  print([1, 2, 3]);
}, 1000);
// 1|2|3

is equivalent to

function print(arr) {
  console.log(arr.join("|"));
}

let arr = [1, 2, 3];
setTimeout(print.bind(null, arr), 1000);
// 1|2|3

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Introduction to Sass

Sass(Syntactically Awesome Style Sheets) is the most mature, stable, and powerful professional grade CSS extension language in the world.

In the post, I will list some of the most useful Sass features.

Nested rules/selectors

.a {
  color: red;
  .b {
    color: green;

    &:hover {
      color: yellow;
    }
  }
}

is equivalent to

.a {
  color: red;
}
.a .b {
  color: green;
}
.a .b:hover {
  color: yellow;
}

Nested properties - from some namespace

.a {
  flex: {
    direction: column;
    wrap: nowrap;
  }
}

is equivalent to

.a {
  flex-direction: column;
  flex-wrap: nowrap;
}

Variables

To define a variable, prefix the name of variable with $: $name-of-variable

Variables in property value

$main-color: #fff;

.a {
  color: $main-color;
}

.b {
  color: $main-color;
}

is equivalent to

.a {
  color: #fff;
}

.b {
  color: #fff;
}

Variables in selectors

$prefix: component;

.#{$prefix} {
  color: #fff;
}

.#{$prefix}-input {
  padding: 10px;
}

is equivalent to

.component {
  color: #fff;
}

.component-input {
  padding: 10px;
}

Lists & Maps

To define a map, wrap the value with parentheses

$border-default: 0.05rem solid black; //list
$colors: (main: #521521, secondary: #f27361); // map, parentheses

.a {
  border: $border-default;
  color: map-get($colors, main);
}

is equivalent to

.a {
  border: 0.05rem solid black;
  color: #521521;
}

Built-in functions

Sass built-in functions

Simple arithmetics

$size-default: 12px;
$size-in-number: 20;

.a {
  padding: $size-default * 3;
  font-size: $size-default + 1px;
  border: $size-default / 10 solid black;
  margin: $size-in-number * 1px; // turn it into pixel
}

is equivalent to

.a {
  padding: 36px;
  font-size: 13px;
  border: 1.2px solid black;
  margin: 20px;
}

Mixin

Reusable custom function

@mixin display-flex() {
  display: -webkit-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;
}

.a {
  @include display-flex();
}

@mixin media-min-width($width) {
  @media (min-width: $width) {
    @content; // slot/placeholder
  }
}

.b {
  @include media-min-width(40rem) {
    font-size: 150%
  }
}

is equivalent to

.a {
  display: -webkit-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;
}

@media (min-width: 40rem) {
  .b {
    font-size: 150%;
  }
}

Generic Programming

Generic programming centers around the idea of abstracting from concrete, efficient algorithms to obtain generic algorithms that can be combined with different data representations to produce a wide variety of useful software.

— Musser, David R.; Stepanov, Alexander A., Generic Programming

Goal

  • To make the algorithm/method/solution work generically by encapsulating the details about data structures and operations.
  • To allow developers to focus on the solution instead of differentiating data structures in the implementation.

Don't talk, show the example

Calculate the sum of an array

function sum(arr) {
  let total = 0;
  for(let i = 0; i < arr.length; i++) {
    total += arr[i];
  }
  return total; 
}

console.log(sum([1,2,3])); //6

We iterate through the array and add up each element to get the sum of the array. But what if we want a different operation, such as finding the max number:

Find the max number in an array

function max(arr) {
  let result = arr[0];
  for(let i = 1; i < arr.length; i++) {
    if(arr[i] > result) {
      result = arr[i]
    }
  }
  return max; 
}

console.log(max([1,2,3])); //3

How can we do better?

There might be many cases in which we need to go through the array and do something with the elements in the array. It should be a good option to encapsulate the operation and leave it to the caller to determine what to do with each element. That's what reduce is designed for. Let's implement our own _reduce.

function _reduce(op, initialValue) {
  return (arr) => {
    let result;
    let index = 0;
    if(initialValue != null) {
      result = initialValue;
    } else {
      result = arr[0];
      index = 1;
    }
    for(let i = index; i < arr.length; i++) {
      result = op(result, arr[i])
    }
    return result;
  }
}

So now we can rewrite sum and max as:

let sumWithReduce = _reduce((sum, item) => sum + item)
let maxWithReduce = _reduce((max, item) => max > item ? max : item)

console.log(sumWithReduce([1,2,3])); //6
console.log(maxWithReduce([1,2,3])); //3

How can we do even better?

In the _reduce implementation above, we have abstracted away the operation detail and only keep the looping logic in the implementation. However, the implementation still relies on for and array index so it only works for arrays. We make it more generic with Iterators.

function _genericReduce(op, initialValue) {
  return (iterables) => {
    let inited, result;
    if(initialValue != null) {
      result = initialValue;
      inited = true;
    }
    for(let item of iterables) {
      if(!inited) {
        result = item;
        inited = true;
      } else {
        result = op(result, item)
      }
    }
    return result;
  }
}

let sumWithGenericReduce = _genericReduce((sum, item) => sum + item)
let maxWithGenericReduce = _genericReduce((max, item) => max > item ? max : item)

console.log(sumWithGenericReduce([1,2,3])); //6
console.log(maxWithGenericReduce([1,2,3])); //3

It now works for all data structures that are iterable. Here is a reading note I wrote about iterator if you are interested in.

class Group {
  constructor(id) {
    this.id = id;
    this.members = [];
  }
  
  addMember(person) {
    this.members.push(person);
  }

  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => ({
        value: this.members[index++],
        done: index > this.members.length
      })
    };
  }
}

class Member {
  constructor(name, salary) { 
    this.name = name;
    this.salary = salary;
  }
}


## try to find the lowest salary in the group

let group = new Group('007');
group.addMember(new Member('mike', 1000))
group.addMember(new Member('john', 2000))
group.addMember(new Member('alfred', 3000))
let lowestSalary = _genericReduce((result, member) => {
  if(member.salary < result) {
    return member.salary
  }
  return result;
},  Number.MAX_SAFE_INTEGER)(group)
console.log(lowestSalary);

Resource

Code Sample

Notice

  • If you want to follow the latest news/articles for the series of my blogs, Please 「Watch」to Subscribe.

Some useful JS techniques that you may not know

I complete reading JavaScript Enlightenment recently. The book is more about basics in JavaScript and suitable for beginners. Here are a list of my benefits and extensions from the book.

Math

JavaScript developers often capitalize the constructor name to distinguish the constructors from normal functions. Therefore, some developers may mistake Math as function since the name is capitalized while Math is really just an object.

console.log(typeof Math); // object
console.log(typeof Array); // function

Function

There are two ways to construct a function:

  • Function declaration/expression
function sum(x, y) {
  return x + y;
}
console.log(sum(3, 4)); //7
let multiply = new Function("x", "y", "return x * y;");
console.log(multiply(3, 4)); //12

In development, we often need to use call or apply to switch the function context. E.g.

function print() {
  console.log(this.a);
}

print.call({ a: "hello" }); //hello

Some interview questions will ask why print doesn't define call as its property but print.call() won't throw error. It's because print is an instance from Function constructor so it inherits all the methods defined in Function.prototype through prototype chain.

print.call === Function.prototype.call;

How to do Array check

typeof can be used to determine the types for primitive datatypes. But it won't be able to distinguish between arrays and objects.

console.log(typeof [1, 2]); //object
console.log(typeof {}); //object

There are several wasy to do Array check:

console.log(Array.isArray([1, 2])); //true
console.log(
  Object.prototype.toString.call([1, 2]).toLowerCase() === "[object array]"
); //true

Note here we have to use Object.prototype.toString with call to switch the calling context, as Array.prototype.toString is overriden.

console.log(Object.prototype.toString.call([1, 2])); //[object Array]
console.log([1, 2].toString()); //1,2
[1, 2] instanceof Array === true;

Object.prototype.toString won't work for custom datatypes. In this case we can use instanceof to determine the type.

class Person {}

let mike = new Person();
console.log(Object.prototype.toString.call(mike)); // [object Object]
console.log(mike instanceof Person); // true

undefined vs null

undefined - no value

There are two cases where a variable is undefined.

  • The variable is deifned but not assigned any value.
let s;
console.log(s); //undefined
  • The variable is NOT defined at all.
let obj1 = {};
console.log(obj1.a); //undefined

null - special value

let obj2 = { a: null };
console.log(obj2.a); //null

If we aim to filter out undefined and null, we can use weak comparison with double equality sign(i.e. ==).

console.log(null == undefined); //true
let arr = [null, undefined, 1];
let fil = arr.filter(it => {
  return it != null;
});
console.log(fil); //[1]

Reference

JavaScript Enlightenment

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Asynchronicity in JavaScript

Preface

For a basic understanding about JS asynchronicity, you can take a loot at
Deep dive into JS asynchronicity. The applications of setTimeout/setInterval, ajax in browser, Node IO won't go far without a deep understanding of Asynchronicity (e.g. Event loop, event queue etc.).

Talk is cheap, show me the code

Assume that we have an array which contains a list of file names. We would like to read the files IN TURN until we successfully retrieve one file. For example, if the array is ['a.txt', 'b.txt'], we read a.txt first, we return the file content of a.txt if the reading succeeds. Otherwise we continue reading b.txt. For reading files, Nodes provides two APIs, one is sync readFileSync and the other is async readFile

Now assume we have two files: a.txt (the content of which is also a.txt ) and b.txt (the content of which is also b.txt ).

Synchronous solution is quite straightforward:

let fs = require("fs"),
  path = require("path");

function readOneSync(files) {
  for (let i = 0, len = files.length; i < len; i++) {
    try {
      return fs.readFileSync(path.join(__dirname, files[i]), "utf8");
    } catch (e) {
      //ignore
    }
  }
  // all fail, throw an exception
  throw new Error("all fail");
}

console.log(readOneSync(["a.txt", "b.txt"])); //a.txt
console.log(readOneSync(["filenotexist", "b.txt"])); //b.txt

The main problem with synchronous reading is that it will block the main thread and the looping of event queue. The program becomes unreactive if the reading is taking a long time to complete, especially when the file is large. Asynchronous reading can effectively avoid the problem. All we need to pay attention to is to deal with the order of file reading (i.e. read next file in the callback of the previous readFile call).

let fs = require("fs"),
  path = require("path");

function readOne(files, cb) {
  function next(index) {
    let fileName = files[index];
    fs.readFile(path.join(__dirname, fileName), "utf8", (err, data) => {
      if (err) {
        return next(index + 1); // if fail, read next file
      } else {
        return cb(data); // use cb to output the result
      }
    });
  }
  next(0);
}

readOne(["a.txt", "b.txt"], console.log); //a.txt
readOne(["filenotexist", "b.txt"], console.log); //b.txt

The asynchronous solution needs to take in another parameter(i.e. cb) to deal with the result. It also defines a next method to recursively read next file.

Fire multiple asynchronous requests simultaneously.

Assume that we have an array which constains a list of file names, we aim to read the files simultaneously and return all the file contents if all readings are successful. Invoke the failing callback if any of them fails.

let fs = require("fs"),
  path = require("path");

function readAllV1(files, onsuccess, onfail) {
  let result = [];
  files.forEach(file => {
    fs.readFile(path.join(__dirname, file), "utf8", (err, data) => {
      if (err) {
        onfail(err);
      } else {
        result.push(data);
        if (result.length === files.length) {
          onsuccess(result);
        }
      }
    });
  });
}

readAllV1(["a.txt", "b.txt"], console.log, console.log);

There is an obvious problem in the implementation above: the order of the file contents in result does not match along with the file order in files . All reading operatioins are asynchronous so that the callback is inserted into event queue when the reading completes. Let's assume files is ['a.txt', 'b.txt'], the file size of a.txt and b.txt are 100M and 10kb respectively. When we read the two files in asynchronous way simultaneously, the reading of b.txt will complete before a.txt so the callback for b.txt will be ahead of that of a.txt in the event queue. The finaly result will be [${content of b.txt }, ${content of a.txt }]. If we want the order of file contents in result to follow the order of file names in files, we can make a minor modification to our implementation:

let fs = require("fs"),
  path = require("path");

function readAllV2(files, onsuccess, onfail) {
  let result = [];
  files.forEach((file, index) => {
    fs.readFile(path.join(__dirname, file), "utf8", (err, data) => {
      if (err) {
        onfail(err);
      } else {
        result[index] = data;
        if (result.length === files.length) {
          onsuccess(result);
        }
      }
    });
  });
}

readAllV2(["a.txt", "b.txt"], console.log, console.log); //结果不确定性

It seems to work at first glance, BUT!

let arr = [];
arr[1] = "a";
console.log(arr.length); //2

Based on the implementation of readAllV2 , if reading b.txt completes before a.txt , then we are setting result[1] = ${content of b.txt }, resulting in result.length === files.length to be true. At the case, we call the success callback to terminate the function without getting result of a.txt . Therefore, we can't simply rely on result.length as the completioin indicator.

let fs = require("fs"),
  path = require("path");

function readAllV3(files, onsuccess, onfail) {
  let result = [],
    counter = 0;
  files.forEach((file, index) => {
    fs.readFile(path.join(__dirname, file), "utf8", (err, data) => {
      if (err) {
        onfail(err);
      } else {
        result[index] = data;
        counter++;
        if (counter === files.length) {
          onsuccess(result);
        }
      }
    });
  });
}

readAllV3(["a.txt", "b.txt"], console.log, console.log); //[ 'a.txt', 'b.txt' ]

If you're somehow familar with Promise, you may know there is a Promise.all method, which does exactly the same thing.

Make your interface consistent

Let's implement our custom read file method which has cache functionality. We simply return the cache if the cache is available for the file. Otherwise we read the file and set up the cache.

let fs = require("fs"),
  path = require("path"),
  cache = {};

function readWithCacheV1(file, onsuccess, onfail) {
  if (cache[file]) {
    onsuccess(cache[file]);
  } else {
    fs.readFile(path.join(__dirname, file), "utf8", (err, data) => {
      if (err) {
        onfail(err);
      } else {
        cache[file] = data;
        onsuccess(data);
      }
    });
  }
}

Let's take a deep look:

  • When cache is available, we invoke onsuccess SYNCHRONOUS
cache['a.txt'] = 'hello'; //mock cache data
readWithCacheV1('a.txt', console.log);//synchronous, completes before going into next call.
console.log('after you');

//console output:
hello
after you
  • When cache isn't available, it's ASYNCHRONOUS due to the asynchronicity of readFile
readWithCacheV1('a.txt', console.log);
console.log('after you');

//console output:
after you
hello

This inconsistency often leads to hidden bugs which are hard to track and debug. We can improve the solution to make it behave consistently.

let fs = require("fs"),
  path = require("path"),
  cache = {};

function readWithCacheV2(file, onsuccess, onfail) {
  if (cache[file]) {
    setTimeout(onsuccess.bind(null, cache[file]), 0);
  } else {
    fs.readFile(path.join(__dirname, file), "utf8", (err, data) => {
      if (err) {
        onfail(err);
      } else {
        cache[file] = data;
        onsuccess(data);
      }
    });
  }
}

Let's reexamine two use cases:

  • with cache available
cache['a.txt'] = 'hello';
readWithCacheV2('a.txt', console.log);
console.log('after you');

//console output:
after you
hello
  • without cache
readWithCacheV2('a.txt', console.log);
console.log('after you');

//console output:
after you
hello

Reference

Code Sample

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Deep dive into JS asynchronicity

Single thread JavaScript

JavaScript has a concurrency model based on event loop. Each message is processed completely before any other message is processed. This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates).

runYourScript();
while (atLeastOneEventIsQueued) {
  fireNextQueuedEvent();
}

Let's use setTimeout as a simple example:

let start = +new Date();
setTimeout(function Task1() {
  let end = +new Date();
  console.log(`Task1: Time elapsed ${end - start} ms`);
}, 500); //500ms later,Task1 will be inserted into event queue

// single thread
setTimeout(function Task2() {
  let end = +new Date();
  console.log(`Task2: Time elapsed ${end - start} ms`);
  /**
   * use while loop to delay the completion of the function for 3 seconds
   * this will block the execution of the next function in event loop
   * i.e. looping through the event queue has to happen after the main thread finishes its task
   */
  while (+new Date() - start < 3000) {}
}, 300); //300ms later,Task2 will be inserted into event queue

while (+new Date() - start < 1000) {} //main thread delay completion for 1 second
console.log("main thread ends");
//output:
//main thread ends
//Task2: Time elapsed 1049 ms
//Task1: Time elapsed 3000 ms

setTimeout will put the first argument(type Function) into event queue. Here is what happens in the code above:

  • 300ms later,Task2 is inserted into event queue.
  • 500ms later,Task1 is inserted into event queue.
  • 1 second later, the main thread finishes, "main thread ends" is printed on the console.
  • The main thread will loop through the event queue when it finishes its job at hand. The first function in the event queue is Task2(as it's inserted first). So Task2 is fetched and executed, resulting the console output "Task2: Time elapsed 1049 ms". The elapsed time you see may differ as setTimeout is not exactly dealying the execution of your function. All it does is to put the function into the event queue with the delay you set. When the function will be executed depends on the queueing functions in the event queue, as well as the status of the main thread (whether it has remaining task running).
  • Execute Task1 after Task2 finishes.

What are Asynchronous functions

Asynchronous functions in JavaScript usually can accept a last parameter of function type (it's usually called callback) and the callback will be inserted into event queue when the function completes. As the callback is in event queue, the function is NON-BLOCKING. Asynchronous functions can guarantee the following unit test will always pass:

let functionHasReturned = false;
asyncFunction(() => {
  console.assert(functionHasReturned);
});
functionHasReturned = true;

Note that NOT all functions with a callback parameter are asynchronous. E.g. Array.prototype.forEach is synchronous.

let before = false;
[1].forEach(() => {
  console.assert(before);
});
before = true;

Exceptions in asynchronous functions

Since the callback function is put in the event queue and executed later with it's own invoking context, wrapping the asynchronous functions with try-catch mechanism won't be able to catch the exception from the callback.

try {
  setTimeout(() => {
    throw new Error("callback error");
  }, 0);
} catch (e) {
  console.error("caught callback error");
}
console.log("try-catch block ends");

In the example, the exception thrown in the callback is not caught by our own program (i.e. the catch block - no 'caught callback error' is printed on the console). We can catch these uncaught exceptions with window.onerror in brower and process.uncaughtException in Node.

If we want to deliberately catch the error, as we should always do, we can do it in the callback. E.g.

let fs = require("fs");
fs.readFile("abc.txt", function(err, data) {
  if (err) {
    return console.error(err);
  }
  console.log(data);
});

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Understanding Recursion

Recursion, simply put, is calling a function on itself. It can used to break down complex problems into smaller manageable similar units that can be handled by the same function.

Recursion vs Iteration

An iterative function is one that loops to repeat some part of the code, and a recursive function is one that calls itself again to repeat the code.

E.g To calculate the sum of an array of numbers

function iterativeSum(arr) {
  let sum = 0;
  for (let i of arr) {
    sum += i;
  }
  return sum;
}

function recursiveSum(arr) {
  if (arr.length === 0) {
    return 0;
  }
  return arr[0] + recursiveSum(arr.slice(1));
}

/**
 *
 * iterativeSum([1,2,3]) => 1 + 2 + 3 => 6
 *
 * recursiveSum([1,2,3])
 *     => 1 + recursiveSum([2,3])
 *     =>       2 + recursiveSum([3])
 *     =>           3 + recursiveSum([])
 *     =>               0
 *     => 0 + 3 + 2 + 1 => 6
 */

console.log(iterativeSum([1, 2, 3])); //6
console.log(recursiveSum([1, 2, 3])); //6

How to use recursion

base condition is a must

Using recursion without a base condition leads to infinite recursion. As recursion is calling the function on itself, base condition indicates when to terminate the process.

function infiniteRecursion() {
  // keeps calling itself without termination
  return infiniteRecursion();
}

break down the problem into smaller units that can be handled by the function itself.

E.g.

the sum of an array = the value of first element + the sum of the rest of array.

That's how we do it recursively in recursiveSum example above.

Practices make perfect

Q1

Question:

By starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite amount of new numbers can be produced. Write a function that, given a number, tries to find a sequence of such additions and multiplications that produce that number.

Thoughts:

To find a solution for a number(let's call it A), we tries adding 5 or multiplying 3 to the current number(let's call it B) and use recursion to
check if there is a solution for the result(i.e. B + 5 or B * 3). The base condition is when the current number is greater than(boom!) or equal to(solution found!) A.

Solution:

function findSolution(num) {
  function find(start, history) {
    if (start > num) {
      return null; // boom!
    } else if (start === num) {
      return history; //solution found
    }
    return (
      find(start + 5, `(${history} + 5)`) || find(start * 3, `(${history} * 3)`)
    );
  }

  return find(1, "1");
}

console.log(findSolution(13)); //(((1 * 3) + 5) + 5)
console.log(findSolution(20)); //null

Q2

  • Question: Inorder(Left, Right, Top) traversal of binary tree

  • Solution

class Node {
  constructor(value, left = null, right = null) {
    this.value = value;
    this.left = left;
    this.right = right;
  }
}

function inorder(node, fn) {
  if (node == null) {
    return;
  }
  inorder(node.left, fn);
  fn(node);
  inorder(node.right, fn);
}

function test() {
  /**
   *        A
   *      /   \
   *    B       C
   *   / \       \
   *  E   F       H
   */
  let E = new Node("E"),
    F = new Node("F"),
    H = new Node("H"),
    B = new Node("B", E, F),
    C = new Node("C", null, H),
    A = new Node("A", B, C);
  inorder(A, node => console.log(node.value)); // E B F A C H
}
test();

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Linux command line

Preface

I would be fooling myself if I claim to be proficient in Linux command line. So I took the Linux Command Line video course to enhance my knowledge in the area.

ls: list directory content

Userful Options:

Option Meaning
-l use a long listing format
-a do not ignore entries starting with .
-h with -l, print sizes in human readable format (e.g. 1K 234M 2G)

whatis: displays short manual page descriptions

whatis cp

// output:
cp(1) - copy files

file: find the type of a file

file README.md

// output:
README.md: ASCII text

head: output the first part of file

Userful Options:

Option Meaning
-n specify the number of first lines to print

tail: output the last part of file

Userful Options:

Option Meaning
-n specify the number of last lines to print
-f loop forever, checking for new data at the end of the file(s)

wildcard: a symbol used to replace or represent one or more characters.

wildcard Meaning
* The asterisk in a wildcard matches any character zero or more times
? A question mark matches a single character once
[] match a single character in a range
touch chapters{1,2,3}.txt

// will create chapters1.txt, chapters2.txt and chapters3.txt 

tar: create, maintain, modify, and extract files that are archived in the tar format.

Userful Options:

Option Meaning Example
-c create a new archive. tar -cf archive.tar file1 file2
-f use archive file or device ARCHIVE
-v verbosely list files processed.
-x untar tar archive file tar -cvf archive.tar

gzip: compress

wget: download file over network.

Userful Options:

Option Meaning Example
-O specify output wget -O file http://foo

id: prints real and effective user and group ID

uid=501(michaelzheng) gid=20(staff) groups=20(staff),12(everyone)

groups: show group memberships

groups
//staff everyone 

whoami: prints the effective user

whoami
//michaelzheng

chmod: change the permissions of files or directories

For a file with listing like this:

-rw-r--r--   1 michaelzheng  staff  1983 Jul 17 16:17 README.md

The first char is the type. The 2-4 is the owner permission for reading, writing and execution respectively. 5-6 is for group members and 9-11 is for others. Taking the example above for illustration:

  • -: normal file
  • rw-: owner(i.e. michaelzheng) can read and write
  • r--: groups members can only read
  • r--: others can only read

To change each permission group, we can convert binary representation to octal format.

r w e
4(i.e. 2^2) 2(i.e. 2^1) 1(i.e. 2^0)

Therefore, if I want to grant owner rwx(4 * 1 + 2 * 1 + 1 * 1 = 7), group member rx(4 * 1 + 2 * 0 + 1 * 1 = 5) and others r (4 * 1 + 2 * 0 + 1 * 0 = 4) then i can use

chmod 750 README.md

ps: displays information about a selection of the active processes

jobs: display status of jobs in the current session

fg: run jobs in the foreground

bg: run jobs in the background

df: report file system usage

Useful options:

Option Meaning
-h print sizes in human readable format
du -h

//output: 
Filesystem      Size   Used  Avail Capacity iused      ifree %iused  Mounted on
/dev/disk1     112Gi   97Gi   15Gi    87% 2771640 4292195639    0%   /
devfs          182Ki  182Ki    0Bi   100%     630          0  100%   /dev
map -hosts       0Bi    0Bi    0Bi   100%       0          0  100%   /net
map auto_home    0Bi    0Bi    0Bi   100%       0          0  100%   /home

du: estimate file space usage

Useful options:

Option Meaning
-h print sizes in human readable format
-s display only a total for each argument

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Learn ECMAScript

String - Character set

In the Unicode character set, every character is represented by a base 10 decimal number called a code point. A code unit is a fixed number of bits in memory to represent a code point. An encoding schema determines the length of code unit. A code unit is 8 bits if the UTF-8 encoding schema is used or 16 bits if the UTF-16 encoding schema is used. If a code point doesn't fit in a code unit, it is split into multiple code units, that is, multiple characters in a sequence representing a single character. JavaScript strings are always a sequence of UTF-16 code points. Any Unicode character with a code point less than 65,536 can be escaped in a JavaScript string or source code using the hexadecimal value of its code point, prefixed with \u.

const \u0061 = "\u0061\u0062\u0063";
console.log(a); // abc

const you = '你';
console.log(you.charCodeAt(0)); // 20320
console.log(you.codePointAt(0)); // 20320

const emoj = '💯';
console.log(emoj.codePointAt(0)); // 128175
console.log(emoj.charCodeAt(0)); //55357
console.log(emoj.charCodeAt(1)); // 56495
console.log(emoj.length); //2

Array.from(arrayLike[, mapFn[, thisArg]])

Creates a new, shallow-copied Array instance from an array-like or iterable object.

Array.from([1, 2, 3], x => x + x); // [2, 4, 6]

Page Visibility

The Page Visibility API allows developers to run specific code whenever the page user goes in focus or out of foucs.

function pageChanged() {
  if (document.hidden) {
    console.log('blur')
  } else {
    console.log('focus')
  }
}

document.addEventListener("visibilitychange", pageChanged);

Clipboard

Related concepts:

<script>
  function copy2Clipboard(text) {
    const textarea = document.createElement('textarea');
    textarea.value = text;
    document.body.appendChild(textarea);
    textarea.focus();
    textarea.setSelectionRange(0, text.length);
    document.execCommand('copy');
    document.body.removeChild(textarea);
  }
</script>

fetch

Related concepts:

const getImages = async () => {
  const image = await fetch('https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png');
  console.log(image); // gives us response as an object
  const blob = await image.blob();
  
  const url = URL.createObjectURL(blob);
  let imgElement = document.createElement('img');
  imgElement.src = url;
  document.body.appendChild(imgElement);
}

Web Worker

// mainscript.js
const awesomeworker = new Worker('worker.js'); // load worker.js and execute
awesomeworker.addEventListener('message', e => {
  if(e.data.task == "add") { 
    // task completed. do something with result
    document.write(e.data.result);
  }
});

const data = {task: "add", nums: [5, 10, 15, 20]};
// send data to worker
awesomeworker.postMessage(data);

// worker.js
self.addEventListener('message', e => {
    if(e.data.task == "add") {
      const res = e.data.nums.reduce((sum, num) => sum+num, 0);
      postMessage({task: "add", result: res}); // self.postMessage will also work
    }
});

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Useful APIs that you probably don't notice

Date

Get the number of days in a month

The 0th day of next month is the last day of the current month.

function daysInMonth(year, month) {
  let date = new Date(year, month + 1, 0);
  return date.getDate();
}

/**
 * Note that JS Date month starts with 0
 * The following computes how many days in March 2017
 */
console.log(daysInMonth(2017, 2)); // 31
// how many days in Feb 2017
console.log(daysInMonth(2017, 1)); // 28
// how many days in Feb 2016
console.log(daysInMonth(2016, 1)); // 29

getTimezoneOffset - get the time zone difference, in minutes, from current locale (host system settings) to UTC.

let now = new Date();
console.log(now.toISOString()); //2018-03-12T01:12:29.566Z
// China is UTC+08:00
console.log(now.getTimezoneOffset()); // -480
// convert to UTC
let UTCDate = new Date(now.getTime() + now.getTimezoneOffset() * 60 * 1000);
console.log(UTCDate.toISOString()); //2018-03-11T17:12:29.566Z
//convert to UTC+03:00
let eastZone3Date = new Date(UTCDate.getTime() + 3 * 60 * 60 * 1000);
console.log(eastZone3Date.toISOString()); //2018-03-11T20:12:29.566Z

JSON

JSON.stringify(value[, replacer[, space]])

When replacer is a function - apply replacer before stringify the value.

JSON.stringify(
  {
    a: 4,
    b: [3, 5, "hello"]
  },
  (key, val) => {
    if (typeof val === "number") {
      return val * 2;
    }
    return val;
  }
); //{"a":8,"b":[6,10,"hello"]}

when replacer is an array - use replacer as a white list to filter the keys

JSON.stringify(
  {
    a: 4,
    b: {
      a: 5,
      d: 6
    },
    c: 8
  },
  ["a", "b"]
); //{"a":4,"b":{"a":5}}

space can be used to beautify the output

JSON.stringify(
  {
    a: [3, 4, 5],
    b: "hello"
  },
  null,
  "|--\t"
);
/**结果:
{
|--	"a": [
|--	|--	3,
|--	|--	4,
|--	|--	5
|--	],
|--	"b": "hello"
}
*/

String

String.prototype.split([separator[, limit]])

"".split(""); // []

separator can be a regular expression!

"abc1def2ghi".split(/\d/); //["abc", "def", "ghi"]

If the seperator is a regular expression that contains capturing groups, the capturing groups will appear in the result as well.

"abc1def2ghi".split(/(\d)/); // ["abc", "1", "def", "2", "ghi"]

String.prototype.match

// get query value from query string with specified query key
function getQueryValue(query, key) {
  let regExp = new RegExp(`${key}=([^&]+)`);
  let [match, group] = query.match(regExp);
  return decodeURIComponent(group);
}

let query =
  "source=hp&ei=LINUW6zsCozw_wTFr5u4Cg&q=test&oq=test&name=i%20am%20michael";
console.log(getQueryValue(query, "q")); // 'test'
console.log(getQueryValue(query, "name")); // 'i am michael'

Tagged string literals

let person = "Mike";
let age = 28;

function myTag(strings, personExp, ageExp) {
  let str0 = strings[0]; // "that "
  let str1 = strings[1]; // " is a "

  // There is technically a string after
  // the final expression (in our example),
  // but it is empty (""), so disregard.
  // var str2 = strings[2];

  let ageStr;
  if (ageExp > 99) {
    ageStr = "centenarian";
  } else {
    ageStr = "youngster";
  }

  return str0 + personExp + str1 + ageStr;
}

let output = myTag`that ${person} is a ${age}`;
console.log(output);
// that Mike is a youngster

Number

Number.prototype.toString

In Javascript strings, each character represents a single 16-bit unit of UTF-16 text.

let s = "你";
let codePoint = s.charCodeAt(0);
console.log(codePoint); // 20320
let hex = codePoint.toString(16); // to hex format
console.log(hex); // 4f60
console.log(String.fromCharCode("0x4f60", 20320)); // 你你
console.log("\u4f60"); // 你

null vs undefined

If we don't want to distinguish null and undefined, we can use ==

undefined == undefined; //true
null == undefined; // true
0 == undefined; // false
"" == undefined; // false
false == undefined; // false

Don't simply use == to check for the existence of a global variable as it will throw ReferenceError. Use typeof instead.

// a is not defiend under global scope
a == null; // ReferenceError
typeof a; // 'undefined'

Spread Operator(...)

spread operator works for objects!

const point2D = { x: 1, y: 2 };
const point3D = { ...point2D, z: 3 };

let obj = { a: "b", c: "d", e: "f" };
let { a, ...other } = obj;
console.log(a); //b
console.log(other); //{c: "d", e: "f"}

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Introduction to JavaScript iterator

Symbol

ES6 introcues a new type called symbol. The Symbol function returns a value of type symbol.

const symbol1 = Symbol();
const symbol2 = Symbol("hi");

console.log(typeof symbol1); //symbol

console.log(symbol3.toString()); //Symbol(foo)

// each symbol value created by Symbol function is unique
console.log(Symbol("foo") === Symbol("foo")); // false

// Symbol itself is a function
console.log(typeof Symbol); //function

Iterator

An iterator is an object that provides a next method which returns the next item in the sequence. This method returns an object with two properties: done and value. For an object to be iterable, it must have a function property with a Symbol.iterator key, which returns a new iterator for each call.

// Array has build-in iteration support
function forOf(arr) {
  for (let i of arr) {
    console.log(i);
  }
}

/**
 * for...of loops is based on iterator
 * so forOf implementation above is basically:
 */
function iterating(arr) {
  let iterator = arr[Symbol.iterator](); //get the iterator for the array
  let next = iterator.next();
  while (!next.done) {
    console.log(next.value);
    next = iterator.next();
  }
}

Make Object iterable

Object doesn't have build-in iteration support.

let obj = { a: "b", c: "d" };
for (let i of obj) {
  //Uncaught TypeError: obj is not iterable
  console.log(i);
}

To make Object iterable, we have to add Symbol.iterator, either to the instance or the prototype.

Object.defineProperty(Object.prototype, Symbol.iterator, {
  value: function() {
    let keys = Object.keys(this);
    let index = 0;
    return {
      next: () => {
        let key = keys[index++];
        return {
          value: `${key}-${this[key]}`,
          done: index > keys.length
        };
      }
    };
  },
  enumerable: false
});

let obj = { a: "b", c: "d" };
for (let i of obj) {
  console.log(i); // a-b c-d
}

Generator

The function* declaration defines a generator function, whose return value is generator object. The generator object conforms to the iterator protocol. Note *generator function* itself is a function.

//generator function
function* generatorFn() {
  yield 1;
  return 2;
}

// generator object - iterator
let generatorObj = generatorFn();
let nextItem = generatorObj.next();

console.log(typeof generatorFn); //function
console.log(typeof generatorObj); //object
console.log(typeof generatorObj.next); //function
console.log(nextItem); //{value: 1, done: false}

Therefore, to make Object iterable, we can define its Symbol.iterator with generator function.

Object.defineProperty(Object.prototype, Symbol.iterator, {
  value: function*() {
    let keys = Object.keys(this);
    for (let key of keys) {
      yield `${key}-${this[key]}`;
    }
  },
  enumerable: false
});

let obj = { a: "b", c: "d" };
for (let kv of obj) {
  console.log(kv); // a-b c-d
}

In practice

With the technique in hand, we can make custom datatypes iterable.

class Group {
  constructor() {
    this._data = [];
  }

  add(it) {
    this._data.push(it);
  }

  delete(it) {
    let index = this._data.indexOf(it);
    if (index >= 0) {
      this._data.splice(index, 1);
    }
  }

  has(it) {
    return this._data.includes(it);
  }

  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => ({
        value: this._data[index++],
        done: index > this._data.length
      })
    };
  }

  static from(iterable) {
    let group = new Group();
    for (let item of iterable) {
      group.add(item);
    }
    return group;
  }
}

let group = Group.from(["a", "b", "c"]);
console.log(group);

for (let value of group) {
  console.log(value);
}

console.log([...group]);

Reference

Notice

  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.