你了解 Vue 3,前端面试项目中的难点

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值;

  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、

Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性;

  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象;

  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值;

  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值;

  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象;

  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值;

  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截;

  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…);

  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args);

以上是目前es6支持的proxy,具体的用法不做赘述,有兴趣的可以到阮一峰老师的es6入门去研究每种的具体用法,其实思想都是一样的,只是每种对应了一些不同的功能~

实际场景中 Proxy 可以做什么?


实现私有变量

js的语法中没有private这个关键字来修饰私有变量,所以基本上所有的class的属性都是可以被访问的,但是在有些场景下我们需要使用到私有变量,现在业界的一些做法都是使用”_变量名“来”约定“这是一个私有变量,但是如果哪天被别人从外部改掉的话,我们还是没有办法阻止的,然而,当Proxy出现后,我们可以用代理来处理这种场景,看代码:

const obj = { _name: 'nanjin', age: 19, getName: () => { return this._name; }, setName: (newName) => { this._name = newName; }} const proxyObj = obj => new Proxy(obj, { get: (target, key) => { if(key.startsWith('_')){ throw new Error(`${key} is private key, please use get${key}`) } return Reflect.get(target, key); }, set: (target, key, newVal) => { if(key.startsWith('_')){ throw new Error(`${key} is private key, please use set${key}`) } return Reflect.set(target, key, newVal); }}) const newObj = proxyObj(obj); console.log(newObj._name) // Uncaught Error: _name is private key, please use get_name newObj._name = 'newname'; // Uncaught Error: _name is private key, please use set_name console.log(newObj.age) // 19 console.log(newObj.getName()) // nanjin

可见,通过proxyObj方法,我们可以实现把任何一个对象都过滤一次,然后返回新的代理对象,被处理的对象会把所有_开头的变量给拦截掉,更进一步,如果有用过mobx的同学会发现mobx里面的store中的对象都是类似于这样的。

有handler 和 target,说明mobx本身也是用了代理模式,同时加上Decorator函数,在这里就相当于把proxyObj使用装饰器的方式来实现,Proxy + Decorator 就是mobx的核心原理啦~

vue响应式数据实现

VUE的双向绑定涉及到模板编译,响应式数据,订阅者模式等等,有兴趣的可以看这里,因为这篇文章的主题是proxy,因此我们着重介绍一下数据响应式的过程。

2.x版本

在当前的vue2.x的版本中,在data中声名一个obj后,vue会利用Object.defineProperty来递归的给data中的数据加上get和set,然后每次set的时候,加入额外的逻辑。来触发对应模板视图的更新,看下伪代码:

const defineReactiveData = data => { Object.keys(data).forEach(key => { let value = data[key]; Object.defineProperty(data, key, { get : function(){ console.log(`getting ${key}`) return value; }, set : function(newValue){ console.log(`setting ${key}`) notify() // 通知相关的模板进行编译 value = newValue; }, enumerable : true, configurable : true }) })}

这个方法可以给data上面的所有属性都加上get和set,当然这只是伪代码,实际场景下我们还需要考虑如果某个属性还是对象我们应该递归下去,来试试:

const data = { name: 'nanjing', age: 19 } defineReactiveData(data) data.name // getting name 'nanjing' data.name = 'beijing'; // setting name

可以看到当我们get和set触发的时候,已经能够同时触发我们想要调用的函数拉,Vue双向绑定过程中,当改变this上的data的时候去更新模板的核心原理就是这个方法,通过它我们就能在data的某个属性被set的时候,去触发对应模板的更新。

现在我们在来试试下面的代码:

const data = { userIds: ['01','02','03','04','05'] } defineReactiveData(data); data.userIds // getting userIds ["01", "02", "03", "04", "05"] // get 过程是没有问题的,现在我们尝试给数组中push一个数据 data.userIds.push('06') // getting userIds

what ? setting没有被触发,反而因为取了一次userIds所以触发了一次getting~,

不仅如此,很多数组的方法都不会触发setting,比如:push,pop,shift,unshift,splice,sort,reverse这些方法都会改变数组,但是不会触发set,所以Vue为了解决这个问题,重新包装了这些函数,同时当这些方法被调用的时候,手动去触发notify();看下源码:

// 获得数组原型const arrayProto = Array.prototype 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) const ob = this.__ob__ let inserted // 调用以下几个函数时,监听新数据 switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 手动派发更新 ob.dep.notify() return result }) })

上面是官方的源码,我们可以实现一下push的伪代码,为了省事,直接在prototype上下手了~

const push = Array.prototype.push; Array.prototype.push = function(...args){ console.log('push is happenning'); return push.apply(this, args); } data.userIds.push('123') // push is happenning

通过这种方式,我们可以监听到这些的变化,但是vue官方文档中有这么一个注意事项

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

  • 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue

  • 当你修改数组的长度时,例如:vm.items.length = newLength

这个最根本的原因是因为这2种情况下,受制于js本身无法实现监听,所以官方建议用他们自己提供的内置api来实现,我们也可以理解到这里既不是defineProperty可以处理的,也不是包一层函数就能解决的,这就是2.x版本现在的一个问。

回到这篇文章的主题,vue官方会在3.x的版本中使用proxy来代替defineProperty处理响应式数据的过程,我们先来模拟一下实现,看看能否解决当前遇到的这些问题;

3.x版本

我们先来通过proxy实现对data对象的get和set的劫持,并返回一个代理的对象,注意,我们只关注proxy本身,所有的实现都是伪代码,有兴趣的同学可以自行完善

const defineReactiveProxyData = data => new Proxy(data,{ get: function(data, key){ console.log(`getting ${key}`) return Reflect.get(data, key); }, set: function(data, key, newVal){ console.log(`setting ${key}`); if(typeof newVal === 'object'){ // 如果是object,递归设置代理 return Reflect.set(data, key, defineReactiveProxyData(newVal)); } return Reflect.set(data, key, newVal); } }) const data = { name: 'nanjing', age: 19 }; const vm = defineReactiveProxyData(data); vm.name // getting name nanjing vm.age = 20; // setting age 20

看起来我们的代理已经起作用啦,之后只要在setting的时候加上notify()去通知模板进行编译就可以了,然后我们来尝试设置一个数组看看;

vm.userIds = [1,2,3] // setting userIds vm.userIds.push(1); // getting userIds 因为我们会先访问一次userids // getting push 调用了push方法,所以会访问一次push属性 // getting length 数组push的时候 length会变,所以需要先访问原来的length // setting 3 通过下标设置的,所以set当前的index是3 // setting length 改变了数组的长度,所以会set length // 4 返回新的数组的长度

回顾2.x遇到的第一个问题,需要重新包装Array.prototype上的一些方法,使用了proxy后不需要了,解决了~,继续看下一个问题

vm.userIds.length = 2 // getting userIds 先访问 // setting length 在设置 vm.userIds[1] = '123' // getting userIds 先访问 // setting 1 设置index=1的item // "123"

从上面的例子中我们可以看到,不管是直接改变数组的length还是通过某一个下标改变数组的内容,proxy都能拦截到这次变化,这比defineProperty方便太多了,2.x版本中的第二个问题,在proxy中根本不会出现了。

总结1


最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-yGwO4cPh-1713230658285)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值