Giter Club home page Giter Club logo

blog's People

Contributors

hankzhuo avatar

Stargazers

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

Watchers

 avatar  avatar  avatar

blog's Issues

Redux v4.x 源码分析

这是分析 redux 源码系列第一篇。

下面是 Redux 版本4.0.1 源码的解读。

createStore

createStore(reducer, [preloadedState], enhancer)

createStore 方法接受三个参数,第一个参数为 reducer,第二个参数是 preloadedState (可选),第三个是参数是 enhancer。返回一个 对象 storestore 中包含方法 dispatchgetStatesubscribereplaceReducer[$$observable]

参数 reducer 有可能是一个 reducer,如果有个多个 reducer,则通过 combineReducer 函数执行后返回函数作为 reducer

参数 preloadedState 代表初始状态,很多时候都不传,不传该参数时候且 enhancer 是函数情况,createStore 函数内部会把 enhancer ,作为第二个参数使用,源码如下:

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

参数 enhancerstore 增强器,是一个函数, createStore 作为 enhancer 函数的参数,这里用到了函数式编程,返回函数又传递 reducerpreloadState 参数,执行最终返回一个增强的 storeenhancer 常有的是 applyMiddleware() 返回值,react-devrools() 等工具,源码如下:

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

介绍方法之前,下面是一些变量:

  let currentReducer = reducer   // 当前的 reducer
  let currentState = preloadedState    // 当前的 state
  let currentListeners = []   // 当前的监听者
  let nextListeners = currentListeners  //  监听者的备份
  let isDispatching = false  // 是否正在执行 dispatch 动作

dispatch

dispatch(action)

dispatch 方法,接受一个参数actionaction 是一个对象,该对象必须包括 type 属性,否则会报错。dispatch 函数内部,主要是执行了 reducer() 函数,得到最新 state,赋值给 currentState,执行订阅者,dispatch 函数返回一个 action 对象。源码如下:

  function dispatch(action) {
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {  // 如果正在 dispatch,再次触发 dispatch,则报错
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true  // 表示正在执行 `dispatch`
      currentState = currentReducer(currentState, action)    // 执行最新的 Reducer,返回最新的 state
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)  // 每次 dispatch 执行,订阅者都会执行
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

getState

getState()

获取最新的 state,就是返回 currentState。源码如下:

  function getState() {
    return currentState
  }

subscribe

添加事件订阅者,每次触发 dispatch(action)
时候,订阅者都会执行。返回取消订阅方法 unSubscribe。这里采用了观察者模式/发布-订阅者模式。源码如下:

注意:订阅者必须是函数,否则报错。

function subscribe(listener) {
    if (typeof listener !== 'function') {  // 订阅者必须是函数
      throw new Error('Expected the listener to be a function.')
    }

    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
      )
    }

    let isSubscribed = true

    nextListeners.push(listener)     // 订阅消息,每次 dispatch(action) 时候,订阅者都会接受到消息

    return function unsubscribe() {  // 返回取消订阅方法
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

      const index = nextListeners.indexOf(listener) 
      nextListeners.splice(index, 1)  // 取消订阅者
    }
  }

observable

让一个对象可被观察。被观察对象必须是对象,否则报错。这里作者用到了第三方库 symbol-observable,对 currentState 进行观察。源码如下:

ECMAScript Observables 目前只是草案,还没正式使用。

function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())   // 对 currentState 进行监听
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

replaceReducer

替换 reducer,一般用不到。源码也简单,把 nextReducer 赋值给 currentReducer 。源码如下:

  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
  }

上面 dispatchgetStatesubscribereplaceReducer[$$observable] 这些方法使用了闭包,一直保持对 currentStatecurrentReducercurrentListenersnextListenersisDispatching 等变量的引用。比如,dispatch 改变 currentState,相应的其他方法中,currentState 也会跟着变,所以这些方法之间是存在联系的。

applyMiddleware

执行中间件的方法,中间件可以有很多,有自定义的如:打印日志,也有比较知名的 如:redux-chunkredux-promiseredux-saga 等,后续会分析。

applyMiddleware() 通常是上面提到的 createStore 方法的第三个参数 enhancer,源码如下:

enhancer(createStore)(reducer, preloadedState)

结合上面代码可以等价于

applyMiddleware(...middlewares)(createStore)(reducer, preloadedState)

applyMiddleware 使用了函数式编程,接收第一个参数元素为 middlewares 的数组,返回值接收createStore 为参数的函数,返回值接收 dispatch 函数,接收一个 action(reducer, state),最终返回增强版的 store

源码如下:

function applyMiddleware(...middlewares) {
  return createStore => (...args) => {  
    const store = createStore(...args)  //  args 为 (reducer, initialState)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
    
    const middlewareAPI = {   // 给 middleware 的 store
      getState: store.getState,
      dispatch: (...args) => dispatch(...args) 
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))  // chain 是一个参数为 next 的匿名函数的数组(中间件执行返回的函数)
    dispatch = compose(...chain)(store.dispatch)  // 经过 compose 函数后返回的结果,是经过 middlewares 包装后的 dispatch 

    return {   // 返回 dispatch 增强后的 store
      ...store,
      dispatch
    }
  }
}

compose

源码如下:

 function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args))) 
}

虽然源代码没几行,作者实现方式太厉害了,如果理解起来有点费劲,可以看看下面例子分析:

var arr = [
  function fn1(next){
    return action => {
      // next 为 fn2 return 函数(dispatch 函数)
      const returnValue = next(action)
      return returnValue
    }
  },
  function fn2(next) {
    return action => {
      // next 为 fn3 return 函数(dispatch 函数)
      const returnValue = next(action)
      return returnValue
    }
  },
  function fn3(next3) {
    return action => {
      // next 为 dispatch。
      const returnValue = next3(action)
      return returnValue
    }
  },
]

var fn = arr.reduce((a, b) => (args) => a(b(args)))
fn // (args) => a(b(args)) 等价于 (dispatch) => fn1(fn2(fn3(dispatch)))

// reduce函数 执行顺序:
// 第一次:
// 变量 a: ƒ fn1(args)
// 变量 b: ƒ fn2(args)
// 返回值: (args) => fn1(fn2(args))

// 第一次返回值赋值给 a
// 变量 a:  (args) => fn1(fn2(args))
// 变量 b: ƒ fn3(args)

// 第二次:
// 变量 a :  (args) => fn1(fn2(args))
// 变量 b: ƒ fn3(args)
// 返回值: (args) => fn1(fn2(fn3(args)))

所以作者的用意是 compose(f, g, h) 返回 (...args) => f(g(h(...args))) ,其中 f、g、h 是一个个中间件 middleware

结合上面 applyMiddleware 源码有一段 dispatch = compose(...chain)(store.dispatch) ,所以 argsstore.dispatch(store.dispatch) => f(g(h(store.dispatch))),执行第一个中间件h(store.dispatch),返回 dispatch 函数(参数为 action 的函数)作为下一个中间件的参数,通过一层层的传递,最终返回一个经过封装的 dispatch 函数。

下面是打印日志中间件例子:

const logger = store => next => action => {
  console.log('dispatch', action)
  next(action)
  console.log('finish', action)
}

const logger2 = store => next2 => action2 => {
  console.log('dispatch2', action2)
  next2(action2)
  console.log('finish2', action2)
}
...
const store = createStore(rootReducer, applyMiddleware(logger, logger2))

注意:但如果在某个中间件中使用了 store.dipsatch(),而不是 next(),那么就会回到起始位置,会造成无限循环了。

每次触发一个 dispatch,中间件都会执行,打印的顺序为 dispatch distapch2 finish2 finish

combineReducer

这个 API 理解为:如果有多个 reducer,那么需要把它们合成一个 reducer,在传给函数 createStore(reducer)

核心源码如下:

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]  // reducer 一定是函数
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  return function combination(state = {}, action) {  // 返回一个新的 reducer 函数
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) { // 每次执行一次 dipatch(action),所有的 reducer 都会执行
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state  // 判断 state 是否有变化,如果有则返回新的 state
  }
}

bindActionCreators

这个 API 可以理解为:生成 action 的方法。主要是把 dispatch 也封装进 bindActionCreator(actionCreator, dispatch) 方法里面去,所以调用时候可以直接触发 dispatch(action),不需要在手动调用 dispatch。源码如下:

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

