A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 天树123 中级黑马   /  2020-5-13 15:21  /  1660 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

12、vue 响应式原理
核心API: Object.defineProperty
Object.defineProperty 的简单使用:
Object.defineProperty(obj, prop, desc)
obj 需要定义属性的当前对象

prop 当前需要定义的属性名

desc 属性描述符()
let Person = {}
let temp = null
Object.defineProperty(Person, 'name', {
        get: function () {
                console.log("get");
                return temp
        },
        set: function (val) {
                console.log("set",val);
                temp = val
        }
})

Person.name = "风信子"
console.log(Person.name)
Object.defineProperty如何监听数组和对象
// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        updateView() // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments)
        // Array.prototype.push.call(this, ...arguments)
    }
})

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
    // 深度监听
    observer(value)

    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {
                // 深度监听 (设置新值的时候)
                observer(newValue)

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 污染全局的 Array 原型
    // Array.prototype.push = function () {
    //     updateView()
    //     ...
    // }

    if (Array.isArray(target)) {
        target.__proto__ = arrProto
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
    nums: [10, 20, 30]
}

// 监听数据
observer(data)

// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
Object.defineProperty 的缺点(Vue3.0启用Proxy)
深度监听,需要递归到底,一次性计算量大
无法监听新增属性/删除属性 (没有在data中初始化的属性,需要vue.set(),vue.delete())
无法原生监听数组,需要特殊处理
Proxy 有兼容性问题 无法兼容polyfill

13、虚拟DOM (virtual DOM)
DOM 操作非常耗费性能,之前需手动操作DOM。Vue和React是数据驱动视图,如何操作DOM.
解决方案-VDOM:
项目有了一定复杂度,想减少计算次数比较难。能不能把计算,更多的转移为js计算,因为js执行速度相对很快的。
vdom: 用js模拟DOM结构,计算出最小的变更,操作DOM
1)用js模拟DOM结构原理
// 原html结构
        <div class="content">
                        <ul style="font-size:20px;" >
                                <li>a</li>
                        </ul>
                </div>
//js模拟DOM结构
{
        tag:"div",
        props:{
                className:"content",
                id:"div1"
        },
        childrent:{
                tag:"ul",
                props:{
                        style:"font-size:20px;",
                },
                childrent:{
                        tag:"li",
                        childrent:"a"
                }
        }
}
2)通过 snabbdom 学习 vdom
(1) diff算法概述
diff即对比,是一个广泛的概念,如linux diff命令、git diff等
两个js 也可以 diff 对比。如:github.com/cujojs/jiff
两棵树做 diff , 如 vdom diff。

diff算法时间复杂度O(n) o(n): 1千个节点 时间复杂度就是1千。
时间复杂度如何优化到O(n)

只比较同级,不跨级比较,减少算法复杂度。
tag 不相同,则直接删除重建,不在深度比较。
tag 和 key ,两个相同,则认为是相同节点,不在深度比较。

(2) diff算法总结
钩子方法 :
// 钩子
export interface Hooks {
    // 在 `patch` 开始执行的时候调用
    pre?: PreHook;

    // 在 `createElm`,进入的时候调用init
    // vnode转换为真实DOM节点时触发
    init?: InitHook;

    // 创建真实DOM的时候,调用 create
    create?: CreateHook;

    // 在`patch`方法接近完成的时候,才收集所有的插入节点,遍历调用响应的钩子
    // 可以认为插入到DOM树时触发
    insert?: InsertHook;

    // 在两个节点开始对比前调用
    prepatch?: PrePatchHook;

    // 更新过程中,调用update
    update?: UpdateHook;

    // 两个节点对比完成时候调用
    postpatch?: PostPatchHook;

    // 删除节点的时候调用,包括子节点的destroy也会被触发
    destroy?: DestroyHook;

    // 删除当前节点的时候调用。元素从父节点删除时触发,和destory略有不同,remove只影响到被移除节点中最顶层的节点
    remove?: RemoveHook;

    // 在`patch`方法的最后调用,也就是patch完成后触发
    post?: PostHook;
}
每个 modules 下的 hook 方法提取出来存到 cbs 里面
sameVnode:判断是否是相同的虚拟节点
patch:init 方法最后返回一个 patch 方法 。
patch 方法主要的逻辑如下 :
触发 pre 钩子
如果老节点非 vnode, 则新创建空的 vnode
新旧节点为 sameVnode 的话,则调用 patchVnode 更新 vnode , 否则创建新节点
触发收集到的新元素 insert 钩子
触发 post 钩子
patchVnode 函数:比较两个vnode节点是否相似 相似patch 不同直接进行移除和添加
patchVnode 方法主要的逻辑如下 :
触发 prepatch 钩子
触发 update 钩子, 这里主要为了更新对应的 module 内容
非文本节点的情况 , 调用 updateChildren 更新所有子节点
文本节点的情况 , 直接 api.setTextContent(elm, vnode.text as string);
updateChildren 方法:patchVnode 里面最重要的方法,也是整个 diff 里面的最核心方法
updateChildren 主要的逻辑如下:
优先处理特殊场景,先对比两端。也就是
旧 vnode 头 vs 新 vnode 头
旧 vnode 尾 vs 新 vnode 尾
旧 vnode 头 vs 新 vnode 尾
旧 vnode 尾 vs 新 vnode 头
拿新节点的key,能否对应上 oldCh 中的某个节点的key。 找不到则新建元素
如果找到 key,但是,元素选择器变化了,也新建元素
如果找到 key,并且元素选择没变, 则移动元素
两个列表对比完之后,清理多余的元素,新增添加的元素
addVnodes 方法:
addVnodes 就比较简单了,主要功能就是添加 Vnodes 到 真实 DOM 中
removeVnodes 方法:
删除 VNodes 的主要逻辑如下:
循环触发 destroy 钩子,递归触发子节点的钩子
触发 remove 钩子,利用 createRmCb , 在所有监听器执行后,才调用 api.removeChild,删除真正的 DOM 节点
createElm 方法:
主要逻辑如下:
触发 init 钩子
处理注释节点
创建元素并设置 id , class
触发模块 create 钩子 。
处理子节点
处理文本节点
触发 vnodeData 的 create 钩子

