本文写给
萌新,请看清分类,不喜请右上角
欢迎补充
直接上代码
1 | <input readonly onfocus="this.removeAttribute('readonly');"/> |
网上的其他方法大多失效, disabled也可以实现效果, 但需要在input加载后清除disabled属性, 逻辑分离不易维护, 也较为麻烦, 这个方法我认为比较优雅
蓝田日暖玉生烟
一弦一柱思华年
请不要吐槽我的标点,
我就喜欢这样,另外,这是百度输入法的锅 ←_←
本文写给萌新,请看清分类,不喜请右上角
欢迎补充,现在这只是极简教程,以后说不定可以补充成《深入浅出charles》也说不定呢(滑稽)
可以去官网下载最新版(收费) https://www.charlesproxy.com/
至于怎么破解不是本文的重点,自己动手搜吧~
可以先下官方正版来使用,每次打开会有10s的等待,不过不影响基本使用
官网上有这么一段介绍:
Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet. This includes requests, responses and the HTTP headers (which contain the cookies and caching information).
通过谷歌机翻可得大概的含义:
Charles是一个HTTP代理/ HTTP监视器/反向代理,使开发人员能够查看其机器和Internet之间的所有HTTP和SSL / HTTPS流量。这包括请求,响应和HTTP标头(包含cookie和缓存信息)。
总得来说谷歌翻译挺牛逼的~
那么,对我等小前端来说charles的就是用来代理和抓包的,接下来我也会针对这两点展开介绍,另外还会教大家怎么代理手机的请求,这个相当实用~
打开charles的软件主界面能看到这个

主界面的几个常用功能按钮我标注了,有些功能比如写新请求可以自己试试,对于请求出错时的调试很有帮助
简单来说就是获取本机所有的http请求
在打开record开关的情况下 可以在Sequence列表里看到炒鸡多的请求冒出来
我放个图
可以在下方request和response区域内找自己想看的内容,我本来也不太懂,直到我被人指着鼻子骂说从来没见过你这么差的前端!!
然后我就好好补了一下header, cookie, session的相关知识~
不过这些内容在浏览器的network部分也都能看到,所以并不是很牛批
比较牛啤实用的是给手机连代理和抓包
先说一下基本原理,就是手机wifi代理连接电脑ip的某个接口,然后手机上所有的请求都会过一遍电脑上的charles,那么平时比较难调试的手机H5请求就能很方便的操作了
ipconfig指令获取,mac可以看wifi设置






http://haoke.163.com 就可以在电脑上看到相应的静态资源请求
这里主要是代理http的请求,https就不能直接解析了
至此,就是手机连接charles代理的过程,结合下一节的http代理就可以实现在家调试线上页面了~
代理的原理其实我说不清楚~举例来说原来线上一个网页的html静态资源路径是这样的http://online/nichousha/index.html
而我希望用我本地的资源替代(本地我有node服务器把资源放到localhost某个端口下)http://localhost/index.html 或者 http://<本机ip>/index.html
那么我就希望把http://online/nichousha/替换为http://localhost:8000/
那这一步替换charles可以帮我们做到,也就是代理的过程
所以你理解了替换这个概念,相关的配置也就很简单了


替换过程配置到里面
感觉云里雾里?还是实际举个栗子吧
我这儿自家官网原来长这样
然后我通过charles代理了本地资源(这里代理了根目录下 html, js, css, 图片文件)
我就可以把官网改成这样了(两次都是在haoke.163.com这个域名下访问的)
一般代理前端资源以后会有缓存,可以强制刷新就能看到代理后的效果了(ctrl+f5)
同样的,可以反向代理解决跨域问题,大家回忆替换这个概念,就是把本地域名请求替换成目标服务器的请求:http://localhost/server/ => http://target.com/server/
这里就不展开了,自己在实际工作中多试几次就理解了
需要注意的是,charles代理的配置项先后顺序是有差别的,在前面的配置项先生效