react-router v5.x 源码分析和理解 SPA router 的原理

这篇文章主要讲的是分析 react-router 源码,版本是 v5.x,以及 SPA 路由实现的原理。

单页面应用都用到了路由 router,目前来看实现路由有两种方法 hash 路由和 H5 History API 实现。

而 react-router 路由,则是用到了 history 库,该库其实是对 hash 路由、history 路由、memory 路由(客户端)进行了封装。

下面先看看 hash 和 history 是怎样实现路由的。

hash router

hash 是 location 的属性,在 URL 中是 #后面的部分。如果没有#,则返回空字符串。

hash 路由主要实现原理是:hash 改变时,页面不发生跳转,即 window 对象可以对 hash 改变进行监听(hashchange 事件),只要 url 中 hash 发生改变,就会触发回调。

利用这个特性,下面模拟实现一个 hash 路由。

实现步骤:

  • 初始化一个类
  • 记录路由、history 历史值
  • 添加 hashchange 事件,hash 发生改变时,触发回调
  • 初始添加所有路由
  • 模拟前进和回退功能

代码如下:

hash.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>hash router</title>
</head>
<body>
  <ul>
    <li><a href="#/">turn yellow</a></li>
    <li><a href="#/blue">turn blue</a></li>
    <li><a href="#/green">turn green</a></li>
  </ul>
  <button id='back'>back</button>
  <button id='forward'>forward</button>
  <script src="./hash.js"></script>
</body>
</html>

hash.js

class Routers {
  constructor() {
    this.routes = {}
    this.currentUrl = ''
    this.history = []; // 记录 hash 历史值
    this.currentIndex = this.history.length - 1;  // 默认指向 history 中最后一个
    // 默认前进、后退
    this.isBack = false;
    this.isForward = false;

    this.onHashChange = this.onHashChange.bind(this)
    this.backOff = this.backOff.bind(this)
    this.forward = this.forward.bind(this)

    window.addEventListener('load', this.onHashChange, false);
    window.addEventListener('hashchange', this.onHashChange, false);  // hash 变化监听事件,support >= IE8
  }

  route(path, callback) {
    this.routes[path] = callback || function () { }
  }

  onHashChange() {
    // 既不是前进和后退,点击 a 标签时触发
    if (!this.isBack && !this.isForward) {
      this.currentUrl = location.hash.slice(1) || '/'
      this.history.push(this.currentUrl)
      this.currentIndex++
    }

    this.routes[this.currentUrl]()

    this.isBack = false
    this.isForward = false
  }
  // 后退功能
  backOff() {
    this.isBack = true
    this.currentIndex = this.currentIndex <= 0 ? 0 : this.currentIndex - 1

    this.currentUrl = this.history[this.currentIndex]
    location.hash = `#${this.currentUrl}`
  }
  // 前进功能
  forward() {
    this.isForward = true
    this.currentIndex = this.currentIndex >= this.history.length - 1 ? this.history.length - 1 : this.currentIndex + 1

    this.currentUrl = this.history[this.currentIndex]
    location.hash = `#${this.currentUrl}`
  }
}
// 初始添加所有路由
window.Router = new Routers();
Router.route('/', function () {
  changeBgColor('yellow');
});
Router.route('/blue', function () {
  changeBgColor('blue');
});
Router.route('/green', function () {
  changeBgColor('green');
});

const content = document.querySelector('body');
const buttonBack = document.querySelector('#back');
const buttonForward = document.querySelector('#forward')

function changeBgColor(color) {
  content.style.backgroundColor = color;
}
// 模拟前进和回退
buttonBack.addEventListener('click', Router.backOff, false)
buttonForward.addEventListener('click', Router.forward, false)

hash 路由兼容性好,缺点就是实现一套路由比较复杂些。

在线预览

history router

history 是 HTML5 新增的 API,允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。

history 包含的属性和方法:

  • history.state 读取历史堆栈中最上面的值
  • history.length 浏览历史中元素的数量
  • window.history.replaceState(obj, title, url) 取代当前浏览历史中当前记录
  • window.history.pushState(obj, title, url) 往浏览历史中添加历史记录,不跳转页面
  • window.history.popstate(callback) 对历史记录发生变化进行监听,state 发生改变时,触发回调事件
  • window.history.back() 后退,等价于浏览器返回按钮
  • window.history.forward() 前进,等价于浏览器前进按钮
  • window.history.go(num) 前进或后退几个页面,num 为负数时,后退几个页面

实现步骤:

  • 初始化一个类
  • 记录路由
  • 添加 popstate 事件,state 发生改变时,触发回调
  • 初始添加所有路由

代码如下:

history.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>h5 router</title>
</head>
<body>
  <ul>
      <li><a href="/">turn yellow</a></li>
      <li><a href="/blue">turn blue</a></li>
      <li><a href="/green">turn green</a></li>
  </ul>
  <script src='./history.js'></script>
</body>
</html>

history.js

class Routers {
  constructor() {
    this.routes = {};
    this._bindPopState();
  }

  route(path, callback) {
    this.routes[path] = callback || function () { };
  }

  go(path) {
    history.pushState({ path: path }, null, path);
    this.routes[path] && this.routes[path]();
  }

  _bindPopState() {
    window.addEventListener('popstate', e => {  // 监听 history.state 改变
      const path = e.state && e.state.path;
      this.routes[path] && this.routes[path]();
    });
  }
}

// 初始时添加所有路由
window.Router = new Routers();
Router.route('/', function () {
  changeBgColor('yellow');
});
Router.route('/blue', function () {
  changeBgColor('blue');
});
Router.route('/green', function () {
  changeBgColor('green');
});

const content = document.querySelector('body');
const ul = document.querySelector('ul');

function changeBgColor(color) {
  content.style.backgroundColor = color;
}

ul.addEventListener('click', e => {
  if (e.target.tagName === 'A') {
    debugger
    e.preventDefault();
    Router.go(e.target.getAttribute('href'));
  }
});

兼容性:support >= IE10

在线预览

react-router

react-router 分为四个包,分别为 react-routerreact-router-domreact-router-configreact-router-native,其中 react-router-dom 是浏览器相关 API,react-router-native 是 React-Native 相关 API,react-router 是核心也是共同部分 API,react-router-config 是一些配置相关。

react-router 是 React指定路由,内部 API 的实现也是继承 React 一些属性和方法,所以 react-router 内 API 也是 React 组件。

react-router 还用到了 history 库,这个库主要是对 hash 路由、history 路由、memory 路由的封装。

下面我们讲到的是 react-router v5.x 版本,用到了 context,context 减少了组件之间显性传递 props,具体用法可以看看官方文档。

创建 context:

var createNamedContext = function createNamedContext(name) {
    var context = createContext();
    context.displayName = name;
    return context;
};

var context = createNamedContext("Router");

Router

定义一个类 Router,它继承 React.Component 属性和方法,所以 Router 也是 React 组件。

_inheritsLoose(Router, _React$Component);
// 等价于 
Router.prototype = Object.create(React.Component)  

在 Router 原型对象上添加 React 生命周期 componentDidMountcomponentWillUnmountrender 方法。

一般情况下,Router 都是作为 Route 等其他子路由的上层路由,使用了 context.Provider,接收一个 value 属性,传递 value 给消费子组件。

var _proto = Router.prototype; 
_proto.componentDidMount = function componentDidMount() {
    // todo
};

_proto.componentWillUnmount = function componentWillUnmount(){
    // todo
};

_proto.render = function render() {
    return React.createElement(context.Provider, props);
};

history 库中有个方法 history.listen(callback(location)) 对 location 进行监听,只要 location 发生变化了,就会 setState 更新 location,消费的子组件也可以拿到更新后的 location,从而渲染相应的组件。

核心源码如下:

