让我们看三个案例:
A: 上小学的时候,李小红来你家叫你出去玩,第一个回应的不是你自己,是你妈:“王小明在家写作业,今天不出去!”
B: 上中学的时候,赵二虎带着小弟们放学在校门口等着揍你,走在前面的不是你自己,是二虎他爸:“考试没及格还学会装黑社会了!”拎起二虎就是一顿胖揍。
C: 上了大学,躺在宿舍里的床上,好饿。出门买饭并交代好不要葱蒜多放辣最后还直接端到床上的不是你自己,是快递小哥。
这些都是代理。
用官方的洋文来说,是 Proxy:
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
通过 Proxy 我们可以拦截并改变一个对象的几乎所有的根本操作,包括但不限于属性查找、赋值、枚举、函数调用等等。
在生活中,通过代理我们可以自动屏蔽小红的邀请、自动赶走二虎的威胁、自动买好干净的饭端到床上。在 JavaScript 世界里,代理也可以帮你做类似的事情,接下来让我们一起琢磨一番。
以小学经历为例子,心里是喜欢小红的,于是我们定义:
const me = { name: '小明', like: '小红' }
这个时候如果调用 console.log(me.like),结果必然是 小红。然而生活并不是这样,作为一个未成年人,总是有各种的代理人围绕在你身边,比如这样:
const meWithProxy = new Proxy(me, {
get(target, prop) {
if (prop === 'like') {
return '学习';
}
return target[prop];
}
});
这个时候如果调用 console.log(me.like) 依然是 小红 ,因为真心不会说谎。但当我们调用 console.log(meWithProxy.like) 的时候,就会可耻的输出 学习 ,告诉大家说我们喜欢的是 学习 。
刚才我们简单了解了代理能够拦截对象属性的获取,可以隐藏真实的属性值而返回代理想要返回的结果,那么对于对象属性的赋值呢?让我们一起来看看。
假设你正在听音乐:
const me = { name: '小明', musicPlaying: true }
此时如果我们执行 me.musicPlaying = false 这样就轻而易举地停止了你的音乐,那么如果我们挂上代理人:
const meWithProxy = new Proxy(me, {
set(target, prop, value) {
if (prop === 'musicPlaying' && value !== true) {
throw Error('任何妄图停止音乐的行为都是耍流氓!');
}
target[prop] = value;
}
});
这时候如果我们执行 me.musicPlaying = false,就会被毫不留情地掀了桌子:
> meWithProxy.musicPlaying = false
Error: 任何妄图停止音乐的行为都是耍流氓!
at Object.set (repl:4:13)
>
现在我们已经知道通过 Proxy 可以拦截属性的读写操作,那然后呢?没什么用?
仅仅是拦截属性的读写操作,的确没有太大的发挥空间,或许可以方便的做一些属性赋值校验工作等等。但是,或许你还没有意识到一个惊人的秘密:Proxy 在拦截属性读写操作时,并不在乎属性是否真的存在!
那么,也就是说:利用 Proxy,我们可以拦截并不存在的属性的读取。
再进一步思考:利用 Proxy,我们可以在属性读取的那一瞬间,动态构造返回结果。
然而,属性并不局限于字符串、布尔值,属性可以是对象、函数、任何东西。
至此,你想到了什么?
没想到?不要紧!根据刚才的分析,让我们一起通过下面 17 行代码,来封装全宇宙所有的 RESTful API !
import axios from 'axios';
const api = new Proxy({}, {
get(target, prop) {
const method = /^[a-z]+/.exec(prop)[0];
const path = '/' + prop
.substring(method.length)
.replace(/([a-z])([A-Z])/g, '$1/$2')
.replace(/\$/g, '/$/')
.toLowerCase();
return (...args) => { // <------ 返回动态构造的函数!
const url = path.replace(/\$/g, () => args.shift());
const options = args.shift() || {};
console.log('Requesting: ', method, url, options);
return axios({ method, url, ...options });
}
}
});
定义了 api 这个代理之后,我们就可以像下面这样调用:
api.get()
// GET /
api.getUsers()
// 获取所有用户
// GET /users
api.getUsers$Books(42)
// 获取 ID 为 42 的用户的所有书籍
// GET /users/42/books
api.getUsers$Books(42, { params: { page: 2 } })
// 获取 ID 为 42 的用户的所有书籍的第二页
// GET /users/42/books?page=2
api.postUsers({ data: { name: '小明' } })
// 创建名字为 小明 的用户
// POST /users Payload { name: '小明' }
以上所有的函数都在你调用的那一瞬间,通过代理人的魔法之手动态生成,供我们随意取用。
简洁、优雅,哇~ 真是太棒啦!
到此,我们仅仅使用 Proxy 改造了对象的属性获取、赋值操作,而对于 Proxy 来说,只是冰山一角。
Proxy 的基本语法如下:
new Proxy(target, handler)
其中 target 是即将被代理的对象(比如:想要出门找小红玩耍的 me),handler 就是代理的魔法之手,用来拦截、改造 target 的行为。
对于 handler 对象,我们刚才仅仅用到了 get、set 函数,而实际上一共有 13 种可代理的操作:
handler.getPrototypeOf()
在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
handler.setPrototypeOf()
在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
handler.isExtensible()
在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
handler.preventExtensions()
在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
handler.getOwnPropertyDescriptor()
在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, “foo”) 时。
handler.defineProperty()
在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, “foo”, {}) 时。
handler.has()
在判断代理对象是否拥有某个属性时触发该操作,比如在执行 “foo” in proxy 时。
handler.get()
在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.set()
在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.deleteProperty()
在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.ownKeys()
在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.apply()
在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.construct()
在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
对于以上 13 种可代理的操作,还需要读者自行研究并实践方可踏上终极魔幻之旅。同学,我看好你。
参考链接:
本文来源链接:https://knownsec-fed.com/2018-01-27-javascript-mo-huan-dai-li/
宏任务:当前调用栈中执行的代码成为宏任务。微任务: 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件。
我听说 Hooks 最近很火。讽刺的是,我想用一些关于 class 组件的有趣故事来开始这篇文章。你觉得如何?本文中这些坑对于你正常使用 React 并不是很重要。 但是假如你想更深入的了解它的运作方式,就会发现实际上它们很有趣。
async 异步加载,立即下载,不应妨碍页面其他操作,标记为 async 的异步脚本并不保证按照指定的先后顺序执行,用async很容易出错,async 是无序执行,自身加载完就会执行;
JavaScript运行三部曲:语法分析、预编译、解释执行。语法分析很简单,就是引擎检查你的代码有没有什么低级的语法错误; 解释执行顾名思义便是执行代码了; 预编译简单理解就是在内存中开辟一些空间,存放一些变量与函数 ;
instanceof 的作用是判断实例对象是否为构造函数的实例,实际上判断的是实例对象的__proto__属性与构造函数的prototype属性是否指向同一引用;constructor 的作用是返回实例的构造函数,即返回创建此对象的函数的引用
说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念。作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于“抽象语法树(AST)”。但这仅仅是个开头而已。编译原理的使用,甚至能让我们利用JS直接写一个能运行JS代码的解释器。
JS中的DOM知识概览:文档对象模型,是针对 HTML 和 XML 文档的一个 API (应用程序编程接口), 描绘了一个层次化的节点树。 web 浏览器浏览一个页面的时候,DOM 就在幕后把你编辑的网页文档转换成一个文档对象。
所有的 JS 代码在运行时都是在执行上下文中进行的。执行上下文是一个抽象的概念,JS 中有三种执行上下文:全局执行上下文,函数执行上下文,Eval 执行上下文。通常,我们的代码中都不止一个上下文,那这些上下文的执行顺序应该是怎样的?
这篇文章将梳理下环境,作用域链,变量对象和活动对象,以及内存管理问题。基本类型和引用类型的值,我们都知道JS中的数据类型有两大类,基本数据类型和引用数据类型,下面从三个方面来解剖他们
作用域外,无法引用作用域内的变量;离开作用域后,作用域的变量的内存空间会被清除,比如执行完函数或者关闭浏览器,作用域与执行上下文是完全不同的两个概念。我曾经也混淆过他们,但是一定要仔细区分。JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。