2021-6面试

vue

  • vue中v-if和v-for的优先级那个更高,应该怎么优化提升性能
    1、在vue2.x中v-for的优先级高
    2、可以将v-if写到v-for的外面,也可以将数据在computed计算属性中进行过滤,在循环计算方法就可以了
    3、在vue3.x中v-if的优先级更高

  • vue中data为什么必须是函数,而vue根实例是对象


    image.png

    1、组件中data要不是一个函数的话,那么会进行报错
    2、在多实例的时候,保护状态不被污染,干扰
    3、函数的话,可以让我们每次调用组件都会创建一个新的data实例
    4、要是对象的话,组件调用多次,改变一个,都会进行改变触发,导致状态会被污染,干扰
    5、根实例跟组件不一样,是单例,每次new vue只会创建一次

  • vue中key的作用和工作原理


    image.png

    1、key的作用主要是高效的更新虚拟dom,通过key可以精准的判断两个节点是否是同一个,要是没有加key的话,会认为是相同的,从而进行强制的更新,导致多做一个dom的更新,消耗性能
    2、不加的话,会触发一些隐蔽的bug,还有不加的话,会报错
    3、vue中使用相同标签名元素过渡切换的时候,也会使用到key的属性,目的就是为了让vue可以区分他们,否则vue只会替换内部属性,而不会触发过渡效果

  • 你怎么理解vue中的diff算法(需要看看课程中,对diff算法更深的理解)
    1、必要性,为了降低组件的watcher,一个组件一个watcher,所以要是用diff
    2、深度优先,同层比较(先从根找,有孩子往下找,有的话,继续,没有的话,同层比较,之后在看父节点的,另一边有没有子节点,有往下找,没有同层比较)

  • vue组件化的理解
    1、高内聚、低耦合,提升代码的健壮性
    2、有属性props,自定义事件,插槽等,用于组件通信,扩展等
    3、可复用性
    4、全局组件和局部组件

  • MVVM
    1、解决model和view耦合的问题
    2、model发生改变会自动更新view,减少dom操作,提高开发效率,可读性还保持了优越的性能表现

  • vue性能优化有哪些?
    1、路由懒加载
    2、keep-alive进行缓存页面
    3、v-show和v-if合理的使用
    4、大数据列表,使用虚拟滚动列表(vue-virtual-scroll,vue-virtual-scroll-list)
    5、有定时器的,组件页面销毁前进行清除
    6、图片的懒加载
    7、第三方UI组件库的按需引入
    8、将无状态的UI组件可以标记为函数式组件,在template标签上添加functional就可以了
    9、组件中的方法进行调用多次的时候,使用变量将方法进行保存,这样调用多次的时候,我们只用了这一次的方法

  • vue3.0的新特性了解
    1、更快:虚拟dom的重写,优化slots的生成,静态树的提升,静态属性的提升,proxy响应式系统
    2、更小:摇树优化
    3、更容易维护:ts+模块化
    4、composition Api

  • vuex的使用及其理解
    1、模块

    state mutations getters action modlues(namespace)

  • vue 组件之间的通信方式
    1、props
    2、on
    3、parent
    4、provide/inject
    5、listeners
    6、ref
    7、$root
    8、eventbus
    9、 vuex

    • vue-router保护路由的安全
      1、获取组件实例只有组件内守卫可以进行获取到,全局守卫,路由独享都是获取不到的


      image.png

      2、触发顺序


      image.png
  • vue响应式的理解
    1、通过数据响应式加上虚拟DOM和patch算法,可以使我们只需要操作数据就可以达到更新视图,完全不需要操作dom,从而大大提升开发效率,降低开发难度
    2、vue2中的数据响应式机制会根据类型来进行不同的处理,如果是对象的话,使用Obiect.defineProperty()的方式定义数据的拦截和响应,如果是数组的话,通过覆盖数组原型的方法,扩展他的7个变更方法,使这些方法额外的做更新通知,可以很好的解决了数据响应式的问题,但是也有一些缺点:比如初始化时递归 遍历时会造成性能损失,新增或者是删除属性时需要使用vue.set/delete这样的api才能生效,对于es6的新增map,,set结构不支持等问题
    3、为解决这些问题,vue3中利用es6的proxy机制代理响应化的数据,不在需要使用vue.set/delete等api,初始化性能和内存消耗都得到了大幅改善,另外将响应式的代码单独抽离为独立的reactivity包,可以让我们更加灵活的使用他,我们甚至不要要引入vue就可以进行体验

  • vue的双向数据绑定原理
    1、首先通过object.defineProperty()来设置和读取数据
    2、通过监听器Observe监听所有的属性,如果属性发生变化了,就需要告诉订阅者是否更新,因为订阅者有很多,此时我们需要Dep专门进行收集订阅者,然后对监听器Objserve和watcher之间进行统一的管理
    3、当watcher接收到相应的属性变化,就会进行更新对应的函数

