乐趣区

关于vue.js:京东前端高频vue面试题边面边更

Redux 和 Vuex 有什么区别,它们的独特思维

(1)Redux 和 Vuex 区别

  • Vuex 改良了 Redux 中的 Action 和 Reducer 函数,以 mutations 变动函数取代 Reducer,无需 switch,只需在对应的 mutation 函数里扭转 state 值即可
  • Vuex 因为 Vue 主动从新渲染的个性,无需订阅从新渲染函数,只有生成新的 State 即可
  • Vuex 数据流的程序是∶View 调用 store.commit 提交对应的申请到 Store 中对应的 mutation 函数 ->store 扭转(vue 检测到数据变动主动渲染)

艰深点了解就是,vuex 弱化 dispatch,通过 commit 进行 store 状态的一次更变; 勾销了 action 概念,不用传入特定的 action 模式进行指定变更; 弱化 reducer,基于 commit 参数间接对数据进行转变,使得框架更加繁难;

(2)独特思维

  • 单—的数据源
  • 变动能够预测

实质上:redux 与 vuex 都是对 mvvm 思维的服务,将数据从视图中抽离的一种计划;
模式上:vuex 借鉴了 redux,将 store 作为全局的数据中心,进行 mode 治理;

v-model 能够被用在自定义组件上吗?如果能够,如何应用?

能够。v-model 实际上是一个语法糖,如:

<input v-model="searchText">

实际上相当于:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

用在自定义组件上也是同理:

<custom-input v-model="searchText">

相当于:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

显然,custom-input 与父组件的交互如下:

  1. 父组件将 searchText 变量传入 custom-input 组件,应用的 prop 名为value
  2. custom-input 组件向父组件传出名为 input 的事件,父组件将接管到的值赋值给searchText

所以,custom-input 组件的实现应该相似于这样:

Vue.component('custom-input', {props: ['value'],
  template: `    <input      v-bind:value="value"      v-on:input="$emit('input', $event.target.value)"    >  `
})

MVC 和 MVVM 区别

MVC

MVC 全名是 Model View Controller,是模型 (model)-视图(view)-控制器(controller) 的缩写,一种软件设计榜样

  • Model(模型):是应用程序中用于解决应用程序数据逻辑的局部。通常模型对象负责在数据库中存取数据
  • View(视图):是应用程序中解决数据显示的局部。通常视图是根据模型数据创立的
  • Controller(控制器):是应用程序中解决用户交互的局部。通常控制器负责从视图读取数据,管制用户输出,并向模型发送数据

MVC 的思维:一句话形容就是 Controller 负责将 Model 的数据用 View 显示进去,换句话说就是在 Controller 外面把 Model 的数据赋值给 View。

MVVM

MVVM 新增了 VM 类

  • ViewModel 层:做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】,行将后端传递的数据转化成所看到的页面。实现的形式是:数据绑定。二是将【视图】转化成【模型】,行将所看到的页面转化成后端的数据。实现的形式是:DOM 事件监听。

MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的主动同步,也就是当 Model 的属性扭转时,咱们不必再本人手动操作 Dom 元素,来扭转 View 的显示,而是扭转属性后该属性对应 View 层显示会主动扭转(对应 Vue 数据驱动的思维)

整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不必再用选择器操作 DOM 元素。因为在 MVVM 中,View 不晓得 Model 的存在,Model 和 ViewModel 也察看不到 View,这种低耦合模式进步代码的可重用性

留神:Vue 并没有齐全遵循 MVVM 的思维 这一点官网本人也有阐明

那么问题来了 为什么官网要说 Vue 没有齐全遵循 MVVM 思维呢?

  • 严格的 MVVM 要求 View 不能和 Model 间接通信,而 Vue 提供了 $refs 这个属性,让 Model 能够间接操作 View,违反了这一规定,所以说 Vue 没有齐全遵循 MVVM。