var Router =
    function (_React$Component) {
        // Router 从 React.Component 原型上的继承属性和方法
        _inheritsLoose(Router, _React$Component);  

        Router.computeRootMatch = function computeRootMatch(pathname) {
            return {
                path: "/",
                url: "/",
                params: {},
                isExact: pathname === "/"
            };
        };

        function Router(props) { // 首先定义一个类 Router,也是 React 组件
            var _this;

            _this = _React$Component.call(this, props) || this; // 继承自身属性和方法
            _this.state = {
                location: props.history.location
            };

            _this._isMounted = false;
            _this._pendingLocation = null;

            if (!props.staticContext) {  // 如果不是 staticRouter,则对 location 进行监听
                _this.unlisten = props.history.listen((location) => { // 监听 history.location 变化,如果有变化,则更新 locaiton
                    if (_this._isMounted) {
                        _this.setState({
                            location: location
                        });
                    } else {
                        _this._pendingLocation = location;
                    }
                });
            }

            return _this;
        }

        var _proto = Router.prototype;  // 组件需要有生命周期,在原型对象上添加 componentDidMount、componentWillUnmount、render 方法

        _proto.componentDidMount = function componentDidMount() {
            this._isMounted = true;

            if (this._pendingLocation) {
                this.setState({
                    location: this._pendingLocation
                });
            }
        };

        _proto.componentWillUnmount = function componentWillUnmount() {
            if (this.unlisten) this.unlisten();  // 停止监听 location
        };

        _proto.render = function render() {
            // 使用了 React Context 传递 history、location、match、staticContext,使得所有子组件都可以获取这些属性和方法
            // const value = {
            //   history: this.props.history,
            //   location: this.state.location,
            //   match: Router.computeRootMatch(this.state.location.pathname),
            //   staticContext: this.props.staticContext
            // }

            // return (
            //   <context.Provider value={value}>
            //     {this.props.children}
            //   </context.Provider>
            // )
            return React.createElement(context.Provider, {
                children: this.props.children || null,
                value: {
                    history: this.props.history,
                    location: this.state.location,
                    match: Router.computeRootMatch(this.state.location.pathname),
                    staticContext: this.props.staticContext // staticContext 是 staticRouter 中的 API,不是公用 API
                }
            });
        };

        return Router;
    }(React.Component);

Route

Route 一般作为 Router 的子组件,主要是匹配 path 和渲染相应的组件。

Route 使用了 context.Consumer(消费组件),订阅了 Router 提供的 context,一旦 location 发生改变,context 也会改变,判断当前 location.pathname 是否与子组件的 path 是否匹配,如果匹配,则渲染对应组件,否则就不渲染。

Route 因为有些库传递组件方式不同,所以有多种渲染,部分代码如下:

const {children, render, component} = this.props
let renderEle = null;

// 如果有 children,则渲染 children
if (children &&  !isEmptyChildren(children)) renderEle = children

// 如果组件传递的是 render 及 match 是匹配的,则渲染 render
if (render && match) renderEle = render(props)

// 如果组件传递 component 及 match 是匹配的,则渲染 component 
if (component && match) renderEle = React.createElement(component, props)

return (
    <context.Provider value={obj}>
        {renderEle}
    </context.Provider>
)

核心源码如下:

var Route =
    function (_React$Component) {
        _inheritsLoose(Route, _React$Component);

        function Route() {
            return _React$Component.apply(this, arguments) || this;
        }

        var _proto = Route.prototype;

        _proto.render = function render() {
            var _this = this;
            // context.Consumer 每个 Route 组件都可以消费 Router 中 Provider 提供的 context
            return React.createElement(context.Consumer, null, function (context$$1) {
                var location = _this.props.location || context$$1.location;
                var match = _this.props.computedMatch ? _this.props.computedMatch
                    : _this.props.path ? matchPath(location.pathname, _this.props) : context$$1.match;  // 是否匹配当前路径

                var props = _extends({}, context$$1, { // 处理用 context 传递的还是自己传递的 location 和 match
                    location: location,
                    match: match
                });

                var _this$props = _this.props,
                    children = _this$props.children,
                    component = _this$props.component,  
                    render = _this$props.render;

                if (Array.isArray(children) && children.length === 0) {
                    children = null;
                }

                // let renderEle = null

                // 如果有 children,则渲染 children
                // if (children &&  !isEmptyChildren(children)) renderEle = children

                // 如果组件传递的是 render 及 match 是匹配的,则渲染 render
                // if (render && props.match) renderEle = render(props)

                //  如果组件传递 component 及 match 是匹配的,则渲染 component 
                // if (component && props.match) renderEle = React.createElement(component, props)

                // return (
                //     <context.Provider value={props}>
                //        {renderEle}
                //     </context.Provider>
                // )

                return React.createElement(context.Provider, { // Route 内定义一个 Provider,给 children 传递 props
                    value: props
                }, children && !isEmptyChildren(children) ? children : props.match ? component ? React.createElement(component, props) : render ? render(props) : null : null);
            });
        };

        return Route;
    }(React.Component);

Redirect

重定向路由:from 是从哪个组件来,to 表示要定向到哪里。

 <Redirect from="home" to="dashboard" />

根据有没传属性 push,有传则是往 state 堆栈中新增(history.push),否则就是替代(history.replace)当前 state。

Redirect 使用了 context.Consumer(消费组件),订阅了 Router 提供的 context,一旦 location 发生改变,context 也会改变,则也会触发重定向。

源码如下:

function Redirect(_ref) {
    var computedMatch = _ref.computedMatch,
        to = _ref.to,
        _ref$push = _ref.push,
        push = _ref$push === void 0 ? false : _ref$push;
    return React.createElement(context.Consumer, null, (context$$1) => { // context.Consumer 第三个参数是一个函数
        var history = context$$1.history,
            staticContext = context$$1.staticContext;

        // method 方法:判断是否是替换(replace)当前的 state,还是往 state 堆栈中添加(push)一个新的 state
        var method = push ? history.push : history.replace;
        // 生成新的 location
        var location = createLocation(computedMatch ? typeof to === "string" ? generatePath(to, computedMatch.params) : _extends({}, to, {
            pathname: generatePath(to.pathname, computedMatch.params)
        }) : to); 

        if (staticContext) { // 当渲染一个静态的 context 时(staticRouter),立即设置新 location
            method(location);
            return null;
        }
        // Lifecycle 是一个 return null 的空组件,但定义了 componentDidMount、componentDidUpdate、componentWillUnmount 生命周期
        return React.createElement(Lifecycle, {
            onMount: function onMount() {
                method(location);
            },
            onUpdate: function onUpdate(self, prevProps) {
                var prevLocation = createLocation(prevProps.to);
                // 触发更新时,对比前后 location 是否相等,不相等,则更新 location
                if (!locationsAreEqual(prevLocation, _extends({}, location, {
                    key: prevLocation.key
                }))) {
                    method(location);
                }
            },
            to: to
        });
    });
}

Switch

Switch 组件的子组件必须是 Route 或 Redirect 组件。

Switch 使用了 context.Consumer(消费组件),订阅了 Router 提供的 context,一旦 location 发生改变,context 也会改变,判断当前 location.pathname 是否与子组件的 path 是否匹配,如果匹配,则渲染对应的子组件,其他都不渲染。

源码如下:

var Switch =
    function (_React$Component) {
        _inheritsLoose(Switch, _React$Component);

        function Switch() {
            return _React$Component.apply(this, arguments) || this;
        }

        var _proto = Switch.prototype;

        _proto.render = function render() {
            var _this = this;

            return React.createElement(context.Consumer, null, function (context$$1) {
                var location = _this.props.location || context$$1.location;
                var element, match; 
               
                React.Children.forEach(_this.props.children, function (child) {
                    if (match == null && React.isValidElement(child)) {
                        element = child;
                        var path = child.props.path || child.props.from;
                        match = path ? matchPath(location.pathname, _extends({}, child.props, {
                            path: path
                        })) : context$$1.match;
                    }
                });
                return match ? React.cloneElement(element, {
                    location: location,
                    computedMatch: match  // 加强版的 match
                }) : null;
            });
        };

        return Switch;
    }(React.Component);

Link

Link 组件作用是跳转到指定某个路由。Link 实际是对 <a> 标签进行了封装。

点击时会触发以下:

  • 改变 url,但使用了 e.preventDefault(),所以页面没有发生跳转。
  • 根据是否传递属性 replace,有传就是替代当前 state(history.replace),否则是往 state 堆栈中新增(history.push),从而路由发生了改变。
  • 路由发生了改变,由于 Router 中有对 location 进行监听,从而通过 context 传递给消费子组件,匹配 path 是否相同,渲染相应的组件。

核心源码如下:

