Title: What you think is not what you think

categories: JavaScript

Window.history is a read-only property that gets a reference to the History object, which provides an interface for manipulating the browser’s session history (pages visited in the browser’s address bar, and pages loaded by frame in the current page), allowing you to jump forward and backward through the user’s browsing history.

history

Window.history is a read-only property that gets a reference to the History object, which provides an interface for manipulating the browser’s session history (pages visited in the browser’s address bar, and pages loaded by frame in the current page), allowing you to jump forward and backward through the user’s browsing history.

Meanwhile, manipulation of the contents of the History stack is provided from HTML5. The History object is part of the Window object and is accessible through the window.history property.

History object property

attribute describe
length Returns the number of urls in the browser history list.
state Returns a value representing the state at the top of the history stack. This is a way to see the status without waiting for a PopState event

History object method

methods attribute
back() Load the previous URL in the history list.
forward() Load the next URL in the history list.
go() Load a specific page in the history list.

The History object was originally designed to represent the browsing History of a window. But for privacy reasons, the History object no longer allows scripts to access actual urls that have already been accessed. The only functionality that remains in use are the back(), forward(), and go() methods.

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>history test</title>
    <script type="text/javascript" src="jquery.min.js"></script>
</head>

<body>
	<button onclick="count()">count</button>
    <button onclick="doForWard()">forward</button>        
    <button onclick="doBack()">back</button>          
    
    <script>
        var index=1;

        function count(){
            console.log(`window.history.length = ${history.length}`);
        }

        function doForWard(){
        	history.forward();
        }

        function doBack(){
        	history.back();
        }
    </script>
</body>

</html>

Copy the code

New in HTML5: Use methods such as History’s pushState to solve the problem of using Ajax to cause pages to move backwards and forwards

With Ajax, you can update parts of a page without refreshing the entire page. This allows you to develop rich client programs that are highly interactive and reduce network traffic. But a long-standing problem is the inability to take advantage of the forward and back buttons provided by the browser itself. For example, when performing an action on a page, the action uses Ajax requests to fetch data from the server and updates some content of the current page. If you want to go back to the previous interface, the user will be used to hitting the browser’s back button, which does not work. HTML5 introduces methods histtory.pushState() and history.replacestate (), which update the contents of the history object. In combination with the window. onpoState event, you can solve this problem.

methods attribute
pushState() Pushes data onto the session history stack with the specified name and URL (if provided)
replaceState() Updates the latest entry on the history stack with the specified data, name, and URL(if provided)
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>history test</title>
    <script type="text/javascript" src="jquery.min.js"></script>
</head>

<body>
	<button onclick="count()">count</button>
    <button onclick="doForWard()">forward</button>        
    <button onclick="doBack()">back</button>        
    <button onclick="doPushState()">pushState</button>        
    <button onclick="doReplaceState()">replaceState</button>        
    
    <script>
        var index=1;

        function count(){
            console.log(`window.history.length = ${history.length}`);
        }

        function doForWard(){
        	history.forward();
        }

        function doBack(){
        	history.back();
        }

        function doPushState(){
            history.pushState({page:index++}, null.'? page='+index);
        }

        function doReplaceState(){
            history.replaceState({page:index++}, null.'? page='+index);
        }
    </script>
</body>

</html>
Copy the code

PushState takes three arguments: 1) The first argument is a JS object that can hold anything and can be retrieved in the onPOState event for easy handling.

2) The second argument is a string, which is not currently implemented by any browser, but may be used in the future. You can pass an empty string.

3) The third argument is a string, which is the URL saved to history.

The browser does not load the URL immediately after calling pushState(), but it may load it later in certain circumstances, such as when the user reopens the browser. The new URL does not have to be an absolute path. If the new URL is a relative path, it is treated as relative to the current URL. The new URL must be of the same origin as the current URL, otherwise pushState() will throw an exception. This parameter is optional. The default value is the current URL. PushState modifies the history by putting the current URL on the history stack when the method is called and displaying the URL in the address bar, but no new URL is loaded. ReplaceState takes the same argument as pushState, but instead of storing the old URL into history, the call replaces the URL in history with the one in the argument.

popState

The POPState event is triggered when the active history entry changes. If the active history entry was created by a call to history.pushState (), or is affected by a call to history.replacestate (). The state property of the popState event contains a copy of the state object of the history entry and is the first argument passed when pushState and replaceState jump to the current page.

The popState event is triggered when the browser moves forward and backward. Together with pushState and replaceState, this solves the problem of ajax pages moving forward and backward.

The solution isto call the pushState event when the Ajax event is triggered to modify the URL information. When the popState event is triggered, the History event is called. Depending on the current URL, the Ajax event is called to restore the corresponding state.