computed 的实现原理

computed 实质是一个惰性求值的观察者。

computed 外部实现了一个惰性的 watcher, 也就是 computed watcher,computed watcher 不会立即求值, 同时持有一个 dep 实例。

其外部通过 this.dirty 属性标记计算属性是否须要从新求值。

当 computed 的依赖状态产生扭转时, 就会告诉这个惰性的 watcher,

computed watcher 通过 this.dep.subs.length 判断有没有订阅者,

有的话, 会从新计算, 而后比照新旧值, 如果变动了, 会从新渲染。(Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 从新渲染,实质上是一种优化。)

没有的话, 仅仅把 this.dirty = true。(当计算属性依赖于其余数据时,属性并不会立刻从新计算,只有之后其余中央须要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)个性。)

Vue3.0 和 2.0 的响应式原理区别

Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够间接监听对象和数组的变动,并且有多达 13 种拦挡办法。

相干代码如下

import {mutableHandlers} from "./baseHandlers"; // 代理相干逻辑
import {isObject} from "./util"; // 工具办法

export function reactive(target) {
  // 依据不同参数创立不同响应式对象
  return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {if (!isObject(target)) {return target;}
  const observed = new Proxy(target, baseHandler);
  return observed;
}

const get = createGetter();
const set = createSetter();

function createGetter() {return function get(target, key, receiver) {
    // 对获取的值进行喷射
    const res = Reflect.get(target, key, receiver);
    console.log("属性获取", key);
    if (isObject(res)) {
      // 如果获取的值是对象类型,则返回以后对象的代理对象
      return reactive(res);
    }
    return res;
  };
}
function createSetter() {return function set(target, key, value, receiver) {const oldValue = target[key];
    const hadKey = hasOwn(target, key);
    const result = Reflect.set(target, key, value, receiver);
    if (!hadKey) {console.log("属性新增", key, value);
    } else if (hasChanged(value, oldValue)) {console.log("属性值被批改", key, value);
    }
    return result;
  };
}
export const mutableHandlers = {
  get, // 当获取属性时调用此办法
  set, // 当批改属性时调用此办法
};

v-model 的原理?

咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:

  • text 和 textarea 元素应用 value 属性和 input 事件;
  • checkbox 和 radio 应用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

<input v-model='something'>

相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>

子组件:<div>{{value}}</div>

props:{value: String},
methods: {test1(){this.$emit('input', '小红')
  },
},

参考 前端进阶面试题具体解答

Vue 组件间通信有哪几种形式?

​ Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。

(1)props / $emit 实用 父子组件通信

这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。

(2)ref$parent / $children 实用 父子组件通信

  • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
  • $parent / $children:拜访父 / 子实例

(3)EventBus($emit / $on) 实用于 父子、隔代、兄弟组件通信

这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。

(4)$attrs/$listeners 实用于 隔代组件通信

  • $attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (class 和 style 除外)。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 (class 和 style 除外),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用。
  • $listeners:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on="$listeners" 传入外部组件

(5)provide / inject 实用于 隔代组件通信

先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。

(6)Vuex 实用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state)。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
  • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。

Vue 组件通信有哪几种形式

  1. props 和 $emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过 $emit 触发事件来做到的
  2. $parent,$children 获取以后组件的父组件和以后组件的子组件
  3. $attrs 和 $listeners A->B->C。Vue 2.4 开始提供了 $attrs 和 $listeners 来解决这个问题
  4. 父组件中通过 provide 来提供变量,而后在子组件中通过 inject 来注入变量。(官网不举荐在理论业务中应用,然而写组件库时很罕用)
  5. $refs 获取组件实例
  6. envetBus 兄弟组件数据传递 这种状况下能够应用事件总线的形式
  7. vuex 状态治理

computed 和 watch 区别

  1. 当页面中有某些数据依赖其余数据进行变动的时候,能够应用计算属性computed

