vue3.0解析

 

 

 

不久前,也就是11月14日-16日于多伦多举办的 VueConf TO 2018 大会上,尤雨溪发表了名为 Vue3.0 Updates 的主题演讲,对 Vue3.0 的更新计划、方向进行了详细阐述(感兴趣的小伙伴可以看看完整的 PPT),表示已经放弃使用了 Object.defineProperty,而选择了使用更快的原生 Proxy !!

这将会消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的很多限制:无法监听 属性的添加和删除数组索引和长度的变更,并可以支持 MapSetWeakMapWeakSet

做为一个 “前端工程师” ,有必要安利一波 Proxy !!

什么是 Proxy?

MDN 上是这么描述的——Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

官方的描述总是言简意赅,以至于不明觉厉...

其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的~

什么?还没表述清楚?下面我们看个例子,就一目了然了~


 
 
  1. let obj = {
  2. a : 1
  3. }
  4. let proxyObj = new Proxy(obj,{
  5. get : function (target,prop) {
  6. return prop in target ? target[prop] : 0
  7. },
  8. set : function (target,prop,value) {
  9. target[prop] = 888;
  10. }
  11. })
  12. console.log(proxyObj.a); // 1
  13. console.log(proxyObj.b); // 0
  14. proxyObj.a = 666;
  15. console.log(proxyObj.a) // 888
  16. 复制代码

上述例子中,我们事先定义了一个对象 obj , 通过 Proxy 构造器生成了一个 proxyObj 对象,并对其的 set(写入) 和 get (读取) 行为重新做了修改。

当我们访问对象内原本存在的属性时,会返回原有属性内对应的值,如果试图访问一个不存在的属性时,会返回0 ,即我们访问 proxyObj.a 时,原本对象中有 a 属性,因此会返回 1 ,当我们试图访问对象中不存在的 b 属性时,不会再返回 undefined ,而是返回了 0 ,当我们试图去设置新的属性值的时候,总是会返回 888 ,因此,即便我们对 proxyObj.a 赋值为 666 ,但是并不会生效,依旧会返回 888!

语法

ES6 原生提供的 Proxy 语法很简单,用法如下:

let proxy = new Proxy(target, handler);

参数 target 是用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理), 参数 handler 也是一个对象,其属性是当执行一个操作时定义代理的行为的函数,也就是自定义的行为。

Proxy 的基本用法就如同上面这样,不同的是 handler 对象的不同,handler 可以是空对象 {} ,则表示对 proxy 操作就是对目标对象 target 操作,即:


 
 
  1. let obj = {}
  2. let proxyObj = new Proxy(obj,{})
  3. proxyObj.a = 1;
  4. proxyObj.fn = function () {
  5. console.log( 'it is a function')
  6. }
  7. console.log(proxyObj.a); // 1
  8. console.log(obj.a); // 1
  9. console.log(obj.fn()) // it is a function
  10. 复制代码

但是要注意的是,handler 不能 设置为 null ,会抛出一个错误——Cannot create proxy with a non-object as target or handler

要想 Proxy 起作用,我们就不能去操作原来对象的对象,也就是目标对象 target (上例是 obj 对象 ),必须针对的是 Proxy 实例(上例是 proxyObj 对象)进行操作,否则达不到预期的效果,以刚开始的例子来看,我们设置 get 方法后,试图继续从原对象 obj 中读取一个不存在的属性 b , 结果依旧返回 undefined


 
 
  1. console.log(proxyObj.b); // 0
  2. console.log(obj.b); // undefined
  3. 复制代码

对于可以设置、但没有设置拦截的操作,则对 proxy 对象的处理结果也同样会作用于原来的目标对象 target 上,怎么理解呢?还是以刚开始的例子来看,我们重新定义了 set 方法,所有的属性设置都返回了 888 , 并没有对某个特殊的属性(这里指的是 obja 属性 )做特殊的拦截或处理,那么通过 proxyObj.a = 666 操作后的结果同样也会作用于原来目标对象(obj 对象)上,因此 obj 对象的 a 的值也将会变为 888 !


 
 
  1. proxyObj.a = 666;
  2. console.log( proxyObj.a); // 888
  3. console.log( obj.a); // 888
  4. 复制代码

API

ES6Proxy 目前提供了 13 种可代理操作,下面我对几个比较常用的 api 做一些归纳和整理,想要了解其他方法的同学可自行去官网查阅 :

--handler.get(target,property,receiver)

用于拦截对象的读取属性操作,target 是指目标对象,property 是被获取的属性名 , receiverProxy 或者继承 Proxy 的对象,一般情况下就是 Proxy 实例。


 
 
  1. let proxy = new Proxy({},{
  2. get : function (target,prop) {
  3. console.log( `get ${prop}`);
  4. return 10;
  5. }
  6. })
  7. console.log(proxy.a) // get a
  8. // 10
  9. 复制代码