The history + popstate application

  1. A web page is not something you can walk away from just by adding a few lines of code
history.pushState(null.null.document.URL);
window.addEventListener('popstate'.function () {
    history.pushState(null.null.document.URL);
});
Copy the code

This code adds the current URL to the URL history first, and there are two identical urls at the top of the history stack. When the back event is clicked, the previous URL is returned, but the page listens for the back event and pushes the same URL onto the stack again. For the user to hit the back button, the URL is never changed and the page does not reload (this method should be used with caution due to ethical conventions)

  1. For the history mode of routes, see Route Implementation

Chrome is considering a new history implementation baijiahao.baidu.com/s?id=163318…

location

History is a new method of HTML5, and it has compatibility problems in the old version of Internet Explorer, and history will not send a request to refresh the page when changing the URL. If you want to load the URL at the same time as switching the URL, You can use the location.assign and location.replace methods

  • Window.location. assign(url) : loads a new HTML document specified by the URL. It is equivalent to a link, jump to the specified URL, the current page will be converted to the new page content, you can click back to return to the previous page.
  • Window.location.replace (URL) : Replaces the current document by loading the document specified by the URL

Simple routing with history and location

The functions of routing are as follows:

  • Click the button, the URL is updated, the resource is not reloaded, only dynamic js execution
  • Refresh the page to keep the current state
  • The push method adds the route history to the page history, the replace method replaces the page in the history, and hits return
  • Monitor the address bar and display the corresponding content when the user manually enters it
  • There are hash mode and history mode

hash

/**
 * hash 模式
 */
function Route(){
    this.routes = {};// Store route path and callback
    this.currentUrl = ' ';

    window.addEventListener('hashchange'.this.refresh);
}

// The route is switched
Route.prototype.refresh = function(){
    console.log(location.hash +' dom is refresh')}// Event and route binding
Route.prototype.route = function(path, callback){
    this.routes[path] = callback;
}
// Execute the event
Route.prototype.push = function(path){
    // Update view dosomething
    location.hash = path;
    this.currentUrl = location.hash.slice(1) | |'/';
    this.routes[this.currentUrl] && this.routes[this.currentUrl]();
}

Route.prototype.replace = function(path){
    // Update view dosomething
    const i = location.href.indexOf(The '#');
    location.replace(location.href.slice(0, i >= 0 ? i : 0) + The '#' + path);
    this.routes[path] && this.routes[path]();
}

var myRouter = new Route();

// Define routing events
myRouter.route('my', () = > {console.log('page1');
})
myRouter.route('home', () = > {console.log('page2');
})

// Use routing
myRouter.push('my');
myRouter.replace('home');
Copy the code

history

function Route(){
    this.routes = {};// Store route path and callback

    window.addEventListener('popState'.this.refresh)
}

Route.prototype.refresh = function(path){
    console.log('dom has refresh');
}
// Event and route binding
Route.prototype.route = function(path, callback){
    this.routes[path] = callback;
}

Route.prototype.replace = function (path) {
    // Update the view
    history.replaceState({path: path}, null, path);
    this.routes[path] && this.routes[path]();
}
Route.prototype.push = function (path) {
    // Update the view
    history.pushState({path: path}, null, path);
    this.routes[path] && this.routes[path]();
}

var myRouter = new Route();

// Define routing events
myRouter.route('/my', () = > {console.log('page1');
})
myRouter.route('/detail', () = > {console.log('page2');
})

// Use routing
myRouter.push('/detail')
Copy the code

The appendix

VueRouter (router) {VueRouter (router)

// src/index.js

export default class VueRouter{
  mode: string; // The string argument passed to indicate the history category
  history: HashHistory | HTML5History | AbstractHistory; // The actual object property in effect must be an enumeration of the above three classes
  fallback: boolean; // If the browser does not support this, the 'history' mode needs to be rolled back to the 'hash' mode
  
  constructor (options: RouterOptions = {}) {
    
    let mode = options.mode || 'hash' // The default is 'hash' mode
    this.fallback = mode === 'history' && !supportsPushState SupportsPushState check whether the browser supports the 'history' mode
    if (this.fallback) {
      mode = 'hash'
    }
    if(! inBrowser) { mode ='abstract' // Enforce 'abstract' mode when not running in a browser environment
    }
    this.mode = mode

    // Determine and instantiate the actual class of history according to mode
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if(process.env.NODE_ENV ! = ='production') {
          assert(false.`invalid mode: ${mode}`)
        }
    }
  }

  init (app: any /* Vue component instance */) {
    
    const history = this.history

    // Perform initialization and listening operations according to the category of history
    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = (a)= > {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

    history.listen(route= > {
      this.apps.forEach((app) = > {
        app._route = route
      })
    })
  }

  // The VueRouter class exposes the following methods that actually call the concrete history objectpush (location: RawLocation, onComplete? :Function, onAbort? :Function) {
    this.history.push(location, onComplete, onAbort) } replace (location: RawLocation, onComplete? :Function, onAbort? :Function) {
    this.history.replace(location, onComplete, onAbort)
  }
}
Copy the code
  1. If the browser does not support HTML5History (determined by the supportsPushState variable), mode is set to hash. If not running in a browser environment, mode is set to abstract.
  2. VueRouter’s onReady(),push() and other methods are proxies that call the corresponding methods of the specific history object. Init () also performs different operations according to the specific class of the history object.