function LinkAnchor(_ref) {
    var innerRef = _ref.innerRef,
        navigate = _ref.navigate,
        _onClick = _ref.onClick,
        rest = _objectWithoutPropertiesLoose(_ref, ["innerRef", "navigate", "onClick"]); // 剩余属性

    var target = rest.target;
    return React.createElement("a", _extends({}, rest, {  // a 标签
        ref: innerRef ,
        onClick: function onClick(event) {
            try {
                if (_onClick) _onClick(event);
            } catch (ex) {
                event.preventDefault(); // 使用 e.preventDefault() 防止跳转
                throw ex;
            }

            if (!event.defaultPrevented && // onClick prevented default
                event.button === 0 && ( // ignore everything but left clicks
                    !target || target === "_self") && // let browser handle "target=_blank" etc.
                !isModifiedEvent(event) // ignore clicks with modifier keys
            ) {
                event.preventDefault();
                navigate(); // 改变 location
            }
        }
    }));
}

function Link(_ref2) {
    var _ref2$component = _ref2.component,
        component = _ref2$component === void 0 ? LinkAnchor : _ref2$component,
        replace = _ref2.replace,
        to = _ref2.to, // to 跳转链接的路径
        rest = _objectWithoutPropertiesLoose(_ref2, ["component", "replace", "to"]);

    return React.createElement(__RouterContext.Consumer, null, function (context) {
        var history = context.history;
        // 根据 to 生成新的 location
        var location = normalizeToLocation(resolveToLocation(to, context.location), context.location);
        var href = location ? history.createHref(location) : "";

        return React.createElement(component, _extends({}, rest, {
            href: href,
            navigate: function navigate() {
                var location = resolveToLocation(to, context.location);
                var method = replace ? history.replace : history.push;
                method(location); // 如果有传 replace,则是替换掉当前 location,否则往 history 堆栈中添加一个 location
            }
        }));
    });
}

BrowserRouter

BrowserRouter 用到了 H5 history API,所以可以使用 pushState、replaceState 等方法。

源码中主要是用到了 history 库 createBrowserHistory 方法创建了封装过 history 对象。

把封装过的 history 对象传递给 Router 的 props。

var BrowserRouter =
    function (_React$Component) {
        _inheritsLoose(BrowserRouter, _React$Component); // BrowserRouter 继承 React.Component 属性和方法

        function BrowserRouter() {
            var _this;

            for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
                args[_key] = arguments[_key];
            }

            _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this; // 继承自身属性和方法
            _this.history = createBrowserHistory(_this.props); // 创建 browser history 对象,是支持 HTML 5 的 history API
            return _this;
        }

        var _proto = BrowserRouter.prototype;

        _proto.render = function render() {
            return React.createElement(Router, { // 以 Router 为 element,history 和 children 作为 Router 的 props
                history: this.history,
                children: this.props.children
            });
        };

        return BrowserRouter;
    }(React.Component);

HashRouter

HashRouter 与 BrowserRouter 的区别,主要是创建是以 window.location.hash 为对象,返回一个 history,主要是考虑到兼容性问题。

var HashRouter =
    function (_React$Component) {
        _inheritsLoose(HashRouter, _React$Component);

        function HashRouter() {
            var _this;

            for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
                args[_key] = arguments[_key];
            }

            _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this;
            _this.history = createHashHistory(_this.props); // 创建 hash history 对象,兼容老浏览器 hash,其他和 BrowserRouter 没有大区别
            return _this;
        }

        var _proto = HashRouter.prototype;

        _proto.render = function render() {
            return React.createElement(Router, {
                history: this.history,
                children: this.props.children
            });
        };

        return HashRouter;
    }(React.Component);

总结

  • react-router 分为四个包,分别为 react-routerreact-router-domreact-router-configreact-router-native,其中 react-router-dom 是浏览器相关 API,react-router-native 是 React-Native 相关 API,react-router 是核心也是共同部分 API,react-router-config 是一些配置相关。

  • react-router 是 React指定路由,内部 API 的实现也是继承 React 一些属性和方法,所以说 react-router 内 API 也是 React 组件。

  • react-router 还用到了 history 库,这个库主要是对 hash 路由、history 路由、memory 路由的封装。

  • Router 都是作为 Route 等其他子路由的上层路由,使用了 context.Provider,接收一个 value 属性,传递 value 给消费子组件。

  • history 库中有个方法 history.listen(callback(location)) 对 location 进行监听,点击某个 Link 组件,改变了 location,只要 location 发生变化了,通过 context 传递改变后的 location,消费的子组件拿到更新后的 location,从而渲染相应的组件。

参考

从现实中理解 Redux

从现实中例子理解 Redux

第一次看 redux 文档时候,说实话我是看的比较抽象的,比如 Action、 dipatch、Reducer、Store 等概念,对于刚入 React 全家桶,这些概念会让人觉得有点绕。

但它太棒了,它是 React 全家桶里很重要的组成部分,如果你项目中使用 React 技术栈(当然 React 与 Redux 不是绑定关系),那么 Redux **是必须要了解下,当你使用过一段时间时候,真的会被其**感染,只能说这些库的作者是真的强,给他们点赞。

偶然间,在国外技术论坛看到一些文章,把 Redux 解释的非常通俗易懂,把 Redux 与现实中例子结合起来,构成了非常清晰的、完整的一套逻辑。

Redux 是什么

为啥要用到 Redux?因为在一个 app 项目中,如果涉及到 state 复杂交互,在 React 组件内部对于 state 的管理是不方便,不便于维护,使用统一管理 state 的库是比较合适的。当然,如果你的项目比较简单,完全可以不需要 redux。

那么 Redux 是什么?官方文档解释:

Redux is a predictable state container for JavaScript apps.

如果你了解过 React,那么肯定知道 state 概念,那么 Redux 可以理解为 state 的外部状态管理容器,是不是还很有点意思了?接下来我会给你一步步解释每个概念。把 Redux 与 “去银行取钱的过程”结合起来,更好地理解它们之间的关系。

解释 API

首先,你的钱都储存在银行金库里,银行保存着你所有的钱,相对应的,把银行金库比作 Redux 里的 Store,而 Store 储存着你 app 中的所有 State

先记住这个对应关系:

金库 <==> Store

你的钱 <==> State

现在,你要去银行取钱,这个想法或者目的,相对应的,Redux 里的Action

取钱想法 <==> Action

去取钱,还是存钱?这个对应的 Redux 里是 Action 里的 Type

取钱还是存钱 <==> Type

