I'm headwindz, living in Hangzhou
headwindz / headwindz.run Goto Github PK
View Code? Open in Web Editor NEWMy personal blog
Home Page: https://n0ruSh.run
My personal blog
Home Page: https://n0ruSh.run
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.
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.
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.
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:
<div></div>
div {
width: 100px;
height: 100px;
background: #0ff;
border: none;
border-radius: 50%;
}
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
.
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)
Percentage unit set to element is relative to the containing block. Containing block is subject to the position
property of the element.
fixed
positioned
element, the containing block is viewport.absolute
positioned
element with position absolute, the containing block is the closest positioned
ancestor, including content and padding
.static
(default) and relative
positioned element, the containing block is the closest block element, including only content
.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
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
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 iterationdone
: a boolean that represents whether the iteration is complete or notfunction 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 }}
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.
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)
}
const [firstPerson, ...rest] = new PersonQuery();
console.log(firstPerson, rest);
const numbers1 = [1, 2,3]
const combined = [...numbers1, ...new PersonQuery()];
The Generator object is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol.
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);
}
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.
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 = {};
}
}
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。
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(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(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() {
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;
});
}
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>
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.
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:
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());
<!--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:
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:
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 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>
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:
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:
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.
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>
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 gives the ability for functions to behave differently based on the number or type of the arugments. E.g. Array.from.
let set = new Set(["foo", "bar", "foo"]);
console.log(Array.from(set)); //["foo", "bar"]
console.log(Array.from([1, 2, 3], x => x + x)); // [2, 4, 6]
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.
First we need to understand two basic concepts in functions.
function A(a, b) {}
// a and b are parameters
console.log(A.length); //2
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.
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 has two main functionalities:
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
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.
.a {
color: red;
.b {
color: green;
&:hover {
color: yellow;
}
}
}
is equivalent to
.a {
color: red;
}
.a .b {
color: green;
}
.a .b:hover {
color: yellow;
}
.a {
flex: {
direction: column;
wrap: nowrap;
}
}
is equivalent to
.a {
flex-direction: column;
flex-wrap: nowrap;
}
To define a variable, prefix the name of variable with $: $name-of-variable
$main-color: #fff;
.a {
color: $main-color;
}
.b {
color: $main-color;
}
is equivalent to
.a {
color: #fff;
}
.b {
color: #fff;
}
$prefix: component;
.#{$prefix} {
color: #fff;
}
.#{$prefix}-input {
padding: 10px;
}
is equivalent to
.component {
color: #fff;
}
.component-input {
padding: 10px;
}
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;
}
$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;
}
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 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
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:
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
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
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);
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.
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
There are two ways to construct a function:
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;
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
There are two cases where a variable is undefined.
let s;
console.log(s); //undefined
let obj1 = {};
console.log(obj1.a); //undefined
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]
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.).
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.
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.
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:
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
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:
cache['a.txt'] = 'hello';
readWithCacheV2('a.txt', console.log);
console.log('after you');
//console output:
after you
hello
readWithCacheV2('a.txt', console.log);
console.log('after you');
//console output:
after you
hello
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:
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;
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);
});
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.
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
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();
}
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.
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.
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.
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
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();
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.
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 cp
// output:
cp(1) - copy files
file README.md
// output:
README.md: ASCII text
Userful Options:
Option | Meaning |
---|---|
-n | specify the number of first lines to print |
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 | 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
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 |
Userful Options:
Option | Meaning | Example |
---|---|---|
-O | specify output | wget -O file http://foo |
uid=501(michaelzheng) gid=20(staff) groups=20(staff),12(everyone)
groups
//staff everyone
whoami
//michaelzheng
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:
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
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
Useful options:
Option | Meaning |
---|---|
-h | print sizes in human readable format |
-s | display only a total for each argument |
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
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]
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);
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>
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);
}
// 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
}
});
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
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.stringify(
{
a: 4,
b: [3, 5, "hello"]
},
(key, val) => {
if (typeof val === "number") {
return val * 2;
}
return val;
}
); //{"a":8,"b":[6,10,"hello"]}
JSON.stringify(
{
a: 4,
b: {
a: 5,
d: 6
},
c: 8
},
["a", "b"]
); //{"a":4,"b":{"a":5}}
JSON.stringify(
{
a: [3, 4, 5],
b: "hello"
},
null,
"|--\t"
);
/**结果:
{
|-- "a": [
|-- |-- 3,
|-- |-- 4,
|-- |-- 5
|-- ],
|-- "b": "hello"
}
*/
"".split(""); // []
"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"]
// 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'
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
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"); // 你
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 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"}
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
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();
}
}
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
}
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
}
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]);
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.