我们拦截了一个空对象的 读取get操作, 当获取其内部的属性是,会输出 get ${prop} , 并返回 10 ;


 
 
  1. let proxy = new Proxy({},{
  2. get : function (target,prop,receiver) {
  3. return receiver;
  4. }
  5. })
  6. console.log(proxy.a) // Proxy{}
  7. console.log(proxy.a === proxy) //true
  8. 复制代码

上述 proxy 对象的 a 属性是由 proxy 对象提供的,所以 receiver 指向 proxy 对象,因此 proxy.a === proxy 返回的是 true

要注意,如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同,也就是不能对其进行修改,否则会抛出异常~


 
 
  1. let obj = {};
  2. Object.defineProperty(obj, "a", {
  3. configurable: false,
  4. enumerable: false,
  5. value: 10,
  6. writable: false
  7. });
  8. let proxy = new Proxy(obj,{
  9. get : function (target,prop) {
  10. return 20;
  11. }
  12. })
  13. console.log(proxy.a) // Uncaught TypeError
  14. 复制代码

上述 obj 对象中的 a 属性不可写,不可配置,我们通过 Proxy 创建了一个 proxy 的实例,并拦截了它的 get 操作,当我们输出 proxy.a 时会抛出异常,此时,如果我们将 get 方法的返回值修改跟目标属性的值相同时,也就是 10 , 就可以消除异常~

--handler.set(target, property, value, receiver)

用于拦截设置属性值的操作,参数于 get 方法相比,多了一个 value ,即要设置的属性值~

严格模式下,set方法需要返回一个布尔值,返回 true 代表此次设置属性成功了,如果返回false且设置属性操作失败,并且会抛出一个TypeError


 
 
  1. let proxy = new Proxy({},{
  2. set : function (target,prop,value) {
  3. if( prop === 'count' ){
  4. if( typeof value === 'number'){
  5. console.log( 'success')
  6. target[prop] = value;
  7. } else{
  8. throw new Error( 'The variable is not an integer')
  9. }
  10. }
  11. }
  12. })
  13. proxy.count = '10'; // The variable is not an integer
  14. proxy.count = 10; // success
  15. 复制代码

上述我们通过修改 set方法,对 目标对象中的 count 属性赋值做了限制,我们要求 count 属性赋值必须是一个 number 类型的数据,如果不是,就返回一个错误 The variable is not an integer,我们第一次为 count 赋值字符串 '10' , 抛出异常,第二次赋值为数字 10 , 打印成功,因此,我们可以用 set 方法来做一些数据校验!

同样,如果目标属性是不可写及不可配置的,则不能改变它的值,即赋值无效,如下:


 
 
  1. let obj = {};
  2. Object.defineProperty(obj, "count", {
  3. configurable: false,
  4. enumerable: false,
  5. value: 10,
  6. writable: false
  7. });
  8. let proxy = new Proxy(obj,{
  9. set : function (target,prop,value) {
  10. target[prop] = 20;
  11. }
  12. })
  13. proxy.count = 20 ;
  14. console.log(proxy.count) // 10
  15. 复制代码

上述 obj 对象中的 count 属性,我们设置它不可被修改,并且默认值,我们给定为 10 ,那么即使给其赋值为 20 ,结果仍旧没有变化!

--handler.apply(target, thisArg, argumentsList)

用于拦截函数的调用,共有三个参数,分别是目标对象(函数)target,被调用时的上下文对象 thisArg 以及被调用时的参数数组 argumentsList,该方法可以返回任何值。

target 必须是是一个函数对象,否则将抛出一个TypeError


 
 
  1. function sum(a, b) {
  2. return a + b;
  3. }
  4. const handler = {
  5. apply: function(target, thisArg, argumentsList) {
  6. console.log( `Calculate sum: ${argumentsList}`);
  7. return target(argumentsList[ 0], argumentsList[ 1]) * 2;
  8. }
  9. };
  10. let proxy = new Proxy(sum, handler);
  11. console.log(sum( 1, 2)); // 3
  12. console.log(proxy( 1, 2)); // Calculate sum:1,2
  13. // 6
  14. 复制代码

实际上,apply 还会拦截目标对象的 Function.prototype.apply()Function.prototype.call(),以及 Reflect.apply() 操作,如下:


 
 
  1. console.log(proxy.call( null, 3, 4)); // Calculate sum:3,4
  2. // 14
  3. console.log( Reflect.apply(proxy, null, [ 5, 6])); // Calculate sum: 5,6
  4. // 22
  5. 复制代码

--handler.construct(target, argumentsList, newTarget)