Computed实质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图。实用于计算比拟耗费性能的计算场景。当表达式过于简单时,在模板中放入过多逻辑会让模板难以保护,能够将简单的逻辑放入计算属性中解决

<template>{{fullName}}</template>
export default {data(){
        return {
            firstName: 'zhang',
            lastName: 'san',
        }
    },
    computed:{fullName: function(){return this.firstName + ' ' + this.lastName}
    }
}
  1. watch用于察看和监听页面上的 vue 实例,如果要在数据变动的同时进行异步操作或者是比拟大的开销,那么 watch 为最佳抉择

Watch没有缓存性,更多的是察看的作用,能够监听某些数据执行回调。当咱们须要深度监听对象中的属性时,能够关上 deep:true 选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话能够应用字符串模式监听,如果没有写到组件中,不要遗记应用 unWatch 手动登记

<template>{{fullName}}</template>
export default {data(){
        return {
            firstName: 'zhang',
            lastName: 'san',
            fullName: 'zhang san'
        }
    },
    watch:{firstName(val) {this.fullName = val + ' ' + this.lastName},
        lastName(val) {this.fullName = this.firstName + ' ' + val}
    }
}

computed:

  • computed是计算属性, 也就是计算值, 它更多用于计算值的场景
  • computed具备缓存性,computed的值在 getter 执行后是会缓存的,只有在它依赖的属性值扭转之后,下一次获取 computed 的值时才会从新调用对应的 getter 来计算
  • computed实用于计算比拟耗费性能的计算场景

watch:

  • 更多的是「察看」的作用, 相似于某些数据的监听回调, 用于察看 props $emit 或者本组件的值, 当数据变动时来执行回调进行后续操作
  • 无缓存性,页面从新渲染时值不变动也会执行

小结:

  • computedwatch 都是基于 watcher 来实现的
  • computed属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性办法不会从新执行
  • watch是监控值的变动,当值发生变化时调用其对应的回调函数
  • 当咱们要进行数值计算, 而且依赖于其余数据,那么把这个数据设计为computed
  • 如果你须要在某个数据变动时做一些事件,应用 watch 来察看这个数据变动

答复范例

思路剖析

  • 先看 computed, watch 两者定义,列举应用上的差别
  • 列举应用场景上的差别,如何抉择
  • 应用细节、注意事项
  • vue3变动

computed特点:具备响应式的返回值

const count = ref(1)
const plusOne = computed(() => count.value + 1)

watch特点:侦测变动,执行回调

const state = reactive({count: 0})
watch(() => state.count,
  (count, prevCount) => {/* ... */}
)

答复范例

  1. 计算属性能够从组件数据派生出新数据,最常见的应用形式是设置一个函数,返回计算之后的后果,computedmethods 的差别是它具备缓存性,如果依赖项不变时不会从新计算。侦听器能够侦测某个响应式数据的变动并执行副作用,常见用法是传递一个函数,执行副作用,watch 没有返回值,但能够执行异步操作等简单逻辑
  2. 计算属性罕用场景是简化行内模板中的简单表达式,模板中呈现太多逻辑会是模板变得臃肿不易保护。侦听器罕用场景是状态变动之后做一些额定的 DOM 操作或者异步操作。抉择采纳何用计划时首先看是否须要派生出新值,根本能用计算属性实现的形式首选计算属性.
  3. 应用过程中有一些细节,比方计算属性也是能够传递对象,成为既可读又可写的计算属性。watch能够传递对象,设置 deepimmediate 等选项
  4. vue3watch 选项产生了一些变动,例如不再能侦测一个点操作符之外的字符串模式的表达式;reactivity API中新呈现了 watchwatchEffect 能够齐全代替目前的 watch 选项,且性能更加弱小

根本应用

// src/core/observer:45;

