前端面试总结(vue篇)

vue的优点

vue是个轻量级的框架,是一个构建数据的视图集合,大小只有几十Kb
vue是组件化开发,适合多人开发
vue中的双向数据绑定更方便操作表单数据
因为vue是MVVM的框架,视图,数据,结构分离使数据的更改更为简单
vuex可以管理所有组件用到的数据,不需要借助之前props在组件间传值
官方文档通俗易懂,易于理解和学习;

vue的核心

 数据驱动和组件化。

mvvm框架的理解和相对mvc的优点

理解

model是数据模型,管理数据和处理业务逻辑
view是视图,负责显示数据
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,负责监听Model中数据的改变并且控制视图的更新
model中的数据改变,view也会跟着改变;用户通过交互操作改变view中的数据,model也会跟着改变。其中的dom操作viewmodel帮我们完成了,不需要自己操作dom

优点

mvvm 主要解决了 mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
当 Model 频繁发生变化,开发者需要主动更新到 View

Vue中双向数据绑定是如何实现的

当data 有变化的时候它通过Object.defineProperty()方法中的set方法进行监控,并调用在此之前已经定义好data 和view关系的回调函数,来通知view进行数据的改变
而view 发生改变则是通过底层的input 事件来进行data的响应更改

vue组件封装的过程

首先,使用Vue.extend()创建一个组件
然后,使用Vue.component()方法注册组件
接着,如果子组件需要数据,可以在props中接受定义
最后,子组件修改好数据之后,想把数据传递给父组件,可以使用emit()方法

vue父子组件之间传递数据

1-1、父组件向子组件传递数据

通过props,例子如下

  1. 父组件 在引用子组件时,通过属性绑定(v-bind:)的形式,把需要传递给子组件的数据,传递到子组件内部,供子组件使用
  2. 在 props数组 中定义父组件传递过来的数据
  3. 在该子组件中使用props数组 中定义好的数据




1-2、父组件调用子组件的方法

父:

子:
method: {
  test() {
     alert(1)
  }
}
在父组件里调用test即 this.$refs.childMethod.test()

2-1、子组件向父组件传递方法

  1. 父组件在组件上定义了一个自定义事件,用于接受子组件传过来的值
  2. 在子组件中定义一个方法,利用 $emit 触发 父组件传递过来的事件把值传给父组件




2-2 、子组件调用父组件的方法

直接在子组件中通过this.$parent.event来调用父组件的方法

兄弟组件之间传值

bus方式
1.新建bus.js

import Vue from 'vue'
export default  new Vue

2.在需要传值和接受值的vue文件中,各自引入bus.js

import bus from '../util/bus'

3.定义传值的方法,使用bus.$emit(‘methodName’,data), methodName是自定义的方法名


methods: {
    trans(){
      bus.$emit('test',this.helloData)
    }
  },

4.在要接收值的组件里,使用bus.on(‘methodName’,val =>{ }) ,val 就是传过来的值

 mounted(){
    bus.$on('test',val=>{
      console.log(val);
      this.cdata = val
    })
  }

vuex方式

1、安装vuex :cnpm install vuex --save
2、创建一个 vuex 文件夹,并在里面新建一个 store.js 写入以下代码:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

3、state 定义数据:state在vuex中用于存储数据

var state={ //存放数据,数据中心
    count:1,
    // 其他数据格式:orderList: [],
    // 其他数据格式:params: {}
}

4、 getters 类似计算属性:

var getters= {
    computedCount: (state) => {
        return state.count*2
    }
}  

5、mutations里面放的是方法,方法主要用于改变state里面的数据

var mutations={
    incCount(){
    ++state.count;
    }
} 

6、异步操作,Action 提交的是 mutation,而不是直接变更状态

var actions= {
  incMutationsCount(context) {    /*因此你可以调用 context.commit 提交一个 mutation*/
    context.commit('incCount');    /*执行 mutations 里面的incCount方法 改变state里面的数据*/
    //此处按照实际情况扩展~
  }
}

7、暴露参数

const store = new Vuex.Store({
  state,
  mutations,
  getters,
  actions
})

export default store;

8、 组件里去使用 Vuex:

(1). 获取state里面的数据

this.$store.state.数据

(2). 获取 getters里面方法返回的的数据 (一般vue 和 store 进行交互 用 $store.getters, getters的值放在计算属性里,动态绑定在计算属性computed里)

  this.$store.getters.computedCount

(3). 触发 mutations 改变 state里面的数据

this.$store.commit('incCount');

(4). 触发 actions里面的方法

 this.$store.dispatch('incMutationsCount'); 
  //这个 incMutationsCount 会再去 执行 mutations 里面的incCount方法

vuex与本地存储的区别

1.区别:vuex存储在内存,localstorage(本地存储)则以文件的方式存储在本地,永久保存;
localStorage和sessionStorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理

2.应用场景:vuex用于组件之间的传值,localstorage,sessionstorage则主要用于不同页面之间的传值。

3.永久性:当刷新页面(这里的刷新页面指的是 --> F5刷新,属于清除内存了)时vuex存储的值会丢失,sessionstorage页面关闭后就清除掉了,localstorage不会。