es6的语法总结

  • 深浅拷贝
    1、引用类型的数据直接赋值就是浅拷贝
    2、基本数据类型直接赋值是深拷贝
    3、使用Object.prototype.toString.call()可以检测到当前数据类型,适用于所有数据类型的检测,返回'[objec Object]','[objec Array]','[objec String]' 等
    4、也可以使用Object.getPrototypeOf()进行检测出当前的数据是对象还是数组
    5、使用Object.create(),创建出的对象或者是数组,都是将对方的属性,方法放到了原型上,没有这个属性可以直接从原型上获取,有的话就不会再使用原型中的属性值
    6、如果是对象的话,可以使用Object.assign()进行克隆,但是,当前的对象有多层,也就是说对象中还有对象或者是数组,那么里面的对象或者数组就是浅拷贝
    7、扩展运算符跟6是一样的,单层是深拷贝,多层嵌套就是浅拷贝了
    8、如果是数组的话,可以使用数组中的方法concat,slice等方法,也可以使用Object.assign(),扩展运算符,情况跟6一样

  • 函数的变量提升
    1、函数的变量提升优先于变量的提升

   function test(a,b){
      console.log(a);//function a(){}
      var a=10;
      function a(){}
      console.log(a);//10
      function c(){}
      console.log(b);//undefined
      var b=function b(){}
      console.log(b)//function b(){}
   }
   test(1);
   var a=121;
   console.log(a);//121
   /**
    * 函数的变量提升:
    *     1、函数的变量提升比变量的提升优先级高,在变量的前面
    *     2、找形参和变量,相同的只需要一个就可以了,值为undefined
    *         a:undefined   b:undefined
    *     3、将实参和形参统一
    *         a:1  b:没有传递实参,值为undefined
    *     4、在函数中找函数,将值赋值给函数体
    *         a:function a(){}  b:由于函数中的b是字面量函数,所以值还是undefined
    *     5、在函数的内部开始运行
    *         a:function a(){}----10
    *         b:undefined-----function b(){}
   */

      var a = 10 ;
      function a(){
          console.log(a);//function a(){}
          a=20;
          function a(){}
          console.log(a)//20
      }
      a();
      console.log(a)//10

      a = 20;
      var a = 10;
      console.log(a);//10  本来a是全局变量20,然后下面有声明了一下,a=10;所以最终输出为10
  • 字符串模板
    1、可以进行简单的运算
    2、比字符串拼接好使
 // todo 字符串模板的使用
    const a = 1;

    const b = 2;

    const c = `${a+b}`

    console.log(c);//3

    const d = 18;

    const e = `小明今年${d}岁`

    const f = '小明今年'+d+'岁'

    console.log(e);//小明今年18岁

    console.log(f);//小明今年18岁
  • 判断当前是否有这个字符串
 // 判断当前是否有这个字符串

    const str = '我现在正在练习es6';

    const str1 = '现在'

    const str2 = '我'

    const str3 = 'es6'

    if(str.indexOf(str1)>-1) console.log('indexOf有返回当前下标,没有返回-1---',str.indexOf(str1));//indexOf有返回当前下标,没有返回-1

    if(str.includes(str1)) console.log('includes有返回true,没有返回false---',str.includes(str1));//includes有返回true,没有返回false

    if(str.startsWith(str2)) console.log('startsWith头部有返回true,没有返回false---',str.startsWith(str2));//startsWith头部有返回true,没有返回false

    if(str.endsWith(str3)) console.log('endsWith尾部有返回true,没有返回false---',str.endsWith(str3));//endsWith尾部有返回true,没有返回false
  • 字符串重复多次的写法(复制字符串)