// 渲染 watcher  /  computed watcher  /  watch
const vm = new Vue({
    el: '#app',
    data: {
        firstname:'张',
        lastname:'三'
    },
    computed:{ // watcher  =>   firstname lastname
        // computed 只有取值时才执行

        // Object.defineProperty .get
        fullName(){ // firstName lastName 会收集 fullName 计算属性
            return this.firstname + this.lastname
        }
    },
    watch:{firstname(newVal,oldVal){console.log(newVal)
        }
    }
});

setTimeout(() => {
    debugger;
    vm.firstname = '赵'
}, 1000);

相干源码

// 初始化 state
function initState (vm: Component) {vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {initData(vm)
  } else {observe(vm._data = {}, true /* asRootData */)
  }

  // 初始化计算属性
  if (opts.computed) initComputed(vm, opts.computed) 

  // 初始化 watch
  if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)
  }
}

// 计算属性取值函数
function createComputedGetter (key) {return function computedGetter () {const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {if (watcher.dirty) { // 如果值依赖的值发生变化,就会进行从新求值
        watcher.evaluate(); // this.firstname lastname}
      if (Dep.target) { // 让计算属性所依赖的属性 收集渲染 watcher
        watcher.depend()}
      return watcher.value
    }
  }
}

// watch 的实现
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    debugger;
    if (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options) // 创立 watcher,数据更新调用 cb
    if (options.immediate) {
      try {cb.call(vm, watcher.value)
      } catch (error) {handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {watcher.teardown()
    }
}

能说下 vue-router 中罕用的 hash 和 history 路由模式实现原理吗?

(1)hash 模式的实现原理

晚期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简略,location.hash 的值就是 URL 中 # 前面的内容。比方上面这个网站,它的 location.hash 的值为 ‘#search’:

https://www.word.com#search

hash 路由模式的实现次要是基于上面几个个性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 局部不会被发送;

hash 值的扭转,都会在浏览器的拜访历史中减少一个记录。因而咱们能通过浏览器的回退、后退按钮管制 hash 的切换;

  • 能够通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会产生扭转;或者应用 JavaScript 来对 loaction.hash 进行赋值,扭转 URL 的 hash 值;
  • 咱们能够应用 hashchange 事件来监听 hash 值的变动,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变动。其中做最次要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录,如下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 路由模式的实现次要基于存在上面几个个性:

  • pushState 和 repalceState 两个 API 来操作实现 URL 的变动;
  • 咱们能够应用 popstate 事件来监听 url 的变动,从而对页面进行跳转(渲染);
  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时咱们须要手动触发页面跳转(渲染)。

vue 中应用了哪些设计模式

1. 工厂模式 – 传入参数即可创立实例

虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode

2. 单例模式 – 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉

3. 公布 - 订阅模式 (vue 事件机制)

4. 观察者模式 (响应式数据原理)

5. 装璜模式: (@装璜器的用法)

6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略

vue 如何监听对象或者数组某个属性的变动

当在我的项目中间接设置数组的某一项的值,或者间接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为 Object.defineProperty()限度,监听不到变动。

解决形式:

  • this.$set(你要扭转的数组 / 对象,你要扭转的地位 /key,你要改成什么 value)
this.$set(this.arr, 0, "OBKoro1"); // 扭转数组 this.$set(this.obj, "c", "OBKoro1"); // 扭转对象
  • 调用以下几个数组的办法
splice()、push()、pop()、shift()、unshift()、sort()、reverse()

vue 源码里缓存了 array 的原型链,而后重写了这几个办法,触发这几个办法的时候会 observer 数据,意思是应用这些办法不必再进行额定的操作,视图主动进行更新。举荐应用 splice 办法会比拟好自定义, 因为 splice 能够在数组的任何地位进行删除 / 增加操作

vm.$set 的实现原理是:

  • 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  • 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决(defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法)

v-if、v-show、v-html 的原理

  • v-if 会调用 addIfCondition 办法,生成 vnode 的时候会疏忽对应节点,render 的时候就不会渲染;
  • v-show 会生成 vnode,render 的时候也会渲染成实在节点,只是在 render 过程中会在节点的属性中批改 show 属性值,也就是常说的 display;
  • v-html 会先移除节点下的所有节点,调用 html 办法,通过 addProp 增加 innerHTML 属性,归根结底还是设置 innerHTML 为 v -html 的值。

如何了解 Vue 中模板编译原理

Vue 的编译过程就是将 template 转化为 render 函数的过程

  • 解析生成 AST 树 template 模板转化成 AST 语法树,应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决
  • 标记优化 对动态语法做动态标记 markup(动态节点如div 下有 p 标签内容不会变动) diff来做优化 动态节点跳过 diff 操作

    • Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够跳过对它们的比对,对运行时的模板起到很大的优化作用
    • 期待后续节点更新,如果是动态的,不会在比拟 children
  • 代码生成 编译的最初一步是将优化后的AST 树转换为可执行的代码

答复范例

思路

  • 引入 vue 编译器概念
  • 阐明编译器的必要性
  • 论述编译器工作流程

答复范例

  1. Vue中有个独特的编译器模块,称为 compiler,它的次要作用是将用户编写的template 编译为 js 中可执行的 render 函数。
  2. 之所以须要这个编译过程是为了便于前端能高效的编写视图模板。相比而言,咱们还是更违心用 HTML 来编写视图,直观且高效。手写 render 函数不仅效率底下,而且失去了编译期的优化能力。
  3. Vue 中编译器会先对 template 进行解析,这一步称为 parse,完结之后会失去一个JS 对象,咱们称为 形象语法树 AST,而后是对 AST 进行深加工的转换过程,这一步成为 transform,最初将后面失去的AST 生成为 JS 代码,也就是 render 函数

可能的诘问

  1. Vue中编译器何时执行?

new Vue()之后。Vue 会调用 _init 函数进行初始化,也就是这里的 init 过程,它会初始化生命周期、事件、propsmethodsdatacomputedwatch等。其中最重要的是通过 Object.defineProperty 设置 settergetter 函数,用来实现「响应式」以及「依赖收集」

  • 初始化之后调用 $mount 会挂载组件,如果是运行时编译,即不存在 render function 然而存在 template 的状况,须要进行「编译」步骤
  • compile编译能够分成 parseoptimizegenerate 三个阶段,最终须要失去 render function
  • React有没有编译器?

react 应用 babelJSX语法解析

<div id="app"></div>
<script>
    let vm = new Vue({
        el: '#app',
        template: `<div>
            // <span>hello world</span> 是动态节点
            <span>hello world</span>    
            // <p>{{name}}</p> 是动静节点
            <p>{{name}}</p>
        </div>`,
        data() {return { name: 'test'}
        }
    });
</script>

源码剖析

export function compileToFunctions(template) {
  // 咱们须要把 html 字符串变成 render 函数
  // 1. 把 html 代码转成 ast 语法树  ast 用来形容代码自身造成树结构 不仅能够形容 html 也能形容 css 以及 js 语法
  // 很多库都使用到了 ast 比方 webpack babel eslint 等等
  let ast = parse(template);
  // 2. 优化动态节点:对 ast 树进行标记, 标记动态节点
    if (options.optimize !== false) {optimize(ast, options);
    }

  // 3. 通过 ast 从新生成代码
  // 咱们最初生成的代码须要和 render 函数一样
  // 相似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))
  // _c 代表创立元素 _v 代表创立文本 _s 代表文 Json.stringify-- 把对象解析成文本
  let code = generate(ast);
  //   应用 with 语法扭转作用域为 this  之后调用 render 函数能够应用 call 扭转 this 不便 code 外面的变量取值
  let renderFn = new Function(`with(this){return ${code}}`);
  return renderFn;
}

说一下 Vue 的生命周期

Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。

  1. beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到 data、computed、watch、methods 上的办法和数据。
  2. created(创立后):实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到 $el 属性。
  3. beforeMount(挂载前):在挂载开始之前被调用,相干的 render 函数首次被调用。实例已实现以下的配置:编译模板,把 data 外面的数据和模板生成 html。此时还没有挂载 html 到页面上。
  4. mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的 html 内容替换 el 属性指向的 DOM 对象。实现模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。
  5. beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。
  6. updated(更新后):在因为数据更改导致的虚构 DOM 从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM 曾经更新,所以能够执行依赖于 DOM 的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
  7. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,this 仍能获取到实例。
  8. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

另外还有 keep-alive 独有的生命周期,别离为 activateddeactivated。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。

Vue 模版编译原理晓得吗,能简略说一下吗?

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST 树
  • 优化
  • codegen

首先解析模版,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是 将优化后的 AST 树转换为可执行的代码

computed 的实现原理

computed 实质是一个惰性求值的观察者。

computed 外部实现了一个惰性的 watcher, 也就是 computed watcher,computed watcher 不会立即求值, 同时持有一个 dep 实例。

其外部通过 this.dirty 属性标记计算属性是否须要从新求值。

当 computed 的依赖状态产生扭转时, 就会告诉这个惰性的 watcher,

computed watcher 通过 this.dep.subs.length 判断有没有订阅者,

有的话, 会从新计算, 而后比照新旧值, 如果变动了, 会从新渲染。(Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 从新渲染,实质上是一种优化。)

没有的话, 仅仅把 this.dirty = true。(当计算属性依赖于其余数据时,属性并不会立刻从新计算,只有之后其余中央须要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)个性。)

Vue 子组件和父组件执行程序

加载渲染过程:

  1. 父组件 beforeCreate
  2. 父组件 created
  3. 父组件 beforeMount
  4. 子组件 beforeCreate
  5. 子组件 created
  6. 子组件 beforeMount
  7. 子组件 mounted
  8. 父组件 mounted

更新过程:

  1. 父组件 beforeUpdate
  2. 子组件 beforeUpdate
  3. 子组件 updated
  4. 父组件 updated

销毁过程:

  1. 父组件 beforeDestroy
  2. 子组件 beforeDestroy
  3. 子组件 destroyed
  4. 父组件 destoryed

Vue 中如何进行依赖收集?

  • 每个属性都有本人的 dep 属性,寄存他所依赖的 watcher,当属性变动之后会告诉本人对应的watcher 去更新
  • 默认会在初始化时调用 render 函数,此时会触发属性依赖收集 dep.depend
  • 当属性产生批改时会触发 watcher 更新dep.notify()

依赖收集简版

let obj = {name: 'poetry', age: 20};

class Dep {constructor() {this.subs = [] // subs [watcher]
    }
    depend() {this.subs.push(Dep.target)
    }
    notify() {this.subs.forEach(watcher => watcher.update())
    }
}
Dep.target = null;
observer(obj); // 响应式属性劫持

// 依赖收集  所有属性都会减少一个 dep 属性,// 当渲染的时候取值了,这个 dep 属性 就会将渲染的 watcher 收集起来
// 数据更新 会让 watcher 从新执行

// 观察者模式

// 渲染组件时 会创立 watcher
class Watcher {constructor(render) {this.get();
    }
    get() {
      Dep.target = this;
      render(); // 执行 render
      Dep.target = null;
    }
    update() {this.get();
    }
}
const render = () => {console.log(obj.name); // obj.name => get 办法
}

// 组件是 watcher、计算属性是 watcher
new Watcher(render);

function observer(value) { // proxy reflect
    if (typeof value === 'object' && typeof value !== null)
    for (let key in value) {defineReactive(value, key, value[key]);
    }
}
function defineReactive(obj, key, value) {
    // 创立一个 dep
    let dep = new Dep();

    // 递归察看子属性
    observer(value);

    Object.defineProperty(obj, key, {get() { // 收集对应的 key 在哪个办法(组件)中被应用
            if (Dep.target) { // watcher
                dep.depend(); // 这里会建设 dep 和 watcher 的关系}
            return value;
        },
        set(newValue) {if (newValue !== value) {observer(newValue);
                value = newValue; // 让 key 对应的办法(组件从新渲染)从新执行
                dep.notify()}
        }
    })
}

// 模仿数据获取,触发 getter
obj.name = 'poetries'

// 一个属性一个 dep,一个属性能够对应多个 watcher(一个属性能够在任何组件中应用、在多个组件中应用)// 一个 dep 对应多个 watcher 
// 一个 watcher 对应多个 dep(一个视图对应多个属性)// dep 和 watcher 是多对多的关系

Vue 中如何检测数组变动

前言

Vue 不能检测到以下数组的变动:

  • 当你利用索引间接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  • 当你批改数组的长度时,例如:vm.items.length = newLength

Vue 提供了以下操作方法

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set 的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

剖析

数组思考性能起因没有用 defineProperty 对数组的每一项进行拦挡,而是抉择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)办法进行重写(AOP 切片思维)

所以在 Vue 中批改数组的索引和长度是无奈监控到的。须要通过以上 7 种变异办法批改数组才会触发数组对应的 watcher 进行更新

  • 用函数劫持的形式,重写了数组办法,具体呢就是更改了数组的原型,更改成本人的,用户调数组的一些办法的时候,走的就是本人的办法,而后告诉视图去更新
  • 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象能力进行观测,观测过的也不会进行观测)