14、模板编译
模板不是html,有指令、插值、js表达式、能实现判断、循环
HTML是标签,只有js才能实现判断、循环(图灵完备语言)
模板一定转换为某种js代码,即编译模板
1)、 js 的 with 语法
// 使用 with 能够改变 {} 内自由变量的查找方式
// 将 {} 内自由变量当做 obj 的属性来查找,如果找不到会报错,
// 不建议使用,打破了作用域规则,
  const obj = {
    a: 1000,
    b: 1000
  }

  with(obj){
    console.log(a);
    console.log(b);
    console.log(c); // 报错 c is not defined
  }
2)、vue template complier 将模板编译成 render 函数
const compiler = require('vue-template-compiler')

// 插值
// const template = `<p>{{message}}</p>`
// with(this){return createElement('p',[createTextVNode(toString(message))])}
// h -> vnode
// createElement -> vnode

// // 表达式
// const template = `<p>{{flag ? message : 'no message found'}}</p>`
// // with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}

// // 属性和动态属性
// const template = `
//     <div id="div1" class="container">
//         <img :src="imgUrl"/>
//     </div>
// `
// with(this){return _c('div',
//      {staticClass:"container",attrs:{"id":"div1"}},
//      [
//          _c('img',{attrs:{"src":imgUrl}})])}

// // 条件
// const template = `
//     <div>
//         <p v-if="flag === 'a'">A</p>
//         <p v-else>B</p>
//     </div>
// `
// with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}

// 循环
// const template = `
//     <ul>
//         <li v-for="item in list" :key="item.id">{{item.title}}</li>
//     </ul>
// `
// with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}

// 事件
// const template = `
//     <button @click="clickHandler">submit</button>
// `
// with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}

// v-model
const template = `<input type="text" v-model="name">`
// 主要看 input 事件
// with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}

// render 函数
// 返回 vnode
// patch

// 编译
const res = compiler.compile(template)
console.log(res.render)

// ---------------分割线--------------

// // 从 vue 源码中找到缩写函数的含义
// function installRenderHelpers (target) {
//     target._o = markOnce;
//     target._n = toNumber;
//     target._s = toString;
//     target._l = renderList;
//     target._t = renderSlot;
//     target._q = looseEqual;
//     target._i = looseIndexOf;
//     target._m = renderStatic;
//     target._f = resolveFilter;
//     target._k = checkKeyCodes;
//     target._b = bindObjectProps;
//     target._v = createTextVNode;
//     target._e = createEmptyVNode;
//     target._u = resolveScopedSlots;
//     target._g = bindObjectListeners;
//     target._d = bindDynamicKeys;
//     target._p = prependModifier;
// }
3)、执行 render 函数生成 vnode
VNode表示Virtual DOM,用JavaScript对象来描述真实的DOM把DOM标签,属性,内容都变成对象的属性

15、前端路由原理
1)、hash模式

hash 变化会触发网页跳转,即浏览器的前进、后退
hash 变化不会刷新页面,spa必须的特点
hsah 永远不会提交到 server 端
window.onhashchange 监听

2)、 H5 history模式

用url规范的路由,但跳转时不刷新页面(只是刷新刚进来的页面)
history.pushState (此方式,跳转浏览器不会刷新页面)
window.onpopstate (监听浏览器前进和后退,由history.pushState()或者history.replaceState()形成的历史节点中前进后退)

to B 的系统推荐使用 hash,简单易用,对 url 规范不敏感。
to C 的系统,可以选着 H5 history,对于 seo 优化的使用 H5 history

16、computed 的特点
缓存,data 不变不会重新计算。
提高性能

17、为何组件 data 必须是个函数
data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象,通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

18、数据请求应该刚在那个生命周期
mounted
js 是单线程的,数据请求异步获取数据
放在 mounted 之前是没有用的,只会让逻辑更加混乱,在 js 没有渲染完 异步数据也是在查询中不会提前加载。

19、多个组件有相同的逻辑,如何抽离
使用 mixin
多个组件可以共享数据和方法,在使用mixin的组件中引入后,mixin中的方法和属性也就并入到该组件中,可以直接使用。数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。钩子函数会两个都被调用,mixin中的钩子首先执行。
存在问题:
变量来源不明确,多 mixin 可能造成命名冲突。 mixin 和组件可能存在多对多的关系,业务逻辑复杂

20、何时使用 keep-alive
缓存组件,不需要重复渲染
多个静态的 tab 页

21、何时使用 beforeDestory
解除自定义事件 event.$off
清除定时器
解除自定义 DOM 事件,如 window scroll 等

22、vue 常见性能优化
合理使用 v-show 和 v-if
合理使用computed
v-for 时加 key , 避免和 v-if 同时使用
自定义事件个dom 事件及时销毁
合理使用异步组件
合理使用 keep-alive
data 层级不要太深(响应式绑定递归遍历比较深)
webpack 层面的优化
前端通用性能优化 (图片懒加载等)
使用SSR

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马