// 字符串重复多次
        const str = '重复我10次'
        console.log(str.repeat(10));//重复我10次重复我10次重复我10次重复我10次重复我10次重复我10次重复我10次重复我10次重复我10次重复我10次
        let str1 = ''
        for (let i = 0; i < 10; i++) {
           str1+=str;
        }
        console.log(str1);
  • Array.from()
    1、可以将对象转化为数组
// 使用Array.from(),将对象转化为数组
    const obj = {
        0:'key要是用数字',
        1:'必须有length这个属性,否则转化不了',
        length:2
    }

2、可以将伪数组转化为数组
3、可以将set,map,string等数据结构转化为数组
4、第二个参数是一个回调函数,相当于数组的map方法,返回一个新的数组,将第一个参数中的数据进行处理

  • Array.of
    1、这个方法不管你是什么数据和类型,都会创建一个新的数组
    2、当我们使用new Array(10);进行声明的时候,这个时候数组的长度是10,在进行传递参数的时候,就会产生一些问题
    const arr = new Array(10);

    console.log(arr);//[empty × 10]

    arr.push(20);

    console.log(arr);//[empty × 10, 20]

3、此时代码中的数组长度就是11了,在添加的时候,前面都是10个空的
4、而我们使用Aarray.of就可以解决上面的问题了

   const arr = Array.of(10);

    console.log(arr);//[10]

    arr.push(20);

    console.log(arr);//[10, 20]
  • Array.fill()填充
 let arr =[1,2,4,5,6,7];

     arr.fill(10);//一个参数默认将数组中的数据全部改变为10

    //第一个参数是填充的参数,第二个参数是填充的位置,第三个下标是填充的个数,从下标0开始算起
    //  只能是数组内的长度,不能超过数组内的下标或者是长度,
     arr.fill('3',0,1);//["3", 2, 4, 5, 6, 7]

     arr.fill('3',1,2);//----[1, "3", 4, 5, 6, 7]

     arr.fill('3',1,4);//---- [1, "3", "3", "3", 6, 7],第三个参数是4个但是从下标1开始填充,又是从0开始计算

    console.log(arr);
  • 判断对象或者是数组中是否有某个值
    1、对象中的检测某个属性使用in或者是object.hasOwnProperty()都可以检测,但是in是从对象的原型和原型链上查找,不太推荐使用
 let obj = {
        id:1,
        title:'1111'
    }

    console.log('id' in obj);

    console.log(obj.hasOwnProperty('id'));

    let arr =[1,2,4,5,6,7];

    console.log( 1 in arr);

    console.log(arr.indexOf(1)>-1);

    console.log(arr.includes(1));

    const index = arr.findIndex(item=>item===1)
    console.log(index!==-1)

    ....
  • object.is()对象比较
 let obj = {id:1,title:'1111'};

    let obj2 = {id:1,title:'2222'};

    console.log(obj.id === obj2.id);//true

    console.log(Object.is(obj.id,obj2.id));//true
  • objec.is和===区别
    1、===为同值相等,is()为严格相等
