summertzz'blog

做一些有趣的事儿


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

Vue-Router 学习笔记

发表于 2020-04-28
字数统计: 10.2k 字 | 阅读时长 ≈ 41 分钟

资源

项目地址

引入

根据 “路” 这个词的头脑风暴

路由

  • 路由是根据不同的 url 地址展示不同的内容或页面

    后端路由

  • 浏览器在地址栏中切换不同的url时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件传给前端显示, 返回不同的页面, 意味着浏览器会刷新页面,网速慢的话说不定屏幕全白再有新内容。后端路由的另外一个极大的问题就是前后端不分离。
  • 优点:分担了前端的压力,html和数据的拼接都是由服务器完成。
  • 缺点:当项目十分庞大时,加大了服务器端的压力,同时在浏览器端不能输入制定的url路径进行指定模块的访问。另外一个就是如果当前网速过慢,那将会延迟页面的加载,对用户体验不是很友好。
  • 举例:分配一个站点,服务器地址是:http://192.168.1.200:8899,在这个网站中提供了三个界面。
    1
    2
    3
    http://192.168.1.200:8899/index.html          主页
    http://192.168.1.200:8899/about/aboutus.html 关于我们页面
    http://192.168.1.200:8899/feedback.html 反馈界面
  • 当我们在浏览器输入 http://192.168.1.200:8899/index.html 访问界面的时候,web 服务器就会接收到这个请求,然后把 index.html 解析出来,并找到相应的 index.html 并展示出来,这就是路由的分发,路由的分发是通过路由功能来完成的。

    前端路由

  • 很重要的一点是页面不刷新,前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,每跳转到不同的URL都是使用 hash 路由. 随着(SPA)单页应用的不断普及,前后端开发分离,目前项目基本都使用前端路由,在项目使用期间页面不会重新加载。
  • 什么时候使用前端路由?在单页面应用,大部分页面结构不变,只改变部分内容的使用
  • 优点:
    • 用户体验好,前端页面的渲染和数据分离了,页面的渲染不会因为请求数据而阻塞。
    • 可以再浏览器中输入指定想要访问的url路径地址。
  • 虽然前端路由和后端路由的实现方式不一样,但是原理都有是相同的,在 H5 的 history Api 出来之前,前端路由的功能都是通过 hash 来实现的,hash 能兼容低版本的浏览器。

PS:后端路由每次访问一个页面都要向浏览器发送请求,然后服务端再响应解析,在这个过程中肯定会存在延迟,但是前端路由中访问一个新的界面的时候只是浏览器的路径改变了,没有和服务端交互「所以不存在延迟」,这个对用户体验来说是大大的提高。如下所示:

1
2
3
http://192.168.1.200:8080/#/index.html
http://192.168.1.200:8080/#/about/aboutus.html
http://192.168.1.200:8080/#/feedback.html

由于 web 服务器不会解析 # 后面的东西(所以通过 hash 能提高性能),但是客户端的 js 可以拿到 # 后面的东西,有一个方法是 window.location.hash 来读取,使用这个方法来匹配到不同的方法上,配合前端的一些逻辑操作就完成路由功能,剩下只是关心接口调用。
举例

1
http://www.xxx.com/path/a/b/c.html?key1=aaa && key2=bbb && key3=ccc#/path/d/e.html
  • 拆分这个地址

    1
    2
    3
    4
    5
    http:协议
    www.xxx.com:域名
    /path/a/b/c.html:路由,即服务器上的资源
    ?key1=aaa && key2=bbb && key3=ccc:Get 请求的参数,
    #/path/d/e.html:hash 也叫散列值,也叫锚点

    上面的 hash 是和浏览器交互的,其它的都是和服务器进行交互。
    通过上述我们知道,前端路由的实现方式有两种:

  • 改变 hash 值,监听 hashchange 事件,可以兼容低版本浏览器

  • 通过 H5 的 history API 来监听 popState 事件,使用 pushState 和 replaceState 实现

    hash 改变路由

    H5 的 history

    文档: https://developer.mozilla.org/zh-CN/docs/Web/API/History_API
    window 的 history 提供了对浏览器历史记录的访问功能,并且它暴露了一些方法和属性,让你在历史记录中自由的前进和后退,并且在 H5 中还可以操作历史记录中的数据。
    在 chrome 浏览器的调试窗口中在 console 中输入 window.history,会得到 history 的一些方法和属性,如下图所示
    图片

  • back():在历史记录中后退

    1
    history.back() ;
  • forward:在历史记录中前进

    1
    history.forward();
  • go():移动到指定的历史记录点

其中正数是前进「+1就是前进一个界面」,负责是后退的意思「-1就是后退一个界面」

1
history.go(-1)
  • length

