Web前端最全Vue双向绑定原理以及原生JS实现,2024年最新被面试官问的Web前端问题难倒了

最后

技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

可以看到,car已经可以主动告诉我们它的属性的读写情况了,这也意味着,这个car的数据对象已经是“可观测”的了。

为了把car的所有属性都变得可观测,我们可以编写如下两个函数:

/**   * 把一个对象的每一项都转化成可观测对象   * @param { Object } obj 对象   */    function observable (obj) {        if (!obj || typeof obj !== ‘object’) {            return;        }        let keys = Object.keys(obj);        keys.forEach((key) =>{            defineReactive(obj,key,obj[key])        })        return obj;    }/**`     `* 使一个对象转化成可观测对象`     `* @param { Object } obj 对象`     `* @param { String } key 对象的key`     `* @param { Any } val 对象的某个key的值`     `*/`    `function defineReactive (obj,key,val) {`        `Object.defineProperty(obj, key, {`            `get(){`                console.log(${key}属性被读取了);                `return val;`            `},`            `set(newVal){`                console.log(${key}属性被修改了);``                val = newVal;            }        })    `}

现在,我们就可以这样定义car:

let car = observable({        ‘brand’:‘BMW’,        ‘price’:3000    })

car的两个属性都变得可观测了。

完成了数据的’可观测’,即我们知道了数据在什么时候被读或写了,那么,我们就可以在数据被读或写的时候通知那些依赖该数据的视图更新了,为了方便,我们需要先将所有依赖收集起来,一旦数据发生变化,就统一通知更新。其实,这就是典型的“发布订阅者”模式,数据变化为“发布者”,依赖对象为“订阅者”。

现在,我们需要创建一个依赖收集容器,也就是消息订阅器Dep,用来容纳所有的“订阅者”。订阅器Dep主要负责收集订阅者,然后当数据变化的时候后执行对应订阅者的更新函数。

创建消息订阅器Dep:

class Dep {        constructor(){            this.subs = []        },        //增加订阅者        addSub(sub){            this.subs.push(sub);        },        //判断是否增加订阅者        depend () {            if (Dep.target) {                this.addSub(Dep.target)            }        },

//通知订阅者更新        notify(){            this.subs.forEach((sub) =>{``sub.update()            })        }    }``Dep.target = null;

有了订阅器,再将defineReactive函数进行改造一下,向其植入订阅器:

function defineReactive (obj,key,val) {        let dep = new Dep();        Object.defineProperty(obj, key, {            get(){                dep.depend();                ``console.log(KaTeX parse error: Expected 'EOF', got '}' at position 59: …;`            `}̲,`            `…{key}属性被修改了);``                dep.notify()                    //数据变化通知所有订阅者            }        })    }

从代码上看,我们设计了一个订阅器Dep类,该类里面定义了一些属性和方法,这里需要特别注意的是它有一个静态属性 target,这是一个全局唯一 的Watcher,这是一个非常巧妙的设计,因为在同一时间只能有一个全局的 Watcher 被计算,另外它的自身属性 subs 也是 Watcher 的数组。

我们将订阅器Dep添加订阅者的操作设计在getter里面,这是为了让Watcher初始化时进行触发,因此需要判断是否要添加订阅者。在setter函数里面,如果数据变化,就会去通知所有订阅者,订阅者们就会去执行对应的更新的函数。

到此,订阅器Dep设计完毕,接下来,我们设计订阅者Watcher.

订阅者Watcher在初始化的时候需要将自己添加进订阅器Dep中,那该如何添加呢?我们已经知道监听器Observer是在get函数执行了添加订阅者Wather的操作的,所以我们只要在订阅者Watcher初始化的时候出发对应的get函数去执行添加订阅者操作即可,那要如何触发get的函数,再简单不过了,只要获取对应的属性值就可以触发了,核心原因就是因为我们使用了Object.defineProperty( )进行数据监听。这里还有一个细节点需要处理,我们只要在订阅者Watcher初始化的时候才需要添加订阅者,所以需要做一个判断操作,因此可以在订阅器上做一下手脚:在Dep.target上缓存下订阅者,添加成功后再将其去掉就可以了。订阅者Watcher的实现如下:

class Watcher {        constructor(vm,exp,cb){            this.vm = vm;            this.exp = exp;            this.cb = cb;            this.value = this.get();  // 将自己添加到订阅器的操作        },

update(){            let value = this.vm.data[this.exp];            let oldVal = this.value;            if (value !== oldVal) {                this.value = value;                this.cb.call(this.vm, value, oldVal);            },        get(){            Dep.target = this;  // 缓存自己            let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数            Dep.target = null;  // 释放自己            return value;        }    }

过程分析:

订阅者Watcher 是一个 类,在它的构造函数中,定义了一些属性:

  • **vm:**一个Vue的实例对象;

  • **exp:**是node节点的v-modelv-on:click等指令的属性值。如v-model="name"exp就是name;

  • **cb:**是Watcher绑定的更新函数;

当我们去实例化一个渲染 watcher 的时候,首先进入 watcher 的构造函数逻辑,就会执行它的 this.get() 方法,进入 get 函数,首先会执行:

Dep.target = this;  // 缓存自己

实际上就是把 Dep.target 赋值为当前的渲染 watcher ,接着又执行了:

let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数

在这个过程中会对 vm 上的数据访问,其实就是为了触发数据对象的getter

每个对象值的 getter都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,也就会执行this.addSub(Dep.target),即把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs 做准备。

这样实际上已经完成了一个依赖收集的过程。那么到这里就结束了吗?其实并没有,完成依赖收集后,还需要把 Dep.target 恢复成上一个状态,即:

Dep.target = null;  // 释放自己

因为当前vm的数据依赖收集已经完成,那么对应的渲染Dep.target 也需要改变。

update()函数是用来当数据发生变化时调用Watcher自身的更新函数进行更新的操作。先通过let value = this.vm.data[this.exp];获取到最新的数据,然后将其与之前get()获得的旧数据进行比较,如果不一样,则调用更新函数cb进行更新。

至此,简单的订阅者Watcher设计完毕。

完成以上工作后,我们就可以来真正的测试了。

index.html

最后

四轮技术面+一轮hr面结束,学习到了不少,面试也是一个学习检测自己的过程,面试前大概复习了 一周的时间,把以前的代码看了一下,字节跳动比较注重算法,面试前刷了下leetcode和剑指offer, 也刷了些在牛客网上的面经。大概就说这些了,写代码去了~

祝大家都能收获大厂offer~

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

篇幅有限,仅展示部分内容

b1c2376da47d727e0dc8a77e76478.png)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值