console.log(+0 === -0);  //true
console.log(NaN === NaN ); //false
console.log(Object.is(+0,-0)); //false
console.log(Object.is(NaN,NaN)); //true
  • reduce简单的计算及数组去重
    //reduce 简单的计算,第一个参数是我们传递的初始值,莫有的话,默认是数组的第一个数
    //reduce 第二个参数是数组中的每一项
    //reduce 第三个参数是数组本身

 let arr = [1,1,1,1,2,2,3,4,5,5,6,7,8,9];
    // 计算出现的次数
    let arr1 = arr.reduce((pre,next)=>{
        if(next in pre){//可以将in替换为hasOwnProperty()
            pre[next]+=1;
        }else{
            pre[next] = 1;
        }
        return pre
    },{})
    console.log(arr1);//返回的是对象
    arr1.length = 10;//给当前的对象添加一个length属性,可以转化为数组
    console.log(Array.from(arr1));

    // 数组去重
    let arr = [1,1,1,1,2,2,3,4,5,5,6,7,8,9];

    // 使用双循环进行判断,当前的数值跟下一个数值进行比较,是否相同,相同的话进行去重
    for (let i = 0; i < arr.length; i++) {
       
        for (let j = i+1; j < arr.length; j++) {
           
            if(arr[i]===arr[j]){
                arr.splice(i,1);
                i--;
                j--;
            }
            
        }
        
    }
    console.log(arr);

    const arr1 = [...new Set(arr)];//使用new Set 进行去重

    console.log(arr1);

    let arr2 = arr.reduce((pre,next)=>{
        // if(!pre.includes(next)){//当数组中没有这个值的时候进行添加就好
        //     pre.push(next);
        // }
        // indexOf()有的话返回下标,没有的话返回-1,所以没有的时候进行添加
        if(pre.indexOf(next)===-1){
            pre.push(next);
        }
        return pre;
    },[])

    console.log(arr2);

    // 简单的计算数组中的和
    let arr = [1,2,3,4,5,6,7];
    let s = arr.reduce((pre,next)=>{
        return pre + next
    },0)

    console.log(s);

  • new Proxy()简单的代理
 let obj = {
        id:1,
        title:'练习proxy代理'
    }

    let obj2 = new Proxy(obj,{

        get(target,key){
            console.log(`你正在读取obj中的${key}属性`);
            // return target[key];//必须要有返回值
            return Reflect.get(target,key);//一般我们推荐使用这种映射的方式进行返回
        },

        set(target,key,val){
            console.log(`你正在对obj的${key}进行设置`);
            // target[key] = val;
            // return target[key];
            return Reflect.set(target,key,val);
        },

        has(target,key){
            console.log(`obj中是否有这个${key}属性`);
            return Reflect.has(target,key);
        },
        ownKeys(target){

            return Reflect.ownKeys(target);
        },
    })

    console.log(obj2.id);
    obj2.name = '张三';
    obj2.id = 333;//设置属性
    console.log(obj2.name);
    console.log(obj2.id);//获取属性

    console.log('id' in obj2);//这种方法进行调用has方法

   console.log(Object.keys(obj2))//返回所有属性

  • web安全攻击手段有哪些?
    1、XSS跨站脚本攻击

    • 定义:就是攻击者可以在我们的网站中嵌入恶意脚本,在进行分享出去,当用户进行浏览的时候,会被攻击或者是泄漏隐私
  • 解决办法:设置httpOnly,防止获取我们的cookie信息。可以将我们页面中的input框等可输入的进行代码,特殊符号的转义,还有就是长度进行限制
    2、CSRF跨站请求伪造

  • 定义:就是我们使用localhost:3000进行访问,攻击者可以在localhost:3000进行访问我们的数据及恶意的攻击

  • 解决办法:使用token进行验证。http头中自定义属性进行验证

  • TCP的三次连接

  • 直接举例说明

  • 小明——客户端,小红——服务端
    1、小明给小红打电话,接通了之后,小明说喂,能听到吗,这就是相当于连接建立了
    2、小红给小明回应,能听到,你能听到我说话吗,这就相当于是请求响应
    3、小明听到小红的回应后,好的,这相当于是连接确认,在这之后小明和小红就可以通话/交换信息了

  • TCP的四次挥手

  • 直接举例说明
    1、小明对小红说,我所有的东西说完了,我要挂电话了
    2、小红说,收到,我这边还有一些东西没说
    3、经过若干秒后,小红也说完了,小红说,我说完了,现在可以挂断了
    4、小明收到消息后,又等了若干时间后,挂断了电话

  • 地址栏输入URL发生了什么

  • 首先输入URL之后,去查找域名是否被本地DNS缓存,解析域名,tcp的连接(三次握手,四次挥手),请求服务器,服务器响应,HTML进行重绘和回流,渲染页面

template 和 JSX的区别

  • 首先他们两个都是render的一种表现形式,最终都会编译成render进行渲染
  • template更加符合我们的视图,结构分离
  • JSX相对于template更加的灵活

SPA单页面的优缺点

  • 优点:
    1、用户体验好
    2、快
    3、内容的改变不用重新加载整个页面,避免了不必要的跳转到渲染
  • 缺点:
    1、首屏加载慢(loading进行解决/骨架屏)
    2、对seo不友好

Vue中的data中某一个属性值发生改变,视图会立即更新渲染吗?

  • 不会立即同步更新渲染
  • vue在更新dom的时候是异步执行的,只要侦听到数据的变化,vue将开启一个队列,并缓冲在同一个事件循环中发生的所有数据的变更
  • 如果同一个watcher被多次进行触发,只会被推入到队列中一次。(这种在缓冲时去除重复的数据对于避免不重要的计算和dom操作是非常重要的)
  • 然后在下一次的事件循环tick中,vue刷新队列并执行实际的工作(已经进行去重的数据)

