clarencef / blog Goto Github PK
View Code? Open in Web Editor NEWLicense: MIT License
License: MIT License
根據 Google I/O 2019 Securing Web Apps with Modern Platform Features session 影片中提到XSS(Cross-site scripting) 仍是最常見、危害最大的網頁安全漏洞,因此我們會需要用 CSP(Content Security Policy) 來防止 XSS 攻擊。
要啟用這個機制則需要在 HTTP response header 設定 Content-Security-Policy 或 Content-Security-Policy-Report-Only(在此模式下,CSP策略不是强制性的,由於任何違規將會report到指定的URI,需搭配report-uri使用,可以用來測試策略而不用實際部署它):
Content-Security-Policy: default-src https:; script-src 'nonce-12345'; object-src 'none'; base-uri 'none';
上面這個設定將會要求必須在HTTPS下請求加載所有資源,只執行script elements中包含正確nonce attribute的script,並且避免下載任何plugins(ex: Flash)。
<script>alert('xss!!!!')</script> // 這將不會被執行
<script nonce='12345'>alert('it works')</script> // 由於 nonce token 相符合 因此視為合法script並執行
由下圖可見,透過 script-src 設置 nonce-[your_token]
(nonce base CSP)而不是實際的url,將可以避免日後維護url列表的困難及複雜度。
但在實作 nonce base CSP 可能會產生以下問題:
// 很多載入第三方套件時我們會需要用到的script 會因為不具備nonce-attribute 而失效
<script nonce='12345'>
var s = document.createElement("script");
s.src = "/path/to/script.js";
document.head.appendChild(s);
</script>
這時候我們就必須加入最新CSP3 standard 所支援的 keyword 'strict-dynamic'
來允許在已經被nonce base CSP認證的script element內可以動態新增script並執行,在那些未支援 'strict-dynamic'
keyword的browser 將會被 fall back 成 [script-src 'unsafe-inline' https: http:]
這表示將不會提供任何 XSS 的保護,但能確保這script能被正常執行。
Inline event handlers (onclick="...", onerror="..."
) 跟 <a href="javascript:...">
links 是可以被用來執行javascript的,這樣的話攻擊者將可以藉由 inject 惡意的 JavaScript 到類似的HTML裡並執行它,Strong CSP 要求對這兩種 dangerous pattern 進行 refactor ,並且將 nonces token 加入 <script>
elements 以確保安全。
以下為 csp.withgoogle.com 提供的範例:
link
<a href="javascript:linkClicked()">foo</a>
into
<a id="foo">foo</a>
<script nonce="${nonce}">
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('foo')
.addEventListener('click', linkClicked);
});
</script>
Inline event handlers
<script> function doThings() { ... } </script>
<span onclick="doThings();">A thing.</span>
into
<span id="things">A thing.</span>
<script nonce="${nonce}">
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('things')
.addEventListener('click', function doThings() { ... });
});
</script>
防護強度:
'unsafe-eval'
: 允許在網站裡使用eval()
,這會在某種程度上降低該網站對 DOM-based XSS 的對抗性,但可以讓你更容易的採用CSP strategy,如果你的網站並沒有使用eval()
, 請移除這個關鍵字來換取更好的安全性。
CSP Mitigator: Chrome extension
CSP Evaluator
Content Security Policy, by Google
Content Security Policy 入门教程, by 阮一峰
Cross origin infoleaks
快捷鍵
Ctrl + a
: 回到句首
Ctrl + e
: 回到句尾
Ctrl + u
: 刪除整行
Ctrl + l
: 清除畫面(直接打 clear 也行)
Ctrl + w
: 刪除游標前方的單字(Word)
Ctrl + r
: 搜尋 command 歷史
Ctrl + z
: 丟入背景暫停執行
檔案管理
cd -
: 回到上一個動作的資料夾
ex:
$ cd / 會到硬碟根目錄
$ cd ~ 到user根目錄
$ cd - 會回到上一動的硬碟根目錄
$ ls -lh
: 呈現更多檔案資訊
$ ls -a
: 顯示隱藏的檔案與目錄
$ ls -alh
: 顯示隱藏的檔案與目錄並顯示更多檔案資訊
$ mkdir [folder name]
: 創建資料夾
$ touch [file name]
: 創建檔案
搜尋
|
: 連接一系列指令的執行
ex:
$ echo "hello world" | pbcopy
$ ps aux | grep .npm
處理程序
$ ps aux
: 顯示較詳細的資訊(all user 含系統)
$ ps au
: 顯示較詳細的資訊(all user)
$ ps -u [username]
: 顯示該user帳號的資訊
$ jobs
: 可顯示目前 shell 內使用者有多少個程式在背景或暫停未結束的程式 , job number 會顯示在中括號〝[ ]〞內。連 PID 一起列出為 jobs -l
$ kill [-9] PID
: 終止任務(-9 是強制終止)
$ command &
: 丟入背景執行
$ bg [job number]
: 帶入背景
$ fg [job number]
: 帶回前景
輸入與輸出
>, >>
: 改變輸出的資料通道 (stdout, stderr)
ex:
$ cmd > file - 把cmd命令輸出導向到文件file中。如file已存在,清空原有文件。
$ cmd >> file - 把cmd命令的輸出導向到文件file中,如果file已存在,把訊息加在原有文件後面。
$ cmd < file - 使cmd命令從file讀入
下載文件
curl
: 支持訪問 HTTP 和 HTTPS 協議,能夠處理 FTP 傳輸,支持 gzip 壓縮,支持安裝許多 SSL/TLS 庫,也支持通過網絡代理訪問,包括 SOCKS。(powered by a library: libcurl)
wget
: 一個獨立的程序,無需額外的資源庫,支持遞歸下載
環境變數
$ export example_env=42
: 設定環境變數example_env並賦值為42
$ example_env=99
: 更新環境變數example_env值為99
undefined: 一個變數尚未被賦值
undeclared: 一個變數尚未被宣告
uninitialized: 一個變數在語法環境(Lexical Environment)中被創造且初始化,但尚未被求值運算進行語法綁定(LexicalBinding),因此造成變數尚不能被觸及。(ES6: let, const)
var a = Number('albert fang');
var b = Number('n/a')
console.warn(a === b) // false
console.warn(0 === -0) // true
console.warn(Object.is(0,-0)) // false
-------------------------------------------------
如何判別 Negative zero
console.warn(1/0); // Infinity
console.warn(1/-0); // -Infinity
function isNegativeZero (v) {
return v == 0 && 1/v == -Infinity
}
[].toString() // ""
[1,2,3].toString() // "1,2,3"
[,,].toString() // ","
(-0).toString() // "0"
(0).toString() // "0"
({}).toString() // "[object Object]"
({ a: 2 }).toString() // "[object Object]"
({toString: function toString() { return 'X'}}).toString(); // "X"
// flat array - 若 array 內包含 object 會拋出 SyntaxError 因此不適用於含有 object 的 array
[1,2,[10,11],3,[4,6,[5,[9]]]].toString().split(',') // ["1", "2", "10", "11", "3", "4", "6", "5", "9"]
---------------------------------------------------------------------------
Number(".") // NaN
Number(undefined) // NaN
Number([1, 2, 3]) // NaN
Number([15]) // 15
Number([undefined]) // 0
Number(null) // 0
Number([null]) // 0
Number("") // 0
Number([""]) // 0
Number('0') // 0
Number('-0') // -1
Number({}) // NaN
Number({valueOf() {return 3;}}) // 3
是一種變數提升 (Hoisting),它會將變數和函式宣告會提升到該执行上下文區塊 (scope)的最上方的一種現象。
console.warn(a);
c();
notHoistingFunc();
var a = "react";
var b = "vue";
function c () {
console.warn('message from function');
}
var notHoistingFunc = function () {
console.warn('message from not hoisting func');
}
如上面的程式碼在執行階段時是這樣被解讀的
function c () {
console.warn('message from function');
}
var a;
var b;
var notHoistingFunc;
console.warn(a); // undefined
c(); // message from function
notHoistingFunc(); // TypeError: notHoistingFunc is not a function
a = "react";
b = "vue";
notHoistingFunc = function () {
console.warn('message from not hoisting func');
}
因此導致了當我們執行console.warn(a)
時,由於變數 a
還沒被賦值我們將會得到undefined
,而我們卻能正常的執行 function c
但須注意的是若我們採用 function expression 來宣告 function,該 function 將不會跟 function declaration 一樣被提升就如同範例中的 function notHoistingFunc
一樣。
其實 let and const 跟 var 一樣會有 hoisting 的效果,只是當它們被 hoisting 後並不會立即被初始化,會產生之前提到的 TDZ(Temporal Dead Zone),因為若 let and cost 沒有 hoisting 的效果 console.warn(a)
的錯誤將不會是 ReferenceError
而是正常印出 a
。
var a = 'a'
{ // block scoping
console.warn(a) // ReferenceError: Cannot access 'a' before initialization
let a = 'changed a';
}
閉包(Closure) 就是一個 function 能觸及被它在被宣告時所在的作用域環境裡的變數或其他 function。
閉包經典範例如下:
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.warn(`i: ${i}`);
}, i * 1000)
}
我們預期的結果是 實際結果卻是
// i: 1 // i: 4
// i: 2 // i: 4
// i: 3 // i: 4
過去我們是這樣解的,利用 IIFE pattern
for (var i = 1; i <= 3; i++) {
(function (i) {
setTimeout(function() {
console.warn(`i: ${i}`);
}, i * 1000)
})(i)
}
如今我們可以用 ES6 的 let 來解
for (let i = 1; i <= 3; i++) {
setTimeout(function() {
console.warn(`i: ${i}`);
}, i * 1000)
}
如預期的結果是顯示
// i: 1
// i: 2
// i: 3
閉包是形成一個 Module 的重要元素,因為一個 Module 必須能夠封裝好它的資料及方法,以確保達到 principle of least privilege 只揭露該揭露的資料與方法。
var getPerson = (function module(leader) {
function toLead(team) {
console.warn(`${leader} to lead ${team} team`)
};
const publicAPI = { toLead };
return publicAPI
})("Tom");
getPerson.toLead('Web'); // Tom to lead Web team
function getPerson(leader) {
function toLead(team) {
console.warn(`${leader} to lead ${team} team`)
};
const publicAPI = { toLead };
return publicAPI
};
const webLeader = getPerson('Tom');
webLeader.toLead('Web'); // Tom to lead Web team
const androidLeader = getPerson('Hu')
androidLeader.toLead('Android'); // Hu to lead Android team
函式內的 this 值取決於該函式如何被呼叫,this 會指向該執行環境的上下文。
function callName() {
console.log(this.name);
}
var name = 'Global';
var area = {
name: 'Taiwan',
callName: callName // 這裡的 function 指向全域的 callName function
};
// 此方法在全域環境下被呼叫除非我們在 strict mode 下執行這程式碼,
// 不然 this 會指向 global 環境 (strict mode 下 this 會等於 undefined)
callName() // 'Global'
area.callName() // 'Taiwan',方法是在物件下調用,那麼 this 則是指向該物件
'use strict';
function greeting(person1, person2){
console.log('Hi', this, ',', person1, 'and', person2)
}
const greeting2 = greeting.bind('you', 'Tom', 'Tina');
greeting('Tom', 'Tina') // Hi undefined , Tom and Tina
greeting.call('you', 'Tom', 'Tina') // Hi you , Tom and Tina
greeting.apply('you', ['Tom', 'Tina']) // Hi you , Tom and Tina
greeting2(); // Hi you , Tom and Tina
它沒有自己的 this、arguments、super、new.target 等語法,當你在 arrow function 內使用 this 時他會指向定義 arrow function 的 Lexical Scoping 的 lexical this。
const course = {
lecturer: 'Albert',
teach(lang) {
setTimeout(() => {
console.warn(this.lecturer, 'teaching' ,lang)
}, 500);
}
}
course.teach('javascript'); // Albert teaching javascript
// 失敗的例子(因為 course1 為一個 object 而不是 scope,因此 arrow function 的 lexical this 變成global 了)
const course1 = {
lecturer: 'Albert',
teach: (lang) => {
console.warn(this.lecturer, 'teaching' ,lang)
}
}
course.teach('javascript'); // undefined "teaching" "javascript"
function Workshop (teacher) {
this.teacher = teacher;
}
Workshop.prototype.ask = function(question) {
console.warn(this.teacher, question);
}
var deepJS = new Workshop('Kyle');
var reactJS = new Workshop('Suzy');
deepJS.ask("Is 'prototype' a class?"); // Kyle Is 'prototype' a class?
reactJS.ask("Isn't 'prototype' a ugly?"); // Suzy Isn't 'prototype' a ugly?
deepJS.constructor === Workshop; // true
deepJS.__proto__ === Workshop.prototype // true
Object.getPrototypeOf(deepJS) === Workshop.prototype // true
Prototypal Inheritance
function Workshop (teacher) {
this.teacher = teacher;
}
Workshop.prototype.ask = function(question) {
console.warn(this.teacher, question);
}
function AnotherWorkshop (teacher) {
Workshop.call(this, teacher);
}
AnotherWorkshop.prototype = Object.create(Workshop.prototype);
AnotherWorkshop.prototype.speakUp = function(msg) {
this.ask(msg.toUpperCase());
}
const JSRecentParts = new AnotherWorkshop("Kyle");
JSRecentParts.speakUp("Is this actually inheritance?"); // Kyle IS THIS ACTUALLY INHERITANCE?
class Workshop {
constructor(teacher) {
this.teacher = teacher
}
ask(question) {
console.warn(this.teacher, question);
}
}
class AnotherWorkshop extends Workshop {
speakUp(msg) {
console.warn(this.ask(msg.toUpperCase()));
}
}
const deepJS = new Workshop('Kyle');
const JSRecentParts = new AnotherWorkshop('Kyle');
deepJS.ask("Is 'prototype' a class?"); // Kyle Is 'prototype' a class?
JSRecentParts.speakUp("Is this actually inheritance?"); // Kyle IS THIS ACTUALLY INHERITANCE?
就只是 object 不是 class 或 function 完全不用考慮 prototype,可以自由地組合出所需的 object 來share method。
const Workshop = {
setTeacher(teacher) {
this.teacher = teacher;
},
ask(question) {
console.warn(this.teacher, question);
}
}
const AnotherWorkshop =Object.assign(
Object.create(Workshop),
{
speakUp(msg) {
console.warn(this.ask(msg.toUpperCase()));
}
}
);
const JSRecentParts = Object.create(AnotherWorkshop);
JSRecentParts.setTeacher('Kyle');
JSRecentParts.speakUp("Is this actually inheritance?"); // Kyle IS THIS ACTUALLY INHERITANCE?
Deep Javascript v3, by frontend masters.
根據 moz 整理出來的文章 On-Page optimization 包含下面幾個元素:
他們的差別在於:
Baby Name Wizard
How Much Does a Website Cost
The Best Instant Noodles of All Time
一個網站的 Title Tag 必須精準的表達出該網頁所要呈現給使用者網頁內容,當 user 使用像 Google,Bing ... 等等搜尋引擎來搜尋我們的網頁時 ,這些引擎將會使用 Title Tag 做為進入網頁的連結的標題並於進入網站後顯示於瀏覽器頁籤上,而它對於網站的 SEO 及當我們分享網頁給他人時也扮演了重要的角色。
只需要在你 HTML head tag 內的 title tag 加入,你想呈現給使用者的訊息即可
<head>
<title>Good for SEO</title>
</head>
主要關鍵字 - 次要關鍵字 | 網站名稱(品牌名稱)
Title Tag 並沒有長度的硬性規定,可能會因搜尋引擎而改變顯示的長度,因此推薦最佳長度 50 - 60 的字元。
不要刻意重複關鍵字或使用過多同義字(關鍵字堆砌 keyword-stuffed),因為這並不會加強網站的SEO,反而可能成為扣分項目。
最好每個頁面都有它獨特的 title,獨特的 title 將有助於搜尋引擎了解該頁面內容的價值與獨特性也可能網頁的增加點擊率(Click-through Rate),因此應該避免用一些過於普遍的字眼來當作 title ("Home", "New") 等等。
標籤運算式(通常是一個函式)會被呼叫來處理樣板字面值,讓你可以在函式回傳之前進行操作。
function upper(strings,...values) {
let result = '';
strings.forEach((item, index) => {
if (values[index]) result += item + values[index].toUpperCase();
else result += item
})
return result;
}
var name = "albert",
twitter = "hr00090241",
topic = "JS Recent Parts";
console.warn(upper`Hello ${name} (@${twitter}), welcome to ${topic}!`)
console.log(
upper`Hello ${name} (@${twitter}), welcome to ${topic}!` ===
"Hello ALBERT (@HR00090241), welcome to JS RECENT PARTS!"
);
想像str.padStart()
可能的使用情境為當我們使用 new Date().getMonth()
去取得月份資訊時 假設今天是 5月 xx 日,我們可能只會拿到 4
但需求要顯示 05
就可以用到str.padStart()
,而str.padEnd()
我想可以用在跟我們要顯示某個user大概的追蹤人數可以用到ex: 當追蹤人數為 12334 但我們只想顯示 12000+
str.padStart() 會將用給定用於填充的字串,以重複的方式,插入到目標字串的起頭(左側),直到目標字串到達指定長度。
Syntax: str.padStart(targetLength [, padString])
str.padEnd() 會將用給定用於填充的字串,以重複的方式,插入到目標字串的起頭(右側),直到目標字串到達指定長度。
Syntax: str.padEnd(targetLength [, padString])
.padStart()
example: const getMonth = String(new Date('2019/5/16').getMonth() + 1);
let month = getMonth.padStart(2);
// 未指定 padString
console.log(month) // ' 5'
// 指定 padString 為 '0'
month = getMonth.padStart(2, '0');
console.log(month) // '05'
// 指定 padString 為 '00',多餘的 padString 會被忽略
month = getMonth.padStart(2, '00');
console.log(month) // '05'
.padEnd()
example: function getDisplayViews(views, replaceStartAt) {
const viewsString = String(views);
if(replaceStartAt >= viewsString.length ) return viewsString;
return `${viewsString.slice(0,replaceStartAt).padEnd(viewsString.length, '0')}+`
}
const view = 12345;
const newViews = getDisplayViews(view, 2);
console.warn(newViews); // 12000+
function giveMeData() {
return ['hi', 'albert', 'fang', '1', '2', '3']
}
// before
const data = giveMeData();
const greeting = data[0];
const firstName = data[1] !== undefined ? data[1] : 'albert';
const lastName = data[2];
const others = data.slice(3); // ['1', '2', '3']
// after
const [
greeting,
firstName = 'albert', // Only when value equal undefined will give it default value.
lastName,
...others, // ['1', '2', '3'] 若該 array 沒有剩餘的值 return empty array
] = giveMeData();
console.warn(greeting, firstName, lastName) // hi albert fang
// Destructuring 是在做賦值動作而不是宣告,因此可以先宣告再 Destructuring 賦值
let greeting, firstName, lastName, others;
[
greeting,
firstName
lastName,
...others,
] = giveMeData();
// 同理也可以將 Destructuring 的值賦予 Object
let o = {};
[
o.greeting,
o.firstName
o.lastName,
...o.others,
] = giveMeData();
let x = 10;
let y = 20;
// before
let temp = x;
x = y;
y = temp
// after
[y, x] = [x, y];
// before
function data (tmp = []) {
let = [
first,
second,
third
] = tmp;
// .............
}
// after
function data ([
first,
second,
third
] = []) {
// ..........
}
function giveMeData() {
return ['hi', ['albert', 'fang'], '1', '2', '3']
}
const data = giveMeData() || [];
const greeting = data[0];
const fullName = data[1] || [];
const firstName = fullName[0];
const lastName = fullName[1];
const others = data.slice(2); // ['1', '2', '3']
// after
const [
greeting,
[
firstName,
lastName
] = [],
...others
] = giveMeData() || [];
function giveMeData() {
return {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5,
}
}
// before
const data = giveMeData() || {};
const first = data.a !== undefined ? data.a : 42;
const second = data.b;
const third = data.c;
// after
const {
a: first = 42, // Only when value equal undefined will give it default value.
b: second,
c: third,
...others, // { d: 4, e: 5 }
} = giveMeData() || {};
// 宣告再 Destructuring 賦值
let first, second;
// before
first = data.a;
second = data.b;
// after 由於 {} 在 javascript 代表 block 直接用 {} = giveMeData() 做 Destructuring 會噴出 syntax error,因此要將整個 Destructuring 用 () 包覆起來或直接賦予新的 Object。
// 用 () 包覆起來
({
a: first,
b: second,
} = giveMeData());
// 直接賦予新的 Object
let temp = {};
temp = {
a: first,
b: second,
} = giveMeData();
// Nested Object Destructuring
function giveMeData() {
return {
a: 1,
b: {
c: 3,
d: 4,
},
e: 5,
}
}
// before
let tmp = giveMeData() || {};
var a = tmp.a;
var b = tmp.b || {};
var c = b.c;
var d = b.d;
// after
temp = {
at,
b = {},
b: {
c,
d,
} = {},
} = giveMeData() || {};
返回陣列內符合條件的項目,若找不到 arr.find()
返回 undefined,arr.findIndex()
返回 -1.
const arr = [{ name: 'albert' }, { name: 'tim' }];
arr.find(function match(item) {
return item && item.name === 'albert';
}) // { name: 'albert' }
arr.find(function match(item) {
return item && item.name === 'bob';
}) // undefined
arr.findIndex(function match(item) {
return item && item.name === 'albert';
}) // 0
arr.findIndex(function match(item) {
return item && item.name === 'bob';
}) // -1
檢查該項目是否存在於陣列內,可以用來取代arr.indexOf()
因為 arr.indexOf()
會返回該項目在陣列內的位置而不是布林值,還需用 arr.indexOf() > -1
來判斷是否存在於該陣列內。
const arr = [10, 20, NaN, 30, 40, 50];
arr.includes(20) // true
arr.includes(60) // false
arr.includes(30, -3 ) // true // 從倒數第三個項目開始搜尋
arr.includes(30, -2 ) // false // 從倒數第二個項目開始搜尋
arr.includes(NaN) // true
const nestedValues = [1, [2, 3], [[]], [4, [5]], 6];
nestedValues.flat(0); // [1, [2, 3], [[]], [4, [5]], 6]
nestedValues.flat(/* default: 1 */); // [1, 2, 3, [], 4, [5], 6]
nestedValues.flat(2); // [1, 2, 3, 4, 5, 6]
先對陣列做 map 再對產生的結果做 flat 深度為1 的 壓縮,若要對陣列做flat > 1
的操作必須先做 arr.map
在執行 arr.flat(deep)
。
[1, 2, 3].map(function (v) {
return [v * 2, String(v * 2)];
}); // [[2, '2'], [4, '4'], [6, '6']]
[1, 2, 3].map(function (v) {
return [v * 2, String(v * 2)];
}).flat(); // [2, '2', 4, '4', 6, '6']
[1, 2, 3].flatMap(function (v) {
return [v * 2, String(v * 2)];
}); // [2, '2', 4, '4', 6, '6']
ECMAScript 2015 中補充了一些協議,其中包含可迭代協議(iterable protocol)以及迭代器協議(iterator protocol)。
- iterable: 允許 JavaScript 物件定義或客制他們的迭代行為,必須有一個 [@@iterator] 屬性,並且回傳一個 iterator 。
- iterator: 定義了一個標準方式來產出一連串(有限或無限)的值,而該值必須有一個 next 屬性,呼叫該屬性每次必須回傳一個 { value: any, done: boolean } 的物件。
Iterable values 包括:
- Arrays
- Strings
- Maps
- Sets
- DOM data structures (work in progress)
以下語法會透過 iteration(迭代) 來取得值:
- Destructuring via an Array pattern:
- for-of loop
- Array.from()
- Spread operator (...)
- Constructors of Maps and Sets
- Promise.all(), Promise.race()
- yield*
const obj = {
a: 'albert',
b: 'ben',
c: 'clarence',
[Symbol.iterator]: function() {
const keys = Object.keys(this);
let index = 0;
return {
next: () => {
return index < keys.length
? { done: false, value: this[keys[index++]] }
: { done: true, value: undefined }
}
}
}
}
console.warn([...obj]);
Generator object 是由一個 generator function 返回的而得到的,且它符合上面提到的迭代協議(iterable protocol)以及迭代器協議(iterator protocol)。
function* main() {
yield 1;
yield 2;
yield 3;
yield 4;
}
const gf = main();
gf.next(); // {value: 1, done: false}
gf.next(); // {value: 2, done: false}
gf.next(); // {value: 3, done: false}
gf.next(); // {value: 4, done: false}
gf.next(); // {value: undefined, done: true}
[...main()] // [1, 2, 3, 4]
當你最後一個值用 return 並在使用上面提到 javascript iteration(迭代)的語法來取得值時,會發現最後一個值並不能被正確的拿到。這是因為 iterator protocol 裡面有提到執行 next() 後返回的值,當 done property 為 true 時 value 將會被省略。
// Generator with return.
function* returnMain() {
yield 1;
yield 2;
yield 3;
return 4;
}
const gfReturn = returnMain();
gfReturn.next(); // {value: 1, done: false}
gfReturn.next(); // {value: 2, done: false}
gfReturn.next(); // {value: 3, done: false}
gfReturn.next(); // {value: 4, done: true}
gfReturn.next(); // {value: undefined, done: true}
[...returnMain()] // [1, 2, 3]
for (item of returnMain()) {
console.warn(item);
}
// 1
// 2
// 3
// undefined
next()
methodconst obj = {
a: 'albert',
b: 'ben',
c: 'clarence',
[Symbol.iterator]: function* () { // generator function
for (let key of Object.keys(this)) {
yield this[key];
}
}
}
console.warn([...obj]);
var numbers = {
[Symbol.iterator]: function* ({
start = 0,
stop = 100,
step = 1,
} = {}) {
for( i = start; i <= stop; i+= step) {
yield i
}
}
}
console.log(
`My lucky numbers are: ${
[...numbers[Symbol.iterator]({
start: 6,
stop: 30,
step: 4,
})
]}`
);
const msg = 'Hello World';
// look ahead.
msg.match('/(l.)(?=o)/g'); // ['ll'] (?=): positive match. 指 l 後面帶一個任意文字或符號且第三的character 必須要是 o 才符合
msg.match('/(l.)(?!o)/g'); // ['lo', 'ld'] (?!): negative match. 指 l 後面帶一個任意文字或符號且第三的character 不能為 o 才符合
// look behind.
msg.match('/(?<=e)(l.)/g'); // ['ll'] (?=): positive match. 指 l 後面帶一個任意文字或符號且 l 前面的character 必須要是 e 才符合
msg.match('/(?<!e)(l.)/g'); // ['lo', 'ld'] (?!): negative match. 指 l 後面帶一個任意文字或符號且 l 前面的character 不能為 e 才符合
const msg = 'Hello World';
const str = `He said: "She's the one!".`;
msg.match(/(?<cap>l.)/).groups // {cap: "ll"}
// Backreference by named capture: \k<named capture>
msg.match(/(?<cap>[jkl])o Wor\k<cap>/); // ['lo Worl', 'l'], groups: {cap: "l"} 往回尋找 l
str.match(/(?<quote>['"])(.*?)\k<quote>/g); // [""She's the one!""] 往回尋找 "
// Replace named capture with new pattern
msg.replace(/(?<cap>l.)/g, '-$<cap>-') // "He-ll-o Wor-ld-"
msg.replace(/(?<cap>l.)/g, function re(...args) {
const [,,,,{cap}] = args;
return cap.toUpperCase();
}) // "HeLLo WorLD"
原本.
為匹配除了換行符號之外的單一字元,但最新的 ECMAScript 規範中,增加了一个新的flag s
用來表示 dotAll。使 . 可以匹配任意字符。
const msg = `
The quick brown fox
jumps over the
lazy dog`;
msg.match(/brown.*over/); // null
msg.match(/brown.*over/s); // "brown fox\njumps over"
Exploring JS, by Dr. Axel Rauschmayer.
ECMAScript 6 入门, by 阮一峰.
Iterable-and-iterator-in-javascript, by Peng Jie.
樣板字面值, by MDN.
Iteration protocols, by MDN.
Generators, by MDN.
Regexp-backreferences, by javascript.info
看 Kent C. Dodds 在 Frontend masters 上教授的一個課程叫 Advanced React Patterns 時,同時想要整理一下自己的思緒因此寫了這篇文章來作為日後參考的依據。
當我們想要清楚的表達 component 之間的關係且想要讓 parent component 可以自定義想要 render 的 child component 時,使用 Compound component 將會是一個很好的方法。
假設我們今天要寫一個可以轉換網站 Theme 的 ToggleTheme component,,並且能夠顯示不同的提示文字告訴使用者現在所使用的 Theme 是什麼,通常我會這樣做
class ToggleTheme extends React.Component {
state = { isDarkMode: false }
render() {
const { darkThemeText, lightThemeText } = this.props;
return this.state.isDarkMode ? (
<div>
{darkThemeText}
<button>Toggle Theme</button>
</div>
) : (
<div>
{lightThemeText}
<button>Toggle Theme</button>
</div>
);
}
}
class Main extends React.Component {
render () {
return (
<div className='main>
<ToggleTheme
darkThemeText ="Dark Mode"
lightThemeText="Light Mode"
/>
</div>
);
}
}
但這樣的設計會缺乏 child component 結構變動的靈活度,例如:今天若使用者想自由的調整按鈕跟文字的顯示順序,他是沒辦法直接從 parent component 來改的,要解決這種情況,就可以用
Compound component 來做到。
Compound component 可以讓你透過 this.props.children 來 render 由 parent component 傳入的 child component,首先利用 React.Children.map() 來遍歷所有傳入的 this.props.children,再透過 React.cloneElement 將 child component 所需要用到的 state 傳入每個 children component 內,對於使用者來說,只需要傳入想要 render 的 children component,不用知道這個 ToggleTheme 內部運作的機制,自然也能夠隨意調整 component render 的順序。
使用 Compound component pattern 來寫剛剛的 ToggleTheme component 會像是這樣:
class ToggleTheme extends Component {
state = { isDarkMode: false };
toggle = () => {
this.setState(
({ isDarkMode }) => ({ isDarkMode: !isDarkMode }),
() => this.props.onToggle(this.state.isDarkMode)
);
};
static Dark = ({ isDarkMode, children }) => {
return isDarkMode ? children : null;
};
static Light = ({ isDarkMode, children }) => {
return isDarkMode ? null : children;
};
static Button = ({ toggle, children }) => {
return <button onClick={toggle}>{children}</button>;
};
render() {
const { isDarkMode } = this.state;
const { children } = this.props;
return React.Children.map(children, child => {
return React.cloneElement(child, {
isDarkMode,
toggle: this.toggle
});
});
}
}
class Main extends Component {
state = {
currentTheme: "light"
};
onToggle = isDarkMode => {
if (isDarkMode) {
this.setState({
currentTheme: "dark"
});
} else {
this.setState({
currentTheme: "light"
});
}
};
render() {
const { currentTheme } = this.state;
const mainClasses = cx("main", {
dark: currentTheme === "dark"
});
return (
<div className={mainClasses}>
<ToggleTheme onToggle={this.onToggle}>
<ToggleTheme.Dark>
<span className="themeTxt">{currentTheme} mode</span>
</ToggleTheme.Dark>
<ToggleTheme.Light>
<span className="themeTxt">{currentTheme} mode</span>
</ToggleTheme.Light>
<ToggleTheme.Button>
Toggle {currentTheme === "light" ? "dark" : "light"} Theme
</ToggleTheme.Button>
</ToggleTheme>
</div>
);
}
}
透過 ToggleTheme Class 的 static properties 來制定它的 children component 名稱,這樣我們就能夠直接從名稱上看出 Parent 與 Children component 之間的關聯性。
當上面例子裡的 children component 被包覆在另一個 DOM element 或 component 時整個 Compound component 就會無法正常運作。
<ToggleTheme onToggle={this.onToggle}>
<ToggleTheme.Dark>
<span className="themeTxt">{currentTheme} mode</span>
</ToggleTheme.Dark>
<ToggleTheme.Light>
<span className="themeTxt">{currentTheme} mode</span>
</ToggleTheme.Light>
<span>
<ToggleTheme.Button>
Toggle {currentTheme === "light" ? "dark" : "light"} Theme
</ToggleTheme.Button>
</span>
</ToggleTheme>
這是因為 React.Children.map 只會對第一層的 children 跑迴圈,所以現在 props 只會傳到新增的span 上並不會再往下傳到<ToggleTheme.Button>
component 了。
若我們要順利的把 props 往下傳就必須使用 React 提供的 context API 來做
const ToggleThemeContext = createContext({
isDarkMode: false,
toggle: () => {}
});
class ToggleTheme extends Component {
static Dark = ({children}) => {
return (
<ToggleThemeContext.Consumer>
{({ isDarkMode }) => (isDarkMode ? children : null)}
</ToggleThemeContext.Consumer>
);
};
static Light = ({children}) => {
return (
<ToggleThemeContext.Consumer>
{({ isDarkMode }) => (isDarkMode ? null : children)}
</ToggleThemeContext.Consumer>
);
};
static Button = ({children}) => {
return (
<ToggleThemeContext.Consumer>
{({ isDarkMode, toggle }) => {
return (
<button onClick={toggle}>{children}</button>
)
}}
</ToggleThemeContext.Consumer>
);
};
toggle = () => {
this.setState(
({ isDarkMode }) => ({ isDarkMode: !isDarkMode }),
() => this.props.onToggle(this.state.isDarkMode)
);
};
state = {
isDarkMode: false,
toggle: this.toggle,
};
render() {
const { children } = this.props;
return (
<ToggleThemeContext.Provider value={this.state}>
{children}
</ToggleThemeContext.Provider>
);
}
}
相較於 compound component, render prop component 較被眾人所知也常常被拿來跟 HOC (High Order Component) 作比較,render prop 就是將 render function 當作 props 傳入,將 render 的控制權,從 component 內部移轉至使用該 component 的 parent component 上,這種方式讓 component 能更容易的被複用及更高的自由度,而它有兩種實作方式:
class ToggleTheme extends Component {
toggle = () => {
this.setState(
({ isDarkMode }) => ({ isDarkMode: !isDarkMode }),
() => this.props.onToggle(this.state.isDarkMode)
);
};
state = {
isDarkMode: false,
toggle: this.toggle
};
render() {
const { renderToggleThemeContent } = this.props;
return renderToggleThemeContent(this.state);
}
}
<div className={mainClasses}>
<ToggleTheme
onToggle={this.onToggle}
renderToggleThemeContent={({ isDarkMode, toggle }) => {
return (
<>
<span className="themeTxt">{currentTheme} mode</span>
<button onClick={toggle}>
Toggle {isDarkMode ? "dark" : "light"} Theme
</button>
</>
);
}}
/>
</div>
class ToggleTheme extends Component {
toggle = () => {
this.setState(
({ isDarkMode }) => ({ isDarkMode: !isDarkMode }),
() => this.props.onToggle(this.state.isDarkMode)
);
};
state = {
isDarkMode: false,
toggle: this.toggle
};
render() {
return this.props.children(this.state);
}
}
<div className={mainClasses}>
<ToggleTheme onToggle={this.onToggle}>
{({ isDarkMode, toggle }) => {
return (
<>
<span className="themeTxt">{currentTheme} mode</span>
<button onClick={toggle}>
Toggle {isDarkMode ? "dark" : "light"} Theme
</button>
</>
);
}}
</ToggleTheme>
</div>
被提出來代替 Mixin 的 pattern,是一個可以將可共用的邏輯抽取出來並讓我們更簡單的去組合、複用這些邏輯的一個 function,這個 function 接收一個 component 作為參數並回傳一個擁有共用邏輯的新 component 。
let withValue = WrappedComponent => {
class Wrapper extends React.Component {
constructor () {
super();
this.state = { val: 0 }
this.update = this.update.bind(this);
}
update () {
this.setState({val:this.state.val+ 1 })
}
render () {
return <InnerComponent update = {this.update} ref={this.ref} {...this.props} {...this.state}/>
}
}
// 若沒給予新 component displayName,debug 時將會看到這 component 顯示 Unknown.
Wrapper.displayName = `withValue(${WrappedComponent.displayName ||
WrappedComponent.name})`;
// hoistNonReactStatics 是 hoist-non-react-statics 這 library 提供的一個函式,能幫我們處理 static method hoisting 的問題
// 若要取得被 HOC 包裹過的 component 的 ref,必須使用 React.forwardRef 將其 forward 下去
return hoistNonReactStatics(React.forwardRef(Wrapper), WrappedComponent);
}
Mixing Component Patterns, by Kent C. Dodds
Advanced React Patterns, by Kent C. Dodds
Higher-order components vs Render Props, by Richard Kotze
進階 React Component Patterns 筆記(上), by arvinh
進階 React Component Patterns 筆記(下), by arvinh
var min = 18;
function checkAge(age) {
return age >= min;
}
console.log(checkAge(15)); // false
min = 15;
console.log(checkAge(15)); // true
上面 checkAge 是一個impure function 他的output受到了外層的變數所影響,只要改變外層的變數相同的input 將不會有相同的output。
比如說我把外層的min變數改成 min = 15,這時候就算我同樣給予checkAge function 參數 15,此時回傳將不會是false而會變成true。
當我們將外層的變數移到function內,他就會變成pure function且可以在每次執行程式時,確保function可以正確的來檢查我們想要檢查的年齡
function checkAge(age) {
var min = 15;
return age >= min;
}
function unary (fn) {
return function (arg) {
return fn(arg);
}
}
function binary (fn) {
return function (arg1, arg2) {
return fn(arg1, arg2);
}
}
function f(...args) {
return args
}
const g = unary(f);
const h = binary(f);
g(1,2,3,4) // [1]
h(1,2,3,4) // [1,2]
function flip(fn) {
return function(arg1,arg2,...args) {
return fn(arg2, arg1, ...args)
}
}
function reverseArgs(fn) {
return function (...args) {
return fn(args.reverse())
}
}
function f(...args) {
return args
}
var g = flip(f)
var h = reverseArgs(f)
g(1,2,3,4) // [2, 1, 3, 4]
h(1,2,3,4) // [4, 3, 2, 1]
function not(fn) {
return function(...args) {
return !fn(...args)
}
}
function isOdd(v) {
return v % 2 === 1;
}
const isEven = not(isOdd);
isEven(4);
簡單來說就是一個function接受另一個function及其他參數作為參數,返回一個帶有這些參數的function。
function partialL(fn,...larg) { // 參數順序由左至右,先進先出
return function (...rest) {
var argtoAplly = [...larg,...rest];
console.log(argtoAplly) //["hello", "world", "hello", "albert"]
return fn.apply(this,argtoAplly);
}
}
function partialR(fn,...rarg) { // 參數順序由右至左,後進先出
return function (...rest) {
var argtoAplly = [...rest,...rarg];
console.log(argtoAplly) // ["hello","albert","hello","world"]
return fn.apply(this,argtoAplly);
}
}
function hello(a=' who',b=' who',c=' who',d=' who') {
return a + b + c + d;
}
var say = partialL(hello,'hello',' world');
console.log(say(' hello',' albert')); // "hello world hello albert"
var say2 = partialR(hello,' hello',' world');
console.log(say2('hello',' albert')); "hello albert hello world"
把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。
var greeting = function(first, lastname, other) {
return 'Hi, ' + first + ' ' + lastname + ' ' + other;
}
function curry (fn) {
var minArgNum = fn.length;
function given (argsNow) {
return function helper () {
var args = Array.prototype.slice.call(arguments, 0);
var updatedArgs = argsNow.concat(args);
if (updatedArgs.length >= minArgNum) {
return fn.apply(this, updatedArgs);
} else {
return given(updatedArgs);
}
}
}
return given([]);
}
var togreet = curry(greeting);
var toalbert = togreet('albert');
console.log(toalbert('fang')('good to see you'));
// "Hi, albert fang good to see you"
將function串連起來並由右至左順序進行,下一個function根據上一個function返回的結果,繼續進行變更。
const compose = function () {
let funcArg = Array.prototype.slice.apply(arguments);
let start = funcArg.length-1;
return function () {
var args = arguments;
for(let i=start;i>=0;i--) {
args = [funcArg[i].apply(this,args)];
}
return args[0];
}
}
var query = function(chr) {
return function(str) {
var index = str.indexOf(chr);
return (index < 0) ? "" : str.substring(index + 1);
};
};
var splitOn = function(chr) {
return function(str) {
return str.split(chr);
};
};
var makeObj = function(term) {
return term.reduce(function(obj, str) {
var parts = str.split("=");
obj[parts[0]] = parts[1];
return obj;
}, {});
};
var url = "http://www.airbnb.com.tw/s/%E9%A6%96%E7%88%BE--%E5%8D%97%E9%9F%93?guests=2&checkin=2016-09-13&checkout=2016-09-19&ss_id=uxu1uf9a&source=bb&s_tag=FT-vBGd6";
var getTerm = compose(makeObj, splitOn("&"), query("?"));
console.log(getTerm(url));
//[object Object] {
checkin: "2016-09-13",
checkout: "2016-09-19",
guests: "2",
s_tag: "FT-vBGd6",
source: "bb",
ss_id: "uxu1uf9a"
}
與composition執行順序相反,由左至右順序進行,下一個function根據上一個function返回的結果,繼續進行變更。
var add = function (number) {return number + 2};
var double = function (number) {return number * 2};
var triple = function (number) {return number * 3};
var pipe = function compose(f) {
var queue = f ? [f] : []; //先將function放入array給foreach用
var fn = function fn(g) {
if (arguments.length) { // 若有function傳入,持續加入function至佇列陣列中
queue.push(g);
return fn;
}
return function() { // 開始執行pipe function;
var args = Array.prototype.slice.call(arguments);
queue.forEach(function(func) {
args = [func.apply(this, args)];
});
console.log(args);
return args[0];
}
};
return fn;
};
var goPipe = pipe(add)(double)(triple)();
console.log(goPipe(2));
--------------------------------------------------------------
ES2015:
const add = (number) => number + 2;
const double = (number) => number * 2;
const triple = (number) => number * 3;
const pipeline = (...fns) => {
return val => {
return fns.reduce((acc,fn) => fn(acc),val);
}
};
let funPipe = pipeline(add,double,triple);
let result = funPipe(8);
console.log(result) // 60
const originObj= {
a:1,
b:2,
}
const originObj2= {
a:1,
b:2,
}
function mutateObj(obj) {
obj.a = 3;
return obj;
}
function notToMutateObj(obj) {
const cloneObj = { ...obj };
cloneObj.a = 3;
return cloneObj;
}
const notMutateObj = notToMutateObj(originObj);
const isMutateObj = mutateObj(originObj2);
console.warn('originObj before', originObj); // originObj before {a: 1, b: 2}
console.warn('originObj2 before', originObj2); //originObj2 before {a: 1, b: 2}
console.warn("-----------------------------------------------");
console.warn(notMutateObj); // {a: 3, b: 2}
console.warn(isMutateObj); // {a: 3, b: 2}
console.warn("-----------------------------------------------");
console.warn('originObj after', originObj); // originObj after {a: 1, b: 2}
console.warn('originObj2 after', originObj2); //originObj2 after {a: 3, b: 2}
function isPalindrome(str) {
if (str.length < 2) return true;
var first = str[0];
var last = str[str.length - 1];
if (first === last) {
return isPalindrome(str.substring(1,str.length-1)); // 取第二的字跟倒數第二個字做下一次比對
}
return false;
}
console.log(isPalindrome("") === true );
console.log(isPalindrome("a") === true );
console.log(isPalindrome("aa") === true );
console.log(isPalindrome("aba") === true );
console.log(isPalindrome("abba") === true );
console.log(isPalindrome("abccba") === true );
console.log(isPalindrome("ab") === false );
console.log(isPalindrome("abc") === false );
console.log(isPalindrome("abca") === false );
console.log(isPalindrome("abcdba") === false );
尾調用(Tail Call)是指該函數的最後一步是調用另一个函數。
function factorial(n) {
console.trace();
if (n === 0) {
return 1;
}
// no tail call
return n * factorial(n - 1);
}
factorial(2);
當我們執行上面這個沒有進行尾調用的函數時,當執行的 recursion 一多很容易就導致 Maximum call stack size exceeded 的錯誤,然後若我們使用以下的尾調用函數,則能使 stack 不會增長以避免 stack overflow 的可能性。
function factorial(n, total = 1) {
console.trace();
if (n === 0) {
return total;
}
return factorial(n - 1, n * total);
}
factorial(2);
Functional-Light JavaScript, v3, by frontend masters.
如何能加速 user 進入我們網頁後的 TTI (Time to Interactive),對一個網頁開發者來說一直是很重要的一項技能, 若網站看起來已經準備好,但當 user 嘗試與你的網站互動時卻沒有任何的反應這是一件很令人挫折的事情,而這個Google I/O session 就是在探討如何使你的網站效能更好,以縮短 Time to Interactive。
Time to Interactive (TTI) - 指的是該頁面要花多久個時間載入才能讓 user 與其互動
以下幾個指標將可以用來判定一個網頁是不是已經為 "Interactive" 的狀態:
- 頁面已呈現有用及重要的內容(可以用是否完成 First Contentful Paint 來評估)
- 該頁面上可見的 DOM element 若有綁定事件那這些事件是否已確實被綁定
- 當 user 在操作頁面時,接受到反饋得時間是否在 50 milliseconds 間
我們都知道當 SPA 沒有做 SSR 而只有 CSR 時,First Contentful Paint 必須等到該頁所需的script 完全被載完且執行 render app 後 user 才能看到頁面並做出互動,如下圖:
因此當 user 網路速度很慢或 bundle 的 script 太大包,都可能使使用者體驗下降,甚至直接離開網頁。然而SSR 因為是在 request 到 server 後就直接先在 server 將頁面 render 出來,少了 client side 的 Javascript render block 因此能更快的讓 user 看到 First Contentful Paint,但動態的內容互動還是必須藉由 client side rendering 來達到。
所以我們就會需要結合這 SSR 跟 CSR (SSR with Hydration) 兩種技術,通常被稱為 isomorphic javascript 或 universal javascript 。
但就算我們使用了SSR with Hydration 還是沒辦法有效提升 Time to Interactive 因為即使我們很快速的render 出頁面但還是必須等 client-side script 準備好 user 才能跟頁面做完整的互動。
前面我們只提到 SSR 的其中一個好處 First Contentful Paint 速度較快,效能較佳
,其實 SSR 對 SEO
也有所幫助,即使 Googlebot (搜尋引擎爬蟲) 現在都會跟 chrome 版本進行更新以確保新的 Javascript syntax 或 API 能正確地被執行,但對在其他的搜尋引擎來說還是有可能無法正確解析 Javascript 的情況發生,所以 SSR 對 SEO 還是有幫助的。
增加 Application 的複雜度:自己在前公司執行這個解決方案時會覺得若是自己單純用 react-dom/server
的 renderToString
若沒有好好的設計架構整個會變得挺複雜的,尤其是如果要跟許多第三方服務或 library 結合時 code 可能會變得較難維護。
當 Application 較大或邏輯及所需資源較複雜時可能一樣會出現 Response Time 過久的現象,將會降低使用者體驗。
Pre-rendering - 是在你 bundle 時就先產好頁面當 request 來時直接拋出對應的 HTML。
SSR - 是等 request 進來後執行 script render 對應的頁面並拋出。
Time to First Byte (TTFB) 就是當使用者的滑鼠點擊網站的那一刻開始,到接收到一個數據資料之間所等待的時間。
在 React 16 之後引入 streaming server-side rendering 概念,讓我們可以以 chunk 的方式傳遞 HTML 並同時 render 它,這將能更快的接受到 First Byte 。
用 react-dom/server
的 renderToNodeStream
來取代 renderToString
簡單來說就是 SSR with partial hydration + lazy load component,一開始只加載需要的 component 隨著user 與頁面的互動再去加載新的 component (ex: 先不載入非 above the fold content 會使用到得 component 隨著 user scroll down 再去加載所需的 script 及 component) 這部分可以藉由 react-loadable 或者 將來 React 原生 API (React.lazy - 目前不支援 SSR) 來達成。
progressive-rendering-frameworks-samples
Rendering-on-the-web, by Google
First Contentful Paint, by Google
Time to Interactive, by Google
Streaming-server-rendering-at-spectrum, by Max Stoiber
Code-splitting, by React
Progressive react, by Houssein Djirdeh
Prerender-with-react-snap, by Houssein Djirdeh
以下整理一些我自己看過的資源列表:
CS50 Lectures 2018
CS50 Beyond 2019
https://martinfowler.com/architecture/
Awsome-Front-End-learning-resource
reactjs-interview-questions
reactjs-interview-questions - 中文
vuejs-interview-questions
javascript-algorithms
算法/深度学习/NLP面试笔记
前端面试每日 3+1
Front-end-Developer-Interview-Questions
front-end-interview-handbook
node-interview
Back-End-Developer-Interview-Questions
coding-interview-university
CS-Interview-Knowledge-Map
CS-Notes
interviews
tech-interview-handbook
30-seconds/30-seconds-of-interviews
前端开发者面试清单
Daily-Interview-Question
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.