前端小记 前端小记
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
    • Vue
  • 学习笔记

    • 《Vue》踩坑笔记
    • TypeScript学习笔记
    • 小程序笔记
    • JavaScript设计模式笔记
  • 工具
  • CentOS
  • Java
  • Docker
  • Linux
  • Maven
  • MySQL
  • 其他
  • 技术文档
  • GitHub部署及推送
  • Nodejs
  • 博客搭建
  • Fullpage全屏轮播插件
  • svn
  • 学习
  • 系统重装
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sweetheart

前端小记
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
    • Vue
  • 学习笔记

    • 《Vue》踩坑笔记
    • TypeScript学习笔记
    • 小程序笔记
    • JavaScript设计模式笔记
  • 工具
  • CentOS
  • Java
  • Docker
  • Linux
  • Maven
  • MySQL
  • 其他
  • 技术文档
  • GitHub部署及推送
  • Nodejs
  • 博客搭建
  • Fullpage全屏轮播插件
  • svn
  • 学习
  • 系统重装
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • HTML

  • CSS

  • JavaScript

  • Vue

  • 学习笔记

    • 《Vue》踩坑笔记
    • css中使用js的变量
    • 从0到1实现一个属于自己的VScode插件
    • 从0到1实现自己的埋点SDK
    • TypeScript学习笔记
    • 浏览器渲染原理解析
    • Vue原理解析
    • 前端性能优化
    • 前端构建工具
    • 常用的前端工具库
    • TypeScript笔记
    • 小程序笔记
    • JavaScript设计模式总结笔记
    • 常用知识
      • 1. Vue 中 v-if 和 v-for 的优先级
      • 2. Vue2.x 中 data 为什么是一个函数而不是一个对象
        • 2.1 原因
        • 2.2 原理分析
      • 3. Vue 中如何创建一个非响应式对象
        • Vue 2 中实现方式
        • 3.2 Vue3 中实现方式
      • 4. Vue nextTick 实现原理
        • 4.1 nextTick 是什么,为什么要有这个概念的出现?
        • 4.2 使用场景
        • 4.3 执行过程
      • 5. Vue 组件之间通信的方式
        • 5.1 props 父传子
        • 5.2 $emits 子传父
        • 5.3 ref 父操作子
        • 5.4 provide/inject 父子或祖孙
        • 5.5 eventBus 事件总线 兄弟组件
        • 5.6 $children $parent 父子之间传值
        • 5.7 $attrs $listeners 父子或祖孙
        • 5.8 Vuex
        • 5.9 localStorage、sessionStorage
        • 参考链接
      • 6. 函数的防抖和节流
        • 6.1 什么是防抖、节流?
        • 6.2 常见使用场景
        • 6.3 用法
        • 参考链接
      • 7. Vue 中计算属性 computed 和 watch 的区别
      • 8. 元素透明实现方式,有何区别?
      • 9. 两个一维数组合并为一个二维数组
  • 工具

  • 其他

  • 前端
  • 学习笔记
sweetheart
2023-07-12
目录

常用知识

# 日常工作知识

