前端路由与后端路由
本篇文章给大家谈谈原生js代码实现前端路由,以及前端路由与后端路由对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
1、vue实现路由跳转的原理是什么,是调用js底层什么方法
2、为什么会有原生javascript无法实现而框架可以实现的功能的情况?
3、router(History,hash)前端路由机制
4、web前端原生js实现瀑布流
5、JSON.stringify()方法的原生JS实现
vue实现路由跳转的原理是什么,是调用js底层什么方法
前端路由是直接找到与地址匹配的一个组件或对象并将其渲染出来。改变浏览器地址而不向服务器发出请求有两种方式:
1. 在地址中加入#以欺骗浏览器,地址的改变是由于正在进行页内导航
2. 使用H5的window.history功能,使用URL的Hash来模拟一个完整的URL。
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
目录结构
先来看看整体的目录结构
和流程相关的主要需要关注点的就是 components、history 目录以及 create-matcher.js、create-route-map.js、index.js、install.js。下面就从 basic 应用入口开始来分析 vue-router 的整个流程。
import Vue from 'vue'
import VueRouter from 'vue-router'
// 1. 插件
// 安装 router-view and router-link 组件
// 且给当前应用下所有的组件都注入 $router and $route 对象
Vue.use(VueRouter)
// 2. 定义各个路由下使用的组件,简称路由组件
const Home = { template: 'divhome/div' }
const Foo = { template: 'divfoo/div' }
const Bar = { template: 'divbar/div' }
// 3. 创建 VueRouter 实例 router
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/', component: Home },
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
})
// 4. 创建 启动应用
// 一定要确认注入了 router
// 在 router-view 中将会渲染路由组件
new Vue({
router,
template: ` div id="app"
h1Basic/h1
ul
lirouter-link to="/"//router-link/li
lirouter-link to="/foo"/foo/router-link/li
lirouter-link to="/bar"/bar/router-link/li
router-link tag="li" to="/bar"/bar/router-link
/ul
router-view class="view"/router-view
/div
`
}).$mount('#app')123456789101112131415161718192021222324252627282930313233343536373839404142
作为插件
上边代码中关键的第 1 步,利用 Vue.js 提供的插件机制 .use(plugin) 来安装 VueRouter,而这个插件机制则会调用该 plugin 对象的 install 方法(当然如果该 plugin 没有该方法的话会把 plugin 自身作为函数来调用);下边来看下 vue-router 这个插件具体的实现部分。
VueRouter 对象是在 src/index.js 中暴露出来的,这个对象有一个静态的 install 方法:
/* @flow */
// 导入 install 模块
import { install } from './install'// ...import { inBrowser, supportsHistory } from './util/dom'// ...export default class VueRouter {
// ...}
// 赋值 install
VueRouter.install = install
// 自动使用插件if (inBrowser window.Vue) {
window.Vue.use(VueRouter)
}123456789101112131415161718
可以看到这是一个 Vue.js 插件的经典写法,给插件对象增加 install 方法用来安装插件具体逻辑,同时在最后判断下如果是在浏览器环境且存在 window.Vue 的话就会自动使用插件。
install 在这里是一个单独的模块,继续来看同级下的 src/install.js 的主要逻辑:
// router-view router-link 组件import View from './components/view'import Link from './components/link'// export 一个 Vue 引用export let _Vue// 安装函数export function install (Vue) {
if (install.installed) return
install.installed = true
// 赋值私有 Vue 引用
_Vue = Vue // 注入 $router $route
Object.defineProperty(Vue.prototype, '$router', {
get () { return this.$root._router }
}) Object.defineProperty(Vue.prototype, '$route', {
get () { return this.$root._route }
}) // beforeCreate mixin
Vue.mixin({
beforeCreate () { // 判断是否有 router
if (this.$options.router) { // 赋值 _router
this._router = this.$options.router // 初始化 init
this._router.init(this) // 定义响应式的 _route 对象
Vue.util.defineReactive(this, '_route', this._router.history.current)
}
}
}) // 注册组件
Vue.component('router-view', View)
Vue.component('router-link', Link)// ...}12345678910111213141516171819202122232425262728293031323334353637383940414243
这里就会有一些疑问了?
· 为啥要 export 一个 Vue 引用?
插件在打包的时候是肯定不希望把 vue 作为一个依赖包打进去的,但是呢又希望使用 Vue 对象本身的一些方法,此时就可以采用上边类似的做法,在 install 的时候把这个变量赋值 Vue ,这样就可以在其他地方使用 Vue 的一些方法而不必引入 vue 依赖包(前提是保证 install 后才会使用)。
· 通过给 Vue.prototype 定义 $router、$route 属性就可以把他们注入到所有组件中吗?
在 Vue.js 中所有的组件都是被扩展的 Vue 实例,也就意味着所有的组件都可以访问到这个实例原型上定义的属性。
beforeCreate mixin 这个在后边创建 Vue 实例的时候再细说。
实例化 VueRouter
在入口文件中,首先要实例化一个 VueRouter ,然后将其传入 Vue 实例的 options 中。现在继续来看在 src/index.js 中暴露出来的 VueRouter 类:
// ...import { createMatcher } from './create-matcher'// ...export default class VueRouter {
// ...
constructor (options: RouterOptions = {}) {
this.app = null
this.options = options
this.beforeHooks = []
this.afterHooks = []
// 创建 match 匹配函数
this.match = createMatcher(options.routes || [])
// 根据 mode 实例化具体的 History
let mode = options.mode || 'hash'
this.fallback = mode === 'history' !supportsHistory if (this.fallback) {
mode = 'hash'
} if (!inBrowser) {
mode = 'abstract'
}
this.mode = 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) break
default:
assert(false, `invalid mode: ${mode}`)
}
}
// ...}123456789101112131415161718192021222324252627282930313233343536373839
里边包含了重要的一步:创建 match 匹配函数。
match 匹配函数
匹配函数是由 src/create-matcher.js 中的 createMatcher 创建的:
/* @flow */
import Regexp from 'path-to-regexp'// ...import { createRouteMap } from './create-route-map'// ...export function createMatcher (routes: ArrayRouteConfig): Matcher {
// 创建路由 map
const { pathMap, nameMap } = createRouteMap(routes)
// 匹配函数 function match (
raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location
): Route {
// ...
} function redirect (
record: RouteRecord,
location: Location
): Route {
// ...
} function alias (
record: RouteRecord,
location: Location,
matchAs: string
): Route {
// ...
} function _createRoute (
record: ?RouteRecord,
location: Location,
redirectedFrom?: Location
): Route { if (record record.redirect) { return redirect(record, redirectedFrom || location)
} if (record record.matchAs) { return alias(record, location, record.matchAs)
} return createRoute(record, location, redirectedFrom)
}
// 返回 return match
}
// ...123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
具体逻辑后续再具体分析,现在只需要理解为根据传入的 routes 配置生成对应的路由 map,然后直接返回了 match 匹配函数。
继续来看 src/create-route-map.js 中的 createRouteMap 函数:
/* @flow */import { assert, warn } from './util/warn'import { cleanPath } from './util/path'// 创建路由 mapexport function createRouteMap (routes: ArrayRouteConfig): {
pathMap: DictionaryRouteRecord,
nameMap: DictionaryRouteRecord
} { // path 路由 map
const pathMap: DictionaryRouteRecord = Object.create(null) // name 路由 map
const nameMap: DictionaryRouteRecord = Object.create(null) // 遍历路由配置对象 增加 路由记录
routes.forEach(route = {
addRouteRecord(pathMap, nameMap, route)
}) return {
pathMap,
nameMap
}
}// 增加 路由记录 函数function addRouteRecord (
pathMap: DictionaryRouteRecord,
nameMap: DictionaryRouteRecord,
route: RouteConfig,
parent?: RouteRecord,
matchAs?: string
) {
// 获取 path 、name
const { path, name } = route
assert(path != null, `"path" is required in a route configuration.`) // 路由记录 对象
const record: RouteRecord = {
path: normalizePath(path, parent),
components: route.components || { default: route.component },
instances: {},
name, parent,
matchAs,
redirect: route.redirect,
beforeEnter: route.beforeEnter,
meta: route.meta || {}
} // 嵌套子路由 则递归增加 记录
if (route.children) {// ...
route.children.forEach(child = {
addRouteRecord(pathMap, nameMap, child, record)
})
} // 处理别名 alias 逻辑 增加对应的 记录
if (route.alias !== undefined) { if (Array.isArray(route.alias)) {
route.alias.forEach(alias = {
addRouteRecord(pathMap, nameMap, { path: alias }, parent, record.path)
})
} else {
addRouteRecord(pathMap, nameMap, { path: route.alias }, parent, record.path)
}
} // 更新 path map
pathMap[record.path] = record // 更新 name map
if (name) { if (!nameMap[name]) {
nameMap[name] = record
} else {
warn(false, `Duplicate named routes definition: { name: "${name}", path: "${record.path}" }`)
}
}
}function normalizePath (path: string, parent?: RouteRecord): string {
path = path.replace(/\/$/, '') if (path[0] === '/') return path if (parent == null) return path return cleanPath(`${parent.path}/${path}`)
}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
可以看出主要做的事情就是根据用户路由配置对象生成普通的根据 path 来对应的路由记录以及根据 name 来对应的路由记录的 map,方便后续匹配对应。
实例化 History
这也是很重要的一步,所有的 History 类都是在 src/history/ 目录下,现在呢不需要关心具体的每种 History 的具体实现上差异,只需要知道他们都是继承自 src/history/base.js 中的 History 类的:
/* @flow */// ...import { inBrowser } from '../util/dom'import { runQueue } from '../util/async'import { START, isSameRoute } from '../util/route'// 这里从之前分析过的 install.js 中 export _Vueimport { _Vue } from '../install'export class History {// ...
constructor (router: VueRouter, base: ?string) { this.router = router this.base = normalizeBase(base) // start with a route object that stands for "nowhere"
this.current = START this.pending = null
}// ...}// 得到 base 值function normalizeBase (base: ?string): string { if (!base) { if (inBrowser) { // respect base tag
const baseEl = document.querySelector('base') base = baseEl ? baseEl.getAttribute('href') : '/'
} else { base = '/'
}
} // make sure there's the starting slash
if (base.charAt(0) !== '/') { base = '/' + base
为什么会有原生javascript无法实现而框架可以实现的功能的情况?
请注意这些框架也是javascript写的。语言都会有框架,类库之类的,他们能让人从重复的工作中解脱出来,快速开发。
router(History,hash)前端路由机制
前端路由机制
前端路由,顾名思义就是一个前端不同页面的状态管理器,可以不向后台发送请求而直接通过前端技术实现多个页面的效果。
前端路由机制原理及两种实现方式
一、History
History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。
用户访问网页的历史记录通常会被保存在一个类似于栈对象中,即history对象:
history 对象包含用户(在浏览器窗口)访问过的url
history对象是window对象的一部分,可以通过window.history属性进行访问。
基本的API用法如back、forward、go不做多解释,可以参考MDN
重点解释html5新增的API:
1、history.pushState() //在history对象中添加一条新的浏览记录
2、History.replaceState() // 是替换history中的当前记录
3、history.state //是一个属性,可以得到当前页的state信息。
4、window.onpopstate //是一个事件,在点击浏览器后退按钮或js调用forward()、back()、go()时触发。
(和它相似的一个方法叫做onhashchange,onhashchange是老API, 浏览器支持度高, 本来是用来监
听hash变化的, 但可以被利用来做客户端前进和后退事件的监听,onpopstate是专门用来监听浏览器
前进后退的, 不仅可以支持hash, 非hash的同源url也支持。)
history.pushState与History.replaceState的区别
history.pushState和History.replaceState都接收三个参数:即(data[,title][,url])
状态对象(state Object):一个object,与pushState方法创建的新历史记录条目关联。
标题:一般传null
地址(url):新的历史记录条目的地址。
pushState和replaceState都会操作浏览器的历史记录,并且不会引起页面的刷新。
不同之处在与:一个新增,一个替换。
History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美观
一、hash
我们经常看到在url中出现#符号,这个在路由中出现的#,叫做hash,很多大型框架的路由系统都是由hash实现的。
上面提到一个方法onhashchange事件,用来监听hash变化,也可以被利用来做客户端前进和后退事件的监听。
web前端原生js实现瀑布流
思路分析
瀑布流所有元素的宽度是固定的,我们用浏览器的宽度除以每个瀑布流块的宽度,就是每一行可容纳的瀑布流块的个数.因为,每个瀑布流块的高度不一,我们姑且把组成一行的这组元素称为成行元素,在成行元素放置完毕后,我们如果要再增加一个元素,那么它的位置应该这样找?
“获取成行元素集合中高度最低的那个元素,待放置的元素的top值应该是这个最低元素的高,left值应该是这个最低元素的left值”
这样,新增的这一个元素我们就找到了它存放的位置.这样成行元素中的最低高度值就变为了原来的高度+新增元素的高度.
步骤二:重复步骤一,依赖成行元素追加新元素
步骤一中我们已经实现了一次成行元素追加一个新的元素,这样新元素增加之后我们就构建了新的成行元素,之后的操作就是在新的成行元素中追加新元素,原理同步骤一.
!doctype html
meta charset="UTF-8"
title瀑布流效果实现
script type="text/javascript" src="scripts/jquery-1.8.2.min.js"
script type="text/javascript" src="scripts/jquery.easydrag.handler.beta2.js"
script type="text/javascript"
window.onload=function(){
//获取父级对象
var oParent = document.getElementById("main");
//获取父级[第一个参数]下的所有的子元素[按照第二个参数匹配]
var aPin =getClassObject(oParent,"pin");
//获取每一个块的宽度
var iPinW = aPin[0].offsetWidth;
// //计算每行放多少个pin(瀑布流块)页面的宽度/每一个瀑布流块的宽度
var num = Math.floor(document.documentElement.clientWidth/iPinW);
//重置父级的样式,这样保证图片整体居中
oParent.style.cssText="width:" + num*iPinW +"px;margin:0 auto;";
var compareArray = [];
//遍历获取到的所有瀑布流块
for (var i =0; i
if(i
//成行元素
compareArray[i] = aPin[i].offsetHeight;
}else{
//获取成行元素中高度最低的值
var minHeight = Math.min.apply('',compareArray);
//alert(compareArray + ",min=" + minHeight);
//获取成行元素中高度最低元素的索引
var minHkey =getMinHeightKey(compareArray,minHeight);
//为新增的瀑布流块设置定位
aPin[i].style.position ="absolute";
aPin[i].style.top = minHeight +"px";
//设定新增加的瀑布流块的top和left
aPin[i].style.left =aPin[minHkey].offsetLeft +"px";
//将该索引位置的高度改变为新增后的高度[原来瀑布流块的高度+新增的瀑布流块的高度]
compareArray[minHkey] += aPin[i].offsetHeight;
}
}
}
/**
* 获取parent下所有样式名为className的对象集合
*/
function getClassObject(parent,className){
var obj = parent.getElementsByTagName("*");
var result = [];
for(var i=0; i
//变量如果匹配className,将匹配的对象放入数组
if(obj[i].className==className){
result.push(obj[i]);
}
}
return result;
}
/**
* 获取arr数组中值为minH的值在数组中的索引
*/
function getMinHeightKey(arr,minH){
for(key in arr){
if(arr[key] == minH){
return key;
}
}
}
style type="text/css"
/*设置每一个瀑布流块*/
#main .pin{
width:220px;
height:auto;
padding:15px 0px 0px 15px; /*上 右 下 左*/
float:left;
}
/*设置每一个瀑布流块中的图像样式*/
#main .pin .box{
width:200px;
height:auto;
padding:10px;
background:#FFF;
border:1px solid #ccc;
box-shadow:0px 0px 6px #ccc; /*中间投影*/
border-radius:5px; /*圆角*/
}
#main .pin .box img{
width:200px;
}
div id="main"
div class="pin"
div class="box"
img src="images/1.webp"
div class="pin"
div class="box"
img src="images/2.webp"
div class="pin"
div class="box"
img src="images/3.webp"
div class="pin"
div class="box"
img src="images/4.webp"
div class="pin"
div class="box"
img src="images/5.webp"
div class="pin"
div class="box"
img src="images/6.webp"
div class="pin"
div class="box"
img src="images/7.webp"
/html
JSON.stringify()方法的原生JS实现
通过递归调用,来实现对转换对象Object的深层value进行遍历。
利用array的join方法实现最终的字符串拼接。
可直接在浏览器运行如下代码
运行结果
本文的JSON.stringify()方法的原生JS实现属于对javascript灵活应用的基本功。
实现思路来自于笔者平日的工作与思考的总结。
希望大家都能掌握以便在面试中脱颖而出。
有需要前端学习资料的同学可以私行我“学习”,获取学习资料一起交流学习
原生js代码实现前端路由的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于前端路由与后端路由、原生js代码实现前端路由的信息别忘了在本站进行查找喔。