思路: 前几篇中watcher
第二个参数exp
也可以传一个函数,然后运行这个函数并获取返回值,运行过 程中,函数里所有的this.xxx
属性都会触发setter
,这样一来就可以让多个dep
都能收集到这个 watcher
3.计算属性不存在于data选项中,需要单独进行初始化;
4.计算属性只能取,不能存,也就是说计算属性的setter无效;
5.计算属性是惰性的:计算属性依赖的其他属性发生变化时,计算属性不会立即重新计算,要等到对获取计算属性的值,也就是求值时才会重新计算;
6.计算属性是缓存的:如果计算属性的依赖的其他属性没有发生变化,即使重新对计算属性求值,也不会重新计算计算属性;
5和6思路 : 给computed
相关的watcher
打一个标记this.lazy = true
,代表这是一个lazy.watcher
, 当dep
通知watcher
进行更新时,如果是lazy watcher
,则只会给自己一个标记 this.dirty = true
等到对计算属性进行求值时,如果watcher
的dirty === true
则 会对watcher
进行求值,并且把得到的值保存在watcher
实例上(watcher.value
), 如果 watcher
的dirty === false
则直接返回watcher.value
实现computed代码
class Vue {
//options是传过来的参数类似一个对象
constructor(options){
//把options存起来,可能会在实例中用到
this.$options = options
//data可能会是函数
this._data = options.data
this.initData()
this.initComputed()
this.initWatch()
}
initData() {
let data = this._data;
//获取到data对象中所有的键
let keys = Object.keys(data)
//循环遍历 实现数据代理
for(let i = 0 ; i < keys.length ; i++){
Object.defineProperty(this,keys[i],{
//设置为true,表示可以被遍历
enumerable:true,
//可以删除属性,或者修改其特性(writable,configurable,enumerable)
configurable:true,
//获取值的时候触发
get:function proxyGetter(){
//获取到的值返回出去
return data[keys[i]]
},
//修改值的时候触发
set:function proxySetter(value) {
//改变值的时候,给获取到的值赋值新值value
data[keys[i]] = value
}
})
}
//调研判断是基本类型还是复杂类型的数据 来实现数据劫持
observe(data)
}
initWatch() {
//$options参数对象中是否有watch这个属性
let watch = this.$options.watch
if(watch) {
let keys = Object.keys(watch)
//遍历watch属性中的值
for(let i = 0; i < keys.length; i++){
//new一个Watcher的实例,
new Watcher(this,keys[i],watch[keys[i]])
}
}
}
initComputed(){
let computed = this.$options.computed
//判断如果存在计算属性,进行遍历
if(computed){
let keys = Object.keys(computed)
for(let i = 0; i < keys.length; i++) {
//传入第四个参数,computed是惰性的
const watcher = new Watcher(this,computed[keys[i]],function() {},{lazy:true})
//把该属性添加到vue实例上,并设置只能取,不能存
Object.defineProperty(this,keys[i],{
enumerable:true,
configurable:true,
get : function computerGetter() {
if(watcher.dirty) {
watcher.get()
watcher.dirty = false
}
//返回出去
return watcher.value
},
set : function computedSetter() {
console.warn(‘请不要给计算属性computed赋值’)
}
})
}
}
}
//实例对象上挂载一个和watch同样的方法
$watch(key,cb) {
new Watcher(this,key,cb)
}
//实现$set方法
$set(target,key,value){
//新增的属性也变成响应式的
defineReactive(target,key,value)
//执行
target.ob.dep.notify()
}
}
//判断类型的函数observe
/**
-
@param {*} data
*/
function observe(data) {
//判断data的数据类型
let type = Object.prototype.toString.call(data)
//如果是基本类型就直接返回
if(type !== ‘[object Object]’ && type !== ‘[object Array]’){
return
}
if(data.ob){
return data.ob
}
//如果是复杂类型,new一个实例
return new Observer(data)
}
//创建一个观察值类,观察data中的数据变化
class Observer {
constructor(data) {
//数组不能使用Object.defineProperty,下标会乱 ,需要判断一下
if(Array.isArray(data)){
data.proto = ArrayMethods
//初始化
this.observeArray(data)
}else{
//调用函数
this.walk(data)
}
//实现$set需要重新new一个Dep实例
this.dep = new Dep()
//在data中新增一个__ob__属性,设置该属性不可遍历
Object.defineProperty(data,‘ob’,{
value:this,
//不可遍历
enumerable:false,
configurable:true,
writable:true
})
}
//walk函数
walk(data) {
let keys = Object.keys(data)
for(let i = 0; i < keys.length; i++){
//代用抽离出去的函数
defineReactive(data,keys[i],data[keys[i]])
}
}
//数组循环,变成响应式的
observeArray(arr){
for(let i = 0 ; i < arr.length; i++){
//调用observe函数
observe(arr[i])
}
}
}
//抽离函数
/**
-
@param {*} obj 传进来的对象
-
@param {*} key 属性
-
@param {*} value 之前的值
*/
function defineReactive(obj,key,value){
//递归,判断数据类型
let childObj = observe(obj[key])
//new一个dep的实例
let dep = new Dep()
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
//获取到属性值时调用该函数
get:function reactiveGetter(){
//调用depend函数收集回调函数
dep.depend()
//也收集一份,先判断是否存在
if(childObj) {
childObj.dep.depend()
}
//或取到的值返回出去
return value
},
//设置值的时候掉用该函数
set:function reactiveSetter(val){
//对比新设置的值和原来的值是否相同
if(val === value){
//相同的话直接返回
return
}
//执行相应回调
dep.notify()
//否则,设置新值
value = val
}
})
}
//新建一个dep的类
class Dep{
constructor(){
//收集的回调存放在数组中
this.subs = []
}
//收集回调函数
depend() {
//把回调添加到数组中
if(Dep.target) {
//把含有不同属性的Watcher实例添加到数组中
this.subs.push(Dep.target)
}
}
//执行相应的回调函数
notify() {
//循环遍历数组,执行相应回调
this.subs.forEach((watcher)=>{
// watcher指的是数组中的每一项,每一项是Watcher实例,实例中包含run函数
//一次执行回调函数
watcher.update()
})
}
}
//每一个watcher的标识
let watcherId = 0;
//存放标识id的数组,用于检查是否有相同的watch
let watcherQueue = [];
//把回调抽象成一个Watcher类
class Watcher {
//参数1:vue实例,参数2:哪一个属性,参数3:回调函数是什么
constructor(vm,exp,cb,options = {}){
this.dirty = this.lazy = !!options.lazy
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.id = ++watcherId;
//如果不是惰性,执行get函数
if(!this.lazy){
this.get()
}
}
//求值
get(){
//把Watcher类的this 挂载到Dep类的静态属性上,相当于把Watcher实例挂载到全局了
Dep.target = this;
//判断第二个参数是函数的话
if(typeof this.exp === ‘function’){
//定义一个实例下的value保存值,把函数exp的this改变为vue实例
this.value = this.exp.call(this.vm)
}else {
//调用下边这个表达式,触发数据劫持中get函数,从而调用depend函数
this.value = this.vm[this.exp]
}
//然后清空
Dep.target = null
}
update() {
if(this.lazy) {
this.dirty = true
}else {
this.run()
}
}
//执行函数
run() {
//当存在于数组中
if(watcherQueue.indexOf(this.id) !== -1){
return
}
watcherQueue.push(this.id)
let index = watcherQueue.length - 1
//不存在的时候,异步执行
Promise.resolve().then(()=>{
//调用回调函数cb时,把this指向改变为vue实例
this.cb.call(this.vm)
//然后把数组中删掉
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
最后
四轮技术面+一轮hr面结束,学习到了不少,面试也是一个学习检测自己的过程,面试前大概复习了 一周的时间,把以前的代码看了一下,字节跳动比较注重算法,面试前刷了下leetcode和剑指offer, 也刷了些在牛客网上的面经。大概就说这些了,写代码去了~
祝大家都能收获大厂offer~
篇幅有限,仅展示部分内容
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
1712541365077)]
最后
四轮技术面+一轮hr面结束,学习到了不少,面试也是一个学习检测自己的过程,面试前大概复习了 一周的时间,把以前的代码看了一下,字节跳动比较注重算法,面试前刷了下leetcode和剑指offer, 也刷了些在牛客网上的面经。大概就说这些了,写代码去了~
祝大家都能收获大厂offer~
篇幅有限,仅展示部分内容
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-kag4iG32-1712541365077)]