# 1. Vue 中 v-if 和 v-for 的优先级

  • v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true 值的时候被渲染。

  • v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组或者对象,而 item 则是被迭代的数组元素的别名,在 v-for 的时候,建议设置 key 值,并且保证每个 key 值是独一无二的,这便于 diff 算法进行优化。

  • Vue2 中 v-for 的优先级高于 v-if,如果两者同时出现会导致 for 循环始终被执行,浪费性能,不建议同时使用。

  • 可以使用 template 包裹 v-for 所在的元素标签,在 template 层使用 v-if 判断。

  • 从 Vue2 源码中查看两者的优先级(源码位置:\vue-dev\src\compiler\codegen\index.js 大致 56 行)

    export function genElement (el: ASTElement, state: CodegenState): string {
    if (el.parent) {
      el.pre = el.pre || el.parent.pre
    }
    if (el.staticRoot && !el.staticProcessed) {
      return genStatic(el, state)
    } else if (el.once && !el.onceProcessed) {
      return genOnce(el, state)
    } else if (el.for && !el.forProcessed) {
      return genFor(el, state)
    } else if (el.if && !el.ifProcessed) {
      return genIf(el, state)
    } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
      return genChildren(el, state) || 'void 0'
    } else if (el.tag === 'slot') {
      return genSlot(el, state)
    } else {
      // component or element
      ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  • Vue3 中 v-if 的优先级高于 v-for,此时两者是不可出现在同一个元素上的。

结论:Vue2 中 v-for 的优先级高于 v-if,Vue3 中 v-if 的优先级高于 v-for ,尽量避免在同一个元素上面同时使用 v-if 和 v-for,建议使用计算属性替代。

# 2. Vue2.x 中 data 为什么是一个函数而不是一个对象

# 2.1 原因

  • Vue 根实例的时候定义 data 属性既可以是一个对象,也可以是一个函数(根实例是单例),不会产生数据污染情况。

  • 在定义好一个组件时,Vue 最终都会通过 Vue.extend()构成组件实例,如果 data 是一个对象,会导致多个组件实例共用同一个 data 数据(多个组件实例指向同一个内存地址,不存在数据隔离),会导致组件之间的数据互相影响,产生数据污染。

  • Vue 组件实例 data 必须是函数,目的是为了防止多个组件实例对象之间共用一个 data,产生数据污染。采用函数的形式, initData 初始化时会将其作为工厂函数都会返回全新 data 对象。

# 2.2 原理分析

  • Vue 初始化 data (源码位置:/src/core/instance/state.js)

    function initData (vm: Component) {
    let data = vm.$options.data
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}
        ...
    }
    
    1
    2
    3
    4
    5
    6
    7
  • Vue 组件在创建时,自定义组件会进入 mergeOptions 进行选项合并(源码位置:/src/core/util/options.js)

    Vue.prototype._init = function (options?: Object) {
        ...
        // merge options
        if (options && options._isComponent) {
        // optimize internal component instantiation
        // since dynamic options merging is pretty slow, and none of the
        // internal component options needs special treatment.
        initInternalComponent(vm, options)
        } else {
        vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
        )
        }
        ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  • 会对定义的 data 进行数据校验,此时的 VM 实例为 undefined,会进入 if 判断,若 data 类型不是 function,则会出现警告。(源码位置:/src/core/instance/init.js)

    strats.data = function (
    parentVal: any,
    childVal: any,
    vm?: Component
    ): ?Function {
    if (!vm) {
        if (childVal && typeof childVal !== "function") {
        process.env.NODE_ENV !== "production" &&
            warn(
            'The "data" option should be a function ' +
                "that returns a per-instance value in component " +
                "definitions.",
            vm
            );
    
        return parentVal;
        }
        return mergeDataOrFn(parentVal, childVal);
    }
    return mergeDataOrFn(parentVal, childVal, vm);
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

# 3. Vue 中如何创建一个非响应式对象

# Vue 2 中实现方式

  • 方式一:将数据定义在 Vue 实例之外(即 export default 之外),注意,这样定义的数据不能在 template 模板内使用,且此变量会被所有实例对象共享,当其中一个实例对象修改了此数据时,另外的实例对象的数据也会被修改,且此方式可能会导致内存泄露,需要谨慎使用。

      const test = {
          //...
      }
    
      export default {
          //...
      }
    
    1
    2
    3
    4
    5
    6
    7
  • 方式二:将数据定义在 created 声明周期钩子函数中,这样定义的数据可以在 template 中使用,但数据的定义被分在了两个地方。

    export default {
    data() {
        return {
        // ... //一些属性
        }
    },
    
    created() {
        this.test = {
        // ​···  //一些属性
        }
    }
    // ··· //一些属性
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  • 方式三:通过 Object.defineProperty 设置目标对象的 configurable 属性为 false,此时可以使用 this.test 访问变量并可在 template 模板中使用。

    export default {
      data() {
          const data = {
          test: {
              // ... //一些属性
          }
    
          //··· // 其他属性
          }
    
          Object.defineProperty(data, 'test', {
          configurable: false
          })
    
          return data
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  • 方式四:使用 Object.freeze()或Object.preventExtensions()或Object.seal() 对数据进行冻结,冻结之后此对象再也不能被修改了。这三个方法返回传递的对象,而不是创建一个被冻结的副本。但需要注意,这三种方法都只是浅冻结即只冻结一层,如果存在嵌套对象则深层对象仍然可以改变。

    export default {
    data() {
        return {
        testData: Object.freeze({})
        }
    }
    }
    
    1
    2
    3
    4
    5
    6
    7
    • 当被冻结对象改变时,重新调用 Object.freeze()赋值
    updateTestData (newTestData) {
      this.testData = Object.freeze(newBigData)
    }
    
    1
    2
    3
  • 方式五:将数据定义在组件的自定义属性上,此时通过 this.$options.testData 就可以正常访问数据,但此时的数据也是被定义在了两个地方,且会增加调用链,如果数据更改,则需要手动调用 this.$forceUpdate()才能使模板更新。

    <template>
      <div>{{ $options.testData.test }}</div>
    </template>
    <script>
    export default {
      // 自定义属性
      testData: {
        test: 'xxx'
      },
    
      data() {
        return {}
      },
    
      methods: {
        doSomething() {
          return this.$options.testData
        }
      }
    }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

# 3.2 Vue3 中实现方式

  • 在 Vue3 中,可以使用 markRaw 标记一个对象,使其永远不会转换为 proxy。返回对象本身,具体可参考 官方文档 (opens new window)。

    const foo = markRaw({})
    
    console.log(isReactive(reactive(foo))) // false
    
    // 嵌套在其他响应式对象中时也可以使用
    
    const bar = reactive({ foo })
    
    console.log(isReactive(bar.foo)) // false
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

# 4. Vue nextTick 实现原理

# 4.1 nextTick 是什么,为什么要有这个概念的出现?

  • Vue 在更新 DOM 时是异步执行的,当监听到数据发生变化的时候不会立即去更新 DOM,而是开启一个异步更新任务队列,并缓存在同一事件循环中发生的所有数据变更,如果一直修改相同数据,异步操作队列还会进行去重,在同一事件循环中的所有数据变化完成之后再统一执行更新 DOM 操作;

  • 这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作 DOM 的次数,提高性能。

  • Vue.nextTick 是全局方法,vm.$nickTick 是实例方法,两者使用方式类似,不同的是 vm.$nickTick 会将回调的 this 自动绑定到调用它的实例上。

# 4.2 使用场景

如果想要在修改数据后立刻得到更新后的 DOM 结构,可以使用 Vue.nextTick(),放在 nextTick 当中的操作不会立即执行,而是等数据更新、DOM 更新完成之后再执行,

第一个参数为:回调函数(可以获取最近的 DOM 结构)

第二个参数为:执行函数上下文

export default {
  data() {
    return {
      msg: '哈哈哈哈'
    }
  },

  created() {
    this.$nextTick(() => {
      // ...DOM 更新后做某些事
    })
  },

  methods: {
    test() {
      // 修改数据
      this.msg = 'changed'

      // DOM 还没有更新
      this.$nextTick(function() {
        // DOM 现在更新了
        // `this` 绑定到当前实例
        console.log('新的值', this.msg)
      })
    }
  }
}
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

# 4.3 执行过程

nextTick 提供了四种异步方法 Promise.then、MutationObserver、setImmediate、setTimeout(fn,0),优先级由高到低

把回调函数放入 callbacks 等待执行,callbacks 也就是异步操作队列

将执行函数放到微任务或者宏任务中

事件循环到了微任务或者宏任务,执行函数依次执行 callbacks 中的回调

# 5. Vue 组件之间通信的方式

# 5.1 props 父传子

# 5.2 $emits 子传父

# 5.3 ref 父操作子

# 5.4 provide/inject 父子或祖孙

  • provide/ inject 是 Vue2.2.0 新增的 api, 简单来说就是父组件中通过 provide 来提供变量, 然后再子组件中通过 inject 来注入变量。

假设有三个组件: A.vue、B.vue、C.vue 其中 C 是 B 的子组件,B 是 A 的子组件

// A.vue

<template>
<div>
    <comB></comB>
</div>
</template>

<script>
import comB from '../components/test/comB.vue'

export default {
name: 'A',

provide: {
    for: 'demo'
},

components: {
    comB
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  // B.vue

  <template>
  <div>
      {{ demo }}
      <comC></comC>
  </div>
  </template>

  <script>
  import comC from '../components/test/comC.vue'

  export default {
  name: 'B',

  inject: ['for'],

  data() {
      return {
      demo: this.for
      }
  },
  components: {
      comC
  }
  }
  </script>
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
  // C.vue

  <template>
  <div>
      {{ demo }}
  </div>
  </template>

  <script>
  export default {
  name: 'C',
  inject: ['for'],
  data() {
      return {
      demo: this.for
      }
  }
  }
  </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 5.5 eventBus 事件总线 兄弟组件

Vue3 不再提供$on 与 emit 函数,Vue 实例不再实现事件接口,具体可查询官网 (opens new window)。Vue 官方已不推荐使用全局事件总线在组件之间通信,如果想用,可以采用其他库来实现,例如 mitt.js (opens new window)

# 5.6 $children $parent 父子之间传值

在#app 上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent 得到的是 undefined,而在最底层的子组件拿$children 是个空数组。$children 的值是数组,而$parent 是个对象。

# 5.7 $attrs $listeners 父子或祖孙

Vue3 中已删除 $listeners 对象,事件监听器现在只是属性,以 on 为前缀,因此是 $attrs 对象的一部分,

# 5.8 Vuex

# 5.9 localStorage、sessionStorage

# 参考链接

  • 掘金高赞文章 (opens new window)

# 6. 函数的防抖和节流

# 6.1 什么是防抖、节流?

  • 防抖函数的作用就是控制函数在一定时间内的执行次数。防抖意味着 N 秒内函数只会被执行一次(最后一次),如果 N 秒内再次被触发,则重新计算延迟时间。

  • 节流函数的作用是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行,如果这个单位时间内多次触发函数,只能有一次生效。

  • 函数防抖关注一定时间连续触发的事件只在最后执行一次,而函数节流侧重于间隔时间执行,在一个单位时间内,只能触发一次函数,如果这个单位时间内触发多次函数,只有一次生效。

  • 节流防抖就好比乘电梯,比如 delay 是 10 秒,防抖就是电梯每进来一个人就要等 10 秒再运行,而节流就是电梯保证每 10 秒可以运行一次。

# 6.2 常见使用场景

  • 防抖的应用场景:(防抖可能用于无法预知的用户主动行为,如用户输入内容去服务端动态搜索结果。用户打字的速度等是无法预知的,具有非规律性。)

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求

  • 手机号、邮箱验证输入检测

  • 搜索联想功能

  • 窗口大小 Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

  • 节流的应用场景:(节流可能用于一些非用户主动行为或者可预知的用户主动行为,如用户滑动商品橱窗时发送埋点请求、滑动固定的高度是已知的逻辑,具有规律性。)

  • 滚动加载,加载更多或滚到底部监听

  • 高频点击提交,表单重复提交

# 6.3 用法

  • 防抖
/**
  * @description 函数防抖
  * @param {fn : function} 回调执行函数
  * @param {delay : number} 延迟时间
  * @return function
*/

export function debounce(fn, delay = 200) {
  let timer

  return function() {
    let that = this

    let args = arguments

    timer && clearTimeout(timer)

    timer = setTimeout(function() {
      fn.apply(that, args)
    }, delay)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 节流
/**
  * @description 函数节流
  * @param {fn : function} 回调执行函数
  * @param {delay : number} 延迟时间
  * @return function
*/

export function throttle(fn, delay = 200) {

  let timer
  return function() {
    let that = this

    let args = arguments

    if (timer) {
      return
    }

    timer = setTimeout(function() {
      fn.apply(that, args)

      timer = null
    }, delay)
  }
}
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

# 参考链接

掘金高赞文章 (opens new window)

# 7. Vue 中计算属性 computed 和 watch 的区别

  • computed

    • computed 会被缓存,不支持异步。

    • 注意,computed 在****响应式****的依赖项发生变化时会触发 getter,前提是 computed 里的值必须要在 template 模板里使用,如果在模板中没有使用 computed 里的值,该计算属性中的 getter 是不会触发的。

    • 如果使用 v-model 绑定 computed 计算后的值,再修改这个计算后的值时,会依次触发 setter --> getter(不一定会触发) --> updated,但并不意味着所有的修改 computed 计算后的值的操作都会依次执行这个顺序,只有在 setter 中修改了 computed 计算所依赖的响应式数据时才会触发 getter。

  • watch

    • watch 不会被缓存,可以支持异步操作。

    • Vue 实例会在实例化的时候调用 $watch(),

# 8. 元素透明实现方式,有何区别?

  • 方式一:使用 opacity 设置元素的不透明度,取值从 0.0 到 1.0,值越小,越透明。

  • 方式二:使用 rgba 设置元素的颜色,其中 a 为元素的透明度,取值从 0.0 到 1.0,0.0 表示完全透明,1.0 为完全不透明,不可继承。

两者都是 css3 新增的属性,opacity 会继承父元素的 opacity 属性,且子元素设置的 opacity 属性只有再小于或等于父元素的 opacity 属性值时才会生效,否则不生效。rgba 属性则只会对当前设置的元素本身生效,不会对子元素产生影响。

# 9. 两个一维数组合并为一个二维数组

let data = ['哈哈哈','嘿嘿嘿','哎呦喂']
let array = ['3','4','5']

data = data.map((currentValue,index)=>[currentValue,array[index]])

//方式二
data = Array.from(data,(item,index)=> [item,array[index]])

console.log(JSON.stringify(data))
1
2
3
4
5
6
7
8
9
完善页面 (opens new window)
上次更新: 2024-11-28 17:23:47
JavaScript设计模式总结笔记
webpack 4 配置

← JavaScript设计模式总结笔记 webpack 4 配置→

最近更新
01
git常用操作手册
12-26
02
常用的前端工具库
12-19
03
前端构建工具
12-19
更多文章>
前端小记 版权所有 | Copyright © 2021-2024
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式