vue页面级组件之间传值

1.使用vue-router通过跳转链接带参数传参。

2.使用本地缓存localStorge。

3.使用vuex数据管理传值。

Vue-Router路由钩子函数(导航守卫)

路由钩子函数有三种:

    1:全局钩子: beforeEach、 afterEach

    2:单个路由里面的钩子:  beforeEnter

    3: 组件路由:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave
  1. 全局前置守卫:beforeEach
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
  // ...
})
to:路由将要跳转的路径信息,信息是包含在对像里边的。
from:路径跳转前的路径信息,也是一个对象的形式。
next:路由的控制参数,常用的有next(true)和next(false)。

  1. 组件路由
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`
  }
}

beforeRouteEnter 钩子 不能 访问 this,因为钩子在导航确认前被调用,因此即将登场的新组件还没被创建。不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

beforeRouteLeave使用场景

1、清除当前组件中的定时器,当一个组件中有一个定时器时, 在路由进行切换的时候, 可使用beforeRouteLeave将定时器进行清楚, 以免占用内存:

beforeRouteLeave (to, from, next) {
 window.clearInterval(this.timer) //清除定时器
 next()
}

2、当页面中有未关闭的窗口, 或未保存的内容时, 阻止页面跳转如果页面内有重要的信息需要用户保存后才能进行跳转, 或者有弹出框的情况. 应该阻止用户跳转

beforeRouteLeave (to, from, next) {
 //判断是否弹出框的状态和保存信息与否
 if (this.dialogVisibility === true) {
  this.dialogVisibility = false //关闭弹出框
  next(false) //回到当前页面, 阻止页面跳转
 }else if(this.saveMessage === false) {
  //弹出警告
  next(false) //回到当前页面, 阻止页面跳转
 }else {
  next() //否则允许跳转
 }
}

vue路由权限控制实现

实现路由权限控制的两种方式

配置静态的路由表,比如登录、注册页,其他路由通过动态注入
登录的时候过滤后台返回的路由列表,拿到符合路由规则的路由表
通过 addRoutes() 这个方法把路由给注入到路由表,这样就可以访问已注入的路由了

Vue项目中实现用户登录及token验证

1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码

2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token

3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面

4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面

5、每次调后端接口,都要在请求头中加token

6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401

7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
  if (to.path === '/login') {
    next();
  } else {
    let token = localStorage.getItem('Authorization');
 
    if (token === 'null' || token === '') {
      next('/login');
    } else {
      next();
    }
  }
});
// 添加请求拦截器,在请求头中加token
axios.interceptors.request.use(
  config => {
    if (localStorage.getItem('Authorization')) {
      config.headers.Authorization = localStorage.getItem('Authorization');
    }
 
    return config;
  },
  error => {
    return Promise.reject(error);
  });
//如果前端拿到状态码为401,就清除token信息并跳转到登录页面
      localStorage.removeItem('Authorization');
      this.$router.push('/login');

说说微信公众号项目授权登录过程

调微信的授权登录地址,成功之后微信会重定向回到我们开发的页面并返回code在回调的url中
拿到code以后,传给后台,让后台去获取用户信息再传给前端。我们拿到用户信息后,比如openId,头像等,可以用localStorage缓存起来

v-show 和 v-if指令的不同点

v-show 本质就是通过控制 css 中的 display 设置为 none,控制隐藏,只会编译一次;v-if 是动态的向 DOM 树内添加或者删除 DOM 元素,若初始值为 false ,就不会编译了。而且 v-if 不停的销毁和创建比较消耗性能。

总结:如果要频繁切换某节点,使用 v-show (切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用 v-if(初始渲染开销较小,切换开销比较大)

说出几种vue当中的指令和它的用法

v-model 双向数据绑定;

v-for 循环;

v-if v-show 显示与隐藏;

v-on 事件;v-once : 只绑定一次。

vue的生命周期

概念:Vue 实例从创建到销毁的过程

生命周期钩子的一些使用方法:

beforecreate : 可以在这加个loading事件,在加载实例时触发 
created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用 
mounted : 挂载元素,获取到DOM节点 
updated : 如果对数据统一处理,在这里写上相应函数 
beforeDestroy : 可以做一个确认停止事件的确认框 nextTick : 更新数据后立即操作dom

vue父子组件生命周期执行顺序

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

created和mounted的区别

created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

计算属性computed和watch的区别

计算属性和侦听属性都可以实现当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,但在特定场景下的处理还是有一些微妙的区别

data: {
    firstName: 'Liu',
    lastName: 'lu'
  },
  computed: {
  fullName:{
   get(){//回调函数 当需要读取当前属性值时执行,根据相关数据计算并返回当前属性的值
      return this.firstName + ' ' + this.lastName
    },
   set(val){//监视当前属性值的变化,当属性值发生变化时执行,更新相关的属性数据
       //val就是fullName的最新属性值
       console.log(val)
        const names = val.split(' ');
        console.log(names)
        this.firstName = names[0];
        this.lastName = names[1];
   }
   }
  }
    watch: {
        pagination: {
            handler (val) {
                this.paginationInfo = val
            },
            immediate: true,
            deep: true
        }
    }

computed常用于值的计算,如简化tempalte里面{{}}计算和处理props或$emit的传值,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数。
watch常用来观察动作,听props,$emit或本组件的值执行异步操作,页面重新渲染时值不变化也会执行
computed名称不能与data里对象重复,只能用同步,必须有return,是多个值变化引起一个值变化,多多对一
watch名称必须与data里对象一样,可以用于异步,没有return,是一对多,监听一个值,一个值变化引起多个值变化

1、watch中的函数名称必须是所依赖data中的属性名称
2、watch中的函数是不需要调用的,只要函数所依赖的属性发生了改变 那么相对应的函数就会执行
3、watch中的函数会有2个参数 一个是新值,一个是旧值
4、watch默认情况下无法监听对象的改变,如果需要进行监听则需要进行深度监听 深度监听需要配置handler函数以及deep为true。(因为它只会监听对象的地址是否发生了改变,而值是不会监听的)
5、watch默认情况下第一次的时候不会去做监听,如果需要在第一次加载的时候也需要去做监听的话需要设置immediate:true
6、watch在特殊情况下是无法监听到数组的变化
- 通过下标来更改数组中的数据
- 通过length来改变数组的长度

v-for和v-if优先级问题与解决方法

原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
不推荐:

  
  • {{ user.name }}

推荐:

computed: {
activeUsers: function () {
return this.users.filter(function (user) {
  return user.isActive
})
}
}



  • {{ user.name }}

keep-alive内置组件的作用

可以让当前组件或者路由不经历创建和销毁,而是进行缓存,凡是被keep-alive组件包裹的组件,除了第一次以外。不会经历创建和销毁阶段的。第一次创建后就会缓存到缓存当中

初次进入时:created > mounted > activated;退出后触发 deactivated
再次进入:会触发 activated;事件挂载的方法等,只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated 中

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。

routers: [{
  path: '/',
  name: 'Home',
  meta: {
    keepAlive: false // 不需要缓存
  }
},{
  path: '/page',
  name: 'Page',
  meta: {
    keepAlive: true  // 需要缓存
  }
},]

$route和 $router的区别是什么?

$router为VueRouter的实例,是一个全局路由对象,包含了路由跳转的方法、钩子函数等。

$route 是路由信息对象||跳转的路由对象,每一个路由都会有一个route对象,是一个局部对象,包含path,params,hash,query,fullPath,matched,name等路由信息参数

vuex知识点总结

  • vuex的理解
    vuex是一个专为vue.js应用程序开发的状态管理模式(它采用集中式存贮管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化)
  • vuex的作用
    由于传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致代码无法维护。所以我们需要把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。
  • vuex核心属性
    state,getter,mutation,action,module

state:存储数据,存储状态;在根实例中注册了store 后,用 this.$store.state 来访问;对应vue里面的data;存放数据方式为响应式,vue组件从store中读取数据,如数据发生变化,组件也会对应的更新。

组件访问State中数据的两种方式
1、this.$store.state.全局数据名称
2、从vuex中按需导入mapState函数,将当前组件需要的全局数据映射为当前组件的computed计算属性

import { mapState } from 'vuex'
computed:{
...mapState( ['count'] ) 
}

getter:可以认为是 store 的计算属性,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

const store = new Vuex.store({
   state:{
      count : 0
  },
   getters:{
      countAfter: state=>{
        return count*2
      }
   }
})
//使用getters的第一种方式
this.$store.getters.countAfter

mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

const store = new Vuex.store({
    state:{
      count : 0
  },
  mutation:{
      add(state){
       //变更状态
      state.count++
      }
   }
})
nethods:{
     handle(){
       //触发mutation的第一种方式
          this.$store.commit('add')
}
//1、从vuex中按需导入mapMutation函数
import { mapMutation } from 'vuex'
//2、将指定的mutation函数,映射为当前组件的methods函数
methods:{
   ...mapMutation(['add','addN'])
   handle(){
      //触发mutation的第二种方式
      this.add()
   }
}

action:包含任意异步操作,通过提交 mutation 间接更变状态。

const store = new Vuex.store({
mutation:{
  add(state){
     state.count++
  }
},
actions:{
  addAsync(context){
  //在actions中不能直接修改state中的数据
  //必须通过context.commit()触发某个mutation才行
    setTimeout(()=>{
       context.commit('add')
    },1000)
  }
}

})
//触发Action
methods:{
  handle(){
    //触发actions的第一种方式
    this.$store.dispatch('addAsync')
  }
}
//1、从vuex中按需导入mapActions函数
import { mapActions} from 'vuex'
//2、将指定的mapAtions函数,映射为当前组件的methods方法
methods:{
   ...mapMutation(['addAsync','addNAsync'])
   handle(){
      //触发mutation的第二种方式
      this.addAsync()
   }
}

module:将 store 分割成模块,每个模块都具有state、mutation、action、getter、甚至是嵌套子模块。

vue中的修饰符

事件修饰符

  1. stop:阻止冒泡(通俗讲就是阻止事件向上级DOM元素传递)
  2. prevent:阻止默认事件的发生(默认事件指对DOM的操作会引起自动执行的动作,比如点击超链接的时候会进行页面的跳转,点击表单提交按钮时会重新加载页面等,使用".prevent"修饰符可以阻止这些事件的发生)
  3. capture:捕获冒泡,即有冒泡发生时,有该修饰符的dom元素会先执行,如果有多个,从外到内依次执行,然后再按自然顺序执行触发的事件
  4. self:将事件绑定到自身,只有自身才能触发,通常用于避免冒泡事件的影响
  5. native:在父组件中给子组件绑定一个原生的事件,就将子组件变成了普通的HTML标签,不加’. native’事件是无法触 发的。

表单元素修饰符

  1. trim能够去除输入内容左右两边的空格

  1. lazy只有标签的change事件执行后才会执行数据的双向绑定

  1. .number 修饰符, 可以将用户输入的值,转换成Number类型。

less和sass的区别

首先,sass和less都是css的预编译处理语言,他们引入了mixins,参数,嵌套规则,运算,颜色,名字空间,作用域,JavaScript赋值等 加快了css开发效率,当然这两者都可以配合gulp和grunt等前端构建工具使用,但是他们两者有什么不同呢?

1.编译环境不同

less是通过js编译 是在客户端处理

sass同通过ruby 是在服务器端处理

2.变量符不一样

less是用@,sass是用$

3.sass支持条件语句,可以使用if{}else{},for{}循环等等。而less不支持。

常用的ES6新语法

let const

var 存在变量提升而let和const不存在变量提升
var存在变量覆盖,而let和const在同级作用域中不能重复定义
var声明的变量会挂载到window上,会放在全局,let 和 const声明的变量不会
let和const声明的变量会形成块级作用域,var不会
const有一个很好的应用场景,当我们引用第三方库的时声明的变量,用const来声明可以避免未来不小心重命名而导致出现bug
console.log(a);//undefined

var a = "hey I am now hoisting";
console.log(a);//Uncaught ReferenceError: a is not defined

let a = "hey I am now hoisting";
    var a="show";
    function hah(){
        alert(a);//undefined
        var a=4;
        alert(a);//4
    }
    hah();
function hah(number){
        var a="show";
        while(number!=0){
            alert(a);//show
            var a=4;
            alert(a);//4
            number--;
        }
    }
   hah(1);   

模板字符串

$("#result").append(`
 There are ${basket.count} items
  in your basket, ${basket.onSale}
 are on sale!