更多功能和用法就留给大家自己去发掘吧~我作为前端还是相当推荐这款工具的,可视化界面和容易理解的操作,就酱~
请不要吐槽我的标点,
我就喜欢这样,另外,这是百度输入法的锅 ←_←
本文写给萌新,请看清分类,不喜请右上角
欢迎补充,可以说说你实际工作中的闭包应用
万恶之源是自己想看rc-tree的源码发现我还年轻,感觉到很无力
然而再牛逼的组件也是从一个原型功能开始,所以就从最简单的代码开始撸
然后就引出了所谓的遍历树结构数据的闭包写法,然后发现这个例子很适合应付面试←_←,既贴合实际工作,又不落俗套,简直面试必备
先来看一个树结构,你可能会在三级联动的城市选择框中看到这种数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const arr = [
{key: '0', name: 'root', children: [
{key: '0-0', name: 'I have a pen'},
{key: '0-1', name: 'I have a apple'},
{key: '0-2', name: 'errrr'},
{key: '0-3', name: 'penapple pen', children: [
{key: '0-3-0', name: 'I forget it'}
]},
]},
{key: '1', name: 'another root', children: [
{key: '1-0', name: '我编不下去了...000'},
{key: '1-1', name: '我编不下去了...111'},
{key: '1-2', name: '我编不下去了...222'},
]}
]
回到引子里面的<原型功能>这个话题,我需要在输入[‘0-3’, ‘1-2’]后获取到对应的数据节点
而这里先讨论查找一个数据节点的简化情况即getTreeNode(arr, '0-3') ====> return node.key === '0-3'
1 | function getTreeNode(treeData, key) { |
回到树结构的特点,类型是<array: object>,即数组的每一项是一个对象,每个对象有各种属性(key, name等),还有一个特殊的属性是children,children是一个新的<array: object>
所以树结构可以无限延伸,遍历的时候递归基本是跑不掉了
那么简单粗暴的代码如下1
2
3
4
5
6
7
8
9
10
11
12function getTreeNode(treeData, key) {
let node;
treeData && treeData.forEach((treeNode) => { // 这里的判空比较简陋,完整的应该判断children的类型为array,length>0
if (treeNode.key === key) {
node = treeNode;
}
if (treeNode.children) { // 同上
getTreeNode(treeNode.children, key);
}
})
return node;
}
相当简单嘛←_←,然后你会发现getTreeNode() did’t work∑(;°Д°)
为啥嘞,因为当1
node = treeNode;
执行的时候,早已不是最初的那个getTreeNode(),已经是某次递归的getTreeNode()了
不信的话你可以试试1
console.log(getTreeNode(arr, '0'));
还是可以正常返回的,因为这个时候forEach的第一个循环就成功了,得以在最初的funciton里面return了值
可能有看官会说那我return递归的返回值是不是可以呢,这里稍微延伸一下
把上面的函数稍微改写一点1
2
3
4
5
6
7
8
9
10
11
12function getTreeNode(treeData, key) {
let node;
treeData && treeData.forEach((treeNode) => {
if (treeNode.key === key) {
node = treeNode;
}
if (treeNode.children) {
node = getTreeNode(treeNode.children, key); // 这里改了
}
})
return node;
}
看着挺对的,然娥It did’t work again,多用console试验几次你会发现如果后面的节点没有children了,就能返回正常的值.比如我们这个arr里1-0, 1-1, 1-2都可以返回值,究其原因主要是因为后面递归的函数返回了undefined把前面正确的值覆盖了
辣么,要是能找到值就把循环停下是不是就可以了呢?确实可以←_←,不过我们放到后面说吧,因为forEach是停不下来的(´・д・`)
其实上一节说到的把前面正确的值覆盖已经有点门道了
因为只要把2.3里面的函数按这个思路小改就能实现了1
2
3
4
5
6
7
8
9
10
11
12let node; // 把node变量移出function变成一个全局变量
function getTreeNode(treeData, key) {
treeData && treeData.forEach((treeNode) => { // 这里的判空比较简陋,完整的应该判断children的类型为array,length>0
if (treeNode.key === key) {
node = treeNode;
}
if (treeNode.children) { // 同上
getTreeNode(treeNode.children, key);
}
})
return node;
}
这回其实已经实现功能了,有没有一种找了半天手机就在手上的感觉…但是这样相当不优雅,而且这个函数的复用受到极大的限制,node成为一个全局变量非常容易被污染,导致一些非常难找的bug,所以这种写法是被前端所唾弃的
辣么,是时候用闭包来解决这个问题了1
2
3
4
5
6
7
8
9
10
11
12
13
14function getTreeNode(treeData, key) {
let node;
(function(treeData) { // 不能用箭头函数
treeData && treeData.forEach((treeNode) => { // 这里的treeData已经不是外层的treeData了
if (treeNode.key === key) {
node = treeNode;
}
if (treeNode.children) {
arguments.callee(treeNode.children); // 改成arguments.callee调用
}
})
})(treeData)
return node;
}
这回才算是真的实现了功能,首先说一个惯用写法1
2() => {...} + ( )()
(() => {...})()
就是在一个函数外套一层( )(),让它自然形成一个闭包
回到上个例子,先理解一下arguments.callee的使用,其实第3行定义function的时候如果给一个名字而不是匿名函数的话就可以直接调用这个function了,arguments.callee就是调用函数本身,这里不展开了
需要注意的是,这里第3行不能用箭头函数() => {},否则arguments.callee会去调用外层getTreeNode
不知道是否会有人疑惑为什么这个闭包只传了treeData,因为每次arguments.callee调用闭包内function的时候这个treeData是一个新变量且只在本次递归内有效的变量,你要是能理解这点,就理解了闭包缓存的特性
要是不太理解,可以试着写一些console,看看每一步的循环结果,慢慢理解
本来只是想给前端萌新的面试加点油,展开来写发现也有不少额外知识点的延伸,感觉能有一点点收获的话我这样写出来也算不亏了吧
还记得2.4的歪路里面说把循环停住也是可以的,这里就顺便把代码放上来了
1 | function getTreeNode(tree, key) { |
有一点取巧,不过也能实现,我的话,更喜欢闭包的写法,感觉优雅一些呢~
最后,想稍微挑战一下自己的可以试试把getTreeNode()改写,让第二个参数可以传数组([‘0-3’, ‘1-2’]),最终输出数组([{}, {}]),欢迎留言~