面试题总结

1.vue项目首次加载慢,如何解决?

  • webpack里有各种压缩包plugin,Gzip压缩图片、css文件等。
  • 屏蔽打包时的map文件 - 在vue.config.js的module.exports = { productionSourceMap:false}
  • 路由懒加载 -  router文件中  const mall = resolve => require(["./mall"], resolve)

兼容性和优化总结

  • 兼容性:

不同浏览器的默认样式存在差异,可以使用 Normalize.css 抹平这些差异。

  • 优化:

请求优化:尽量减少请求,开启Gzi压缩合并cssjs文件(需要下载这个包 compression-webpack-plugin,并在config文件配置)。

页面优化:减少DOM访问,减少重绘和重排、节流防抖、路由懒加载、使用字体矢量图标、按需引入组件。

图片懒加载vue-lazyload 或者elementui自带lazy属性。npm run build --report  后生成分析报告

使用cdn,图片和css文件都交给cdn服务器,浏览器访问index.html时从cdn服务器返回。

不使用iframe gif ,用css代替js动画,小图标使用base64格式,html中不能有空的src herf会阻塞下载进程,减少全局变量。

使用link标签 代替@import引入css文件,因为link可以并行下载css文件不会停止对当前文档处理。

2.事件循环机制

Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。

  • 同步任务、异步任务

JavaScript 单线程中的任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。

  • Event Loop

调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。

  • 宏任务(macro-task)、微任务(micro-task)

除了广义的同步任务和异步任务,JavaScript 单线程中的任务

可以细分为宏任务和微任务。

macro-task包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task包括:process.nextTick, Promises中的then catch, setState()、Object.observe, MutationObserver。

console.log(1);
setTimeout(function() {
    console.log(2);
})
var promise = new Promise(function(resolve, reject) {
    console.log(3);
    resolve();
})
promise.then(function() {
    console.log(4);
})
console.log(5);

//1
//3
//5
//4
//2
  • 上面的示例中,第一次事件循环,整段代码作为宏任务进入主线程执行。
  • 遇到了 setTimeout ,就会等到过了指定的时间后将回调函数放入到宏任务的任务队列中。
  • 遇到 Promise,将 then 函数放入到微任务的任务队列中。
  • 整个事件循环完成之后,会去检测微任务的任务队列中是否存在任务,存在就执行。
  • 第一次的循环结果打印为: 1,3,5,4。
  • 接着再到宏任务的任务队列中按顺序取出一个宏任务到栈中让主线程执行,那么在这次循环中的宏任务就是 setTimeout 注册的回调函数,执行完这个回调函数,发现在这次循环中并不存在微任务,就准备进行下一次事件循环。
  • 检测到宏任务队列中已经没有了要执行的任务,那么就结束事件循环。
  • 最终的结果就是 1,3,5,4,2。

重点:

  1. 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
  2. 全局Script代码执行完毕后,执行栈Stack会清空;
  3. 微队列中取出位于队首的回调任务,放入执行栈Stack中执行,执行完后微队列长度减1;
  4. 继续循环取出位于微队列的任务,放入执行栈Stack中执行,以此类推,直到直到把微任务执行完毕。注意,如果在执行微任务的过程中,又产生了微任务,那么会加入到微队列的末尾,也会在这个周期被调用执行;
  5. 微队列中的所有微任务都执行完毕,此时微队列为空队列,执行栈Stack也为空;
  6. 取出宏队列中的任务,放入执行栈Stack中执行;
  7. 执行完毕后,执行栈Stack为空;
  8. 重复第3-7个步骤

例:

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

前端干货:JS的执行顺序 - 简书JS的运行机制 先来一个今日头条的面试题 1. 单线程的JavaScript js是单线程的,基于事件循环,非阻塞IO的。特点: 处理I/O型的应用,不适合CPU运算密集型...icon-default.png?t=O83Ahttps://www.jianshu.com/p/62c7d633a879