construct 用于拦截 new 操作符,为了使 new 操作符在生成的 Proxy对象上生效,用于初始化代理的目标对象自身必须具有[[Construct]]内部方法;它接收三个参数,目标对象 target ,构造函数参数列表 argumentsList 以及最初实例对象时,new 命令作用的构造函数,即下面例子中的 p


 
 
  1. let p = new Proxy( function() {}, {
  2. construct: function(target, argumentsList, newTarget) {
  3. console.log(newTarget === p ); // true
  4. console.log( 'called: ' + argumentsList.join( ', ')); // called:1,2
  5. return { value: ( argumentsList[ 0] + argumentsList[ 1] )* 10 };
  6. }
  7. });
  8. console.log( new p( 1, 2).value); // 30
  9. 复制代码

另外,该方法必须返回一个对象,否则会抛出异常!


 
 
  1. var p = new Proxy( function() {}, {
  2. construct: function(target, argumentsList, newTarget) {
  3. return 2
  4. }
  5. });
  6. console.log( new p( 1, 2)); // Uncaught TypeError
  7. 复制代码

--handler.has(target,prop)

has方法可以看作是针对 in 操作的钩子,当我们判断对象是否具有某个属性时,这个方法会生效,典型的操作就是 in ,改方法接收两个参数 目标对象 target 和 要检查的属性 prop,并返回一个 boolean 值。


 
 
  1. let p = new Proxy({}, {
  2. has: function(target, prop) {
  3. if( prop[ 0] === '_' ) {
  4. console.log( 'it is a private property')
  5. return false;
  6. }
  7. return true;
  8. }
  9. });
  10. console.log( 'a' in p); // true
  11. console.log( '_a' in p ) // it is a private property
  12. // false
  13. 复制代码

上述例子中,我们用 has 方法隐藏了属性以下划线_开头的私有属性,这样在判断时候就会返回 false,从而不会被 in 运算符发现~

要注意,如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏,如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏,否则将会抛出 TypeError


 
 
  1. let obj = { a : 1 };
  2. Object.preventExtensions(obj); // 让一个对象变的不可扩展,也就是永远不能再添加新的属性
  3. let p = new Proxy(obj, {
  4. has: function(target, prop) {
  5. return false;
  6. }
  7. });
  8. console.log( 'a' in p); // TypeError is thrown
  9. 复制代码

数据绑定

上面介绍了这么多,也算是对 Proxy 又来一个初步的了解,那么我们就可以利用 Proxy 手动实现一个极其简单数据的双向绑定(Object.defineProperty() 的实现方式可以参考我上篇文章的末尾有涉及到)~

主要看功能的实现,所以布局方面我就随手一挥了~

页面结构如下:


 
 
  1. <!--html-->
  2. <div id="app">
  3. <h3 id="paragraph"> </h3>
  4. <input type="text" id="input"/>
  5. </div>
  6. 复制代码

主要还是得看逻辑部分:


 
 
  1. //获取段落的节点
  2. const paragraph = document.getElementById( 'paragraph');
  3. //获取输入框节点
  4. const input = document.getElementById( 'input');
  5. //需要代理的数据对象
  6. const data = {
  7. text: 'hello world'
  8. }
  9. const handler = {
  10. //监控 data 中的 text 属性变化
  11. set: function (target, prop, value) {
  12. if ( prop === 'text' ) {
  13. //更新值
  14. target[prop] = value;
  15. //更新视图
  16. paragraph.innerHTML = value;
  17. input.value = value;
  18. return true;
  19. } else {
  20. return false;
  21. }
  22. }
  23. }
  24. //添加input监听事件
  25. input.addEventListener( 'input', function (e) {
  26. myText.text = e.target.value; //更新 myText 的值
  27. }, false)
  28. //构造 proxy 对象
  29. const myText = new Proxy(data,handler);
  30. //初始化值
  31. myText.text = data.text;
  32. 复制代码

上述我们通过Proxy 创建了 myText 实例,通过拦截 myTexttext 属性 set 方法,来更新视图变化,实现了一个极为简单的 双向数据绑定~

总结

说了这么多 , Proxy 总算是入门了,虽然它的语法很简单,但是要想实际发挥出它的价值,可不是件容易的事,再加上其本身的 Proxy 的兼容性方面的问题,所以我们实际应用开发中使用的场景的并不是很多,但不代表它不实用,在我看来,可以利用它进行数据的二次处理、可以进行数据合法性的校验,甚至还可以进行函数的代理,更多有用的价值等着你去开发呢~

况且,Vue3.0 都已经准备发布了,你还不打算让学习一下?

加油!


作者:Chris威
链接:https://juejin.im/post/5bfcbab0518825741e7bd67f
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

# 欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t &ThinSpace; . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

Mon 06 Mon 13 Mon 20 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.2.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值