hisgory 的属性,显示 history 的长度

  • pushState(data,title[,url]:给历史记录堆栈顶部添加一条记录
    1
    history.pushState(data,title[,url])
    使用 H5 的 history 的 pushState 可以代替 hash,并且更加优雅,点击各个导航没有刷新浏览器,并且点击浏览器的回退按钮,会显示上一次记录,这都是使用 h5 history 的 pushState 和监听 onpopstate 实现的,这就是一个简单的 SPA ,基本上实现了和上面 hash 一样的功能。

总结

  • 后端路由:每次访问都要向 server 发送一个请求,server 需要响应解析,会有延迟「网络不好更严重」

  • 前端路由:只是改变浏览器的地址,不刷新浏览器,不和服务端交互,所以性能有大大的提高「用户体验提高」,并且前端路由有两种实现方式。

    • 实现 hash 并监听 hashchange 事件来实现
    • 使用 H5 的 hisgory 的 pushState() 监听 popstate 方法来实现

      Vue Router

      概述

  • vue-router 和 vue.js 是深度集成的,适合用于单页面应用.传统的路由是用一些超链接来实现页面切换和跳转.而vue-router在单页面应用中,则是组件之间的切换.其本质就是:建立并管理 url 和对应组件之间的映射关系.

  • Vue Router 使构建单页面应用变得简单。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当我们引入 Vue Router 后,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们( & )。

    抓核心

    vue router 最核心的两个点

  • 路由配置

  • 路由使用

    环境搭建

  • 下载 vue, vue router

    1
    npm i vue vue-router -s

    总体思路

  • vue-router 是 vue.js 的一个插件,vue-router 要进行注册

  • 需要定义模板,即 template (很多个)

  • vue - router 的定义规则,完成模板和 hash 的匹配规则

  • 如果在页面中定义调用转发的路由 – a 标签

  • 模板的内容放在什么地方

    vue router 简易关系图

  • 关系图:如 ***vue router 简易关系图.xmind** 所示

  • 图片

    思考

  • router,routes, route 的区别*

  • router:一般指的就是路由实例.如$router.

  • *routes: *指router路由实例的routes API.用来配置多个route路由对象.

  • *route: *指的就是路由对象.例如;$route指的就是当前路由对象.

    vue router 的两种模式

  • 一般单页面应用是(SPA)不会请求页面而是只更新视图***.** vue-router提供了两种方式来实现前端路由:Hash模式和History模式,可以用mode参数来决定使用哪一种方式.

hash 模式

vue-router默认使用Hash模式.使用url的hash来模拟一个完整的url.此时url变化时,浏览器是不会重新加载的**.Hash(即#)是url的锚点,代表的是网页中的一个位置,仅仅改变#后面部分,浏览器只会滚动对应的位置,而不会重新加载页面.#仅仅只是对浏览器进行指导,而对服务端是完全没有作用的!它不会被包括在http请求中,故也不会重新加载页面**.同时hash发生变化时,url都会被浏览器记录下来,这样你就可以使用浏览器的后退了.

总结:Hash模式的本质就是通过改变#后面的值,实现浏览器渲染指定的组件**.**

history模式

history模式利用了HTML5 History新增的pushState()和replaceState()方法**.** 除了之前的back,forward,go方法,这两个新方法可以应用在浏览器历史记录的增加替换功能上.使用History模式,通过历史记录修改url,但它不会立即向后端发送请求. 注意点: 虽然History模式可以丢掉不美观的#,也可以正常的前进后退,但是刷新之后,此时浏览器就会访问服务器,在没有后台支持的情况下,此时就会得到一个404!官方文档给出的描述是:”不过这种模式要玩好,还需要后台配置支持.因为我们的应用是单个客户端应用,如果后台没有正确的配置,当用户直接访问时,就会返回404.所以呢,你要在服务端增加一个覆盖所有情况的的候选资源;如果url匹配不到任何静态资源,则应该返回同一个index.html页面.”

总结:History模式就是通过pushState()方法来对浏览器的浏览记录进行修改,来达到不用请求后端来渲染的效果.不过建议,实际项目还是使用history模式.

1
2
3
4
const router = new VueRouter({
  mode: 'history', //如果这里不写,路由默认为hash模式
  routes: [...]
})

history模式服务端配置(增添内容)

配置这个的原因是当你进入某个路由之后,再次刷新页面时(或者是浏览器直接输入某个路由路径时),当刷新页面,浏览器就会重新dns解析,tcp协议,这个时候会根据浏览器的url去服务器找对应资源,当然我们vue-router是为单页面服务的,对应的url在服务端是肯定没有静态资源的,就会出现404,当配置了以下url重写语句,注意是重写,不是重定向,不改变url的情况重写浏览器内容,重写到index.html,因为这个index.html使我们项目的入口,index.html里面会读取当时打包好的app.js,就可以读取到路由配置,以实现我们浏览器的url对应的路由页面。

hash模式不需要配置,因为浏览器会忽略#和?后面的参数

1
2
3
4
5
6
7
8
打包文件在根目录时,
location / {
try_files $uri $uri/ /index.html;
}
打包文件在非根目录时,
location /admin {
try_files $uri $uri/ /admin/index.html;
}

动态路由匹配

目的:如何将多路径映射至同一组件?如何在该组件下监听路径的变化?

本质:**动态路由匹配本质上就是通过url进行传参**

** **路由对象属性:

  • $route.path 类型: string 字符串,对应当前路由的路径,总是解析为绝对路径,如 “/foo/bar”。
  • $route.params 类型: Object 一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
  • $route.query 类型: Object 一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。
  • $route .name 当前路由的名称。这里建议最好给每个路由对象命名,方便以后编程式导航.同时name必须唯一!
  • $route.hash 类型: string 当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
  • $route.fullPath 类型: string 完成解析后的 URL,包含查询参数和 hash 的完整路径。
  • $route.matched 类型: Array 一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。 $route.redirectedFrom 如果存在重定向,即为重定向来源的路由的名字。

params 配置路由

1
2
3
4
5
routes:[{
//动态路径参数,以冒号开头
path:'/user/:id',
component:User
}]

就是使用params进行配置.像/user/foo和/user/bar都将映射到相同的路由.

  • 一个路径参数使用’:’冒号进行标记.
  • 当匹配到一个路由时,参数就会被设置到this.$route.params,可以在每个组件内使用.例如/user/foo在this.$route.params.id就为foo

图片

举例

1
2
3
routes:[
{path:'/user/:shot/foo/:id', component:shotCat}
]
1
2
3
4
5
6
7
<p>
<router-link to="/user/shot/foo">/user/shot/foo</router-link>  <!--无法匹配到对应路由-->
<router-link to="/user/shot/cat/foo">/user/shot/cat/foo</router-link> <!--无法匹配到对应路由-->
<router-link to="/user/foo/foo/foo">/user/foo/foo/foo</router-link> <!--成功匹配,$route.params.shot为foo;$route.params.cat为foo;-->
<router-link to="/user/shot/foo/cat">/user/shot/foo/cat</router-link>
<!--成功匹配,$route.params.shot为shot;$route.params.cat为cat;-->
</p>

总结:

  • 同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序.谁先定义的,谁的优先级就最高.
  • 由于路由参数对组件实例是复用的.例如:/user/foo 和 /user/bar在使用路由参数时,复用的都是User组件.此时组件的生命周期钩子不会再被调用。如果你想路径切换时,进行一些初始化操作时,可以用以下两种解决办法:
    • 在组件内 watch $route 对象:
      1
      2
      3
      4
      5
      6
      7
      8
      const User = {
       template: '...',
       watch: {
         '$route' (to, from) {
           // 对路由变化作出响应...
         }
       }
      }
    • 使用beforeRouteUpdate 路由守卫
      1
      2
      3
      4
      5
      6
      7
      const User = {
        template: '...',
        beforeRouteUpdate (to, from, next) {
      // react to route changes...
      // don't forget to call next()
        }
      }
  • query进行配置传参*

在项目里我们可以通过上面提到的params进行传参.同时也可以用query进行传参. 比如: foo vue-router会自动将?后的id=foo封装进this.$route.query里. 此时,在组件里this.$route.query.id值为’foo’. 除了通过router-link的to属性. query也可以导航进行传参。

命名路由

目的:给路由起一个名字

如何使用?

可以通过一个名称来标识一个路由,特别是在链接一个路由,或者是执行一些跳转的时候。可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。

图片

图片

注意:**path 和 params 一同出现的时候,params 会被忽略掉,你需要提供路由的 name 或者提供完整的带有参数的 path,**此处需要注意。在使用编程式导航时,使用 this.$router.push() 改变当前路由也需要注意,path 和 params 是不能共存的:

1
2
3
4
5
const id = 123;
router.push({ name: 'user', params: { id } }); // -> /user/123
router.push({ path: `/user/${ id }` }); // -> /user/123
// 这里的 params 不生效,path 和 params 不能共存
router.push({ path: '/user', params: { id } }); // -> /user

声明式和编程式

什么是编程式导航

编程式导航就是在vue组件内部通过this.$router访问路由实例,完全用 js 去操作路由,进行路由映射,所以 它的作用是和是一样的!

编程式导航的使用场景

如果想在路由跳转前做点其他事情,例如权限验证等.但是用的话,就直接跳转了.此时就可以使用编程式导航!

编程式导航的写法

router.push() 方法

编程式导航用到router.push方法.该方法的参数可以是一个字符串路径,或者一个描述地址的对象.例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
//字符串
this.$router.push('home')


//对象
this.$ruter.push({path:'home'})


//命名路由
this.$router.push({name:'user',params:{userId:2333}})


//带查询参数,变成/register?plan=private

this.$router.push({path:’register’,query:{plan:’private’}})

注意: path和params是不能同时生效的!,否则params会被忽略掉.所以使用对象写法进行params传参时,要么就是path加冒号:,要么就是像上例中的’命名路由’.通过name和params进行传参.然而query却并不受影响,有没有path都可以进行传参.
*router.replace()方法 *

router.replace和router.push很像,写法一样.但实际效果不一样.push是向history里添加新记录.而replace是直接将当前浏览器history记录替换掉!

对比router.push() 和 router.replace() 的区别:

  • 用push方法,页面1跳转到页面2,你使用浏览器的后退可以回到页面1
  • 用replace方法,页面1被替换成页面2,你使用浏览器的后退,此时你回不到页面1,只能回到页面1的前一页,页面0.

router.replace() 使用场景

当你不想让用户回退到之前的页面时,常见于权限验证,验证后就不让用户回退到登录页重复验证.

router.go(n)方法

方法的参数就是一个整数,意思是在history记录中前进或后退多少步.类似window.history.go(n).这样就能控制页面前进或者后退多少步.

总结:

实际上不通过routes配置,也可以用下面这种方法直接在router-link上通过to进行传参. 关于to的更详细用法: 可以参考官方的api文档.

1
2
3
routes:[
{name:'shotCat',path:'/shotCat', component:shotCat}
]
1
2
3
4
5
6
<router-link :to="{ name:'shotCat',params:{paramId:'hello'},
query:{queryId:'world'}}">helloWorld</router-link>
<!--此时通过name匹配到路由对象shotCat.-->  
<router-link :to="{ path:'/shotCat',params:{paramId:'hello'},
query:{queryId:'world'}}">helloWorld</router-link> 
<!--此时通过path匹配到路由对象shotCat.但是!!!!!此时`paramId`并不能添加到`$route.params`里,只有`queryId`成功添加到`$route.query`-->

通过两个router-link.可以发现这种写法和编程式导航的规则一样, path和params是不能同时生效的! 所以最好给每个路由对象进行命名!
小结:

  • 等同于this.$router.push(). path和params是不能同时存在的!,想通过params,就得加上name属性.query不受影响.
  • 和this.$router.push()的实际效果也是一样的.
    • params参数都不会显示在url地址栏中.除了在路由中通过routes进行配置的.所以用户刷新页面后,params参数就会丢失!
    • query参数可以正常显示在url地址栏中.刷新页面后也不会丢失

通过to虽然可以进行params,query传参.但是注意此时页面url并不会改变!.所以你刷新页面后,params 参数就没有了,但是query的参数还在.

核心代码:如编程式导航.html 所示。

嵌套路由与命名视图

  • 嵌套路由:就是父路由嵌套子路由.url上就是/user嵌套两个子路由后就是/user/foo和/uer/bar.用一张图表示就是:
  • 图片

一个对应展示的就是一个组件 因此实现嵌套路由有两个要点:

  • 路由对象中定义子路由(嵌套子路由)
  • 组件内的使用.

路由对象定义子路由

图片

组件内 的使用

图片

图片

命名视图

  • 命名视图:就是一个组件里有多个视图进行展示.即包含有多个

如果一个组件有多个视图,来展示多个子组件.这个时候就需要用到命名视图 。

图片

重定向和别名

重定向其实就是通过路由拦截path,然后替换url跳转到redirect所指定的路由上. 重定向是通过 routes 配置来完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//从 /a 重定向到 /b
const router = new VueRouter({
routes:[
{path:'/a',rediret:'/b'}
]
})

///从 /a 重定向到 命名为'foo'的路由
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

//甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
  const { hash, params, query } = to
  //这里使用了ES6的解构写法,分别对应了to的hash模式,params,query参数.这里解构就不具体说明了.
        if (query.to === 'foo') {
          return { path: '/foo', query: null }
        }
        if (hash === '#baz') {
          return { name: 'baz', hash: '' }
        }
        if (params.id) {
          return '/with-params/:id'
        } else {
          return '/bar'
        }
    }}
  ]
})

别名

重定向是替url换路径,达到路由跳转.那别名就是一个路由有两个路径.两个路径都能跳转到该路由. 别名是在routes里的alias进行配置:

1
2
3
4
5
6
const router = new VueRouter({
//这时,路径'/fxxksky'和'/two-dogs' 都会跳转到A
  routes: [
    { path: '/fxxksky', component: A, alias: '/two-dogs' }
//当有多个别名时,alias也可以写成数组形式.  alias: ['/two-dogs', 'three-dogs','four-dogs','five-dogs'] 
  ]

})

路由组件传参
目的:通过传参将组件与路由解耦,使得组件的使用更加灵活。