`);

箭头函数

function(x, y) {
   x++;
   y--;
   return x + y;
}

(x, y) => {x++; y--; return x+y}

字符串方法

// 3.新增字符串方法
let str = "hello.vue";
// 开头
console.log(str.startsWith("hello"));//true
// 后缀
console.log(str.endsWith(".vue"));//true
// 包含
console.log(str.includes("e"));//true
console.log(str.includes("hello"));//true

当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this

普通函数中的this总是代表它的直接调用者,在默认情况下,this指的是window

node.js相关

  • 概念
    Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境
  • npm
    即一个包管理工具,我们一般利用它来安装一些包
  • nodejs模块系统
    我们接触到的require是nodejs提供的一个接受对象,与之相对的是exports倒出对象

webpack相关

  • 作用
    webpack是一个模块打包的工具,它的作用是把互相依赖的模块处理成静态资源
  • 优点
依赖管理:方便引用第三方模块、让模块更容易复用、避免全局注入导致的冲突、避免重复加载或加载不需要的模块。
合并代码:把各个分散的模块集中打包成大文件,减少 HTTP 的请求链接数,配合 UglifyJS 可以减少、优化代码的体积。
各路插件:babel 把 ES6+ 转译成 ES5 ,eslint 可以检查编译期的错误……
  • 原理
由于 webpack 并不支持除 .js 以外的文件,
从而需要使用 loader 转换成 webpack 支持的模块,
plugin 用于扩展 webpack 的功能,
在 webpack 构建生命周期的过程在合适的时机做了合适的事情。
 简单的说就是分析代码,找到“require”、“exports”、“define”等关键词,并替换成对应模块的引用。
 把你的项目当成一个整体,通过一个给定的主文件(index.js),
 webpack将从这个文件开始找到你的项目的所有的依赖文件,
 使用loaders处理他们,最后打包为一个浏览器可以识别的js文件
  • 有哪些常见的Loader?他们是解决什么问题的?
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
source-map-loader:加载额外的 Source Map 文件,以方便断点调试
image-loader:加载并且压缩图片文件
babel-loader:把 ES6 转换成 ES5
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
eslint-loader:通过 ESLint 检查 JavaScript 代码
  • 配置开发环境和生产环境?
process.env是Node.js用于存放当前进程环境变量的对象;
而NODE_ENV则可以让开发者指定当前的运行时环境,
当它的值为production时即代表当前为生产环境
库和框架在打包时如果发现了它就可以去掉一些开发环境的代码,如警告信息和日志等。这将有助于提升代码运行速度和减小资源体积
  • 打包优化
首先可以优化Loader的搜索范围,只在src文件夹下查找,node_modules下的代码是编译过的,没必要再去处理一遍
使用HappyPack可以将Loader的同步执行转为并行,从而执行Loader时的编译等待时间
代码压缩相关,启用gzip压缩

说下对 Virtual DOM(虚拟dom) 算法的理解

概念

虚拟dom就是一个能代表dom树的js对象,通常含有标签名、标签属性还有一些子元素等等

优点

虚拟dom借助dom diff算法可以减少不必要的dom操作
diff算法是在新虚拟DOM和老虚拟DOM进行diff(精细化比对),实现最小量更新,最后反映到真正的DOM上

v-for循环中key的作用

v-for默认使用就地复用策略,列表数据修改的时候,他会根据key值去判断某个值是否修改,
如果修改,则重新渲染这一项,否则复用之前的元素
key的作用主要是为了高效的更新虚拟DOM

不建议用index作为key值的原因

如果用index作为key导致的问题就是以前的数据和重新渲染后的数据随着 key 值的变化从而没法建立关联关系. 这就失去了 key 值存在的意义.

promise对象与回调地狱

回调地狱概念

回调函数是作为参数传递给另一个函数的函数,然后在外部函数内调用该函数以完成某种例程或操作
函数作为参数层层嵌套就是回调地狱

promise对象采用链式的 then方法,可以指定一组按照次序调用的回调函数。
前一个 then 里的一个回调函数,返回的可能还是一个 Promise对象,(即有异步操作)这时后面的回调函数,就会等待该 Promise对象的状态发生变化才会被调用,由此实现异步操作按照次序执行。

promise缺点:

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

// 采用ajax异步回调的方式改装成promise

function getIp(){
   var promise = new Promise(function(resolve,reject){
      var xhr = new XMLHttpRequest()
      xhr.open('GET','https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getIp', true) //设置一个ajax的参数)
      xhr.onload = function(){
         var retJson = JSON.parse(xhr.responseText)     // {"ip":"58.100.211.137"} 数据到来对数据进行解析
         resolve(retJson.ip)      //初始化-完成状态-变为成功状态
      }
      xhr.onerror = function(){
         reject('获取IP失败')     //初始化-拒绝状态-变为失败状态
      }
      xhr.send()
   })
      return promise
}
function getCityFromIp(ip){
   var promise = new Promise(function(resolve,reject){
      var xhr = new XMLHttpRequest()
      xhr.open('GET','https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getCityFromIp?ip='+ip, true)
      xhr.onload = function(){
         var retJson = JSON.parse(xhr.responseText)      // {"city": "hangzhou","ip": "23.45.12.34"}
         resolve(retJson.city)
      }
      xhr.onerror = function(){
         reject('获取city失败')
      }
      xhr.send()
})
   return promise
}
function getWeatherFromCity(city){
   var promise = new Promise(function(resolve,reject){
      var xhr = new XMLHttpRequest()
      xhr.open('GET','https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getWeatherFromCity?city='+city, true)
      xhr.onload = function(){
         var retJson = JSON.parse(xhr.responseText)   // {"weather": "晴天","city": "beijing"}
         reslove(retJson)
      }
      xhr.onerror = function(){
         reject('获取天气失败')
      }
      xhr.send()
   })
   return promise
}
// getIp获取IP-IP获取城市-城市获取天气
getIp().then(function(ip){
   return getCityFromIp(ip)    // 得到ip
}).then(function(city){
    return getWeatherFromCity(city)    // 得到城市
}).then(function(){
   console.log(data)    // 得到具体的城市其他状况(如天气、人口等等)
}).catch(function(e){
   console.log('出现了错误',e)
})

Promise的几个重要方法

Promise.all()

const p = Promise.all([p1, p2, p3]);

Promise.all方法接收一个数组作为参数,p1、p2、p3都是Promise实例。
当所有Promise对象都成功的时候,整个Promise.all才成功,
即Promise.all()方法会等指定的promise对象全部执行结束后才执行

Promise.race()

const p = Promise.race([p1, p2, p3]);

Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

async/await和promise的区别

async函数
只要函数名之前加上async关键字,就表明该函数内部有异步操作。
该异步操作应该返回一个Promise对象,前面用await关键字注明。
当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句

  • await关键字只能用在aync定义的函数内。async函数会隐式地返回一个promise,该promise的reosolve值就是函数return的值
  • 使用Async/Await明显节约了不少代码。我们不需要写.then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码

axios和ajax区别

axios是通过promise实现对ajax技术的一种封装,就像jQuery实现ajax封装一样


axios({
            url: '/getUsers',
            method: 'get',
            responseType: 'json', // 默认的
            data: {
                //'a': 1,
                //'b': 2,
            }
        }).then(function (response) {
            console.log(response);
            console.log(response.data);
        }).catch(function (error) {
            console.log(error);
            })

$.ajax({
            url: '/getUsers',
            type: 'get',
            dataType: 'json',
            data: {
                //'a': 1,
                //'b': 2,
            },
            success: function (response) {
                console.log(response);
            }
        })

从 node.js 创建 http 请求
支持 Promise API
客户端支持防止CSRF
提供了一些并发请求的接口

直接对对象属性进行添加和删除会不会直接响应到视图中?说明原因

直接对对象属性进行添加和删除是不会响应到视图中的,需要用到this.$set()方法
由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。

开发中遇到的一些难点

(1)路由传参的功能的坑。之前一直使用路由传参,但是当本页面刷新的时候,页面上是没有参数的,因为参数是从上个页面传入进来的。 解决办法:使用了缓存,和vuex状态管理。但是由于项目并不是很大型的项目,所以使用最多的是缓存。

(2)页面缓存的坑。有个填写信息的页面,需要填写一部分信息,进入查新协议页面,返回的时候,页面上填写的信息还需要留存。 解决办法:使用vue提供的keep-alive,来完成页面的缓存的。

你有对 Vue 项目进行哪些优化?

  • (1)代码层面的优化
v-if 和 v-show 区分使用场景
computed 和 watch 区分使用场景
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
长列表性能优化
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染 SSR or 预渲染
  • (2)Webpack 层面的优化
Webpack 对图片进行压缩
减少 ES6 转为 ES5 的冗余代码
提取公共代码
模板预编译
提取组件的 CSS
优化 SourceMap
构建结果输出分析
Vue 项目的编译优化

组件中 data 为什么是一个函数?

因为js本身的特性带来的,如果data是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有vue实例的数据,如果将data作为一个函数返回一个对象,那么每一个实例的data属性是独立的,不会相互影响。

  • Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
    javascipt只有函数构成作用域(注意理解作用域,只有函数的{}构成作用域,对象的{}以及if(){}都不构成作用域),data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响

说说你对 SPA 单页面的理解,它的优缺点分别是什么?

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

  • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
  • 基于上面一点,SPA 相对对服务器压力小;
  • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

  • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
  • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
  • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

vue-router的两种模式(hash和history)及区别

1:hash 模式下,仅hash符号之前的内容会被包含在请求中,如http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误。

history模式下,前端的URL必须和实际向后端发起请求的URL一致。
history 模式需要后端配合将所有访问都指向 index.html,否则用户刷新页面,会导致 404 错误

常见兼容性问题

浏览器默认的 margin 和 padding 不同。解决方案是加一个全局的 *{margin: 0; padding: 0;} 来统一。

IE下 event 对象有 event.x,event.y 属性,而 Firefox 下没有。Firefox 下有 event.pageX,event.PageY 属性,而 IE 下没有。 解决办法:var mx = event.x?event.x:event.pageX;

Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示, 可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决.

超链接访问过后 hover 样式就不出现了,被点击访问过的超链接样式不在具有 hover 和 active 了,解决方法是改变 CSS 属性的排列顺序: L-V-H-A : a:link {} a:visited {} a:hover {} a:active {}

常见的http状态码

1**     信息,服务器收到请求,需要请求者继续执行操作
2**     成功,操作被成功接收并处理
3**     重定向,需要进一步的操作以完成请求
4**     客户端错误,请求包含语法错误或无法完成请求
5**     服务器错误,服务器在处理请求的过程中发生了错误

301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替

302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI

304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源

400 Bad Request 客户端请求的语法错误,服务器无法理解

401 Unauthorized 请求要求用户的身份认证

403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求

404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面

JSON的了解

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
它是基于 JavaScript 的一个子集。
数据格式简单,易于读写,占用带宽小。
格式:采用键值对。例如:{ “age‟: ‟12‟, ”name‟: ‟back‟ }

你有哪些性能优化的方法

web 前端是应用服务器处理之前的部分,前端主要包括:HTML、CSS、javascript、image 等各种资源,针对不同的资源有不同的优化方式。

内容优化

  • 减少 HTTP 请求数。这条策略是最重要最有效的,因为一个完整的请求要经过 DNS寻址,与服务器建立连接,发送数据,等待服务器响应,接收数据这样一个消耗时间成本和资源成本的复杂的过程。
    常见方法:
   合并多个 CSS 文件和js 文件,
   利用 CSS Sprites 整合图像,Inline Images (使用 data:URL scheme在实际的页面嵌入图像数据 ),
   合理设置 HTTP 缓存等。
  • 减少 DNS 查找
  • 避免重定向
  • 使用 Ajax 缓存
  • 延迟加载组件,预加载组件
  • 减少 DOM 元素数量。页面中存在大量 DOM 元素,会导致 javascript 遍历 DOM 的效率变慢。
  • 最小化 iframe 的数量。iframes 提供了一个简单的方式把一个网站的内容嵌入到另一个网站中。但其创建速度比其他包括
    JavaScript 和 CSS 的 DOM 元素的创建慢了 1-2 个数量级。
  • 避免 404。HTTP 请求时间消耗是很大的,因此使用 HTTP 请求来获得一个没有用处的响应(例如 404 没有找到页面)是完全没有必要的,它只会降低用户体验而不会有一点好处。

服务器优化

  • 使用内容分发网络(CDN)。把网站内容分散到多个、处于不同地域位置的服务器上可以加快下载速度。
  • GZIP 压缩
  • 设置 ETag:ETags(Entity tags,实体标签)是 web
    服务器和浏览器用于判断浏览器缓存中的内容和服务器中的原始内容是否匹配的一种机制。
  • 提前刷新缓冲区
  • 对 Ajax 请求使用 GET 方法
  • 避免空的图像 src

Cookie 优化

减小 Cookie 大小
针对 Web 组件使用域名无关的 Cookie
CSS 优化

将 CSS 代码放在 HTML 页面的顶部
避免使用 CSS 表达式
使用 < link> 来代替 @import
避免使用 Filters

javascript 优化

  • 将 JavaScript 脚本放在页面的底部。
  • 将 JavaScript 和 CSS 作为外部文件来引用。 在实际应用中使用外部文件可以提高页面速度,因为 JavaScript 和 CSS 文件都能在浏览器中产生缓存。
  • 缩小 JavaScript 和 CSS
  • 删除重复的脚本
  • 最小化 DOM 的访问。使用 JavaScript 访问 DOM 元素比较慢。
  • javascript 代码注意:谨慎使用 with,避免使用 eval Function 函数,减少作用域链查找。

图像优化

优化图片大小
通过 CSS Sprites 优化图片
不要在 HTML 中使用缩放图片
favicon.ico 要小而且可缓存

js 的 ready 和 onload 事件的区别

onload 是等 HTML 的所有资源都加载完成后再执行 onload 里面的内容,所有资源包括 DOM 结构、图片、视频 等资源;
ready 是当 DOM 结构加载完成后就可以执行了,相当于 jQuery 中的 $(function(){ js 代码 });
另外,onload 只能有一个,ready 可以有多个。

js 中防抖节流

防抖

所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

  • 防抖的中心思想在于:我会等你到底。在某段时间内,不管你触发了多少次回调,我都只认最后一次。
/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */
function debounce(func,wait,immediate) {
    let timeout;

    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(() => {
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}

简单版

	function debounce(fn,wait){
		let timeOut = null
		return args =>{
			if(timeOut) clearTimeout(timeOut)
			timeOut = setTimeout(fn,wait)
		}
	}

节流

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。

  • throttle 的中心思想在于:在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应

对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。

function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

防抖应用场景

文本输入搜索联想
文本输入验证(包括 Ajax 后端验证)

节流应用场景

鼠标点击
监听滚动 scroll
窗口 resize
mousemove 拖拽

call、apply与bind有什么区别?

  • call、apply与bind都用于改变this绑定,但call、apply在改变this指向的同时还会执行函数,而bind在改变this后是返回一个全新的boundFcuntion绑定函数,这也是为什么上方例子中bind后还加了一对括号
    ()的原因。
  • bind属于硬绑定,返回的 boundFunction 的 this 指向无法再次通过bind、apply或 call 修改;call与apply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。
  • call与apply功能完全相同,唯一不同的是call方法传递函数调用形参是以散列形式,而apply方法的形参是一个数组。在传参的情况下,call的性能要高于apply,因为apply在执行时还要多一步解析数组。

有关性能优化的方法总结

 图片进行懒加载或者延迟加载。
 对于多次http请求的数据,用localStorage或者sessionStorage进行缓存。
 对ui框架使用按需加载。
 使用防抖节流尽可能地减少http请求
 使用事件委托尽量减少直接操作dom节点,事件委托就是通过事件冒泡的原理,利用父级去触发子级的事件(即事件绑定在父节点上)

有大量图片的页面优化加载速度

  • 图片懒加载,滚动到相应的位置才加载图片
  • 使用特殊编码的图片,加载时先加载一张压缩图片

vue项目性能优化方法

  • 使用 CDN 外链的方式引入echart和elementUI等第三方库,加快构建的速度(通过 CDN 引入的资源不会经 webpack 打包),可以优化首次页面加载的速度
    //1、在index.html中cdn引入
    
    
    
    
    
    
2、在vue.config.js中配置
config.externals({
            'vue': 'Vue',
            'vue-router': 'VueRouter',
            'Vuex': "Vuex",
            // 'store': 'store',
            'axios': 'axios',
            'element-ui': 'ELEMENT'
        })
  • v-if 和 v-show 区分使用场景
  • computed 和 watch 区分使用场景
  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
  • 长列表性能优化

Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

如何渲染几万条数据并不卡住界面?

  • 如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条 都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来 每 16 ms 刷新一次

前端处理相对复杂的返回数据?

可以借助lodash和moment等工具库
Lodash内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数
momen.js是日期处理类库

跨域及解决方法

跨域常用解决方案

面试官问“你还有什么问题吗?”该怎么答?

  • 我所应聘的这个职位为什么会出现空缺?
  • 贵公司是否有正式或非正式培训?
  • 贵公司是否有外派、轮调、转岗的机会?
  • 贵公司是否鼓励在职进修?
  • 对这个职位来说最重要的能力是什么

面试官问“找工作最看重什么?”该怎么答?

  • 岗位的契合与发展
  • 公司的优势和前景
  • 技术氛围浓厚会提高自己的深度与广度,成员之间乐于分享敢于尝试新的技术

面试官问“你觉得你性格怎么样?”该怎么答?

  • 细心沉稳
  • 乐于助人

开发流程简述

  • 产品需求会议,讨论修改最后确定原型与UI图初稿
  • 明确开发任务后进行技术选型,对比权衡可能用到的新的插件组件或者新技术
  • 敲定后技术团队内部开会,定义数据类型,接口名称,方便我们前端mock数据
  • 前后端完成开发后进行本地联调,确定功能完善后发布测试环境
  • 测试在测试环境上进行测试提出修改意见
  • 前后端配合改bug知道所有功能通过测试
  • 发布正式环境

说说你们是怎么进行前后端联调的?

  • 首先是要跟后端确定好产品逻辑和接口返回的数据以及格式
  • 然后在后端接口开发的过程中,前端可以先使用mock进行接口数据的模拟
  • 等后端接口开发完毕后,再切换到后端接口联调

说说你最近学习的新技术?

vue3

  1. data
export default {
  data(){
    return{

    }
  }
},
///
取而代之是使用以下的方式去初始化数据:

import {reactive} from 'vue' 
export default {
 setup(){
   const name = reactive({
     name:'hello 番茄'
   })
   return {name}
 }  
}

在新版当中setup等效于之前2.0版本当中得到beforeCreate,和created,它是在组件初始化的时候执行,甚至是比created更早执行。值得注意的是,在3.0当中如果你要想使用setup里的数据,你需要将用到值return出来,返回出来的值在模板当中都是可以使用的

  1. 生命周期
created -> 请使用 setup()

beforeMount -> onBeforeMount

mounted -> onMounted

beforeUpdate -> onBeforeUpdate

updated -> onUpdated

beforeDestroy -> onBeforeUnmount

destroyed -> onUnmounted

如果要想在页面中使用生命周期函数的,根据以往2.0的操作是直接在页面中写入生命周期,而现在是需要去引用的,这就是为什么3.0能够将代码压缩到更低的原因

  1. ref
// Vue3
import { ref } from 'vue'
export default {
  setup () {
    const count = ref(0) // 声明 count,初始值为 0
    const str = ref('hello') // 声明 str,初始值为 'hello'
    return {
      count,
      str
    }
  }
}

我们要先引入 ref 方法,然后使用它来声明响应式变量。重要的是,这些操作需要在 setup 函数中进行,而且添加 methods,watch 和 computed 都需要在 setup 中进行

react

Vue组件分为全局注册和局部注册,在react中都是通过import相应组件,然后模版中引用;

每个Vue实例都实现了事件接口,方便父子组件通信,小型项目中不需要引入状态管理机制,而react必需自己实现;

多了指令系统,让模版可以实现更丰富的功能,而React只能使用JSX语法;

Vue增加的语法糖computed和watch,而在React中需要自己写一套逻辑来实现;

react做的事情很少,很多都交给社区去做,vue很多东西都是内置的,写起来确实方便一些, 比如 redux的combineReducer就对应vuex的modules, 比如reselect就对应vuex的getter和vue组件的computed, vuex的mutation是直接改变的原始数据,而redux的reducer是返回一个全新的state,所以redux结合immutable来优化性能,vue不需要。

react是整体的思路的就是函数式,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以做到,比如结合redux-form,组件的横向拆分一般是通过高阶组件。而vue是数据可变的,双向绑定,声明式的写法,vue组件的横向拆分很多情况下用mixin。
模板语法的不同:
react通过JSX渲染模板,vue通过拓展的html语法进行渲染。
比如react中插值、条件、循环都通过JS语法实现,
vue是通过指令v-bind、v-if、v-for实现的。

监听数据变化原理不同:
vue通过getter、setter劫持通知数据变化,
react通过比较引用的方式进行。
vue使用的响应式数据,而react是不可变数据。
vue改变数据直接赋值,react需要调用setState方法(用新的state替换旧的state)。

H5/C3新特性

H5新特性


1. 语义化更好的内容标签(header,nav,footer,aside,article,section) 

2. 音频、视频API(audio,video) 

3. 画布(Canvas) API 

4. 数据存储 localStorage、sessionStorage  

5. 表单控件,calendar、date、time、email、url、search 

CSS3新特性

1. 文字阴影(text-shadow) 

2. 边框: 圆角(border-radius)边框阴影: box-shadow 

3. 盒子模型:box-sizing 

4. 过渡:transition,可实现动画 

5. 媒体查询,多栏布局 

6. 2D转换:transform:translate(x,y) rotate(x,y) skew(x,y) scale(x,y) 

7.新增选择器:属性选择器、伪类选择器、伪元素选择器。 

移动端适配方案

rem是相对于根元素也就是 html的字体大小的单位
只要我们根据不同屏幕设定好根元素的font-size,其他已经使用了rem单位的元素就会自适应显示相应的尺寸了

  1. JS计算
    通过JavaScript读取屏幕宽度,然后根据宽度计算出对应的尺寸并设置根元素的font-size
const oHtml = document.getElementsByTagName('html')[0]
const width = oHtml.clientWidth;
// 320px的屏幕基准像素为12px
oHtml.style.fontSize = 12 * (width / 320) + "px";
  1. 媒体查询
@media screen and (min-width: 375px){
    html {
        font-size: 14.0625px;   
    }
}
@media screen and (min-width: 360px){
    html {
        font-size: 13.5px;
    }
}
@media screen and (min-width: 320px){
    html {
        font-size: 12px;
    }
}
html {
    font-size: 16px;
}

你可能感兴趣的:(PC,移动端,vue,js)