Code Monkey home page Code Monkey logo

blog's People

Contributors

clarencef avatar

Watchers

 avatar  avatar

blog's Issues

Linux commands

  • 快捷鍵
    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

Advanced React Patterns

看 Kent C. Dodds 在 Frontend masters 上教授的一個課程叫 Advanced React Patterns 時,同時想要整理一下自己的思緒因此寫了這篇文章來作為日後參考的依據。

React Patterns 的種類

  • Compound comopnent
  • Render props comopnent
  • Prop Collections and Getters - 屬於管理 props 的取用不會在本篇文章內被提到
  • State Initializers - 只是一個 reset function 把改變後的 state,用 default props 恢復成預設值。
  • State Reducer - 覺得實用性應該不高,提升整體複雜度及維護的難度。
  • Control Props - 類似 getDerivedStateFromProps().
  • Provider - 用新的 context API 來傳遞 component 所需要的 props,避免掉 prop drilling 的問題。
  • Higher-order component

Compound component

當我們想要清楚的表達 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 之間的關聯性。

Demo link

Flexible Compound 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>
    );
  }
}

Demo link

Render Prop Component

相較於 compound component, render prop component 較被眾人所知也常常被拿來跟 HOC (High Order Component) 作比較,render prop 就是將 render function 當作 props 傳入,將 render 的控制權,從 component 內部移轉至使用該 component 的 parent component 上,這種方式讓 component 能更容易的被複用及更高的自由度,而它有兩種實作方式:

將 render function 當作 props 傳入:

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>

Demo link

或用 this.props.children 來當作 render function:

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>

Demo link

High-order component

被提出來代替 Mixin 的 pattern,是一個可以將可共用的邏輯抽取出來並讓我們更簡單的去組合、複用這些邏輯的一個 function,這個 function 接收一個 component 作為參數並回傳一個擁有共用邏輯的新 component 。

HOC 提供了一些好處

  • 提供一個方法讓我們可以在 ES6 class 內來共用邏輯(class component 不支援 mixin)。
  • 由於我們可以把一些共用的邏輯細分集中到不同的 HOC 內,因此他也提供了我們更容易遵守 single responsibility principle。
  • 當我們使用像 Recompose 所提供的 compose function 時,可以自由的組合我們所需要的邏輯及方法,並且可以讓我們更清楚這個 component 裡包含了什麼共用邏輯增加了他的可讀性(readability)。

同時也有一些壞處

  • 當我們在某一個 component 上使用多個 HOC 時,必須注意若有相同的 props 可能會在沒有任何警示下被互相覆蓋掉。
  • 很容易造成過度巢狀包覆的情況發生,造成debug上的困難。
  • 若我們會需要用到被 HOC 包覆 component 裡的一些 static method 可能會發現那些 static method 都取不到了,必須透過其他方法來使他們能夠被取到。

HOC 範例:

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);
}

Reference

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

On-Page optimization

根據 moz 整理出來的文章 On-Page optimization 包含下面幾個元素:

Uniquely valuable (網頁獨特內容 !== 網頁獨特的價值)

他們的差別在於:

  • 獨特內容指的單單只是在其他的網站上不會看見一樣的內容。
  • 但獨特的價值在於它能提供造訪者可帶走且有用的內容,許多的網頁都可以很有價值,但在它們之中卻很少能真正提供獨特的價值。

如何達到目標元素:

  • 提供可靠且顯見的價值而非網頁擁有者的自我行銷
  • 網頁與其他網頁內容的質量必須有顯著的差別
  • 網頁所提供的文字、圖片及多媒體資訊是卓越獨特的
  • 80%+造訪者給與網頁評價為有用的、高品質的、獨特的網頁
  • 讓搜尋並進到頁面的使用者得到他所想要的資訊,而不會再需要造訪其他網頁

moz也提供幾個範例網站做為參考:

Baby Name Wizard
How Much Does a Website Cost
The Best Instant Noodles of All Time

Provides phenomenal UX(給予我們的使用者非凡的使用者體驗)

Javascript: The Recent parts

標籤樣板字面值 (tagged template literal}

標籤運算式(通常是一個函式)會被呼叫來處理樣板字面值,讓你可以在函式回傳之前進行操作。

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!"
    );

Padding

想像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+

Array Destructuring

  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();

Swap x and y

  let x = 10;
  let y = 20;
  
  // before  
  let temp = x;
  x = y;
  y = temp

  // after
  [y, x] = [x, y];

Destructuring parameter

  // before  
  function data (tmp = []) {
     let = [
       first,
       second,
       third
     ] = tmp;

     // .............
  }

  // after  
  function data ([
      first,
      second,
      third
  ] = []) {
     // ..........
  }

Destructuring nested array

  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() || [];
 

Object Destructuring

 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() || {};

find, findIndex, includes

arr.find, arr.findIndex

返回陣列內符合條件的項目,若找不到 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.includes(searchElement[, fromIndex])

檢查該項目是否存在於陣列內,可以用來取代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

flat & flatMap

arr.flat([depth])

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]