路由传参,可以通过params和query进行传参.但这两种传参方式,本质上都是把参数放在url上,通过改变url进行的.这样就会造成参数和组件的高度耦合. 如果想传参的时候,可以更自由,摆脱url的束缚.这时就可以使用 route的props进行解耦,提高组件的复用,同时不改变url.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//路由配置:
const Hello = {
  props: ['name'], //使用route的props传参的时候,对应的组件一定要添加props进行接收,否则根本拿不到传参
  template: '<div>Hello {{ $route.params}}和{{this.name}}</div>'
  //如果this.name 有值,那么name已经成功成为组件的属性,传参成功
}
const router = new VueRouter({
mode: 'history',
  routes: [
    { path: '/', component: Hello }, // 没有传参  所以组件什么都拿不到
    { path: '/hello/:name', component: Hello, props: true }, //布尔模式: props 被设置为 true,此时route.params (即此处的name)将会被设置为组件属性。
    { path: '/static', component: Hello, props: { name: 'world' }},
// 对象模式: 此时就和params没什么关系了.此时的name将直接传给Hello组件.注意:此时的props需为静态!
    { path: '/dynamic/:years', component: Hello, props: dynamicPropsFn },
// 函数模式: 1,这个函数可以默认接受一个参数即当前路由对象.
// 2,这个函数返回的是一个对象.
// 3,在这个函数里你可以将静态值与路由相关值进行处理.
    { path: '/attrs', component: Hello, props: { name: 'attrs' }}
  ]
})

function dynamicPropsFn (route) {
  return {
    name: (new Date().getFullYear() + parseInt(route.params.years)) + '!'
  }
}
new Vue({
  router,
  el: '#app'
})
1
2
3
4
5
6
7
8
9
10
11
12
<!--html部分-->
    <div id="app">
      <h1>Route props</h1>
      <ul>
        <li><router-link to="/">/</router-link></li>
        <li><router-link to="/hello/you">/hello/you</router-link></li>
        <li><router-link to="/static">/static</router-link></li>
        <li><router-link to="/dynamic/1">/dynamic/1</router-link></li>
        <li><router-link to="/attrs">/attrs</router-link></li>
      </ul>
      <router-view></router-view>
    </div>

路由懒加载

利用 Vue 提供的异步组件以及 Webpack 提供的代码分割功能提高组件加载效率

vue主要用于单页面应用,此时webpack会打包大量文件,这样就会造成首页需要加载资源过多,首屏时间过长,给用户一种不太友好的体验. 如果使用路由懒加载,仅在你路由跳转的时候才加载相关页面.这样首页加载的东西少了,首屏时间也减少了. vueRouter的懒加载主要是靠Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。 比较简单的写法只需要将组件以promise形式引入即可.

1
2
3
4
5
6
routes:[
      path:'/',
      name:'HelloWorld',
      component:resolve=>require(['@/component/HelloWorld'],resolve)
  ]
  //此时HelloWorld组件则不需要在第一步import进来

把组件分块
把组件按组分块可以把路由下的所有组件都打包在同个异步块 (chunk) 中,并且在devtools 的network里面看到动态加载的组件名字.

前提条件:

  • Webpack版本 > 2.4
  • 需要在webpack.base.conf.js里面的output里面的filename下面加上chunkFileName
    1
    2
    3
    4
    5
    6
    7
    8
    9
    output: {
     path: config.build.assetsRoot,
     filename: '[name].js',
     // 需要配置的地方
     chunkFilename: '[name].js',
     publicPath: process.env.NODE_ENV === 'production'
       ? config.build.assetsPublicPath
       : config.dev.assetsPublicPath
    }
    此时在引入组件时的写法需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name
    1
    2
    3
    const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
    const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
    const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

    导航守卫

    路由导航守卫,通俗点说就是路由钩子.作用也和生命周期钩子类似,在路由跳转过程进行操作控制. 导航守卫有很多钩子,

导航守卫分类

  • 全局守卫:异步执行,每个路由跳转都会按顺序执行.
    • router.beforeEach 全局前置守卫
    • router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用.
    • router.afterEach *全局后置钩子 进入路由之后 *注意:不支持next(),只能写成这种形式router.afterEach((to, from) => {});

每个守卫方法接收三个参数:

to: Route: 即将要进入的目标 路由对象

