2024年Web前端最新手写Vue2,字节跳动算法工程师面试经验-CSDN博客

2024年Web前端最新手写Vue2,字节跳动算法工程师面试经验

web浏览器中的javascript

window对象

  • 计时器

  • 浏览器定位和导航

  • 浏览历史

  • 浏览器和屏幕信息

  • 对话框

  • 错误处理

  • 作为window对象属性的文档元素

observe(newValue);

value = newValue;

dep.notify(); // 通知渲染watcher去更新–派发更新

},

});

}

上诉代码就是依赖收集和派发更新的核心 其实就是在数据被访问的时候 把我们定义好的渲染 Watcher 放到 dep 的 subs 数组里面 同时把 dep 实例对象也放到渲染 Watcher 里面去 数据更新时就可以通知 dep 的 subs 存储的 watcher 更新

5.完善 watcher

// src/observer/watcher.js

import { pushTarget, popTarget } from “./dep”;

// 全局变量id 每次new Watcher都会自增

let id = 0;

export default class Watcher {

constructor(vm, exprOrFn, cb, options) {

this.vm = vm;

this.exprOrFn = exprOrFn;

this.cb = cb; //回调函数 比如在watcher更新之前可以执行beforeUpdate方法

this.options = options; //额外的选项 true代表渲染watcher

this.id = id++; // watcher的唯一标识

this.deps = []; //存放dep的容器

this.depsId = new Set(); //用来去重dep

// 如果表达式是一个函数

if (typeof exprOrFn === “function”) {

this.getter = exprOrFn;

}

// 实例化就会默认调用get方法

this.get();

}

get() {

pushTarget(this); // 在调用方法之前先把当前watcher实例推到全局Dep.target上

this.getter(); //如果watcher是渲染watcher 那么就相当于执行 vm._update(vm._render()) 这个方法在render函数执行的时候会取值 从而实现依赖收集

popTarget(); // 在调用方法之后把当前watcher实例从全局Dep.target移除

}

// 把dep放到deps里面 同时保证同一个dep只被保存到watcher一次 同样的 同一个watcher也只会保存在dep一次

addDep(dep) {

let id = dep.id;

if (!this.depsId.has(id)) {

this.depsId.add(id);

this.deps.push(dep);

// 直接调用dep的addSub方法 把自己–watcher实例添加到dep的subs容器里面

dep.addSub(this);

}

}

// 这里简单的就执行以下get方法 之后涉及到计算属性就不一样了

update() {

this.get();

}

}

watcher 在调用 getter 方法前后分别把自身赋值给 Dep.target 方便进行依赖收集 update 方法用来更新

6.完善 dep

// src/observer/dep.js

// dep和watcher是多对多的关系

// 每个属性都有自己的dep

let id = 0; //dep实例的唯一标识

export default class Dep {

constructor() {

this.id = id++;

this.subs = []; // 这个是存放watcher的容器

}

depend() {

// 如果当前存在watcher

if (Dep.target) {

Dep.target.addDep(this); // 把自身-dep实例存放在watcher里面

}

}

notify() {

// 依次执行subs里面的watcher更新方法

this.subs.forEach((watcher) => watcher.update());

}

addSub(watcher) {

// 把watcher加入到自身的subs容器

this.subs.push(watcher);

}

}

// 默认Dep.target为null

Dep.target = null;

// 栈结构用来存watcher

const targetStack = [];

export function pushTarget(watcher) {

targetStack.push(watcher);

Dep.target = watcher; // Dep.target指向当前watcher

}

export function popTarget() {

targetStack.pop(); // 当前watcher出栈 拿到上一个watcher

Dep.target = targetStack[targetStack.length - 1];

}

定义相关的方法把收集依赖的同时把自身也放到 watcher 的 deps 容器里面去

思考? 这时对象的更新已经可以满足了 但是如果是数组 类似{a:[1,2,3]} a.push(4) 并不会触发自动更新 因为我们数组并没有收集依赖

7.数组的依赖收集

// src/observer/index.js

// Object.defineProperty数据劫持核心 兼容性在ie9以及以上