原理

Vuedata 中的数组,进行了原型链重写。指向了本人定义的数组原型办法,这样当调用数组api 时,能够告诉依赖更新,如果数组中蕴含着援用类型。会对数组中的援用类型再次进行监控。

手写简版剖析

let oldArray = Object.create(Array.prototype);
['shift', 'unshift', 'push', 'pop', 'reverse','sort'].forEach(method => {oldArray[method] = function() { // 这里能够触发页面更新逻辑
        console.log('method', method)
        Array.prototype[method].call(this,...arguments);
    }
});
let arr = [1,2,3];
arr.__proto__ = oldArray;
arr.unshift(4);

源码剖析

// 拿到数组原型拷贝一份
const arrayProto = Array.prototype 
// 而后将 arrayMethods 继承自数组原型
// 这里是面向切片编程思维(AOP)-- 不毁坏封装的前提下,动静的扩大性能
export const arrayMethods = Object.create(arrayProto) 
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

methodsToPatch.forEach(function (method) { // 重写原型办法 
    const original = arrayProto[method] // 调用原数组的办法 

    def(arrayMethods, method, function mutator (...args) { 
        // 这里保留原型办法的执行后果
        const result = original.apply(this, args) 
        // 这句话是要害
        // this 代表的就是数据自身 比方数据是{a:[1,2,3]} 那么咱们应用 a.push(4)  this 就是 a  ob 就是 a.__ob__ 这个属性就是上段代码减少的 代表的是该数据曾经被响应式察看过了指向 Observer 实例
        const ob = this.__ob__ 

        // 这里的标记就是代表数组有新增操作
        let inserted
        switch (method) { 
            case 'push': 
            case 'unshift': 
                inserted = args 
                break 
            case 'splice': 
                inserted = args.slice(2) 
                break 
        }
        // 如果有新增的元素 inserted 是一个数组 调用 Observer 实例的 observeArray 对数组每一项进行观测
        if (inserted) ob.observeArray(inserted) 

        ob.dep.notify() // 当调用数组办法后,手动告诉视图更新 

        return result 
    }) 
})

this.observeArray(value) // 进行深度监控

vue3:改用 proxy,可间接监听对象数组的变动

退出移动版