from: Route: 当前导航正要离开的路由对象

  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next(‘/‘) 或者 next({ path: ‘/‘ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      //1,可以在main.js 或者在单独的路由配置文件router.js中进行设置
      router.beforeEach((to, from, next) => { 
      ...
            next();
          });

      //2,也可以在组件内部设置
      this.$router.beforeEach((to, from, next) => { 
      ...
            next();
          });

      //3,对函数及next()的详细使用说明
          router.beforeEach((to, from, next) => { 
      //首先to和from 其实是一个路由对象,所以路由对象的属性都是可以获取到
      //例如:我想获取获取to的完整路径就是to.path.
      //获取to的子路由to.matched[0].
            next();//使用时,千万不能漏写next!!!
      //next()  表示直接进入下一个钩子.
      //next(false)  中断当前导航
      //next('/path路径')或者对象形式next({path:'/path路径'})  跳转到path路由地址
      //next({path:'/shotcat',name:'shotCat',replace:true,query:{logoin:true}...})  这种对象的写法,可以往里面添加很多.router-link 的 to prop 和 router.push 中的选项(具体可以查看api的官方文档)全都是可以添加进去的,再说明下,replace:true表示替换当前路由地址,常用于权限判断后的路由修改.
      //next(error)的用法,(需2.4.0+) 
          }).catch(()=>{
        //跳转失败页面
        next({ path: '/error', replace: true, query: { back: false }})
      })
      //如果你想跳转报错后,再回调做点其他的可以使用 router.onError()
      router.onError(callback => { 
            console.log('出错了!', callback);
          });
  • 路由独享的守卫: 即路由对象独享的守卫
    • beforeEnter:路由只独享这一个钩子,在routes里配置
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const router = new VueRouter({
        routes: [
          {
            path: '/foo',
            component: Foo,
            beforeEnter: (to, from, next) => {
              // 使用方法和上面的beforeEach一样
            }
          }
        ]
      })
  • 组件内的守卫: 注意:这类路由钩子是写在组件内部的,
    • beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到this
    • beforeRouteUpdate (2.2) 路由复用同一个组件时
    • *beforeRouteLeave *离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      //在组件内部进行配置,这里的函数用法也是和beforeEach一样
      const Foo = {
        template: `...`,
        beforeRouteEnter (to, from, next) {
          // 在渲染该组件的对应路由被 confirm 前调用
          // 不!能!获取组件实例 `this`
          // 因为当守卫执行前,组件实例还没被创建
        },
        beforeRouteUpdate (to, from, next) {
          // 在当前路由改变,但是该组件被复用时调用
          // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
          // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
          // 可以访问组件实例 `this`
        },
        beforeRouteLeave (to, from, next) {
          // 导航离开该组件的对应路由时调用
          // 可以访问组件实例 `this`
        }
      }

      路由元信息

  • 什么是路由元信息*

一句话概括:路由配置的meta对象里的信息.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

可以看出就是给路由添加了一个自定义的meta对象,并在里面设置了一个requiresAuth状态为true.
它有什么用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requireAuth)) {
//数组some方法,如果meta.requiresAuth为ture,则返回true.此时,说明进入该路由前需要判断用户是否已经登录 
    if (!auth.loggedIn()) {   //如果没登录,则跳转到登录页
      next({
        path: '/login',
        query: { redirect: to.fullPath } 
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

我们可以通过在meta里设置的状态,来判断是否需要进行登录验证.如果meta里的requiresAuth为true,则需要判断是否已经登录,没登录就跳转到登录页.如果已登录则继续跳转.path,params,query都可以存储信息,作为登录验证的状态标记.的确,它们也可以达到同样的效果.如果是少量单个的验证,使用它们问题不大. 但如果是多个路由都需要进行登录验证呢?path,params,query是把信息显性地存储在url上的.并且多个路径都把一个相同的状态信息加在url上.这样就使url不再单纯,并且也很不优雅美观. 所以要优雅要隐性地传递信息,就使用meta对象吧!

滚动行为

当切换路由时,可以使页面滚动到你想要的某个地方,或者是保持之前滚动的位置,这时你就需要使用scrollBehavior这个方法.

注意点:

  • 这里控制和记住的滚动位置都是仅对整个组件页面而言的,并不包含你组件里面其他的滚动条.
  • 这里路由的模式只能是history.因为它使用了History新增的pushState()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const router = new VueRouter({
    mode:'history',//这个不能忘,默认是hash模式
      routes: [...],
      scrollBehavior (to, from, savedPosition) {
         // to:要进入的目标路由对象,到哪里去.和导航守卫的beforeEach一样
    //from:离开的路由对象,哪里来
    //savedPosition: 点击前进/后退的时候记录值{x:?,y:?}.并且只有通过浏览器的前进后退才会触发.
        // return 期望滚动到哪个的位置 { x: number, y: number }或者是{ selector: string, offset? : { x: number, y: number }},这里selector接收字符串形式的hash,如'#foo',同时你还可以通过offset设置偏移,版本需要大于2.6+
    //举个实例
    if(savePosition) { //如果是浏览器的前进后退就,返回之前保存的位置
          return savePosition;
        }else if(to.hash) {//如果存在hash,就滚动到hash所在位置
          return {selector: to.hash}
        }else{
      return {x:0,y:0}//否则就滚动到顶部
    }
      }
    })

    数据获取

    数据可以在导航完成前获取或者导航完成后获取,以给予用户不同的用户体验。

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

  • 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
  • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

从技术角度讲,两种方式都不错,就看你想要的用户体验是哪种。

过渡动画

给路由切换时添加过渡效果。

是基本的动态组件,所以我们可以用 组件给它添加一些过渡效果,如果你想让每个路由组件有各自的过渡效果,可以在各路由组件内使用 并设置不同的 name

vue router 工具

图片

图片

模块化开发

  • 如果使用 vue 开发移动端app ,那么页面跳转就非常多,路由使用的非常非常多,这样就更能体现出路由的强大之处。

  • 分析

    Vue Router 采坑记

  • query方式传参和接收参数

    • 传参
      1
      2
      3
      4
      5
      this.$router.push({
      path:'/xxx',
      query:{
      id:id
      }
    • 接收参数
      1
      this.$route.query.id
  • 备注:传参是this.$router,接收参数是this.$route*

  • params方式传参和接收参数

    • 传参
      1
      2
      3
      4
      5
      6
      this.$router.push({
      name:'xxx',
      params:{
      id:id
      }
      })
    • 接收参数
      1
      this.$route.params.id
  • 备注: params传参,push里面只能是 name:’xxxx’,不能是path:’/xxx’,因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined。*
    query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示。

  • this.$router 和this.$route的区别

    • $route为当前router跳转对象,里面可以获取name、path、query、params等。
    • $router为VueRouter实例,想要导航到不同URL,则使用$router.push方法。
  • name 是什么?有什么用?

    • 注册时为该条路由取的名字
    • 重定向
      • 在重定向的时候直接用name 代表 path 进行跳转,是一种快捷语法。
        1
        redirect:{name:'about'} //name为注册路由时对应路由所取的名字,为上面的快捷用法
    • linkTo
      • 在router-link上也可使用注册路由时路由所取的name对href进行设置,点击link时会自动跳转到该name路由的path
        1
        <router-link :to="{name:'about'}" tag="li">work</router-link>
    • 一路多图
      • 是一个路由对应多个视图时对除了路由默认视图的视图进行标注,以便引入对应的路由。在路由切换时,一个路由可以对应多个视图router-view,这时候我们就需要区分不同的 router-view ,所以就需要取个名字。
        1
        <router-view name="addV"></router-view> <router-view></router-view> //----------------------------------------------------- ,{ path: '/document', name: 'Document', // component: components:{ //一个路由对应多个视图 default:main, //default为关键字 ,没有取名的路由视图,必须的。main为引入的组件名 addV:other //addV为附加的router-view上取的名字,other为引入的组件所取的名字 } }
  • 如何更改 link 的标签类型

    • 直接在ink标签上利用tag属性进行修改
      1
      <router-link :to="index" exact tag="li" class="class1" event="mouseover">
  • 如何更改激活样式的类名

    1
    <router-link to="/about" active-class="diy">about</router-link>
  • 路由变化,页面数据不刷新

    • 原因
      • 出现这种情况是因为依赖路由的params参数获取写在created生命周期里面,因为相同路由二次甚至多次加载的关系 没有达到监听,退出页面再进入另一个文章页面并不会运行created组件生命周期,导致文章数据还是第一次进入的数据
    • 解决方案
      • watch监听路由是否变化
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
         watch: {
        // 方法1
        '$route' (to, from) { //监听路由是否变化
        if(this.$route.params.articleId){// 判断条件1 判断传递值的变化
        //获取文章数据
        }
        }
        //方法2
        '$route'(to, from) {
        if (to.path == "/page") { /// 判断条件2 监听路由名 监听你从什么路由跳转过来的
        this.message = this.$route.query.msg
        }
        }
        }
    • 使用beforeRouteUpdate 守卫:
      1
      2
      3
      4
      5
      6
      7
      const User = {
      template: '...',
      beforeRouteUpdate (to, from, next) {
      // react to route changes...
      // don't forget to call next()
      }
      }
  • 按需加载

一般配合 Vue-Router 使用,适用于大型应用,将应用分割成小的代码块,只在需要的时候才从服务器加载。

  • 实现方式:
    • 异步组件实现
    • es6 import
  • 好处:
    • 按需加载,节省首次加载实践,提高速度,也算是一个性能优化;
    • 组件只会加载一次,加载完成后会缓存下来,使用一个组件多次使用的场景
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      // 异步组件实现
      export default new Router({
      routes: [
      {
      path: '/test',
      name: 'test',
      component: resolve => require(['../components/Test'], resolve)
      },
      ]
      })
      // ES6 import
      const Test1 = () => import('../components/Test1')
      const Test2 = () => import('../components/Test2')
      export default new Router({
      routes: [
      {
      path: '/test1',
      name: 'test1',
      component: Test1
      },
      {
      path: '/test2',
      name: 'test2',
      component: Test2
      }
      ]
      })
    • 路由的懒加载
      1
      2
      3
      4
      // 方式一
      component: () => import("@/views/Login.vue")
      // 方式二
      component: resolve => require(["@/views/Register.vue"], resolve)

面试总结与学习(上)

发表于 2020-04-28
字数统计: 4.7k 字 | 阅读时长 ≈ 17 分钟
  1. http 请求的方式, HEAD 方式
    head: 类似于 get 请求,只不过返回的响应中没有具体的内容,用户获取报头。
    options: 允许客户端查看服务器的性能,比如说服务器支持的请求方式。
  2. web Quality(无障碍)
    能够被残障人士使用的网站才能称得上一个易用(易访问)的网站,比如:使用 alt 属性
    1
    <img src="person.jpg"  alt="this is a person"/>
    • 有时候浏览器无法显示图像,具体的原因有:
    • 用户关闭了图像显示
    • 浏览器是不支持图形显示的迷你浏览器
    • 浏览器是语音浏览器,如果您使用了alt 属性,那么浏览器至少可以显示或读出有关图像的描述。
  3. 对 HTML 语义化标签的理解
    HTML5 语义化标签是指正确的标签包含了正确的内容,结构良好,便于阅读,比如 nav 表示导航条,类似的还有 article,header,footer等标签。
  4. iframe 是什么?有什么缺点?
    定义:iframe 元素会创建另一个文档的内联框架
    提示: 可以将提示文字放在<iframe> </iframe> 之间,来提示某些不支持 iframe 的浏览器。
    缺点:回阻塞主页面的 onload 事件;搜索引擎无法解读这种页面,不利于 SEO;iframe 和主页面共享连接池,而浏览器对相同区域有限制所以影响性能。
  5. Cookie 和 Session 的区别
    http 是一个无状态协议,因此 Cookie 的最大作用就是存储 sessionid用来唯一标识用户。
  6. click 在 ios 上有 300ms 延迟,原因及如何解决?
  • 暴力解决 - 禁用缩放
    1
    <meta name="viewport" content="width=device-width, user-scalable=no">
  • 利用 FastClick
    检测到 touchend 事件后,like触发模拟 click 事件,并且把浏览器 300ms 之后真正触发的事件给阻塞掉。
  1. Cookie, sessionStroage, localStroage 的区别
    共同点:都是保存在浏览器端,并且都是同源的。
    Cookie: cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器和服务器间来回传递。而 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。cookie 数据还有路径(path)的概念,可以限制 cookie 只属于某个路径下,存储的大小很小只有 4k 左右(key:可以在浏览器和服务器来回传递,储存容量小,只有 4k 左右)。
    sessionStorage: 仅在当浏览器窗口关闭前有效,自然也就不可能持久保持。
    localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据; cookie 只在设置的 cookie 过期时间之前一直有效,及时窗口或浏览器关闭。(key: 本身就是一个回话过程,关闭浏览器后消失,session 为一个回话,当页面不同即使是同一页面打开两次,也被视为同一次回话)。
    localStorage: localStorage 在所有同源窗口都是共享的; cookie 也是在所有同源窗口共享的。(key: 同源窗口都会共享,并且不会失效,不管窗口或者浏览器关闭与否始终生效)。
    cookie的作用:
    保存用户登录状态。例如将用户 ID 存储在一个 cookie 内,这样当用户下次访问该页面时就不需要重新登录了,现在很多论坛和社区都提供这样的功能。 cookie 还可以设置过期时间,当超过过期时间后,cookie 就会自动消失,因此, 系统往往可以提示用户保持登录状态的时间。
    跟踪用户行为。例如一个天气预报网站,能够根据用户选择的地区显示当地的天气情况。如果每次需要选择所在地是繁琐的,当利用了 cookie 后就会显得很人性化了,系统能够用记住上一次访问的地区,当下次再打开你该页面时,他就会自动显示上次用户所在地区的天气情况。因为一切都是在后台完成,所以这样的页面就像为没够用户所定制的一样,使用起来非常方便定制页面。如果网站提供了换肤或者更换布局的功能,name可以使用 cookie 来记录用户的选项,例如:背景色,分辨率等,当用户下次访问时,仍然可以保存上一次访问的界面风格。
  2. 讲讲 304
    304:如果客户端发送了一个带条件的 GET 请求且该请求被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个 304 状态码。
  3. 前端优化
    降低请求量:合并资源,减少 http 请求数, minify/gzip 压缩, webP, lazyLoad.
    加快请求速度:预解析 DNS, 减少域名数, 并行加载, CDN 分发。
    缓存:http 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage。
    渲染:JS/CSS 优化,加载顺序,服务端渲染, pipeline。
  4. GET 和 POST 的区别
    • get参数通过url传递,post放在request body中。
    • get请求在url中传递的参数是有长度限制的,而post没有。
    • get比post更不安全,因为参数直接暴露在url中,所以不能用来传递敏感信息。
    • get请求只能进行url编码,而post支持多种编码方式
    • get请求会浏览器主动cache,而post支持多种编码方式。
    • get请求参数会被完整保留在浏览历史记录里,而post中的参数不会被保留。
    • GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
    • GET产生一个TCP数据包;POST产生两个TCP数据包。
  5. HTTP 支持的方法

    GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE, CONNECT

  6. 画一个三角形
    1
    2
    3
    4
    5
    6
    7
    8
    div {
    width:0px;
    height:0px;
    border-top:10px solid red;
    border-right:10px solid transparent;
    border-bottom:10px solid transparent;
    border-left:10px solid transparent;
    }
  7. HTML5新增的元素
    首先 HTML5 为了更好的实践 web 语义化,增加了 header, footer, nav, aside,section等语义化标签,在表单方面,为了增强表单, 为 input 提供了 color,email, data, range 等类型,在存储方面, 提供了 sessionStorage, localStorage 和离线存储,通过这些村存储方式方便数据在客户端的存储和获取,在多媒体方面规定了音频和视频元素 audio 和 vedio, 另外还有地理定位, canvas 画布, 拖放,多线程编程的 web worker 和 websocket 协议。
  8. 在地址栏里输入一个URL,到这个页面呈现出来,中间会发生什么?
    输入url后,首先需要找到这个url域名的服务器ip,为了寻找这个ip,浏览器首先会寻找缓存,查看缓存中是否有记录,缓存的查找记录为:浏览器缓存 –> 系统缓存 –> 路由器缓存,缓存中没有则查找系统的hosts文件中是否有记录,如果没有则查询DNS服务器,得到服务器的ip地址后,浏览器根据这个ip以及相应的端口号,构造一个http请求,这个请求报文会包括这次请求的信息,主要是请求方法,请求说明和请求附带的数据,并将这个http请求封装在一个tcp包中,这个tcp包会依次经过传输层,网络层,数据链路层,物理层到达服务器,服务器解析这个请求来作出响应,返回相应的html给浏览器,因为html是一个树形结构,浏览器根据这个html来构建DOM树,在dom树的构建过程中如果遇到JS脚本和外部JS连接,则会停止构建DOM树来执行和下载相应的代码,这会造成阻塞,这就是为什么推荐JS代码应该放在html代码的后面,之后根据外部央视,内部央视,内联样式构建一个CSS对象模型树CSSOM树,构建完成后和DOM树合并为渲染树,这里主要做的是排除非视觉节点,比如script,meta标签和排除display为none的节点,之后进行布局,布局主要是确定各个元素的位置和尺寸,之后是渲染页面,因为html文件中会含有图片,视频,音频等资源,在解析DOM的过程中,遇到这些都会进行并行下载,浏览器对每个域的并行下载数量有一定的限制,一般是4-6个,当然在这些所有的请求中我们还需要关注的就是缓存,缓存一般通过Cache-Control、Last-Modify、Expires等首部字段控制。 Cache-Control和Expires的区别在于Cache-Control使用相对时间,Expires使用的是基于服务器 端的绝对时间,因为存在时差问题,一般采用Cache-Control,在请求这些有设置了缓存的数据时,会先 查看是否过期,如果没有过期则直接使用本地缓存,过期则请求并在服务器校验文件是否修改,如果上一次 响应设置了ETag值会在这次请求的时候作为If-None-Match的值交给服务器校验,如果一致,继续校验 Last-Modified,没有设置ETag则直接验证Last-Modified,再决定是否返回304.
    总结:DNS解析; TCP连接; 发送HTTP请求; 服务器处理请求并返回HTTP报文; 浏览器解析渲染页面; 连接结。
  9. cache-control的值有哪些
    cache-control是一个通用消息头字段被用于HTTP请求和响应中,通过指定指令来实现缓存机制,这个缓存指令是单向的,常见的取值有private、no-cache、max-age、must-revalidate等,默认为private。
  10. 浏览器在生成页面的时候,会生成那两颗树?
    构造两棵树,DOM树和CSSOM规则树。当浏览器接收到服务器相应来的HTML文档后,会遍历文档节点,生成DOM树; CSSOM规则树由浏览器解析CSS文件生成。
  11. 怎么看网站的性能如何
    检测页面加载时间一般有两种方式:
    被动去测:就是在被检测的页面置入脚本或探针,当用户访问网页时,探针自动采集数据并传回数据库进行分析;
    主动监测的方式,即主动的搭建分布式受控环境,模拟用户发起页面访问请求,主动采集性能数据并分析,在检测的精准度上,专业的第三方工具效果更佳,比如说性能极客.
  12. 200和304的区别
  • 200 OK 请求成功。一般用于GET与POST请求
  • 304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源。
  1. HTML5和CSS3用的多吗?你了解它们的新属性吗?有在项目中用过吗?
    HTML5:
    • 标签增删:
      8个语义元素 header section footer aside nav main article figure。
      新的表单控件:calander date time email url search
      新的 input 类型:color data datetime datetime-local email
      移除过时的标签:big font frame frameset
  • canvas 绘图:支持内联 SVG, 支持 MATHML
  • 多媒体 audio video source embed track
  • 本地离线存储,把需要离线存储在本地的文件爱你列在一个 manifest 配置文件
  • web 存储 localStorage sessionStorage
    CSS3
    边框: border-radius,box-shadow 等;
    背景: background-size,background-origin
    2D, 3D 转换: transform
    动画:animation
  1. 画一条 0.5px 的线
  • 采用 meta viewport 方式
    1
    <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  • 采用border-image的方式
  • 采用transform: scale()的方式
  1. 垂直居中的方法
    margin: auto; 定位为上下左右为0,margin:0可以实现脱离文档流的居中.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <div>
    <img src="mm.jpg">
    </div>
    div {
    position: relative;
    width: 400px;
    height: 400px;

    border: 1px solid #465468;
    }
    img {
    position: absolute;
    margin: auto;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    }

    margin 负值法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    .container{
    position: relative;
    width: 500px;
    height: 400px;
    border: 2px solid #379;
    }
    .inner{
    position: absolute;
    top: 50%;
    left: 50%;

    width: 480px;
    height: 380px;
    background-color: #746;

    tabel-cell(未脱离文档流):设置父元素的display:table-cell,并且vertical-align:middle,这样子元素可以实现垂直居中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    div{
    width: 300px;
    height: 300px;
    border: 3px solid #555;
    display: table-cell;
    vertical-align: middle;
    text-align: center;
    }
    img{
    vertical-align: middle;
    }

    flex

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    .container{
    width: 300px;
    height: 200px;
    border: 3px solid #546461;
    display: -webkit-flex;
    display: flex;
    -webkit-align-items: center;
    align-items: center;
    -webkit-justify-content: center;
    justify-content: center;
    }
    .inner{
    border: 3px solid #458761;
    padding: 20px;
    }
  2. 说一下块元素和行元素
    块元素:独占一行,并且有自动填满父元素,可以设置margin和pading以及高度和宽度
    行元素:不会独占一行,width和height会失效,并且在垂直方向的padding和margin会失效

  3. 多行元素的文本省略号

    1
    2
    3
    4
    5
    6
    div {
    display: -webkit-box
    -webkit-box-orient:vertical
    -webkit-line-clamp:3
    overflow:hidden
    }
  4. visibility=hidden, opacity=0,display:none 区别
    opacity=0,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定一些事件,如click事件,那么点击该区域,也能触发点击事件的。
    visibility=hidden,该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已经绑定的事件。
    display=none,把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素删除掉一样。

  5. 介绍一下盒模型
    CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。
    标准盒模型:一个块的总宽度=width+margin(左右)+padding(左右)+border(左右)
    怪异盒模型:一个块的总宽度=width+margin(左右)(既width已经包含了padding和border值)
    设置盒模型:box-sizing:border-box

  6. position相关属性
    固定定位fixed:元素的位置相对于浏览器窗口是固定位置,即使窗口是滚动的它也不会移动。Fixed定位使元素的位置与文档流无关,因此不占据空间。 Fixed定位的元素和其他元素重叠。
    相对定位relative:如果对一个元素进行相对定位,它将出现在它所在的位置上。然后,可以通过设置垂直或水平位置,让这个元素“相对于”它的起点进行移动。 在使用相对定位时,无论是否进行移动,元素仍然占据原来的空间。因此,移动元素会导致它覆盖其它框。
    绝对定位absolute:绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那么它的位置相对于。 absolute 定位使元素的位置与文档流无关,因此不占据空间。 absolute 定位的元素和其他元素重叠。
    粘性定位sticky:元素先按照普通文档流定位,然后相对于该元素在流中的flow root(BFC)和 containing block(最近的块级祖先元素)定位。而后,元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。
    默认定位Static:默认值。没有定位,元素出现在正常的流中(忽略top, bottom, left, right 或者 z-index 声明)。
    inherit:规定应该从父元素继承position 属性的值。

  7. 如何实现图片在某个容器中居中的?

  • 父元素固定宽高,利用定位及设置子元素margin值为自身的一半。
  • 父元素固定宽高,子元素设置position: absolute,margin:auto平均分配margin
  • css3属性transform。子元素设置position: absolute; left: 50%; top: 50%;transform: translate(-50%,-50%);即可。
  • 将父元素设置成display: table, 子元素设置为单元格 display: table-cell。
  • 弹性布局display: flex。设置align-items: center; justify-content: center
  1. 如何实现元素的垂直居中
  • 父元素display:flex,align-items:center;
  • 元素绝对定位,top:50%,margin-top:-(高度/2)
  • 高度不确定用transform:translateY(-50%)
  • 父元素table布局,子元素设置vertical-align:center;
  1. 三栏布局的实现方式,尽可能多写,浮动布局时,三个div的生成顺序有没有影响,
    三列布局又分为两种,两列定宽一列自适应,以及两侧定宽中间自适应
    两列定宽一列自适应:
    float+margin:给div设置float:left,left的div添加属性margin-right:left和center的间隔px,right的div添加属性margin-left:left和center的宽度之和加上间隔
    float+overflow:给div设置float:left,再给right的div设置overflow:hidden。这样子两个盒子浮动,另一个盒子触发bfc达到自适应
    position:父级div设置position:relative,三个子级div设置position:absolute,这个要计算好盒子的宽度和间隔去设置位置,兼容性比较好,
    table实现:父级div设置display:table,设置border-spacing:10px//设置间距,取值随意,子级div设置display:table-cell,这种方法兼容性好,适用于高度宽度未知的情况,但是margin失效,设计间隔比较麻烦,
    flex实现:parent的div设置display:flex;left和center的div设置margin-right;然后right 的div设置flex:1;这样子right自适应,但是flex的兼容性不好
    grid实现:parent的div设置display:grid,设置grid-template-columns属性,固定第一列第二列宽度,第三列auto.

对于两侧定宽中间自适应的布局,对于这种布局需要把center放在前面,可以采用双飞翼布局:圣杯布局,来实现,也可以使用上述方法中的grid,table,flex,position实现。
30. 有一个width300,height300,怎么实现在屏幕上垂直水平居中
对于行内块级元素,

  • 父级元素设置text-alig:center,然后设置line-height和vertical-align使其垂直居中,最后设置font-size:0消除近似居中的bug
  • 父级元素设置display:table-cell,vertical-align:middle达到水平垂直居中
  • 采用绝对定位,原理是子绝父相,父元素设置position:relative,子元素设置position:absolute,然后通过transform或margin组合使用达到垂直居中效果,设置top:50%,left:50%,transform:translate(-50%,-50%)
  • 绝对居中,原理是当top,bottom为0时,margin-top&bottom设置auto的话会无限延伸沾满空间并平分,当left,right为0时,margin-left&right设置auto会无限延伸占满空间并平分,
  • 采用flex,父元素设置display:flex,子元素设置margin:auto
  • 视窗居中,vh为视口单位,50vh即是视口高度的50/100,设置margin:50vh auto 0,transform:translate(-50%)。
  1. 设置一个元素的背景颜色,背景颜色会填充哪些区域?
    background-color设置的背景颜色会填充元素的content、padding、border区域

字符串常用方法总结

发表于 2020-04-19
字数统计: 537 字 | 阅读时长 ≈ 2 分钟
1
2
3
4
5
let str = 'summertzzisagoodgirl';
//循环输出字符串中的每一个字符
for (let i = 0; i < str.length; i++) {
console.log(str[i])
}

charAt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* charAt:根据索引获取指定位置的字符
* charCodeAt:获取指定字符的ASCII码值(Unicode编码值)
* @params
* n [number] 获取字符指定的索引
* @return
* 返回查找到的字符
* 找不到返回的是空字符串不是undefined,或者对应的编码值
*/
let str = 'summertzzisagoodgirl';
console.log(str.charAt(0)); // s
console.log(str[0]); // s
console.log(str.charAt(10000)); // undefined
console.log(str.charCodeAt(0)); //115
console.log(String.fromCharCode(122)); // z

substr / substring / slice

1
2
3
4
5
6
/*
* 都是为了实现字符串的截取(在原来字符串中查找到自己想要的)
* substr(n,m):从索引n开始截取m个字符,m不写截取到末尾(后面方法也是)
* substring(n,m):从索引n开始找到索引为m处(不含m)
* slice(n,m):和substring一样,都是找到索引为m处,但是slice可以支持负数作为索引,其余两个方法是不可以的
*/

indexOf / lastIndexof / includes

1
2
3
4
5
6
/*
* 验证字符是否存在
* indexOf(x,y):获取x第一次出现位置的索引,y是控制查找的起始位置索引
* lastIndexOf(x):最后一次出现位置的索引
* =>没有这个字符,返回的结果是-1
*/

toUpperCase / toLowerCase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* 字符串中字母的大小写转换
* toUpperCase():转大写
* toLowerCase():转小写
*/
let str = 'SummerTzz';
str = str.toUpperCase();
console.log(str); //=>'SUMMERTZZ'

str = str.toLowerCase();
console.log(str); //=>'summer'

// 实现首字母大写
str = str.substr(0, 1).toUpperCase() + str.substr(1);
console.log(str); //=>'Summertzz'

split

1
2
3
4
5
6
7
8
9
/*
* split([分隔符]):把字符串按照指定的分隔符拆分成数组(和数组中join对应)
* split支持传递正则表达式
*/
// 需求:把|分隔符变为,分隔符
let str = 'music|movie|eat|sport';
let ary = str.split('|'); //=>["music", "movie", "eat", "sport"]
str = ary.join(',');
console.log(str); //=>"music,movie,eat,sport"

replace

1
2
3
4
5
6
7
8
9
/*
* replace(老字符,新字符):实现字符串的替换(经常伴随着正则而用)
*/
let str = 'summer@tzz@lsbbd';
// str = str.replace('@', '-');
// console.log(str); //=>"summer-tzz@lsbbd" 在不使用正则表达式的情况下,执行一次REPLACE只能替换一次字符

str = str.replace(/@/g, '-');
console.log(str); //=>summer-tzz-lsbbd

数组常用方法的总结

发表于 2020-04-19
字数统计: 2.5k 字 | 阅读时长 ≈ 11 分钟

数组及数组中常用的方法

数组是对象数据类型的,它属于特殊的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const arr = [12, 23, 34, 45];
console.log(typeof arr); //=>"object"
console.dir(arr);
/*
* arr = {
* 0:12,
* 1:23,
* 2:34,
* 3:45,
* length:4
* }
*
* 数字作为索引(KEY 属性名)
* length代表长度
*
* arr[0] 根据索引获取指定项的内容
* arr.length 获取数组的长度
* arr.length-1 最后一项的索引
*/
数组中常用的方法
  • 方法的作用和含义
  • 方法的实参(类型和含义)
  • 方法的返回值
  • 原来的数组是否会发生改变

1.实现数组增删改的方法

这一部分方法都会修改原有的数组

push

1
2
3
4
5
6
7
8
9
10
11
12
/*
* push : 向数组末尾增加内容
* @params
* 多个任意类型
* @return
* 新增后数组的长度
*/
let arr = [10, 20];
let res = arr.push(30, 'AA');
// 基于原生JS操作键值对的方法,也可以向末尾追加一项新的内容
arr[arr.length] = 40;
console.log(res, arr); //=>4 [10,20,30,'AA',40]

unshift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* unshift : 向数组开始位置增加内容
* @params
* 多个任意类型
* @return
* 新增后数组的长度
*/
let arr = [10, 20];
let res = arr.unshift(30, 'AA');
console.log(res, arr); //=>4 [30,'AA',10,20]

// 基于ES6展开运算符,把原有的arr拷贝一份,在新的数组中创建第一项,其余的内容使用原始arr中的信息即可,也算实现了向开始追加的效果
arr = [100, ...arr];
console.log(arr); //=>[100,30,'AA',10,20]

shift

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* shift : 删除数组中的第一项
* @params
* @return
* 删除的那一项
*/
let arr = [10, 20, 30, 40];
let res = arr.shift();
console.log(res, arr); //=>10 [20, 30, 40]

// 基于原生JS中的DELETE,把数组当做普通的对象,确实可以删除掉某一项内容,但是不会影响数组本身的结构特点(length长度不会跟着修改),真实项目中杜绝这样的删除使用
delete arr[0];
console.log(arr); //=>{1:30,2:40,length:3}

pop

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* pop : 删除数组中的最后一项
* @params
* @return
* 删除的那一项
*/
let arr = [10, 20, 30, 40];
let res = arr.pop();
console.log(res, arr); //=>40 [10,20,30]

// 基于原生JS让数组数组长度减少一位,默认减少的就是最后一项
arr.length--; //=>arr.length = arr.length - 1;
console.log(arr);

splice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* splice : 实现数组的增加、删除、修改
* @params
* n,m 都是数字 从索引n开始删除m个元素(m不写,是删除到末尾)
* @return
* 把删除的部分用新数组存储起来返回
*/
let arr = [10, 20, 30, 40, 50, 60, 70, 80, 90];
let res = arr.splice(2, 4);
console.log(res, arr); //=>[30, 40, 50, 60] [10, 20, 70, 80, 90]

// 基于这种方法可以清空一个数组,把原始数组中的内容以新数组存储起来(有点类似数组的克隆:把原来数组克隆一份一模一样的给新数组)
/* res = arr.splice(0);
console.log(res, arr);//=>[10, 20, 70, 80, 90] [] */

// 删除最后一项和第一项
arr.splice(arr.length - 1);
arr.splice(0, 1);
console.log(arr);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* splice : 实现数组的增加、修改
* @params
* n,m,x 从索引n开始删除m个元素,用x占用删除的部分
* n,0,x 从索引n开始,一个都不删,把x放到索引n的前面
* @return
* 把删除的部分用新数组存储起来返回
*/
let arr = [10, 20, 30, 40, 50];
let res = arr.splice(1, 2, 'summertzz', 'lsbbd');
console.log(res, arr); //=> [20,30] [10,'summertzz','lsbbd', 40, 50]

// 实现增加
arr.splice(3, 0, '呵呵呵');
console.log(arr); //=>[10, "summertzz", "lsbbd", "呵呵呵", 40, 50]

// 向数组末尾追加
arr.splice(arr.length, 0, 'AAA');

// 向数组开始追加
arr.splice(0, 0, 'BBB');

2.数组的查询和拼接

此组学习的方法,原来数组不会改变

slice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* slice : 实现数组的查询
* @params
* n,m 都是数字 从索引n开始,找到索引为m的地方(不包含m这一项)
* @return
* 把找到的内容以一个新数组的形式返回
*/
let arr = [10, 20, 30, 40, 50];
let res = arr.slice(1, 3);
console.log(res); //=>[20,30]

// m不写是找到末尾
res = arr.slice(1);
console.log(res); //=>[20, 30, 40, 50]

// 数组的克隆,参数0不写也可以
res = arr.slice(0);
console.log(res); //=>[10, 20, 30, 40, 50]

concat

1
2
3
4
5
6
7
8
9
10
11
/*
* concat : 实现数组拼接
* @params
* 多个任意类型值
* @return
* 拼接后的新数组(原来数组不变)
*/
let arr1 = [10, 20, 30];
let arr2 = [40, 50, 60];
let res = arr1.concat('summer', arr2);
console.log(res); // [10, 20, 30, 'summer', 40, 50, 60]

3.把数组转换为字符串

原有数组不变

toString

1
2
3
4
5
6
7
8
9
10
11
/*
* toString : 把数组转换为字符串
* @params
* @return
* 转换后的字符串,每一项用逗号分隔(原来数组不变)
*/
let arr = [10, 20, 30];
let res = arr.toString();
console.log(res); //=>"10,20,30"
console.log([].toString()); //=>""
console.log([12].toString()); //=>"12"

join

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* join : 把数组转换为字符串
* @params
* 指定的分隔符(字符串格式)
* @return
* 转换后的字符串(原来数组不变)
*/
let arr = [10, 20, 30];
let res = arr.join('');
console.log(res); //=>"102030"

res = arr.join(',');
console.log(res); //=>"10,20,30"

res = arr.join('|');
console.log(res); //=>"10|20|30"

res = arr.join('+');
console.log(res); //=>"10+20+30"
console.log(eval(res)); //=>60 eval把字符串变为JS表达式执行

4.检测数组中的是否包含某一项

indexOf / lastIndexOf / includes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* indexOf / lastIndexOf : 检测当前项在数组中第一次或者最后一次出现位置的索引值(在IE6~8中不兼容)
* @params
* 要检索的这一项内容
* @return
* 这一项出现的位置索引值(数字),如果数组中没有这一项,返回的结果是-1
* 原来数组不变
*/
let arr = [10, 20, 30, 10, 20, 30];
console.log(arr.indexOf(20)); //=>1
console.log(arr.lastIndexOf(20)); //=>4

// 想验证arr中是否包含'summertzz'
if (arr.indexOf('summertzz') === -1) {
// 不包含
}
// 也可以直接使用ES6新提供的includes方法判断
if (arr.includes('summertzz')) {
// 包含:如果存在返回的是TRUE
}

5.数组的排序或者排列

reverse

1
2
3
4
5
6
7
8
9
10
/*
* reverse : 把数组倒过来排列
* @params
* @return
* 排列后的新数组
* 原来数组改变
*/
let arr = [12, 15, 9, 28, 10, 22];
arr.reverse();
console.log(arr); //=>[22, 10, 28, 9, 15, 12]

sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* sort : 实现数组的排序
* @params
* 可以没有,也可以是个函数
* @return
* 排序后的新数组
* 原来数组改变
*/
let arr = [7, 8, 5, 2, 4, 6, 9];
arr.sort();
console.log(arr); //=>[2, 4, 5, 6, 7, 8, 9]

// SORT方法中如果不传递参数,是无法处理10以上数字排序的(它默认按照每一项第一个字符来排,不是我们想要的效果)
/* arr = [12, 15, 9, 28, 10, 22];
arr.sort();
console.log(arr); //=> [10, 12, 15, 22, 28, 9] */

// 想要实现多位数正常排序,需要给SORT传递一个函数,函数中返回 a-b 实现升序,返回 b-a 实现降序
arr = [12, 15, 9, 28, 10, 22];
arr.sort((a, b) => a - b);
console.log(arr);

6.遍历数组中每一项的方法

forEach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* forEach:遍历数组中的每一项内容
* @params
* 回调函数
* @return
* 原来数组不变
*/
let arr = [12, 15, 9, 28, 10, 22];

/* // 基于原生JS中的循环可以实现
for (let i = 0; i < arr.length; i++) {
// i:当前循环这一项的索引
// arr[i]:根据索引获取循环的这一项
console.log('索引:' + i + ' 内容:' + arr[i]);
} */

arr.forEach((item, index) => {
// 数组中有多少项,函数就会被默认执行多少次
// 每一次执行函数:item是数组中当前要操作的这一项,index是当前项的索引
console.log('索引:' + index + ' 内容:' + item);
});

数组去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// 基于ES6的Set(对应的Map)实现去重
let arr = [12, 23, 12, 15, 25, 23, 25, 14, 16];
arr = [...new Set(arr)];
// [12, 23,15, 25, 14, 16];

// 利用for嵌套,然后splice去重(ES5中最常用)
// 双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
function uniqueArr(arr) {
for(let i = 0; i < arr.length; i++) {
for(let j = i + 1; j < arr.length; j++) {
if(arr[i] == arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}

uniqueArr([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {}, {}] //NaN和{}没有去重,两个null直接消失了

// 利用indexOf去重
// 新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组
function uniqueArr(arr) {
if(!Array.isArray(arr)) {
return;
}
let arr1 = [];
for(let i = 0; i < arr.length; i++) {
if(arr1.indexOf(arr[i]) === -1){
arr1.push(arr[i]);
}
}
return arr1;
}

uniqueArr([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重

// 利用sort()
// 利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。
function uniqueArr(arr) {
if(!Array.isArray(arr)) {
return;
}
arr = arr.sort();
let arr1 = [arr[0]];
for(let i = 0; i < arr.length; i++) {
if(arr[i] !== arr[i -1]) {
arr1.push(arr[i]);
}
}
return arr1;
}
uniqueArr([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重

// 利用includes
function uniqueArr(arr) {
if(!Array.isArray(arr)) {
return;
}
let arr1 = [];
for(let i = 0; i < arr.length; i++) {
if(!arr1.includes(arr[i])) {
arr1.push(arr[i]);
}
}
return arr1;
}
uniqueArr([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
// [1, "true", true, 15, false, undefined, null, NaN, 0, "a", {…}, {…}] // {}没有去重

// 利用filter
function uniqueArr(arr) {
return arr.filter((item, index, arr)=> {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
})
}
uniqueArr([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
// [1, "true", true, 15, false, undefined, null, NaN, 0, "a", {…}, {…}] // {}没有去重

Math 常用方法的总结

发表于 2020-04-19
字数统计: 553 字 | 阅读时长 ≈ 2 分钟

Math 的常用方法

一、Math

数学函数:但是它不是一个函数,它是一个对象,对象中存储了很多操作数字的属性方法,因此被称为数学函数

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(typeof Math); //=>"object"
console.dir(Math);
/*
* Math = {
* PI:3.141592653589793,
* abs:function(){[native code]},
* ceil:function(){[native code]},
* ...
* }
*
* Math.abs();
* Math.PI;
*/
Math中常用的属性和方法
  1. Math.abs([number value])

获取绝对值(绝对值永远是正数或者零)

1
2
3
4
5
6
7
console.log(Math.abs(-12.5)); //=>12.5
console.log(Math.abs(12)); //=>12
console.log(Math.abs(0)); //=>0
// 传递的不是数字类型的值:先基于Number()转换为数字再处理
console.log(Math.abs('-1')); //=>1
console.log(Math.abs('-1px')); //=>NaN
console.log(Math.abs(true)); //=>1
  1. Math.ceil / floor([number value])

把一个数向上取整 / 向下取整

1
2
3
4
5
6
7
8
9
10
11
console.log(Math.ceil(12)); //=>12
console.log(Math.ceil(12.1)); //=>13
console.log(Math.ceil(12.9)); //=>13
console.log(Math.ceil(-12.1)); //=>-12
console.log(Math.ceil(-12.9)); //=>-12

console.log(Math.floor(12)); //=>12
console.log(Math.floor(12.1)); //=>12
console.log(Math.floor(12.9)); //=>12
console.log(Math.floor(-12.1)); //=>-13
console.log(Math.floor(-12.9)); //=>-13
  1. Math.round()

四舍五入

1
2
3
4
5
6
7
console.log(Math.round(12));  //=>12
console.log(Math.round(12.1)); //=>12
console.log(Math.round(12.5)); //=>13 正数中.5属于入
console.log(Math.round(12.9)); //=>13
console.log(Math.round(-12.1)); //=>-12
console.log(Math.round(-12.5)); //=>-12 负数中.5属于舍
console.log(Math.round(-12.9)); //=>-13
  1. Math.max / min ([val1],[val2],…)

获取一堆数中的最大值和最小值

1
2
3
4
5
console.log(Math.max(12, 5, 68, 23, 45, 3, 27)); //=>68
console.log(Math.min(12, 5, 68, 23, 45, 3, 27)); //=>3

//基于Math.max/min获取数组中的最大值最小值
Math.max([12, 5, 68, 23, 45, 3, 27]); //=>NaN 此处是只传第一个值,是个数组,和内置的语法要求不符
  1. Math.sqrt / pow()

sqrt:给一个数开平方

pow:计算一个数的多少次幂

1
2
3
console.log(Math.sqrt(9)); //=>3  符合N*N=M 这样的M才能整开平方
console.log(Math.sqrt(-9)); //=>NaN 负数开不了平方
console.log(Math.pow(2, 10)); //=>1024
  1. Math.random()

获取0~1之间的随机小数

1
2
3
4
5
6
7
8
9
for (let i = 1; i <= 10; i++) {
console.log(Math.random());
}
/*
* 0.09453770227521763
* 0.06700581113042259
* 0.10092020814995206
* ...
*/

获取 [n~m] 之间的随机整数

包含n也包含m,其中 n<m

1
Math.round(Math.random()*(m-n)+n)

axios 的简单封装

发表于 2020-04-17
字数统计: 396 字 | 阅读时长 ≈ 1 分钟

尝试对 axios 进行简单的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import axios from 'axios';

switch (process.env.NODE_ENV) {
case 'production':
axios.defaults.baseURL = 'http://api.balabala.cn';
break;
case 'test':
axios.defaults.baseURL = 'http://192.168.1.1:8080';
break;
default:
axios.defaults.baseURL = 'http://127.0.0.1:3000';
}

/**
* 设置超时时间和跨域是否允许携带凭证
*/
axios.defaults.timeout = 10000;
axios.defaults.withCredentials = true;

/**
* 设置请求传递的数据格式(需要看服务器要求什么格式)
* 比如:x-www-form-urlencoded
*/

axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = paramData => qs.stringify(paramData);

/**
* 设置请求拦截器
* 客户端发送请求 -> [请求拦截器] -> 服务器
* token 校验(JWT): 接收服务器返回的 token,存储到 vuex /本地存储。每一次向服务器发请求,应该把 token 带上
*/
axios.interceptors.request.use((config) => {
// 携带 token
let token = localStorage.getItem('token');
token && (config.headers.Authorization = token);
return config;
}, error => {
return Promise.reject(error);
})

/**
* 设置响应拦截器
* 服务器返回信息 -> [拦截的统一处理] -> 客户端获取到的信息
*/

// axios.defaults.validateStatus = status => {
// // 自定义响应成功的 http 状态码
// return /^(2|3)\d{2}$/.test(status);
// };

axios.interceptors.response.use(response => {
return response.data;
}, error => {
let {
response
} = error;
if (response) {
// 服务器返回结果了
switch (response.status) {
case 401: // 权限
break;
case 403: // 服务器拒绝执行(token 过期)
break;
case 404: // 找不到页面
break;
case 401: // 权限
break;
}
} else {
// 服务器没返回结果
// 1.客服端断网了
if (!window.navigator.onLine) {
// 断网处理,跳转到断网页面
return;
}
// 2.服务器崩了
return Promise.reject(error);
}
});

export default axios;

小小小小技巧

发表于 2020-04-17
字数统计: 1.1k 字 | 阅读时长 ≈ 4 分钟
  1. 修改 svg 图片的属性(宽, 高, 颜色)
    用IDE 打开目标 svg 图片,修改对象代码的 width 属性, height属性, fill 属性即可。

  2. vue 项目的全局样式文件直接在 main.js 页面使用即可, 比如:import './assets/styles/global.scss';。

  3. 热键的绑定, 如果用了封装组件的话, 比如 Element UI, 在使用按键修饰符要加上 .native,
    比如:<el-form @keyup.enter.native="handleLogin('loginForm'))"> </el-form>。

  4. 记住密码功能前端简单的实现方法: 用 cookie 来做, 大概思路就是通过存/取/删 cookie 来实现, 每次进入登录页,先读取 cookie , 如果浏览器中的 cookie 里面有帐号的相关信息,就自动填充到登录框中,存 cookie 是在登录成功之后,判断当前用户是否勾选了记住密码,如果勾选了,则把帐号信息存到 cookie 中。这里可以用 js-cookie 这个库。 主要方法有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 引入 js-cookie
    import Cookies from 'js-cookie';
    // 获取用户存入的 cookie,初始化登录表单
    this.loginForm = Cookies.get();
    // 登录成功后,存 cookie
    Cookies.set('loginName', that.loginForm.loginName, {
    expires: 7,
    path: '/',
    });
  5. 去除 chrome 浏览器记住密码后默认填充的样式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* 去掉浏览器默认填充样式 */
    input:-webkit-autofill,
    input:-webkit-autofill:hover,
    input:-webkit-autofill:focus {
    font-size: 22px;
    color: $white !important;

    -webkit-text-fill-color: $white;
    -webkit-transition: background-color 10000s ease-in-out 0s;
    transition: background-color 10000s ease-in-out 0s;
    }
  6. document.documentElement.clientWidth与document.documentElement.clientHeight, window.innerWidth与window.innerHeight, window.outerWidth与window.outerHeight, document.body.clientWidth与document.body.clientHeight, offsetWidth 与 offsetHeight 的区别。

  • document.documentElement.clientWidth与document.documentElement.clientHeight:获得的是屏幕可视区域的宽高,不包括滚动条与工具条
    1
    2
    document.documentElement.clientWidth = width + padding
    document.documentElement.clientHeight = height + padding
  • window.innerWidth与window.innerHeight:获得的是可视区域的宽高,但是window.innerWidth宽度包含了纵向滚动条的宽度,window.innerHeight高度包含了横向滚动条的高度(IE8以及低版本浏览器不支持)。
    1
    2
    window.innerWidth = width + padding + border + 纵向滚动条宽度
    window.innerHeight = height + padding + border + 横向滚动条高度
  • window.outerWidth与window.outerHeight:获得的是加上工具条与滚动条窗口的宽度与高度。
    1
    2
    window.outerWidth = width + padding + border + 纵向滚动条宽度
    window.outerHeight = height + padding + border + 横向滚动条高度 + 工具条高度
  • document.body.clientWidth与document.body.clientHeight:document.body.clientWidth获得的也是可视区域的宽度,但是document.body.clientHeight获得的是body内容的高度,如果内容只有100px,那么这个高度也是 100px,如果想通过它得到屏幕可视区域的宽高,需要样式设置,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    body {
    height: 100%;
    overflow: hidden;
    }
    body, div, p, ul {
    margin: 0;
    padding: 0;
    }
  • offsetWidth 与 offsetHeight 返回本身的宽高 + padding + border + 滚动条。
  1. 路由的设置应该与要修改的东西解耦,换句话说,就是如果存在一些属性是可以修改的,不是设计在路由参数里面去。
  2. 拉平多维数组常用的方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    const arr = [1, 2, 3, [11, 22, 33, [4, 5, [99, 100]]]]
    // 只适用数组内全部是数字的情况
    arr.join(',').split(',')
    // ['1', '2', '3', '11', '22', '33', '4', '5', '99', '100']

    // 只适用数组内全部是数字的情况
    arr.toString().split(',')
    // ['1', '2', '3', '11', '22', '33', '4', '5', '99', '100']

    // 使用 flat() 拉平数组过程中,会移除数组的空项:
    arr.flat()
    // ['1', '2', '3', '11', '22', '33', '4', '5', '99', '100']

    // 递归 1
    function flatArr() {
    let res = [];
    for(let i = 0; i < arr.length; i++) {
    if(Array.isArray(arr[i])) {
    res = res.concat(flatArr(arr[i]));
    }else {
    res.push(arr[i]);
    }
    }
    return res;
    }
    // ['1', '2', '3', '11', '22', '33', '4', '5', '99', '100']

    // 递归 2
    function flatArr(arr) {
    for(let i = 0; i < arr.length; i++) {
    if(arr[i] instanceof Array) {
    parseArr(arr[i], res);
    } else {
    res.push(arr[i])
    }
    }
    }
    // es6 的扩展运算符
    使用 es6 的扩展运算符,但是扩展运算符一次只能展开一层数组,因此只要数组中海油数组,就使用扩展运算符展开一次。
    function flatArr(arr) {
    while(arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr);
    }
    return arr;
    }
    flatArr([1, 2, 3, [11, 22, 33, [4, 5, [99, 100]]]]) //[1, 2, 3, 11, 22, 33, 4, 5, 99, 100]

    // 利用 reduce 递归降纬
    // 判断每一项是不是数组。如果是,则进一步递归,直到其不是为止。如果不是,则用新数组接收它。
    // reduce
    const flatArr = (arr) => arr.reduce((prev, curr, index, list) => {
    if (Array.isArray(curr)) {
    return prev.concat(...flatArr(curr));
    }
    return prev.concat(curr);
    }, []);

    flatArr(arr);
    //[1, 2, 3, 11, 22, 33, 4, 5, 99, 100]

我的第一篇博客

发表于 2020-03-31
字数统计: 414 字 | 阅读时长 ≈ 1 分钟

人的一生,会走很远的路,遇到很多的人,做很多不一样的事,人与人的邂逅,人与风景的邂逅,造就了不同的经历,或幸福,或遗憾。我很幸运地能够也有机会感知这一切的情愫,我很幸福能够遇见今天的我。没上大学之前,在别人眼里,我一直是个比较文艺的女孩,多愁善感情绪的我,总是喜欢写点东西,一段感性的文字,一首顺畅的打油诗,一个自以为很了不起的想法,我都会习惯性的拿出自己的笔记本来记录此刻的情绪,那时候的我,觉得整个世界都装在我那温暖的笔记本里。谁曾想到,大学里我选择了计算机科学专业,不一样节奏的生活习惯我开始远离了当初那个我觉得很温暖的世界,我学会了怀念,最后变成了些许遗憾吧。我想定格每一个有意义的时刻,亦苦亦甜,我想把这些回忆永恒地锁在我的世界里,待青丝染成了暮雪,能够成为与那一位跟我共度余生的人的饭后余资。我想跟他慢慢讲述这一生的故事。直到现在,才拥有了自己的博客,一直未了的心愿,终于在今天要开始动工啦,我很开心,也很期待。我想对现在的自己说:如果爱,请深爱,珍惜当下,把握现在,学会享受学习的乐趣,学会做一个有趣的人。

summertzz

summertzz

如果爱,请深爱

8 日志
GitHub
Links
  • lsbbd
© 2020 summertzz
由 Hexo 强力驱动
|
主题 — NexT. v5.1.4
博客全站共20.4k字
访问人数 访问总量 次