vue中的watch,computed和methods的区别

  • 首先methods没有缓存,只要调用就会进行触发
  • computed 有缓存,只有里面的数据发生了改变,才会进行更新,不支持异步,只支持data,props
  • watch没有缓存,支持异步的数据,只要数据发生变化就会触发,有两个参数新老值

如何减少重绘和回流

  • 重绘:就是页面的颜色啥的进行改变,不会影响页面的布局
  • 回流:就是页面的布局发生改变,需要重新进行计算和渲染
  • 使用transfrom替代top等
  • 使用visibility替换display:none;(因为前者只会引起重绘,后者会触发回流)

url输入到渲染的过程?

  • url解析(是否是合法的http)
  • DNS查询(将http解析为ip地址)
  • tcp链接(三次挥手,4次握手)
  • 向服务器发送请求
  • 服务器响应
  • 页面进行渲染:
    1、解析html,构建dom树
    2、解析css,生成css树
    3、计算html,css元素的尺寸,位置
    4、进行绘制

rsa加密是非对称加密

  • 非对称加密:就是需要我们知道公钥和私钥,然后通过算法进行加密
  • 对称加密:就是我们只知道公钥,私钥是我们请求的时候进行传递过来的,但是要是被劫持了,那么就会有泄露的风险
  • 总结:一般都是使用非对称加密,安全

文件的大小

  • 文件的大小我们可以通过file文件中的属性size进行指导,在通过slice进行截取切割,
  • 也可以进行判断文件的大小
  • 要是文件要判断是不是png图片,使用二进制流读取文件前六位,每个文件都是不同的

Vue组件模板会在某些情况下受到HTML的限制,如在table表格中使用组件是无效的,常见的还有在ul,ol,select中使用组件也是会被限制的

  • 我们为了解决在table表格标签中写上我们的组件,使用is属性来进行挂载
  • 我们也可以使用is这个属性,进行动态的渲染组件,写一个公共的部分,通过绑定is来实现不同的地方进行渲染组件

插槽

  • 默认插槽:
    1、就是在组件中可以进行写入一些标签,可以在组建的内部进行渲染
    2、直接在组件的内部使用就可以了

  • 具名插槽:
    1、就是我们要在不同的地方,显示不同数据,这个时候就要显示了
    2、使用name在slot标签上进行起不同的名字
    3、在使用组件插槽的时候,在标签上显示不同的名字就行slot='名字'

  • 作用域插槽:
    1、就是子组件传递数据给父组件,然后在子组件中进行显示
    2、在子组件的slot标签上动态的绑定我们需要的数据,或者是静态的传递
    3、在父组件的子组件标签中使用template标签进行渲染,在template标签上使用v-slot=“随便起一个名字(aa)”,在里面就是aa.传递过来的数据就可以了

  • this.$slot.default 是获取默认的插槽数据

  • this.$slot.head 获取头部slot的数据

vue3.0的语法的一些总结

vue2.0 ======================= vue3.0

beforeCreatec ================ setup

created ====================== setup

beforeMount ================== onBeforeMount

mounted ====================== onMounted

beforeUpdate ================= onBeforeUpdate

updated ====================== onUpdated

beoreDestory ================= onBeforeUnmount

destory ====================== onUmmounted

flex弹性盒模型布局

  • display:flex; //申明弹性盒模型,默认水平排列
  • flex-direction:row | row-reverse | column | column-reverse //排列的方向,水平,水平倒序, 垂直, 垂直倒序
  • flex-wrap; nowrap | wrap | wrap-reverse //默认不换行, 换行, 换行倒序
  • flex-flow: || //这是排列和换行的简写 row nowrap 默认水平不换行
  • justify-content: flex-start | flex-end | center | space-between | space-around //水平的对齐方式,头部(左边)对齐,尾部(右边)对齐 中间 两端 均匀对齐
  • align-item: flex-start | flex-end | center | baseline | stretch //垂直对齐方式, 顶部(上) 尾部 (下) 中间 文字对齐 上下占满空间(没有设置高度或auto,占满上下空间)
  • align-content: flex-start | flex-end | center | space-between | space-around | stretch ///这个属性是对多个盒子进行垂直对齐的方式
  • order: 1 - 9999999 //数值越小,排列顺序越往前
  • flex-grow:0-1 //填充,默认是0,有剩余空间,也不放大
  • flex-shrink: 1 // 缩放默认为1,空间不足,可以进行缩小,如果其他都是1,当前为0,其他进行缩小,当前不变
  • flex-basis: auto //默认auto 本来的大小, 分配多余空间之前占据的大小
  • flex: || || //三者简写