action 形式简单表示如下:
{ type: "WITHDRAW_MONEY", amount: "¥1,000" }
把卡给收银员或者 ATM 取钱,然后把钱给你,相对应的,就是 Redux 里的,发起一个action,传递给Reducer返回一个新的State`。

收银员 / ATM <==> Reducer

总结:

本篇内容主要讲 Redux 的理论,把 Redux 比喻成去银行取钱,能更好理解 Redux。

Redux 与实际的对应关系:

  1. 金库或银行 <==> Store
  2. 你的钱 <==> State
  3. 取钱的想法或目的 <==>Action
  4. 取钱还是存钱 <==> Type
  5. 收银员 / ATM <==> Reducer

流程:
去银行取钱,把卡给收银员(或 ATM 机),取多少钱,然后收银员把钱取出来给你的过程。

在 app 中,更新 state 的过程:dipatch 一个 actionreducer(收银员),reducer(收营员) 返回一个新的 state(你银行还剩下的钱) 。

Hello Redux

安装环境

理论与代码结合,实践动手,才能更好理解 Redux。

老样子,学一门技术,都是从简单的应用开始的。

首先,为了节省时间和以学习 Redux 的目的,使用 React 脚手架 create-react-app 安装 React 环境。

React 内部 state

安装好环境后,按照提示把项目跑起来,修改 src/App.js 文件,

src/App.js

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

class App extends Component {
    constructor(props) {
      super(props)
      this.state = {
        tech: 'react'
      }
  }
  render() {
    return <HelloWorld tech={this.state.tech}/>
  }
}

export default App;

src 目录下创建一个 React 无状态组件 <HelloWorld/>:

import React from 'react'

const HelloWorld = ({tech}) => {
  return <div>
    Hello World <span>{tech}!</span>
  </div>
}

export default HelloWorld

可以看到,默认情况下是, React 内部通过 this.state.tech,传递给子组件。

Redux 传递 state

接下来,把内部传递 state 修改成 Redux 传递的 state

npm install redux --save 安装 Redux 库,安装成功后,在 src/App.js 中添加 redux 进来,如下:

import { createStore } from 'redux'
const store = createStore() 

通过 createStore() 是生成 storecreateStore 函数接受一些参数,第一个参数是 reducer,如下:

const store = createStore(reducer) 

现在把 reducer 传进来,新建一个 reducer,在 src/reducers 目录下新建一个 index.js 文件。reducer 返回一个 state(这里先简单地返回一个 state),index.js 文件如下:

export default (state) => {
  return state
}

createStore 还可以接受第二个参数 initialStateinitialState 是初始状态,修改后如下:

const initialState = { tech: "Redux"};
const store = createStore(reducer, initialState);

还记得上一篇文章,state(你的钱)是从 store(收营员) 返回的吗?使用 store.getState().tech 返回新的 statesrc/App.js 完整代码如下:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import HelloWorld from './HelloWorld'
import { createStore } from 'redux'
import reducer from './reducers'

const initialState = { tech: 'Redux'}
const store = createStore(reducer, initialState)

class App extends Component {
  render() {
    return <HelloWorld tech={store.getState().tech}/>
  }
}

export default App

现在为止,项目从组件内部 state 传递,现在从 redux 传递 state 了。
以上就是简单的 Hello Redux 入门程序。

总结

这一篇文章主要讲了,从 React 中内部更新 state 到 Redux 获取 state。
下面总结下知识点:

  1. Redux 是一个可预测的 JavaScript app 的状态管理
  2. createStore 是一个工厂函数,用于生成 store
  3. createStore 第一个参数 reducer,是必须传递的,第二个参数是初始 状态 initialState
  4. reducer 是一个纯函数,接受两个参数,一个是初始化 state,另一个是 action
  5. Reducer 总是返回一个新的 state
  6. store.getState() 将会返回当前的 state

七种方法使你的 JavaScript 代码更简洁

前言

同样一个功能,可以用很多方法来实现,有时候由于项目时间紧张,导致很多时候只是实现了功能,往往忽视了代码质量。下面几种代码重构方法,以便大家可以写出更漂亮的代码。本文内容是我的读书笔记,摘自《Javascript设计模式与开发》。

一,提炼函数

var getUserInfo = function(){
  ajax( 'http:// xxx.com/userInfo', function( data ){
      console.log( 'userId: ' + data.userId );
      console.log( 'userName: ' + data.userName );
      console.log( 'nickName: ' + data.nickName );
  })
})

//  重构后
var getUserInfo = function(){
  ajax( 'http:// xxx.com/userInfo', function( data ){
    printDetails( data ); });
};

var printDetails = function( data ){ 
  console.log( 'userId: ' + data.userId ); 
  console.log( 'userName: ' + data.userName ); 
  console.log( 'nickName: ' + data.nickName );
};

二,合并重复的条件片段

var paging = function( currPage ){
  if ( currPage <= 0 ){
    currPage = 0;
    jump( currPage );
  }else if ( currPage >= totalPage ){
    currPage = totalPage;
    jump( currPage );
  }else{
    jump( currPage );
  }
}

// 重构后,把重复函数独立出来
var paging = function( currPage ){
    if ( currPage <= 0 ){
        currPage = 0;
    }else if ( currPage >= totalPage ){
        currPage = totalPage; 
    }
    jump( currPage );
};

三,把条件分支语句提炼成函数

var getPrice = function( price ){
  var date = new Date();
  if ( date.getMonth() >= 6 && date.getMonth() <= 9 ){
    return price * 0.8;
  }
  return price; 
};

// 重构后,改成能够理解的函数
var isSummer = function(){
  var date = new Date();
  return date.getMonth() >= 6 && date.getMonth() <= 9;
};

var getPrice = function( price ){ 
  if ( isSummer() ){ 
  return price * 0.8;
}
  return price; 
};

四,合理使用循环

var createXHR = function(){ 
  var xhr;
  try{
    xhr = new ActiveXObject( 'MSXML2.XMLHttp.6.0' );
  }catch(e){ 
    try{
      xhr = new ActiveXObject( 'MSXML2.XMLHttp.3.0' ); 
    }catch(e){
      xhr = new ActiveXObject( 'MSXML2.XMLHttp' ); 
    }    
  }
  return xhr; 
};
var xhr = createXHR();

// 重构后,使用遍历更简洁
var createXHR = function(){
  var versions= [ 'MSXML2.XMLHttp.6.0ddd', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp' ];
  for ( var i = 0, version; version = versions[ i++ ]; ){
    try{
      return new ActiveXObject( version );
    }catch(e){
      // ...
    }
  }
};
var xhr = createXHR();

五,提前让函数退出代替嵌套条件分支

var del = function( obj ){
  var ret;
  if ( !obj.isReadOnly ){   
    if ( obj.isFolder ){ 
      ret = deleteFolder( obj );
    }else if ( obj.isFile ){
      ret = deleteFile( obj );
    }
  }
  return ret;
}

// 函数只有一个出口,改成多个出口
var del = function( obj ){
  if ( obj.isReadOnly ){
    return;     //反转 if 表达式
  }
  if ( obj.isFolder ){
    return deleteFolder( obj );
  }
  if ( obj.isFile ){
    return deleteFile( obj ); 
  }
};

六,传递对象参数代理过长的参数列表

var setUserInfo = function( id, name, address, sex, mobile, qq ){ 
  console.log( 'id= ' + id );
  console.log( 'name= ' +name );
  console.log( 'address= ' + address );
  console.log( 'sex= ' + sex ); console.log( 'mobile= ' + mobile ); console.log( 'qq= ' + qq );
};
setUserInfo( 1314, 'sven', 'shenzhen', 'male', '137********', 377876679 );

// 把参数用一个对象包裹,不用再关系参数的数量和顺序
var setUserInfo = function( obj ){
  console.log( 'id= ' + obj.id );
  console.log( 'name= ' + obj.name );
  console.log( 'address= ' + obj.address );
  console.log( 'sex= ' + obj.sex );
  console.log( 'mobile= ' + obj.mobile );
   console.log( 'qq= ' + obj.qq );
};

setUserInfo({
  id: 1314,
  name: 'sven',
  address: 'shenzhen',
  sex: 'male',
  mobile: '137********',
  qq: 377876679
});

七,分解大型类

var Spirit = function( name ){
  this.name = name;
};
Spirit.prototype.attack = function( type ){
  if ( type === 'waveBoxing' ){
    console.log( this.name + ': 使用波动拳' );
  }else if( type === 'whirlKick' ){
    console.log( this.name + ': 使用旋风腿')
    }
};

var spirit = new Spirit( 'RYU' );
spirit.attack( 'waveBoxing' );
spirit.attack( 'whirlKick' );


// Spirit.prototype.attack 如果要添加方法,那会变得很大,所以必要作为一个单独的类存在
var Attack = function( spirit ){
  this.spirit = spirit;
};

Attack.prototype.start = function( type ){
  return this.list[ type ].call( this );
};

Attack.prototype.list = {
  waveBoxing: function(){
    console.log( this.spirit.name + ': 使用波动拳' );
  },
  whirlKick: function(){
    console.log( this.spirit.name + ': 使用旋风腿' );
  }
};

JavaScript 常用的设计模式

前言

设计模式几十种,阅读了《JavaScript设计模式与开发实践》这本书后,个人感觉js就是围绕对象来设计的,发现日常写代码能用上的并不多,下面是常用的几种设计模式。

模式列表

  • 单例模式
  • 策略模式
  • 模板方法模式
  • 职责链模式
  • 发布订阅模式

设计模式

单例模式

单一模式的核心是确保只有一个实例,并提供全局访问,在 JS 开发中,经常把用一个对象包裹,这样减少了全局变量的污染,比如 var a = {}。

普通写法:

  // 每次点击点击按钮,都会创建一个 div
  var createLayer1 = (function () {
    var div = document.createElement('div');
      div.innerHTML = '我是内容';
      div.style.display = 'none';
      document.body.appendChild(div);
      return div;
  })()

  document.getElementById('#btn').onclick = function () {
    var layer1 = createLayer1();
    layer1.style.display = 'block';
  }

单例模式:

  //实例对象总是在我们调用方法时才被创建,而不是在页面加载好的时候就创建。  
  // 这样就不会每次点击按钮,都会创建一个 div 了
  var createLayer2 = function () {
    var div;
    return function () {
      if (!div) {
        document.createElement('div');
        div.innerHTML = '我是内容';
        div.style.display = 'none';
        document.body.appendChild(div);
      }
      return div;
    }
  }

  document.getElementById('#btn').onclick = function () {
    var layer2 = createLayer2();
    layer2.style.display = 'block';
  }

策略模式

策略模式代码非常优雅,最喜欢模式之一,也很便于修改,请看代码。

普通模式:

  var awardS = function (salary) {
    return salary * 4
  };

  var awardA = function (salary) {
    return salary * 3
  };

  var awardB = function (salary) {
    return salary * 2
  };

  var calculateBonus = function (level, salary) {
    if (level === 'S') {
      return awardS(salary);
    }
    if (level === 'A') {
      return awardA(salary);
    }
    if (level === 'B') {
      return awardB(salary);
    }
  };

  calculateBonus('A', 10000);

策略模式:

  var strategies = {
    'S': function (salary) {
      return salary * 4;
    },
    'A': function (salary) {
      return salary * 3;
    },
    'B': function (salary) {
      return salary * 2;
    }
  }

  var calculateBonus = function (level, salary) {
    return strategies[level](salary);
  }

  calculateBonus('A', 10000);

模板方法模式

模板方法模式使用了原型链的方法,封装性好,复用性差。

  var Coffee = function () {

  };
  Coffee.prototype.boilWater = function () {
    // todo
    console.log('把水煮沸');
  };
  Coffee.prototype.brewCoffee = function () {
    // todo
    console.log('冲咖啡');
  };
  Coffee.prototype.pourInCup = function () {
    // todo
    console.log('把咖啡倒进杯子');
  };
  Coffee.prototype.addSugarAndMilk = function () {
    // todo
    console.log('加糖和牛奶');
  };
  Coffee.prototype.init = function () {
    this.boilWater();
    this.brewCoffee();
    this.pourInCup();
    this.addSugarAndMilk();
  }

  var coffee = new Coffee();
  coffee.init();

职责链模式

没错,我刚开始写第一个项目时候就这么嵌套的,重复代码太多,逻辑太乱,�维护下太差。

  var order = function (orderType, pay, stock) {
    // 500 元定金模式
    if (orderType === 1) {
      if (pay === true) {
        console.log('500元定金预购,得到100元优惠券');
      } else {
        if (stock > 0) {
          console.log('普通购买,无优惠券');
        } else {
          console.log('手机库存不足');
        }
      }
    // 200 元定金模式
    } else if (orderType === 2) {
      if (pay === true) {
        console.log('200元定金预购,得到50元优惠券');
      } else {
        if (stock > 0) {
          console.log('普通购买,无优惠券');
        } else {
          console.log('手机库存不足');
        }
      }
    // 没有定金模式  
    } else if (orderType === 3) {
      if (stock > 0) {
        console.log('普通购买,无优惠券');
      } else {
        console.log('手机库存不足');
      }
    } 
  }

  order(1, true, 500);

职责链,一系列可能处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,减少了很多重复代码。

  var order500 = function (orderType, pay, stock) {
    if (orderType === 1 && pay === true) {
      console.log('500元定金预购,得到100元优惠券');
    } else {
      order200(orderType, pay, stock);
    }
  }

  var order200 = function (orderType, pay, stock) {
    if (orderType === 2 && pay === true) {
      console.log('200元定金预购,得到50元优惠券');
    } else {
      orderNormal(orderType, pay, stock);
    }
  }

  var orderNormal = function (orderType, pay, stock) {
    if (stock > 0) {
      console.log('普通购买,无优惠券');
    } else {
      console.log('手机库存不足');
    }
  }

  order500(1, true, 500);
  order500(1, false, 500);
  order500(2, true, 500);

发布-订阅模式

观察者对象有三个方法:订阅消息方法、取消订阅消息方法、发送订阅消息方法

var Observer = (function() {
  var messages = {}
  return {
    // 注册信息接口
    regist: function (type, fn){
      // 如果消息不存在,则新建一个消息类型
      if (typeof messages[type] === 'undefined') {
        messages[type] = [fn]
      } else {
        // 保证多个模块注册同一消息,都能够顺利执行
        messages[type].push(fn)
      }
    },
    // 发布信息接口
    fire: function (type, args){
      // 消息没有注册,则返回
      if (!messages[type]) {
        return;
      }
      // 定义消息信息:消息类型和消息数据
      var events = {
        type: type,
        args: args || {}
      }
      var i, len = messages[type].length;
      for (i = 0;i < len; i++) {
        messages[type][i].call(this, events)
      }
    },
    // 移除信息接口
    remove: function (type, fn){
      // 有注册过的消息
      if (messages[type] instanceof Array) {
        var i = messages[type].length - 1;
        for (;i >= 0; i--) {
          console.log('xxxx', messages[type][i] === fn)
          messages[type][i] === fn && messages[type].splice(i, 1)
        }
      }
      console.log('messages....', messages)
    }
  }
})()

var fn = function(e) {
  console.log(e)
}

Observer.regist('test', fn)
Observer.fire('test', {msg: '测试数据'})
Observer.remove('test', fn)

参考

  • 来自《JavaScript设计模式与开发实践》读书笔记
  • 《JavaScript 设计模式》

理解 JavaScript 异步操作

一,同步和异步区别?

相信大家在工作过程中,肯定有或多或少听说过同步和异步。那么同步和异步到底是什么呢?它们之间有什么区别?举个栗子,煮开水,同步就是把水放上去烧,得一直等水开,中途不能做其他事情。而异步,则是把水放上去烧,让水在烧,你可以玩手机看电视,等水开了把火关掉。同样的,代码中也是一样,同步是现在发生的,异步是未来某个时刻发生的。

同步代码:

// 烧开水
function boilWater() {
    var water;
    while(water_is_boiled) {
    // search
    }
    return water;
}

var boiledWater = boilWater();

// 做其他事情
doSomethingElse();

二,JS 运行机制

在介绍异步编程前,先介绍下JavaScript运行机制,因为JS 是单线程运行的,所以这意味着两段代码不能同时运行,而是必须一个接一个地运行,所以,在同步代码执行过程中,异步代码是不执行的。只有等同步代码执行结束后,异步代码才会被添加到事件队列中。

(学习1: 阮一峰-Event-Loop)。

三,JS 中异步有几种?

JS 中异步操作还挺多的,常见的分以下几种:

  • setTimeout (setInterval)
  • AJAX
  • Promise
  • async/await

3.1,setTimeout

setTimeout(
  function() { 
    console.log("Hello!");
}, 1000);

setTimout(setInterval)并不是立即就执行的,这段代码意思是,等 1s后,把这个 function 加入任务队列中,如果任务队列中没有其他任务了,就执行输出 'Hello'。

var outerScopeVar; 
helloCatAsync(); 
alert(outerScopeVar);

function helloCatAsync() {     
    setTimeout(function() {         
        outerScopeVar = 'hello';     
    }, 2000); 
}

执行上面代码,发现 outerScopeVar 输出是 undefined,而不是 hello。之所以这样是因为在异步代码中返回的一个值是不可能给同步流程中使用的,因为 console.log(outerScopeVar) 是同步代码,执行完后才会执行 setTimout。

helloCatAsync(function(result) {
console.log(result);
});

function helloCatAsync(callback) {
    setTimeout(
        function() {
            callback('hello')
        }
    , 1000)
}

把上面代码改成,传递一个callback,console 输出就会是 hello。

3.2, AJAX

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) {
        console.log(xhr.responseText);
    } else {
        console.log( xhr.status);
    }
}
xhr.open('GET', 'url', false);
xhr.send();

上面这段代码,xhr.open 中第三个参数默认为 false 异步执行,改为 true 时为同步执行。

3.3,Promise

语法:

new Promise( function(resolve, reject) {...});

Promise 对象是由关键字 new 及其构造函数来创建的。这个“处理器函数”接受两个函数 resolve 和 reject 作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。

new Promise 返回一个 promise 对象,在遇到 resolve 或 reject之前,状态一直是pending,如果调用 resolve 方法,状态变为 fulfilled,如果调用了 reject 方法,状态变为 rejected。

function delay() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(666)
        }, 2000)
    })
}

delay()
    .then(function(value){
        console.log('resolve...',value);
    })
    .catch(function(err){
        cosole.log(err)
    })

上面代码中,2s 后调用 resolve 方法,然后调用 then 方法,没有调用 cacth方法。

注意:then() 和 catch() 都是异步操作。

下面 promise 和 ajax 结合例子:

function ajax(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) {
                resovle(xhr.responseText);
            } else {
                reject( xhr.status);
            }
        }
        xhr.open('GET', url, false);
        xhr.send();
    });
}

ajax('/test.json')
    .then(function(data){
        console.log(data);
    })
    .cacth(function(err){
        console.log(err);
    });

学习1:promise-mdn

学习2:promise-google

3.4,async/await

语法:

async function name([param[, param[, ... param]]]) { statements }

调用 async 函数时会返回一个 promise 对象。当这个 async 函数返回一个值时,Promise 的 resolve 方法将会处理这个值;当 async 函数抛出异常时,Promise 的 reject 方法将处理这个异常值。

async 函数中可能会有await 表达式,这将会使 async 函数暂停执行,等待 Promise 正常解决后继续执行 async 函数并返回解决结果(异步)

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function add1(x) {
  var a = resolveAfter2Seconds(20);
  var b = resolveAfter2Seconds(30);
  return x + await a + await b;
}

add1(10).then(v => {
  console.log(v);  // 2s 后打印 60, 两个 await 是同时发生的,也就是并行的。
});

async function add2(x) {
  var a = await resolveAfter2Seconds(20);
  var b = await resolveAfter2Seconds(30);
  return x + a + b;
}

add2(10).then(v => {
  console.log(v);  // 4s 后打印 60,按顺序完成的。
});

上面代码是 mdn 的一个例子。

  1. 说明了 async 返回的是一个 promise对象;
  2. await 后面跟的表达式是一个 promise,执行到 await时,函数暂停执行,直到该 promise 返回结果,并且暂停但不会阻塞主线程。
  3. await 的任何内容都通过 Promise.resolve() 传递。
  4. await 可以是并行(同时发生)和按顺序执行的。

看下面两段代码:

async function series() {
    await wait(500);
    await wait(500);
    return "done!";
}

async function parallel() {
  const wait1 = wait(500);
  const wait2 = wait(500);
  await wait1;
  await wait2;
  return "done!";
}

第一段代码执行完毕需要 1000毫秒,这段 await 代码是按顺序执行的;第二段代码执行完毕只需要 500 毫秒,这段 await 代码是并行的。

学习1:async-mdn

学习2:async-google

四,思考题

下面代码输出顺序是什么?

async function fn1() {
    console.log(1);
    await fn2();
    console.log(3);
};

function fn2() {
    console.log(2);
};

fn1();
new Promise(function(resolve, reject) {
    console.log(4);
    resolve();
}).then(function(){
    console.log(5)
})

CSS一些动画效果

前言

由于 CSS3 的推出,让有些动画不在以 JS 来实现,仅仅依靠 CSS 就可以实现许多动画效果。提高了性能同时,又增加了很多趣味性。
接下来我会持续更新大家常用到的CSS效果,供大家学习。。。

例子

一,皮球掉地上反弹起来

效果

// html

<div class="ball"></div>

//css

@keyframes bounce {
60%, 80%, to {
  transform: translateY(400px); 
  animation-timing-function: ease;        
  }        
  70% { transform: translateY(300px); }        
  90% { transform: translateY(360px); }
}

.ball {
    width: 50px;        
    height: 50px;        
    border-radius: 50%;
    margin: auto;        
    background: rgba(0,100,100,0.5);        
    animation: bounce 2s cubic-bezier(.58,.13,.94,.64) forwards;
}

技术分析:主要技术点是利用贝塞尔曲线 和 ease 来控制动画速度,tansition-timing-function 还有 linear 属性。

二,纯 CSS 实现 gif 效果

效果

// html

    <div id="frame"></div>
// css
#frame {
  width: 50px;
  height: 72px;
  border: 1px solid transparent;  
  background: url(https://s.cdpn.io/79/sprite-steps.png) no-repeat left top;
  animation: frame-animation 1s steps(10) infinite;    
}

@keyframes frame-animation {
  0% { background-position: 0px 0; }
  100% { background-position: -500px 0; }
}

技术分析:主要技术点是 steps(10) ,实现原理是,图片分为 10 部分,总共需要 10 步来完成,其中动画不是滑动实现的,而是每一步只显示一个区域。注意:steps(number) 中的 number*(每小张图片的长度)= 图片总长度相对应,才能实现 gif 效果。

三,图片移动

效果

// html

  <div class="pic"></div>

// css

@keyframes panoramic {
    to { background-position: 100% 0; }
}

.pic {
    width: 150px; height: 150px;
    background: url('http://c3.staticflickr.com/3/2671/3904743709_74bc76d5ac_b.jpg');
    background-size: auto 100%;     
    animation: panoramic 10s linear infinite alternate;
    animation-play-state: paused;
}

.pic:hover, .pic:focus {
    animation-play-state: running;
}

技术分析: 主要技术点是 animation-play-state: paused 暂停动画。

四,实现打字输入效果

效果

// html 

  <h1 class="pic">CSS is awesome!</div>

// css

@keyframes typing {
    from { width: 0 }
}

/* 光标 */
@keyframes caret {
    50% { border-right-color: transparent; }
}

h1 {
    font: bold 200% Consolas, Monaco, monospace;
    width: 15ch;
    white-space: nowrap;
    overflow: hidden;
    border-right: .05em solid;
    animation: typing 8s steps(15),
                caret 1s steps(1) infinite;

}

技术分析:CH宽度单位是每个字体长度,还是利用了 steps() 一次只显示一个特性,而不是滑动,可以把 step() 换成其他属性如:linear,ease等

redux-chunk、redux-promise 源码分析

上一篇文章里,action 都是同步的,也就是说 dispatch(action),经过中间件,更新得到 state 是同步的。

下面介绍几种 action 是异步执行的。

redux-thunk

redux-chunk 是 redux 的一个中间件 middleware,核心是创建一个异步的 action。

具体是怎么实现的呢?首先了解一个概念,什么是 thunk?

thunk

thunk 是一个函数,函数内有一个表达式,目的是为了延时执行。

let x = 1 + 2;
let foo = () => 1 + 2;

只有 foo() 执行时,才返回 3,那么 foo 就是 chunk 函数。

redux-thunk 源码很少,但又是经典,源码如下:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
export default thunk;

如源码所示,redux-thunk 是个中间件,和其他中间件区别就是,判断 action 类型是一个函数时候,执行这个 action。而同步 action 执行返回是一个对象。

例子:

// dispatch(action) 时,经过中间件时,执行 next(action),更新得到 state 是同步的。
const loading = text => ({
  type: 'LOADING',
  text
})

const cancelLoading = text => ({
  type: 'CANCEL_LOADING',
  text
})

// dispatch(action) 时,经过中间件时,执行 action(...args),更新得到 state 是异步的。
const fetchPeople = (url, params) => {
  return ({dispatch, getState}) => {
    dispatch(loading('start loading'));
    fetch(url, params).then(delayPromise(3000)).then(data => {
      dispatch(cancelLoading('cancel loading'));
    });
  };
};

在线例子

redux-promise

redux-promise 也是中间件 middleware,原理和 redux-thunk 差不多,主要区别是 redux-promise 用到了 promise 异步。

源码如下:

import isPromise from 'is-promise';
import { isFSA } from 'flux-standard-action';

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action) ? action.then(dispatch) : next(action);
    }

    return isPromise(action.payload)
      ? action.payload
          .then(result => dispatch({ ...action, payload: result }))
          .catch(error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          })
      : next(action);
  };
}

redux-promise 兼容了 FSA 标准(结果信息放在 payload 里),实现过程就是判断 action 或 action.payload 是否为 promise 对象,是则执行 then().catch(),否则 next(action)。

这些Promise题目,让你受益匪浅

引言

promise我看了好机会,但做题时候都会出错,后来我知道学一个知识点,光看不动手敲代码是不够的。下面几道题做完,真的受益匪浅,题目大部分来自网上,文章后面会提到。

首先,promise 基础知识可以看 mdn。

promise-mdn

题目

第一题

const request = (url) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  })
}

request(url1)
  .then(data1 => request(data1.url))
  .then(data2 => request(data2.url))
  .catch(err => { 
     throw new Error(err)
  })

request 是个promise对象,有三个状态 pedding、fulfilled、rejected。初始状态为 pendding,调用 resolve时,状态变为 fulfilled,调用rejected时,状态变为 rejected

第二题

const promise  = new Promise((resolve, reject) => {
  // resolve('fulfilled');
  reject('111');
})

promise.then(result => {
  console.log('success...', result)
}, err => {
  console.log("err...", err);
})

promise.then(onFulfilled, onRejected) then接受两个参数,onFulfilled 成功时调用,onRejected 失败时候调用。 一次只会调用其中一个函数。

第三题

const p1 = new Promise((resolve, reject) => {
    resolve(1);
});

const p2 = new Promise((resolve, reject) => {
    resolve(2);
});

const p3 = new Promise((resolve, reject) => {
    resolve(3);
});

Promise.all([p1, p2, p3])
  .then(data => console.log(data))  // [1, 2, 3]
  .catch(err => console.log('err...',err))

Promise.race([p1, p2, p3])
  .then(data => console.log(data)) // 1
  .catch(err => console.log("err...", err));
  • Promise.all()等待所有完成后才调用 then
  • Promise.race() 谁先处理完就直接调用后面函数
  • Promise.resolve() 返回一个 fulfilled 状态的promise对象
  • Promise.reject() 返回一个 rejected 状态的promise对象

第四题

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve('resolve');
  console.log(2);
});

console.log(promise);

promise.then(() => {
  console.log(3)
});

console.log(4); 

// 1 2 Promise { 'resolve' } 4 3
  • Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的
  • 注意:上面 promise 内,resolve() 是同步代码,所以立刻被执行了

第五题

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})

console.log('promise1', promise1)
console.log('promise2', promise2)

setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

// promise1 Promise { <pending> }
// promise2 Promise { <pending> }
// promise1 Promise { 'success' }
// promise2 Promise {
//  <rejected> Error: error!!!

promise 有 3 种状态:pending、fulfilledrejected。状态改变只能是 pending->fulfilled 或者 pending->rejected,状态一旦改变则不能再变。上面 promise2 并不是 promise1,而是返回的一个新的 Promise 实例

第六题

Promise.resolve()
  .then(() => {
    return new Error("error!!!");
  })
  .then(res => {
    console.log("then: ", res);
  })
  .catch(err => {
    console.log("catch: ", err);
  });
  
//输出 then: Error: error!!!
  • .then 或者 .catchreturn 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获
  • 因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resovle(new Error('error!!!'))

6.1

Promise.resolve()
  .then(() => {
    throw new Error("error!!!");
    // return Promise.reject(new Error("error!!!"));
  }, err => {
    console.log('then1: ', err)
  })
  .then(res => {
    console.log("then2: ", res);
  })
  .catch(err => {
    console.log("catch: ", err);
  });
  
//输出 catch:  Error: error!!!

第一个 then 抛出错误,需要后面的捕获错误。

6.2

Promise.reject(1)
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  });
  
  // 输出 catch 1  

前面两个then并没有捕捉错误,所以错误抛到了最后面的catch,中间 then 并不执行了。

6.3

Promise.reject(1)
  .then(() => {
    return new Error("error!!!");
  })
  .then(res => {
    console.log("then: ", res);
  }, err => {
    console.log("haha", err);
  })
  .catch(err => {
    console.log("catch: ", err);
  });
  
//输出 haha 1

错误被第二个 then 捕捉了,最后的 catch 也就不执行了。

第七题

var p = new Promise(function(resolve, reject){
  resolve(1);
});
p.then(function(value){               //第一个then
  console.log(value);
  return value*2;
}).then(function(value){              //第二个then
  console.log(value);
}).then(function(value){              //第三个then
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){              //第四个then
  console.log(value);
  return Promise.reject('reject');
}).then(function(value){              //第五个then
  console.log('resolve: '+ value);
}, function(err){
  console.log('reject: ' + err);
})

// 1 
// 2
// undefined ,因为上一个 then() 没有返回值
// resolve 
// reject: reject

第八题

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log);
  
// 1

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

第九题

const promise = Promise.resolve().then(() => {
  return promise;
});
promise.catch(console.error);

// TypeError: Chaining cycle detected for promise #<Promise>

.then .catch`` 返回的值不能是 promise``` 本身,否则会造成死循环。