arr.flatMap

先對陣列做 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']

Iterators & Generators

Iterators

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*

使 Object 具備可被迭代性(Iterability) - imperative iterator

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]);

Generators

什麼是 Generator ?

Generator object 是由一個 generator function 返回的而得到的,且它符合上面提到的迭代協議(iterable protocol)以及迭代器協議(iterator protocol)

Generator function

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

使 Object 具備可被迭代性(Iterability) - declarative iterator 不用自己去實作 Iterator 裡面的 next()method

const 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]);

根據給予的條件做 iteration

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,
    })
  ]}`
);

Regular Expressions

Look Ahead & Behind

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 才符合

Named Capture Groups

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"

dotall mode

原本. 為匹配除了換行符號之外的單一字元,但最新的 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"

References

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

Content Security Policy

根據 Google I/O 2019 Securing Web Apps with Modern Platform Features session 影片中提到XSS(Cross-site scripting) 仍是最常見、危害最大的網頁安全漏洞,因此我們會需要用 CSP(Content Security Policy) 來防止 XSS 攻擊。
螢幕快照 2019-05-11 上午10 31 38

Enable CSP

要啟用這個機制則需要在 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列表的困難及複雜度。
螢幕快照 2019-05-11 上午11 08 57

但在實作 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能被正常執行。

Adopting strict CSP

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>

防護強度:
螢幕快照 2019-05-11 下午12 12 29
'unsafe-eval': 允許在網站裡使用eval(),這會在某種程度上降低該網站對 DOM-based XSS 的對抗性,但可以讓你更容易的採用CSP strategy,如果你的網站並沒有使用eval(), 請移除這個關鍵字來換取更好的安全性。

Tools

CSP Mitigator: Chrome extension
CSP Evaluator

References

Content Security Policy, by Google
Content Security Policy 入门教程, by 阮一峰
Cross origin infoleaks

Performance Implications of Application Architecture (Google I/O ’19) - 觀念梳理

如何能加速 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 間

First Contentful Paint

1. SSR

我們都知道當 SPA 沒有做 SSR 而只有 CSR 時,First Contentful Paint 必須等到該頁所需的script 完全被載完且執行 render app 後 user 才能看到頁面並做出互動,如下圖:
螢幕快照 2019-05-12 下午2 47 14

因此當 user 網路速度很慢或 bundle 的 script 太大包,都可能使使用者體驗下降,甚至直接離開網頁。然而SSR 因為是在 request 到 server 後就直接先在 server 將頁面 render 出來,少了 client side 的 Javascript render block 因此能更快的讓 user 看到 First Contentful Paint,但動態的內容互動還是必須藉由 client side rendering 來達到。

螢幕快照 2019-05-12 下午3 38 36

所以我們就會需要結合這 SSR 跟 CSR (SSR with Hydration) 兩種技術,通常被稱為 isomorphic javascript 或 universal javascript 。

螢幕快照 2019-05-12 下午3 47 55

但就算我們使用了SSR with Hydration 還是沒辦法有效提升 Time to Interactive 因為即使我們很快速的render 出頁面但還是必須等 client-side script 準備好 user 才能跟頁面做完整的互動。

SSR 優點

前面我們只提到 SSR 的其中一個好處 First Contentful Paint 速度較快,效能較佳,其實 SSR 對 SEO
也有所幫助,即使 Googlebot (搜尋引擎爬蟲) 現在都會跟 chrome 版本進行更新以確保新的 Javascript syntax 或 API 能正確地被執行,但對在其他的搜尋引擎來說還是有可能無法正確解析 Javascript 的情況發生,所以 SSR 對 SEO 還是有幫助的。

SSR 缺點

  • 增加 Application 的複雜度:自己在前公司執行這個解決方案時會覺得若是自己單純用 react-dom/serverrenderToString 若沒有好好的設計架構整個會變得挺複雜的,尤其是如果要跟許多第三方服務或 library 結合時 code 可能會變得較難維護。

  • 當 Application 較大或邏輯及所需資源較複雜時可能一樣會出現 Response Time 過久的現象,將會降低使用者體驗。

2. Pre-rendering

特性與優點:

螢幕快照 2019-05-12 下午4 21 55

與 SSR 的不同之處:

Pre-rendering - 是在你 bundle 時就先產好頁面當 request 來時直接拋出對應的 HTML。
SSR - 是等 request 進來後執行 script render 對應的頁面並拋出。
螢幕快照 2019-05-12 下午4 26 06

不足之處:

螢幕快照 2019-05-12 下午4 39 43

實作:

Time to First Byte (TTFB)

Time to First Byte (TTFB) 就是當使用者的滑鼠點擊網站的那一刻開始,到接收到一個數據資料之間所等待的時間。

Streaming SSR

  • 同時 render 多個 request,不會因為 render 資料量較大的 request 就 block 住較輕量的 request
  • 在 response 完成之前 Browser 就同時 render 頁面

螢幕快照 2019-05-12 下午5 44 15
螢幕快照 2019-05-12 下午5 53 06

React

在 React 16 之後引入 streaming server-side rendering 概念,讓我們可以以 chunk 的方式傳遞 HTML 並同時 render 它,這將能更快的接受到 First Byte 。
螢幕快照 2019-05-12 下午6 39 40

react-dom/serverrenderToNodeStream 來取代 renderToString

Progressive Hydration

簡單來說就是 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) 來達成。

螢幕快照 2019-05-12 下午11 23 50

Examples

progressive-rendering-frameworks-samples

References

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

關於 Title Tag

Title Tag 的主要功用?

一個網站的 Title Tag 必須精準的表達出該網頁所要呈現給使用者網頁內容,當 user 使用像 Google,Bing ... 等等搜尋引擎來搜尋我們的網頁時 ,這些引擎將會使用 Title Tag 做為進入網頁的連結的標題並於進入網站後顯示於瀏覽器頁籤上,而它對於網站的 SEO 及當我們分享網頁給他人時也扮演了重要的角色。

如何設定網頁的 Title Tag?

只需要在你 HTML head tag 內的 title tag 加入,你想呈現給使用者的訊息即可

<head>
  <title>Good for SEO</title>
</head>

moz 推薦的 Title Tag 結構

主要關鍵字 - 次要關鍵字 | 網站名稱(品牌名稱)

Title Tag 並沒有長度的硬性規定,可能會因搜尋引擎而改變顯示的長度,因此推薦最佳長度 50 - 60 的字元。

注意事項

  • 不要刻意重複關鍵字或使用過多同義字(關鍵字堆砌 keyword-stuffed),因為這並不會加強網站的SEO,反而可能成為扣分項目。

  • 最好每個頁面都有它獨特的 title,獨特的 title 將有助於搜尋引擎了解該頁面內容的價值與獨特性也可能網頁的增加點擊率(Click-through Rate),因此應該避免用一些過於普遍的字眼來當作 title ("Home", "New") 等等。

學習資源

Functional Javascript

Function Purity

Pure Function定義:

  • Pure function只藉由操作傳入的參數,進而產生output,給予相同的輸入(input),永遠返回相同的結果(output)
  • Pure function只仰賴local state產生結果,並且不會改變external state
  • 不會產生任何副作用(side effect)
  • 不能在pure function內呼叫執行impure function

副作用(side effect)的潛在來源:

  • 改變檔案存在的系統
  • 從資料庫內建入/更新/刪除資料
  • 對伺服器執行一個請求
  • Mutations
  • 印出log
  • 獲取使用者的輸入(Obtaining User Input)
  • 存取系統的狀態(Accessing system state)

用Pure Function的好處:

  • 可快取性(Cacheable)
  • 本身即文件(Self-Documenting)- 用途明確易於觀察並理解
  • 可測試性(Testable)
  • 引用透明性(referential transparency):函數無副作用
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;
}

Argument adapters

unary & binary

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]

flip & reverse

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]

not

function not(fn) {
  return function(...args) {
    return !fn(...args)
  }
}

function isOdd(v) {
  return v % 2 === 1;
}

const isEven = not(isOdd);

isEven(4);

Partial application & Currying

Partial application

簡單來說就是一個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"

Currying

把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。

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"

composition & pipe

composition

將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"
}

pipe

與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

Immutability

  • Assignment immutability: 當你完成變數賦值後,就不被允許進行 change 或 reassign value。
  • Value immutability

Don't mutate, copy it

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}

Recursion

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 );

Proper Tail Calls

尾調用(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);

References

Functional-Light JavaScript, v3, by frontend masters.

Javascript Foundation

Types

undefined v.s. undeclared v.s. uninitialized(TDZ- Temporal Dead Zone)

undefined: 一個變數尚未被賦值
undeclared: 一個變數尚未被宣告
uninitialized: 一個變數在語法環境(Lexical Environment)中被創造且初始化,但尚未被求值運算進行語法綁定(LexicalBinding),因此造成變數尚不能被觸及。(ES6: let, const)

NaN 永遠不等於另一個 NaN

var a = Number('albert fang');
var b = Number('n/a')

console.warn(a === b)  // false

Negative zero && Object.is(value1, value2)(ES6)

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 & toNumber

[].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

Scope

Hoisting

是一種變數提升 (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 don't Hoisting?

其實 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

閉包(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  

Modules

閉包是形成一個 Module 的重要元素,因為一個 Module 必須能夠封裝好它的資料及方法,以確保達到 principle of least privilege 只揭露該揭露的資料與方法。

Classic/Revealing module pattern(singleton)

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

Module Factory

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

Object

this

函式內的 this 值取決於該函式如何被呼叫,this 會指向該執行環境的上下文。

implicit binding

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 則是指向該物件

explicit binding (call, bind, apply)

'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

arrow function

它沒有自己的 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"

Prototypal Class

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

The Prototype Chain
螢幕快照 2019-08-21 下午5 19 18

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?

ES6 Class

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? 

OLOO(Objects Linked to Other Objects)

就只是 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?

References

Deep Javascript v3, by frontend masters.

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.