3. JS中的new

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

new 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 为这个空对象新创建的对象添加隐式原型__proto__,将该属性链接至构造函数的原型对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this

4.原型链

每个实例对象(object)都有一个私有属性隐式原型(称之为 __proto__ )指向它的构造函数的原型对象显示原型(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null

5.promise

<script>
    function test(){
      return new Promise((res,rej)=>{
        setTimeout(()=>{
          console.log(1);
          res()
        },1000)
      })
    }
    function test2(){
      return new Promise((res,rej)=>{
        setTimeout(()=>{
          console.log(5);
          res()
        },2000)
      })
    }
    async function test1(){ 
      console.log(2);
      await test();
      await test2();
      console.log(3);
    }
    test1().then(()=>{console.log(4)})
  </script>
//2 1 5 3 4

注意await后面要跟 promise 才能按自己写得顺序执行。

new Promise接受两个回调 一个是resovle 一个是rejected,可以利用使promise在某种if情况下进入resolve(),或reject()。因为promise有三种状态,pending、fulfilled、rejected。resolve()方法对应着fulfilled状态,reject()方法对应rejected状态。

promise还可以链式调用.then() .catch() .all() .race()他们都返回一个promise对象,所以()里写箭头函数保证this指向。

在promise中调用resolve()方法就会进入.then()第一个回调, 调用reject()方法就会进入.then()的第二个回调,否则就进入.cathc()

扩展: .all() .race()的使用方法

.all()的用法

let wake = (time) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${time / 1000}秒后醒来`)
    }, time)
  })
}

let p1 = wake(3000)
let p2 = wake(2000)

Promise.all([p1, p2]).then((result) => {
  console.log(result)       // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
  console.log(error)
})

.all()里面p1计时器3秒比p2的2秒长,但是先执行。所以可以用来执行特定顺序的异步。.all()后面接.then()可以看到then返回的是每个异步函数执行回调后的结果组成的数组。所以all()括号里的函数都执行。

那么问题来了,既然是都执行,.all()之后的.then()返回的是所有resolve()的结果。那如果一个进入reject状态 一个进入resolve状态的情况呢?

答案是:只要有一个进入reject()方法里,.all.catch()就会执行并返回那个reject()的方法。

.rece()的用法

顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  },1000)
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)  // 打开的是 'failed'
})

6.flex布局

主要就是

justify-content:主轴对齐方式.

align-items:交叉轴对齐方式.

flex-wrap:项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行.

Flex 布局语法教程 | 菜鸟教程网页布局(layout)是CSS的一个重点应用。 布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。 2009年,W3C提出了一种新的方案—-Flex布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。 Flex布局将成为未来布..icon-default.png?t=O83Ahttps://www.runoob.com/w3cnote/flex-grammar.html

7.spa的理解

spa:单页面应用 single page web application

整个应用只有一个完整的页面;

点击页面中的链接不会刷新页面,只会做页面的局部更新;

数据都要通过ajax请求获取,并在前端异步展现

8.LocalStorage sessionStorage cookie区别

共同:都是保存在浏览器、而且同源

区别:

存放地址不同:cookie在http路径中携带,而其他两个是存在浏览器中的,不会发给后端。

大小不同:cookie只能存4K 其他两个5M。

有效期不同:sesstionStorage在浏览器关闭之前有效,cookie在其设置的有效期内有效,localStorage长期有效。

作用域不同:sessionStorage在不同浏览器窗口不能共享,即使是同一个页面;localstotage、cookie在同源窗口是共享的。

9.使一个盒子水平垂直居中

1.父盒子position relative;子盒子absolute,top left各50%,margin-top left自己的一半px。

2.父盒子position relative;子盒子absolute ;margin:auto;topleftrightbottom都是0.

3.父盒子display:table-cell;vertical-align:middle;text-align:center;子盒子:display:inline-block.

4.父盒子:display:flex;justify-content:center;align-items:center;

5.直接计算父子之间的距离,在子盒子margin-top left。

6.父盒子:position relative;子盒子absolute,top left各50%;tansform:translate(-50%,-50%)

10.css盒模型

box-sizing:border-box;怪异盒子模型;width=设置的宽+padding+border

box-sizing:content-box;标准盒模型;标准盒模型下width就是设置的值

11.块级元素 行内元素 css权重

块级元素:div ul li dl dt dd p h1-h6 form

行内:a span b img input select button

css权重:

!important最大

内联:1000

id选择器:100

类、伪类、属性:10

标签 伪元素:1

12.js数据类型及判断

基本数据类型:number、string、boolean、null、undefined

引用数据类型:function object array

typeof可以判断除了null、array的所有类型,判断null 返回object,像判断null可以用===

instanceof可以判断new过的数据类型

construcor 也可以返回类型,但是构造函数可以改的所以也不是很准。

最好的方法:Object.prototype.toString.call(data) 返回 [object type] 准确的。

13.array数组方法

length

join()           将数组转成字符串,返回一个字符串。

delete         删除。

shift            删除第一个元素,返回删除的元素长度减1.

pop            删除最后一个,返回删除的元素长度加1.

unshift        数组头加一个或多个

push          数组尾加一个或多个

concat       连接两个数组

slice          剪切一部分

sort          排序

splice       插入 删除 替换,接受三个参数,第一个是index,第二个是删除几个,第三个是要替换添加的数组

every()     判断每一个

Array.from  将伪数组转换成真数组(伪数组只有长度没有poppush方法)

Array.of      将一系列值转成数组

14. 字符串方法

substr()      

substring()

split()          将字符串转数组

concat        连接字符串

indexof       返回一个字符在字符串中的索引值

slice           剪切

match        匹配正则的字符

??和?.

空值合并操作符?? 这个和 || 很像,但是它不会屏蔽掉false和 0

  只有当左侧为null和undefined时,才会返回右侧的数

可选链操作符( ?. )

?. 可选链运算符,检查每个级别,如果碰到的是 undefined 或 null 属性,直接返回 undefined,不会继续往下检查

??= 逻辑空赋值运算符 (x ??= y) 仅在 x 是 nullish (null 或 undefined) 时对其赋值

15.dom事件类型和冒泡

DOM事件类型就是:事件捕捉、事件冒泡

捕捉:比如点击事件,会从根元素开始触发,向内传播,一直到目标元素。祖先元素---> 目标的父元素--->目标元素。

冒泡:点击事件从目标元素传递到目标父元素,在传递到目标祖先元素。

先捕捉在冒泡。

event.stopPropagition()阻止冒泡

event.preventDefault()阻止捕捉。

事件委托就是运用了冒泡机制。这样就不用给每个子元素绑定事件,给他们的父元素绑定一次事件就可以。

16.浏览器渲染过程

解析HTML结构构件DOM树,并请求css js img。

css文件下载完成构建css树

然后dom树和css树合成rendertree(渲染树)

布局:计算出每个节点在屏幕的位置,通过显卡把页面显示到屏幕上。

17.闭包

就是在一个函数内部可以访问,另一个函数内部的变量

function f(){
    var a = 1;
    var b = 2;
    function add (){
        return a+b
    }
    return
}



18.this指向

以函数调用this,指向window

以方法调用,指向的是调用方法的对象。

以构造函数调用,指向就是新创建的对象。

使用call apply 调用,指向的是对象

箭头函数调用,指向的是外层函数,没如果没有外层函数,就是window

19.ES6总结

let const 块级作用域、没有变量提升,暂时性死区、不能重复声明。

模板字符串 `${}`

函数参数可以设置默认值,占位符_ , 箭头函数(this指向父级,不能用arguments对象,不能new,不能使用Generator函数)

对象的简写,Object.keys() 

for of循环数组

import导入模块和export 输出模块

promise

解构赋值

展开运算符

async await

20.一个页面从输入URL到页面加载完成都发生了什么?

1.浏览器查找域名对应的ip地址(先从浏览器缓存找、系统缓存、路由器缓存、DNS缓存)

2.浏览器向web服务器发送一个http请求 三次握手

3.服务器301重定向,浏览器跟踪重定向地址并请求

4.服务器处理请求结果,返回http相应

5.浏览器渲染dom树,发送请求获取cssimgjs等资源,最后渲染页面。

21.vue相关

1.优势:

        轻量级框架、简单易学、组件化开发、双向绑定,数据结构分离、虚拟dom、diff算法,单页面应用跳转路由不会刷新页面,第三方ui组件库多。

2.mvvm原理:

        vue.js是采用数据劫持结合发布者-订阅模式的方法,通过Object.defineProperty()劫持每个属性的getter和setter,在数据变化时发布消息给订阅者,触发相应的监听回调。

  •  需要observe的数据对象进行递归遍历,包括子属性的对象,都加上getter和setter,这样某个变化就会触发setter,监听到数据变化。
  • compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面试图,并将每个指定对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据变化收到通知更新视图。
  • watcher订阅者是observe和compile之间通信的桥梁,主要做:

                在自身实例化时往属性订阅器(dep)添加自身

                自身有个updata()方法

                属性变动dep.notice()通知时,调用自身update()方法,并触发compolie中绑定的回调。

3.生命周期

       创建vue实例newVue()初始化->  beforeCreate() -> 数据侦听、配置事件/侦听器 -> created() ->watcher、computed()、metheds、事件/侦听器的回调函数都准备完毕 -> beforeMount() -> 相关的 render 函数首次被调用,虚拟dom完成 ->  mounted() -> 实力挂载完成dom渲染完成。

        在数据发生改变后 -> beforeUpdate() -> 虚拟 DOM 重新渲染和更新完毕 -> updated()。

        activated()被 keep-alive 缓存的组件激活时调用,deactivated()被 keep-alive 缓存的组件失活时调用。

        beforeDestroy()-> 实力销毁 -> destroyed -> 所有指令解绑,事件监听器移除,子实例被销毁

        errorCaptured() 后代组件的错误时被调用。

4.为什么避免v-for和v-if连用

v-for比v-if有更高的优先级,会造成不必要的浪费,解决方法:可以用计算属性,或者把v-if放在父元素上,子元素标签里写v-for

computed和watch的区别

coputed:依赖于data()中的值,而且只有依赖的数据发生改变才会重新计算。而且需要返回值,支持缓存,不支持异步操作。定义的计算属性不需要写在data()中。适用于:一个数据受多个数据影响。

watch:支持异步操作 也依赖data()中的某个数据。适用于:一个数据影响多个数据

5.vuex

五大核心:

state:存放集中状态。

mutations:同步修改state,类似事件

action:异步提交mutations

getter:state的计算属性,第一个属性必须时state

module:分割模块 每个模块有自己的四大属性。需要开启命名空间namespaced: true

第一步在src目录下的store文件夹里创建read子文件夹创建store.js文件,如下

const state = {
  bigPages: '',
}
const getters = {
  getBigPages(state) {
    return state.bigPages   //getter一定要把state作为参数传进去 而且有返回值
  }
 
}
const mutations = {
  setBigPages(state, str) {  //mutations第一个参数是state,第二个参数是传入的值
    state.bigPages = str
  }
}
const actions = {
  changeBigPages(context, str) {  // 异步action第一个参数是context,第二个是传入的值
    context.commit('setBigPages', str) // 通过context的commit方法改变值
  }
}

export default {
  namespaced: true,//命名空间
  state,
  getters,
  mutations,
  actions,
}

第二步:在store文件夹下创建store.js

import Vue from 'vue' //引入vue
import Vuex from 'vuex' // 引入vuex

import read from './read/store' // 引入模块化vuex

Vue.use(Vuex) //使用vuex

const state = {
  requestUrl: '/api',
  token: localStorage.getItem('ACCESS_TOKEN'),
}
const getters = {
  getRequestUrl() {
    return state.requestUrl
  },
  getToken(state) {
    return state.token
  }
}
const mutations = {
  setRequestUrl(state, str) {
     state.requestUrl= str
   },
}
const actions = {
    changeRequestUrl(context,str){
        context.commit('setRequestUrl',str)
    }
}

const store = new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  modules: {
    read //这里一定要引入模块
  },
})

export default store

第三步:入口文件一定要引入store,在vue中注入store。这样全局this.$store可以调用

 第四步:取值和使用

  • state

        方法一:this.$store.read.bigPages

        方法二:从vuex引入mapStates,在计算属性中加上路径读取

import {mapState} from 'vuex'
computed:{
  ...mapState({
     bigPages(state) {
        return state.read.bigPages
     },
     cart(state){
        return state.cart.cart;
      }
   })
}
  • mutations

方法一:this.$store.commit('read/setBigPages','参数')

方法二:辅助函数mapMutations

import {mapMutations} from 'vuex'
methods:{
  ...mapMutations({
    setBigPages:'read/setBigPages',
  })
     
}

mapActions 和 mapGetter方法一样。第一种方法调用action,需要this.$store.dispatch()

6.vue-router及相关

更新视图但不刷新页面是vue路由的核心之一

1.跳转方式

编程式:

this.$router.push({path:'',query:{key:value}})             

this.$router.push({name:'',params:{key:value}})  页面刷新params获取不到了

this.$router.replace()

this.$router.go(n) 向前或向后跳转n个页面,前后由n是正数还是负数决定。

声明式导航:

<router-link :to={path:'',quert:{key:value}}></router-link>

2.$route和$router的区别

$route是当前路由信息对象 存放着path params hash query fullPath name matched等信息。

$router是VueRouter的实例,是全局路由对象 push replace go等方法都在里面。

22.JS 面试题

1 .赋值,浅拷贝与深拷贝

赋值:没有创建新对象,仅仅是拷贝了原对象的指针

浅拷贝:是创建一个新对象,这个对象仅对原对象的属性进行拷贝,属性值是基本类型时,拷贝的是原数据,属性值是引用类型时,拷贝的是指针。因此,如果原对象的属性有引用类型数据,无论修改新对象还是原对象的引用类型数据,另一个都会随之改变

深拷贝:也是创建一个新对象,但不仅对原对象的属性进行拷贝,还在堆内存中开辟一个新的地址用来存储新对象,所以新对象和原对象没有关联,修改其中一个另一个不会变化

方法:

浅拷贝:Object.assign()     Array.concat      Array.slice()  展开运算符[..., arr]

深拷贝:JSON.parse(JSON.stringfy(data)), 缺点undefined类型和function类型不会被拷贝、不能拷贝循环引用对象。自己手写递归遍历,缺点可能失误写错或者在网上找。lodash里方法cloneDeep()。

2. 防抖与节流

防抖:n秒内重复点击,重新计时,只执行最后一次。

输入框掉接口搜索、 校验onchange事件、窗口大小改变。


      function debounce(fn, delay) {

        let timer = null;

        return function () {

          clearTimeout(timer);

          timer = setTimeout(() => {

            fn.apply(this, arguments);

          }, delay);

        };

      }

节流:n秒内重复点击,只有一次生效。

页面滚动事件、懒加载、防止表单重复提交。

      function throttle(fn, delay) {

        let valid = true;

        return function () {

          if (valid) {

            valid = false;

            setTimeout(() => {

              fn.apply(this, arguments);

              valid = true;

            }, delay);

          }

        };

      }

3. Proxy和Reflect

这俩都可以对 对象或函数进行拦截并执行一些额外操作,比如getter setter函数中打印输出或其他复杂操作。那为什么两个要一起用 用一个不行吗?

原因就是这个proxy中getter setter的第三个参数 reciver(除了get set 其实还有很多方法是两者皆有的比如has deleteProperty ownKeys等等)

let parent = {
  get value() {
    return "parent";
  },
};
 
let proxy = new Proxy(parent, {
  get(target, key, receiver) {
    // 这里receiver应该是proxy的 但是变成了child,Proxy局限性,用Reflect解决。
    console.log(receiver === proxy);
    return target[key];
  },
});
 
let child = { name: "Jerry" };
// 设置 child 继承 代理对象 proxy
Object.setPrototypeOf(child, proxy);
 
child.value; // false

从这里看出其实receiver就是类似this 上下文,get函数的receiver是Proxy 或者继承 Proxy 的对象。就有了局限性,要解除这个局限性,就要用Reflect 映射: 

let parent = {
  name: "Tom",
  get value() {
    return this.name;
  },
};
 
let proxy = new Proxy(parent, {
  get(target, key, receiver) {

    // 相当于 return target[key].call(receiver)
    return Reflect.get(target, key, receiver);
  },
});
 
let child = { name: "小Tom" };
// 设置 child 继承 代理对象 proxy
Object.setPrototypeOf(child, proxy);
 
console.log(child.value); // 小Tom

注意上面代码中的// 相当于 return target[key].call(receiver)

其实就是this指向问题,如果target对象中指定了getterreceiver则为getter调用时的this值。

23.css面试题

1.移动端1px被渲染成2px的问题。

全局解决:在<meta>标签中viewport属性设置,initial-scale=0.5

局部:使用transform:scale(0.5)

2. display:none visible:hidden区别

都能隐藏元素。

前者:让元素从渲染树中消失,不占空间。非继承性子孙节点直接消失。导致重排重绘。

后者:存在渲染树中,占据空间。继承性,子孙节点可以通过visiblity:visible显示。导致重绘不会重排。

3. BFC 块级格式化上下文 block format context

作用:可以包含住浮动元素、不被浮动元素遮挡、阻止父子margin折叠。

创建规则:根元素、浮动元素(float不为none)、绝对定位absolu fixed、display flex inline-block inline-flex等、overflow不为visible的元素。

24.Object.keys不能访问到,obj.b可以访问到?

Object.keys()只能遍历出 除 symbol 类型外的可枚举属性。

25.什么情况下只能用get请求,不能用post?

单页面应用 vue react,刷新的时候会发出一次get请求,为了防止白屏需要nginx配置。

jsonP解决跨域。

26.react自定义hook与普通函数(如utils)的区别?

hook只能在react组件中或hook组件中使用,不然会报错。

useEffect useState等只能在hook、react组件中使用,不能在普通函数中使用。

自定义Hooks需要以use开头,普通函数则没有这个限制。

27.react函数编程与vue3的区别?

原理上:vue是响应式状态管理、更新单个状态dom节点;而react是手动setState,会更新整个组件与子组件。

 Vue 是对数据进行劫持/代理,它对监测数据的变化更加精准,动了多少数据就触发多少更新,更新粒度很小,而 React 推崇函数式,这是没办法感知数据变化的,就是说不知道什么时候应该刷新,而且即便是手动 setState 触发更新,它也也不知道哪些组件需要刷新,而是渲染整个 DOM,说白了就是无脑刷新嘛,这样就导致性能不好,所以后面只能不断通过其他办法来避免不必要的刷新,或者优化无脑刷新的性能。当然 Vue 也不是那么完美,它实现精准刷新也是有代价的,就是需要给每个组件配置监视器,管理依赖收集和派发更新,这同样是有消耗的。

https://segmentfault.com/a/1190000043837947

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值