参考

学习 JavaScript 垃圾回收机制

简介

本篇文章讲解JavaScript 中垃圾回收机制,内存泄漏,结合一些常遇到的例子,相信各位看完后,会对JS 中垃圾回收机制有个深入的了解。

内存生命周期

首先,不管什么程序语言,内存生命周期基本是一致的:

  • 分配你所需要的内存
  • 使用分配到的内存(读、写)
  • 不需要时将其释放归还

 在所有语言中第一和第二部分都很清晰。最后一步在低级语言中(C语言等)很清晰,但是在像JavaScript 等高级语言中,这一步是隐藏的、透明的。因为JavaScript 具有自动垃圾收集机制(Garbage collected )。在编写 JS 时,不需要关心内存使用问题,所需内存分配以及无用内存的回收完全实现了自动管理。

内存泄漏

内存泄漏(memory leaks),什么情况下回导致内存泄漏?可以简单理解为有些代码本来要被回收的,但没有被回收,还一直占用着操作系统内存,从而越积越多,最终会导致内存泄漏(可以理解为,内存满了,就溢出了)。

管理内存(Memory Management)

分配给web浏览器的可用内存数量通常要比分配给桌面应用程序少。这样做的目的主要是处于安全方面考虑,目的是防止运行JS 的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用。这个方法叫做解除引用。这一做法适用于大多数的全局变量和全局对象的属性。局部变量会在他们离开执行环境时自动被解除引用。

