history源码阅读笔记

history为react-router的主要依赖项之一,用于操纵浏览器url的位置跳转

1. 浏览器相关API介绍

Location

location是最有用的BOM(浏览器对象模型)对象之一,提供了与当前窗口中加载的文档有关信息,还提供了一些导航功能。location对象即是window对象的属性,也是document对象的属性。故有:
window.location === document.location // true

Location 对象包含的属性及方法

// 以
// http://anonymous:flabada@www.xujsp.com:8080/javascript/doc?query=location#fragment
// 为例
// 页面完整URL
  href: 'http://anonymous:wordpass@www.xujsp.com:8080/javascript/doc?query=location#fragment',
// 页面使用的协议
  protocol: 'http',
// 服务器名称及端口号
  host: 'www.xujsp.com:8080',
// 服务器名称
  hostname: 'www.xujsp.com',
// 路径
  pathname: '/javascript/doc',
// 查询字符串
  search: '?query=location',
// hash
  hash: '#fragment',
// 用户登录用户名
  username: 'anonymous',
// 用户登录信息密码
  password: 'wordpass',
}
  • 修改 location 属性(除 hash )外,页面都会以新URL重新加载,并生成一条新记录
  • 通过使用 location.replace() 方法可以禁止生成新记录,此时用户不能通过“后退”按钮导航到前一个页面
  • location.reload() 强制页面重新加载,不会生成记录
  • location.assign() 打开行URL,并在浏览器中生成新记录

event: hashchange

  • HTML5中新增的 hashchange 事件,在URL参数列表发生变化时触发。便于开发人员利用URL参数列表来保存状态及导航信息
  • hashchange 事件处理程序添加到 window 对象
  • 对应的 event 对象包含 oldURL newURL 对象,分别保存参数列表变化前后的完整URL

History

  • History 接口用于操作浏览器会话历史记录

History 包含的属性及方法

{
  // 会话历史中元素的数目
  length: number, 
  state: object,
}
  • history.pushState() 将新的状态信息加入历史状态栈,包括状态对象和相对url,但不会向服务器发出请求
  • history.replaceState() 重写当前状态,不会在历史状态栈中创建状态

event: popState

  • 当点击后退、前进按钮或调用 history.back() history.forward() history.go() 时触发
  • history.pushState() history.pushState() 不会触发 popState 事件
  • 对应的 event 对象包含一个 state 属性,为 pushState 函数传入的第一个参数
  • 浏览器加载的第一个页面没有状态,此时 event.state === null

2. history API简介

history提供一系列API,用于管理历史堆栈、导航、确认以及在会话之间保持状态
  • history 提供了三个不同方法来创建 history 对象
  • createBrowserHistory 使用 HTML5 history API 实现
  • createMemoryHistory 用于非DOM环境中,如React-native
  • createHashHistory 相对于 createBrowserHistory 兼容性更好

属性

history 对象包含以下属性
- history.length :历史堆栈元素数量
- history.location :当前location
- history.action :当前导航的动作形式: enum {'PUSH', 'REPLACE', 'POP'} ,取决于用户操作

此外 createMemoryHistory 还提供了 history.index history.entries 来查看历史堆栈

监听

可以通过 history.listen 监听当前 location 的变化