箭头函数和普通函数的区别

1、写法不同,箭头函数使用()=>{} ,普通函数使用function(){}
2、箭头函数都是匿名函数,普通函数可以是匿名函数也可以是命名函数
3、箭头函数不能使用构造函数,使用new进行声明
4、箭头函数本身是没有this的,声明时可以捕获上下文的this供自己使用,普通函数的this总是指向调用它的对象
5、箭头函数没有arguments对象,普通函数有
6、箭头函数的this永远指向其上下文的this,任何方法都改变不了它的指向,如call,apply,bind都不行

vue和react的相同点和不同点

  • 相同点
    1、使用虚拟dom
    2、都是响应式数据和组件化
  • 不同点
    1、vue是双向数据绑定
    2、react 是单向数据流
    3、vue内置dalailng

原生的上拉加载和下拉刷新

  • 上拉加载
    image.png
  • 看到上面的图片,大致的意思就是scrollTop+clientHeight >= scrollHeight-触底的距离
let clientHeight  = document.documentElement.clientHeight; //浏览器高度
let scrollHeight = document.body.scrollHeight;
let scrollTop = document.documentElement.scrollTop;
 
let distance = 50;  //距离视窗还用50的时候,开始触发;

if ((scrollTop + clientHeight) >= (scrollHeight - distance)) {
    console.log("开始加载数据");
}
  • 下拉刷新
  • 首先要经历3个过程,touchstart,touchmove,touchend,确定用户开始触发时候的y轴距离,移动的距离-开始的距离,结束的时候,在进行请求或者是做一些其他的操作

  • 111
  • 222
  • 333
  • 444
  • 555
  • ...
  • touchstart
var _element = document.getElementById('refreshContainer'),
    _refreshText = document.querySelector('.refreshText'),
    _startPos = 0,  // 初始的值
    _transitionHeight = 0; // 移动的距离

_element.addEventListener('touchstart', function(e) {
    _startPos = e.touches[0].pageY; // 记录初始位置
    _element.style.position = 'relative';
    _element.style.transition = 'transform 0s';
}, false);
  • touchmove
_element.addEventListener('touchmove', function(e) {
    // e.touches[0].pageY 当前位置
    _transitionHeight = e.touches[0].pageY - _startPos; // 记录差值

    if (_transitionHeight > 0 && _transitionHeight < 60) { 
        _refreshText.innerText = '下拉刷新'; 
        _element.style.transform = 'translateY('+_transitionHeight+'px)';

        if (_transitionHeight > 55) {
            _refreshText.innerText = '释放更新';
        }
    }                
}, false);
  • touchend
_element.addEventListener('touchend', function(e) {
    _element.style.transition = 'transform 0.5s ease 1s';
    _element.style.transform = 'translateY(0px)';
    _refreshText.innerText = '更新中...';
    // todo...

}, false);
  • vue中父组件监听子组件的生命周期
  • 方法一
// Parent.vue

// Child.vue
mounted() {
  this.$emit("mounted");
}
  • 方法二

//  Parent.vue

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},
//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},     
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
  • 可以在mounted中监听到beforeDistory生命周期进行清除定时器
  • 参考链接https://blog.csdn.net/T_shiyi/article/details/108668263
  • 重绘和回流
  • 重绘:一般页面中的背景,颜色等变化
  • 回流:页面的宽度,高度等发生变化,需要进行重新计算,渲染
  • 如何减少回流:
    1、用translate替代top改变
    2、使用opacity代替visibility
    3、不要使用table布局,一个小的改动都需要进行重新计算渲染
  • this的指向问题
  • 关于这个this的指向问题,要靠自己的理解,不用死记硬背,但是要多注意看题

你可能感兴趣的:(2021-6面试)