解除一个值的引用并不意味着自动回收改值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

标记清除(Mark and Sweep)

通常,垃圾收集器(garbage collector)在运行时候会给储存在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除的工作。

那标记清除具体是如何呢?有以下几种算法:

  • 在JavaScript 中,全局变量(Global)和window 对象会一直存在,不会被垃圾收集器回收;
  • 递归所用到的所有(包括变量和方法),都不会被回收;
  • 所有没有被标记为“活跃(active)”的,都会被认为是垃圾,收集器释放会回收垃圾,并把内存还给操作系统。

例子:

例一:

var n = 123; 
// 给数值变量分配内存

var s = "azerty"; 
// 给字符串分配内存

// 给对象及其包含的值分配内存
var o = {
  a: 1,
  b: null
};

// 给函数(可调用的对象)分配内存
function f(a){
  return a + 2;
}

例二:

function foo(arg) {
  // 此处bar 是全局变量,window.bar 可以访问,所以也不会被回收
  bar = "this is a hidden global variable";
} 

function foo() {
  // 此处this 代表 window
  this.variable = "potential accidental global";
} 

例三:

var someResource = getData();
setInterval(function() {
  var node = document.getElementById('Node');
  if(node) {
    node.innerHTML = JSON.stringify(someResource));
  }
}, 1000);

// 上面这段代码,定时器setInterval 和 someResource 一直存在,不会被回收。可以改成下面代码

var element = document.getElementById('button');

function onClick(event) {
    element.innerHtml = 'text';
}

element.addEventListener('click', onClick);
// 手动移除事件监听器和变量
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);

例四:

var intervalId = null, params;

function createChunks() {
  var div, foo, i, str;
  for (i = 0; i < 20; i++) {
    div = document.createElement("div");
    str = new Array(1000000).join('x');
      foo = {
        str: str,
        div: div
      };
      div.foo = foo;
  }
}

function start() {
  if (intervalId) {
    return;
  }
  intervalId = setInterval(createChunks, 1000);
}

function stop() {
  if (intervalId) {
    // 清除定时器
    clearInterval(intervalId);
  }
  // 清除变量
  intervalId = null;
}

链接观察垃圾回收是怎么工作的—Google: Watching the GC work

在上面图片中,可以观察到,点击 start 按钮,内存和节点数暴增,当点击stop 时,垃圾收集器回收了这些定时器、变量等,从而释放了内存。

参考:

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.