function defineReactive(data, key, value) {

let childOb = observe(value); // childOb就是Observer实例

let dep = new Dep(); // 为每个属性实例化一个Dep

Object.defineProperty(data, key, {

get() {

// 页面取值的时候 可以把watcher收集到dep里面–依赖收集

if (Dep.target) {

// 如果有watcher dep就会保存watcher 同时watcher也会保存dep

dep.depend();

if (childOb) {

// 这里表示 属性的值依然是一个对象 包含数组和对象 childOb指代的就是Observer实例对象 里面的dep进行依赖收集

// 比如{a:[1,2,3]} 属性a对应的值是一个数组 观测数组的返回值就是对应数组的Observer实例对象

childOb.dep.depend();

if (Array.isArray(value)) {

// 如果数据结构类似 {a:[1,2,[3,4,[5,6]]]} 这种数组多层嵌套 数组包含数组的情况 那么我们访问a的时候 只是对第一层的数组进行了依赖收集 里面的数组因为没访问到 所以五大收集依赖 但是如果我们改变了a里面的第二层数组的值 是需要更新页面的 所以需要对数组递归进行依赖收集

if (Array.isArray(value)) {

// 如果内部还是数组

dependArray(value); // 不停的进行依赖收集

}

}

}

}

return value;

},

set(newValue) {

if (newValue === value) return;

// 如果赋值的新值也是一个对象 需要观测

observe(newValue);

value = newValue;

dep.notify(); // 通知渲染watcher去更新–派发更新

},

});

}

// 递归收集数组依赖

function dependArray(value) {

for (let e, i = 0, l = value.length; i < l; i++) {

e = value[i];

// e.__ob__代表e已经被响应式观测了 但是没有收集依赖 所以把他们收集到自己的Observer实例的dep里面

e && e.ob && e.ob.dep.depend();

if (Array.isArray(e)) {

// 如果数组里面还有数组 就递归去收集依赖

dependArray(e);

}

}

}

如果对象属性的值是一个数组 那么执行 childOb.dep.depend()收集数组的依赖 如果数组里面还包含数组 需要递归遍历收集 因为只有访问数据触发了 get 才会去收集依赖 一开始只是递归对数据进行响应式处理无法收集依赖 这两点需要分清

8.数组的派发更新

// src/observer/array.js

methodsToPatch.forEach((method) => {

arrayMethods[method] = function (…args) {

// 这里保留原型方法的执行结果

const result = arrayProto[method].apply(this, args);

// 这句话是关键

// this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4) this就是a ob就是a.ob 这个属性代表的是该数据已经被响应式观察过了 __ob__对象指的就是Observer实例

const ob = this.ob;

let inserted;

switch (method) {

case “push”:

case “unshift”:

inserted = args;

break;

case “splice”:

inserted = args.slice(2);

default:

break;

}

if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测

ob.dep.notify(); //数组派发更新 ob指的就是数组对应的Observer实例 我们在get的时候判断如果属性的值还是对象那么就在Observer实例的dep收集依赖 所以这里是一一对应的 可以直接更新

return result;

};

});

关键代码就是 ob.dep.notify()

9.渲染更新的思维导图

渲染更新

小结


整体流程

这里放一张 整个 Vue 响应式原理的图片 咱们从数据劫持–>模板解析–>模板渲染–>数据变化视图自动更新整个流程已经手写了一遍 尤其是此篇介绍的渲染更新相关的知识点 建议反复理解原理之后自己动手实现一遍 因为Vue 很多核心原理和 api 都跟这里的知识点相关哈
至此 Vue 的渲染更新原理已经完结 遇到不懂或者有争议的地方欢迎评论留言

最后如果觉得本文有帮助 记得点赞三连哦 十分感谢!

最后

在面试前我花了三个月时间刷了很多大厂面试题,最近做了一个整理并分类,主要内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。

包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。

  • HTML5新特性,语义化

  • 浏览器的标准模式和怪异模式

  • xhtml和html的区别

  • 使用data-的好处

  • meta标签

  • canvas

  • HTML废弃的标签

  • IE6 bug,和一些定位写法

  • css js放置位置和原因

  • 什么是渐进式渲染

  • html模板语言

  • meta viewport原理

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值