history.listen((location, action) => {
  console.log(
    `The current URL is ${location.pathname}${location.search}${location.hash}`
  console.log(`The last navigation action was ${action}`)

location 对象实现了 window.location 接口的子集,包括
- location.pathname :URL路径
- location.search :查询字符串
- location.hash :hash

Locations可能包含以下属性
- location.state :为当前location设置一些额外状态( createBrowserHistory createMemoryHistory 支持)
- location.key :当前location的唯一字符串( createBrowserHistory createMemoryHistory 支持)

导航

history 可以使用以下方法修改当前location
- history.push(path, [state])
- history.replace(path, [state])
- history.go(n)
- history.goBack()
- history.goForward()
- history.canGo(n) (仅在 createMemoryHistory 中)

在使用 push replace 时,可以将URL路径和状态以不同的参数指定,也可以将所有内容包含在单个对象中:

history.push("/home")
// Location 状态不会出现在URL中
history.push("/home?the=query", { some: "state" })
// 同上
history.push({
  pathname: "/home",
  search: "?the=query",
  state: { some: "state" }
// 返回到前一个状态,两者功能相同
history.go(-1)
history.goBack()

阻塞导航变换

history 可以注册一个提示消息,会在location监听函数收到通知之前显示给用户。可以确保用户在离开之前确认是否离开当前页面。

// 注册一条提示消息,在用户离开当前页面之前将显示给用户。
const unblock = history.block("Are you sure you want to leave this page?")
// 也可以是一个函数
history.block((location, action) => {
  // location和action参数指明如何导航到下一个location
  // 通常在用户离开当前页面时尚未提交表单
  if (input.value !== "") return "Are you sure you want to leave this page?"
// 停止阻塞
unblock()

注意: 在 createMemoryHistory 中,需要提供一个 getUserConfirmation 函数来使用此特性

自定义确认对话框

默认情况下,使用 window.confirm 向用户显示提示消息。如果您需要重写此行为(或者如果您使用的是不假定DOM环境的createMemoryHistory),需要在创建历史记录对象时提供getUserConfirmation函数。

const history = createHistory({
  getUserConfirmation(message, callback) {
    // 显示自定义对话框,
    // callback(true)继续
    // callback(false)停止变换

使用"base" URL

如果应用中的所有URLs都与其他“base”URL相关,使用 basename 选项。该选项将给定的字符串添加到使用的所有URL的前面。

const history = createHistory({
  basename: "/the/base"
history.listen(location => {
  console.log(location.pathname) // /home
history.push("/home") // URL为/the/base/home

注意: createMemoryHistory 不支持 basename

在createBrowserHistory中强制网页刷新

默认情况下, createBrowserHistory 使用HTML5中的 pushState replaceState 来防止在浏览时从服务器重新加载整个页面。如果您想在URL更改时重新加载,使用forceRefresh选项。

const history = createBrowserHistory({
  forceRefresh: true

修改 createHashHistory 中的hash类型

默认情况下, createHashHistory 在基于hash的URL中使用前导斜杠。您可以使用hashType选项来使用不同的hash格式。

const history = createHashHistory({
  hashType: "slash" // 默认值
history.push("/home") // window.location.hash is #/home
const history = createHashHistory({
  hashType: "noslash" // 忽略前导斜杠
history.push("/home") // window.location.hash is #home
const history = createHashHistory({
  hashType: "hashbang" // google已弃用
history.push("/home") // window.location.hash is #!/home

3. 源码解析(version: 4.7.2)

PathUtil.js

用于操作path

// 添加和去除前导'/'
export var addLeadingSlash = function addLeadingSlash(path) {
  return path.charAt(0) === '/' ? path : '/' + path;
export var stripLeadingSlash = function stripLeadingSlash(path) {
  return path.charAt(0) === '/' ? path.substr(1) : path;
// 检查path是否以${prefix}开头
// 后缀可能为/ ? #或者path === prefix
export var hasBasename = function hasBasename(path, prefix) {
  return new RegExp('^' + prefix + '(\\/|\\?|#|$)', 'i').test(path);
// 如果path以prefix,删除prefix
export var stripBasename = function stripBasename(path, prefix) {
  return hasBasename(path, prefix) ? path.substr(prefix.length) : path;
// 删除末尾'/'
export var stripTrailingSlash = function stripTrailingSlash(path) {
  return path.charAt(path.length - 1) === '/' ? path.slice(0, -1) : path;
// 将path解析为pathname、search、hash
export var parsePath = function parsePath(path) {
  var pathname = path || '/';
  var search = '';
  var hash = '';
  // url的hash以#开头,并处于url最后部分
  var hashIndex = pathname.indexOf('#');
  if (hashIndex !== -1) {
    hash = pathname.substr(hashIndex);
    pathname = pathname.substr(0, hashIndex);
  // 查询字符串在hash的前面
  var searchIndex = pathname.indexOf('?');
  if (searchIndex !== -1) {
    search = pathname.substr(searchIndex);
    pathname = pathname.substr(0, searchIndex);
  return {
    pathname: pathname,
    search: search === '?' ? '' : search,
    hash: hash === '#' ? '' : hash
// 根据Location中的pathname、search、hash生成path
export var createPath = function createPath(location) {
  var pathname = location.pathname,
      search = location.search,
      hash = location.hash;
  var path = pathname || '/';
  if (search && search !== '?') path += search.charAt(0) === '?' ? search : '?' + search;
  if (hash && hash !== '#') path += hash.charAt(0) === '#' ? hash : '#' + hash;
  return path;



LocationUtil.js

用于生成Location及判断Location是否相等

var _extends = Object.assign || function (target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];
    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
  return target;
// 以第二参数为基准,默认为'/',解析第一个参数得到最终路径
import resolvePathname from 'resolve-pathname';
// 利用Object.prototype.valueOf比较是否相等
import valueEqual from 'value-equal';
import { parsePath } from './PathUtils';
// 创建location对象,包含pathname,search,hash,state,key
export var createLocation = function createLocation(path, state, key, currentLocation) {
  var location = void 0;
  if (typeof path === 'string') {
    // Two-arg form: push(path, state)
    // 参数格式为(path: String, state: Object)
    location = parsePath(path);
    location.state = state;
  } else {
    // 参数格式为(path: Object),基于Location对象创建location
    location = _extends({}, path);
    if (location.pathname === undefined) location.pathname = '';
    if (location.search) {
      if (location.search.charAt(0) !== '?') location.search = '?' + location.search;
    } else {
      location.search = '';
    if (location.hash) {
      if (location.hash.charAt(0) !== '#') location.hash = '#' + location.hash;
    } else {
      location.hash = '';
    if (state !== undefined && location.state === undefined) location.state = state;
  try {
    location.pathname = decodeURI(location.pathname);
  } catch (e) {
    if (e instanceof URIError) {
      // decodeURI在解析非合法的URI编码是抛出URIError类型错误
      throw new URIError('Pathname "' + location.pathname + '" could not be decoded. ' + 'This is likely caused by an invalid percent-encoding.');
    } else {
      throw e;
  if (key) location.key = key;
  if (currentLocation) {
    // 相对于currentLocation解析location.pathname
    if (!location.pathname) {
      location.pathname = currentLocation.pathname;
    } else if (location.pathname.charAt(0) !== '/') {
      location.pathname = resolvePathname(location.pathname, currentLocation.pathname);
  } else {
    // 如果path为空且没有currentLocation,将location.pathname设置为/
    if (!location.pathname) {
      location.pathname = '/';
  return location;
// 判断标准为pathname、search、hash及state对象都相等
export var locationsAreEqual = function locationsAreEqual(a, b) {
  return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash && a.key === b.key && valueEqual(a.state, b.state);



DOMUtils.js

判断浏览器对相关API的支持情况

// 是否为浏览器环境
export var canUseDOM = !!(
  typeof window !== 'undefined'
  && window.document
  && window.document.createElement
// 添加及删除监听函数,同时适用于IE及chrome
export var addEventListener = function addEventListener(node, event, listener) {
  return node.addEventListener
    ? node.addEventListener(event, listener, false)
    : node.attachEvent('on' + event, listener);
export var removeEventListener = function removeEventListener(node, event, listener) {
  return node.removeEventListener
    ? node.removeEventListener(event, listener, false)
    : node.detachEvent('on' + event, listener);
export var getConfirmation = function getConfirmation(message, callback) {
  return callback(window.confirm(message));
}; // eslint-disable-line no-alert
// 判断是否支持HTML5 history API
export var supportsHistory = function supportsHistory() {
  var ua = window.navigator.userAgent;
  if (
    (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1)
    && ua.indexOf('Mobile Safari') !== -1
    && ua.indexOf('Chrome') === -1
    && ua.indexOf('Windows Phone') === -1
  ) return false;
  return window.history && 'pushState' in window.history;
 * Returns true if browser fires popstate on hash change.
 * IE10 and IE11 do not.
// 判断hash改变时是否触发popstate事件,IE10和IE11不支持
export var supportsPopStateOnHashChange = function supportsPopStateOnHashChange() {
  return window.navigator.userAgent.indexOf('Trident') === -1;
// todo: 以下两种情况尚未尝试
 * Returns false if using go(n) with hash history causes a full page reload.
export var supportsGoWithoutReloadUsingHash = function supportsGoWithoutReloadUsingHash() {
  return window.navigator.userAgent.indexOf('Firefox') === -1;
 * Returns true if a given popstate event is an extraneous WebKit event.
 * Accounts for the fact that Chrome on iOS fires real popstate events
 * containing undefined state when pressing the back button.
export var isExtraneousPopstateEvent = function isExtraneousPopstateEvent(event) {
  return event.state === undefined && navigator.userAgent.indexOf('CriOS') === -1;


createTransitionManager.js

控制路由跳转及添加路由跳转监听函数

// warning(condition: bool, messsage: String):当condition为false时,打印警告信息
import warning from 'warning';
var createTransitionManager = function createTransitionManager() {
  var prompt = null;
  // 设置prompt,并返回清空函数,参数为string或function
  var setPrompt = function setPrompt(nextPrompt) {
    warning(prompt == null, 'A history supports only one prompt at a time');
    prompt = nextPrompt;
    return function () {
      if (prompt === nextPrompt) prompt = null;
  var confirmTransitionTo = function confirmTransitionTo(location, action, getUserConfirmation, callback) {
    // TODO: If another transition starts while we're still confirming
    // the previous one, we may end up in a weird state. Figure out the
    // best way to handle this.
    if (prompt != null) {
      // 如果prompt不为空,计算提示信息
      var result = typeof prompt === 'function' ? prompt(location, action) : prompt;
      if (typeof result === 'string') {
        // 提示用户是否跳转
        if (typeof getUserConfirmation === 'function') {
          getUserConfirmation(result, callback);
        } else {
          warning(false, 'A history needs a getUserConfirmation function in order to use a prompt message');
          callback(true);
      } else {
        // Return false from a transition hook to cancel the transition.
        // 如果提示信息不是字符串,result为false,取消跳转,其余情况跳转
        callback(result !== false);
    } else {
      // 如果prompt为空,callback(true)直接调转
      callback(true);
  // 存储监听函数
  var listeners = [];
  // 添加监听函数
  var appendListener = function appendListener(fn) {
    // isActive 用于指示是否真正调用监听函数fn
    // 在使用notifyListeners依次调用监听函数时,
    // 如果在某个监听函数中取消其他监听函数时,可以防止对应监听函数被调用
    var isActive = true;
    var listener = function listener() {
      if (isActive) fn.apply(undefined, arguments);
    listeners.push(listener);
    // 返回取消监听函数
    return function () {
      isActive = false;
      listeners = listeners.filter(function (item) {
        return item !== listener;
  // 依次调用监听函数
  var notifyListeners = function notifyListeners() {
    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    listeners.forEach(function (listener) {
      return listener.apply(undefined, args);
  return {
    setPrompt: setPrompt,
    confirmTransitionTo: confirmTransitionTo,
    appendListener: appendListener,
    notifyListeners: notifyListeners
export default createTransitionManager;


createBrowserHistory.js

创建基于HTML5 history API的自定义history对象

// 使_typeof可以支持判断Symbol类型
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol"
  ? function (obj) { return typeof obj; }
  : function (obj) {
      return obj
        && typeof Symbol === "function"
        && obj.constructor === Symbol
        && obj !== Symbol.prototype ? "symbol" : typeof obj;
// Object.assign垫片函数
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
// 当条件不满足时,打印警告信息
// warning(condition: bool, message: string)
import warning from 'warning';
// 当条件不满足时,抛出错误信息
// invariant(condition: bool, message: string)
import invariant from 'invariant';
import { createLocation } from './LocationUtils';
import { addLeadingSlash, stripTrailingSlash, hasBasename, stripBasename, createPath } from './PathUtils';
import createTransitionManager from './createTransitionManager';
import { canUseDOM, addEventListener, removeEventListener, getConfirmation, supportsHistory, supportsPopStateOnHashChange, isExtraneousPopstateEvent } from './DOMUtils';
var PopStateEvent = 'popstate';
var HashChangeEvent = 'hashchange';
// 获取history.state 等价于popstate事件中的ev.state
var getHistoryState = function getHistoryState() {
  try {
    return window.history.state || {};
  } catch (e) {
    // IE 11 sometimes throws when accessing window.history.state
    // See https://github.com/ReactTraining/history/pull/289
    return {};
 * Creates a history object that uses the HTML5 history API including
 * pushState, replaceState, and the popstate event.
// 利用HTML5 history API创建history对象
var createBrowserHistory = function createBrowserHistory() {
  var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  // 确保支持DOM操作,在浏览器环境中
  invariant(canUseDOM, 'Browser history needs a DOM');
  var globalHistory = window.history;
  var canUseHistory = supportsHistory();
  // 当hash改变时,如果不能触发popstate事件,则添加hashchange事件
  var needsHashChangeListener = !supportsPopStateOnHashChange();
  // 是否强制刷新
  var _props$forceRefresh = props.forceRefresh,
      forceRefresh = _props$forceRefresh === undefined ? false : _props$forceRefresh,
  // 自定义用户确认函数,默认为 (message, callback) => callback(window.confirm(message))
      _props$getUserConfirm = props.getUserConfirmation,
      getUserConfirmation = _props$getUserConfirm === undefined ? getConfirmation : _props$getUserConfirm,
  // 随机key的长度,默认为6
      _props$keyLength = props.keyLength,
      keyLength = _props$keyLength === undefined ? 6 : _props$keyLength;
  // baseurl 转换为 /name 格式
  var basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : '';
  var getDOMLocation = function getDOMLocation(historyState) {
    var _ref = historyState || {},
        key = _ref.key,
        state = _ref.state;
    // 获取当前location中的pathname、search、hash
    var _window$location = window.location,
        pathname = _window$location.pathname,
        search = _window$location.search,
        hash = _window$location.hash;
    // 拼接path
    var path = pathname + search + hash;
    // 如果当前path不以basename为前缀,打印警告信息
    warning(!basename || hasBasename(path, basename), 'You are attempting to use a basename on a page whose URL path does not begin ' + 'with the basename. Expected path "' + path + '" to begin with "' + basename + '".');
    // 去除前缀basename
    if (basename) path = stripBasename(path, basename);
    // 创建location
    return createLocation(path, state, key);
  // 生成随机key
  var createKey = function createKey() {
    return Math.random().toString(36).substr(2, keyLength);
  var transitionManager = createTransitionManager();
  // 在路由发生跳转是,更新history中的部分属性,如action,location等
  // 路由跳转完成后,触发所有监听函数
  var setState = function setState(nextState) { // nextState: { action, location }
    _extends(history, nextState);
    history.length = globalHistory.length;
    transitionManager.notifyListeners(history.location, history.action);
  // event.state包含key和传入的state两部分内容
  // handleHashChange为handlePopState的辅助监听函数,用于处理IE10和IE11在hash改变时,不能触发popstate事件的备选方案
  var handlePopState = function handlePopState(event) {
    // Ignore extraneous popstate events in WebKit.
    // 当触popstate事件时,event.state属性不能被正确支持时,直接返回
    if (isExtraneousPopstateEvent(event)) return;
    handlePop(getDOMLocation(event.state));
  var handleHashChange = function handleHashChange() {
    handlePop(getDOMLocation(getHistoryState()));
  // forceNextPop用于popstate事件是否是用于用户拒绝跳转导致触发的再次触发popstate事件
  var forceNextPop = false;
  // 当触发popstate事件时,此时页面已跳转完成,location记录当前页面的相关信息
  var handlePop = function handlePop(location) {
    // forceNextPop为true,说明此时为用户拒绝跳转后的popstate触发,此时的setState()作用为调用其中的监听函数,此时的action仍为前一个action,不一定为pop
    if (forceNextPop) {
      forceNextPop = false;
      setState();
    } else {
      var action = 'POP';
      // 弹出跳转确认信息时,此时页面已经跳转
      // 如果用户确定跳转,则更新history中action和location信息
      // 如果用户取消跳转,则需要通过revertPop重新跳转至前一个页面
      transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
        if (ok) {
          setState({ action: action, location: location });
        } else {
          revertPop(location);
  var revertPop = function revertPop(fromLocation) {
    // history.location仍保持popstate事件触发前的location信息
    var toLocation = history.location;
    // TODO: We could probably make this more reliable by
    // keeping a list of keys we've seen in sessionStorage.
    // Instead, we just default to 0 for keys we don't know.
    // 通过计算fromLocation.key和toLocation.key在allKey中的索引差值,作为是否调转的标准
    var toIndex = allKeys.indexOf(toLocation.key);
    if (toIndex === -1) toIndex = 0;
    var fromIndex = allKeys.indexOf(fromLocation.key);
    if (fromIndex === -1) fromIndex = 0;
    var delta = toIndex - fromIndex;
    if (delta) {
      // go的调用会再次触发popstate事件
      // 因此,当用户拒绝跳转时,handlePop会被调用两次
      forceNextPop = true;
      go(delta);
  // 初始情况下的getHistoryState()通常为{}, History.state 为null
  var initialLocation = getDOMLocation(getHistoryState());
  var allKeys = [initialLocation.key];
  // Public interface
  // 生成完整链接
  var createHref = function createHref(location) {
    return basename + createPath(location);
  var push = function push(path, state) {
    // 当path为包含state属性的对象时,不应该在传入state参数,此时state会被忽略
    warning(
      !((typeof path === 'undefined' ? 'undefined' : _typeof(path)) === 'object' && path.state !== undefined && state !== undefined),
      'You should avoid providing a 2nd state argument to push when the 1st ' + 'argument is a location-like object that already has state; it is ignored',
    var action = 'PUSH';
    // 创建新的location
    var location = createLocation(path, state, createKey(), history.location);
    // (location, action, getUserConfirmation, callback)
    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
      if (!ok) return;
      // callback(true)跳转
      var href = createHref(location);
      var key = location.key,
          state = location.state;
      if (canUseHistory) {
        // 支持history时,使用pushState
        globalHistory.pushState({ key: key, state: state }, null, href);
        if (forceRefresh) {
          window.location.href = href;
        } else {
          // 更新存储的allKeys
          // 当location处于history队尾时,实际为push
          // 当location处于history中间时,会删除之后的keys,并添加新key
          var prevIndex = allKeys.indexOf(history.location.key);
          var nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
          nextKeys.push(location.key);
          allKeys = nextKeys;
          // 更新history,并触发监听函数
          setState({ action: action, location: location });
      } else {
        // 不支持history API时,在包含state的情况下,打印警告信息,并使用location实现跳转
        warning(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history');
        window.location.href = href;
  // 与push基本相同
  // 唯一的区别在于使用history的replaceState替换当前path,避免添加历史记录
  var replace = function replace(path, state) {
    warning(!((typeof path === 'undefined' ? 'undefined' : _typeof(path)) === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to replace when the 1st ' + 'argument is a location-like object that already has state; it is ignored');
    var action = 'REPLACE';
    var location = createLocation(path, state, createKey(), history.location);
    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
      if (!ok) return;
      var href = createHref(location);
      var key = location.key,
          state = location.state;
      if (canUseHistory) {
        globalHistory.replaceState({ key: key, state: state }, null, href);
        if (forceRefresh) {
          window.location.replace(href);
        } else {
          var prevIndex = allKeys.indexOf(history.location.key);
          if (prevIndex !== -1) allKeys[prevIndex] = location.key;
          setState({ action: action, location: location });
      } else {
        // 在不支持history API时,使用Location.replace实现替换功能
        warning(state === undefined, 'Browser history cannot replace state in browsers that do not support HTML5 history');
        window.location.replace(href);
  // 前进后退
  var go = function go(n) {
    globalHistory.go(n);
  var goBack = function goBack() {
    return go(-1);
  var goForward = function goForward() {
    return go(1);
  var listenerCount = 0;
  var checkDOMListeners = function checkDOMListeners(delta) {
    // 修改监听计数器数量,
    listenerCount += delta;
    // 如果监听计数器为1,添加handlePopState处理函数
    // 如果监听计数器为0,删除handlePopState处理函数
    // 其中的handleHashChange函数,用于处理IE10和IE11在hash改变时,不能触发popstate事件的备选方案
    if (listenerCount === 1) {
      addEventListener(window, PopStateEvent, handlePopState);
      if (needsHashChangeListener) addEventListener(window, HashChangeEvent, handleHashChange);
    } else if (listenerCount === 0) {
      removeEventListener(window, PopStateEvent, handlePopState);
      if (needsHashChangeListener) removeEventListener(window, HashChangeEvent, handleHashChange);
  var isBlocked = false;
  // block被调用时会修改监听计数器数量listenerCount,
  // 只有当listenerCount不为0时,才会监听popstate事件,以确保popstate时对应逻辑得以执行,包括弹出用户确认框及调用监听函数
  // 如果block和listen函数都没有调用,则listernCount为0
  // 则不存在任何注册事件,仅执行浏览器默认动作:更新路由
  var block = function block() {
    // 参数不存在时,prompt为false,此时transitionManager会直接阻止跳转
    var prompt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    // unblock用于清空prompt,prompt为null时,直接跳转,unblock函数不会返回任何值
    var unblock = transitionManager.setPrompt(prompt);
    // 没有阻塞时,监听计数器加一,并将isBlocked记为true,说明存在Block
    // 第二次调用block函数时,如果未调用block返回的函数,则会重新设置prompt信息,并给出警告,不会产生其他效果
    if (!isBlocked) {
      checkDOMListeners(1);
      isBlocked = true;
    // 返回的函数用于取消block信息,并将监听计数器减一
    return function () {
      if (isBlocked) {
        isBlocked = false;
        checkDOMListeners(-1);
      return unblock();
  // 添加自定义监听函数,并返回取消监听函数
  var listen = function listen(listener) {
    var unlisten = transitionManager.appendListener(listener);
    checkDOMListeners(1);
    return function () {
      checkDOMListeners(-1);
      unlisten();
  var history = {
    length: globalHistory.length,
    action: 'POP',
    location: initialLocation,
    createHref: createHref,
    push: push,
    replace: replace,
    go: go,
    goBack: goBack,
    goForward: goForward,
    block: block,
    listen: listen