1-2 HashHistory process:

$router.push()–>HashHistory.push()–>History.transitionTo()–>History.updateRoute()–>{app._route=route}–>vm.render()

HashHistory.push()

push (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  this.transitionTo(location, route => {
    pushHash(route.fullPath)
    onComplete && onComplete(route)
  }, onAbort)
}

function pushHash (path) {
  window.location.hash = path // Assign hash directly
}
Copy the code

HashHistory.replace()

replace (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  this.transitionTo(location, route => {
    replaceHash(route.fullPath)
    onComplete && onComplete(route)
  }, onAbort)
}
  
function replaceHash (path) {
  const i = window.location.href.indexOf(The '#')
  window.location.replace(
    window.location.href.slice(0, i >= 0 ? i : 0) + The '#' + path // Call location.replace() to replace the hash directly)}Copy the code

1-3 transitionTo()

// The transitionTo() method in the parent class HistorytransitionTo (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  const route = this.router.match(location, this.current)
  this.confirmTransition(route, () => {
    this.updateRoute(route)
    ...
  })
}

updateRoute (route: Route) {
  
  this.cb && this.cb(route)
  
}

listen (cb: Function) {
  this.cb = cb
}

The listen method is defined in the VueRouter class
init (app: any /* Vue component instance */) {
    
  this.apps.push(app)

  history.listen(route= > {
    this.apps.forEach((app) = > {
      app._route = route
    })
  })
}

//install.js inserts _route where the plugin is loaded
// With the vue.mixin () method, globally register a mix that affects all Vue instances created after registration,
// This blend defines the reactive _route attribute in the beforeCreate hook via vue.util.definereActive ().
// When the _route value changes, the render() method of the Vue instance is automatically called to update the view
export function install (Vue) {
  
  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._router = this.$options.router
        this._router.init(this)
        Vue.util.defineReactive(this.'_route'.this._router.history.current)
      }
      registerInstance(this.this)}})}Copy the code

1-4 Listen on the address bar

setupListeners () {
  window.addEventListener('hashchange', () = > {if(! ensureSlash()) {return
    }
    this.transitionTo(getHash(), route => {
      replaceHash(route.fullPath)
    })
  })
}
Copy the code

1-5 HTML5History

push (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  const { current: fromRoute } = this
  this.transitionTo(location, route => {
    pushState(cleanPath(this.base + route.fullPath))
    handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) } replace (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  const { current: fromRoute } = this
  this.transitionTo(location, route => {
    replaceState(cleanPath(this.base + route.fullPath))
    handleScroll(this.router, route, fromRoute, false)
    onComplete && onComplete(route)
  }, onAbort)
}

// src/util/push-state.js
export function pushState (url? : string, replace? : boolean) {
  saveScrollPosition()
  // try... catch the pushState call to get around Safari
  // DOM Exception 18 where it limits to 100 pushState calls
  const history = window.history
  try {
    if (replace) {
      history.replaceState({ key: _key }, ' ', url)
    } else {
      _key = genKey()
      history.pushState({ key: _key }, ' ', url)
    }
  } catch (e) {
    window.location[replace ? 'replace' : 'assign'](url)
  }
}

export function replaceState (url? : string) {
  pushState(url, true)}Copy the code

PopState listens for changes in the address bar

constructor(router: Router, base: ? string) {window.addEventListener('popstate', e => {
    const current = this.current
    this.transitionTo(getLocation(this.base), route => {
      if (expectScroll) {
        handleScroll(router, route, current, true)}})})}Copy the code

SupportsPushState Checks whether the browser supports HTML5 history

// src/util/push-state.js

export const supportsPushState = inBrowser && (function () {
  const ua = window.navigator.userAgent

  if (
    (ua.indexOf('Android 2.')! = =- 1 || ua.indexOf('the 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
})()
Copy the code