yuuhei's Home


  • 首页

  • 归档

  • 标签

36个JS手写题

发表于 2021-05-23

该文章为转载,仅用于记录学习

死磕 36 个 JS 手写题(搞懂后,提升真的大)

为什么要写这类文章

作为一个程序员,代码能力毋庸置疑是非常非常重要的,就像现在为什么大厂面试基本都问什么 API 怎么实现可见其重要性。我想说的是居然手写这么重要,那我们就必须掌握它,所以文章标题用了死磕,一点也不过分,也希望不被认为是标题党。

作为一个普通前端,我是真的写不出 Promise A+ 规范,但是没关系,我们可以站在巨人的肩膀上,要相信我们现在要走的路,前人都走过,所以可以找找现在社区已经存在的那些优秀的文章,比如工业聚大佬写的 100 行代码实现 Promises/A+ 规范,找到这些文章后不是收藏夹吃灰,得找个时间踏踏实实的学,一行一行的磨,直到搞懂为止。我现在就是这么干的。

能收获什么

这篇文章总体上分为 2 类手写题,前半部分可以归纳为是常见需求,后半部分则是对现有技术的实现;

  • 对常用的需求进行手写实现,比如数据类型判断函数、深拷贝等可以直接用于往后的项目中,提高了项目开发效率;
  • 对现有关键字和 API 的实现,可能需要用到别的知识或 API,比如在写 forEach 的时候用到了无符号位右移的操作,平时都不怎么能够接触到这玩意,现在遇到了就可以顺手把它掌握了。所以手写这些实现能够潜移默化的扩展并巩固自己的 JS 基础;
  • 通过写各种测试用例,你会知道各种 API 的边界情况,比如 Promise.all, 你得考虑到传入参数的各种情况,从而加深了对它们的理解及使用;

阅读的时候需要做什么

阅读的时候,你需要把每行代码都看懂,知道它在干什么,为什么要这么写,能写得更好嘛?比如在写图片懒加载的时候,一般我们都是根据当前元素的位置和视口进行判断是否要加载这张图片,普通程序员写到这就差不多完成了。而大佬程序员则是会多考虑一些细节的东西,比如性能如何更优?代码如何更精简?比如 yeyan1996 写的图片懒加载就多考虑了 2 点:比如图片全部加载完成的时候得把事件监听给移除;比如加载完一张图片的时候,得把当前 img 从 imgList 里移除,起到优化内存的作用。

除了读通代码之外,还可以打开 Chrome 的 Script snippet 去写测试用例跑跑代码,做到更好的理解以及使用。

在看了几篇以及写了很多测试用例的前提下,尝试自己手写实现,看看自己到底掌握了多少。条条大路通罗马,你还能有别的方式实现嘛?或者你能写得比别人更好嘛?

好了,还楞着干啥,开始干活。

阅读全文 »

掘金spam沸点脚本

发表于 2021-05-22

用于攻击某些发言有问题的用户,刷他沸点评论,将textarea放到浏览器里,放上垃圾话后,在浏览器执行脚本(开小号执行,会被掘金检测到后然后禁言,慎用)

阅读全文 »

使用iframe引入微信公众号的链接

发表于 2020-07-21

本文实现了以下功能:

  • iframe可加载微信公众号内容
  • 可加载微信的视频或外链的视频
  • 可绕过微信图片的防盗链
  • iframe里的微信公众号的跳转链接实现浏览器新窗口跳转

前言:由于微信前端页面的的服务器设置了Content-Security-Policy,导致他的资源如果在非白名单的网页被引用,就会拒绝返回资源,导致iframe加载内容失败

注:开启了CSP的时候要记得配置一下unsafe-eval和unsafe-inline,否则一些eval(),setTimeout,setInterval会无法执行;内联script,内联样式,内联事件都会失效

关于CSP的一些详细说明:

阅读全文 »

node实现上传文件并保存到硬盘

发表于 2020-07-21

上传文件到node服务器保存并返回文件信息

关键是要使用一个中间件multer

使用express:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const express = require('express')
const app = express()
const port = 3000
const multer = require('multer')

const storage = multer.diskStorage({
// 注意这里,需要具体处理文件的路径
destination: function (req, file, cb) {
cb(null, './uploads/')
},
// 注意这里,需要具体处理文件的名称,因为存在中文等
filename: function (req, file, cb) {
cb(null, Date.now() + '-' + file.originalname)
}
})

const upload = multer({ storage: storage })

// 多文件就要 upload.array(),单文件就用 upload.single()
// single要与const form = new FormData();form.append('file', event.target.files[0])的key一致;
// single要与<input id='upload' type="file" name="file" />name属性一致
app.post('/upload', upload.single('file'), (req, res, next) => {
// 这里的req.file返回的是一个json对象
if (req.file) {
return res.status(200).json({
code: 0,
msg: '上传成功',
data: req.file
});
} else {
return res.status(200).json({
code: 1,
msg: '上传失败',
data: null
});
}
})

// node console.log(req.file)
// {
// fieldname: 'file',
// originalname: '19730B75-15AF-4ede-9852-0392DEDD96A4.png',
// encoding: '7bit',
// mimetype: 'image/png',
// destination: './uploads/',
// filename: '1591588463244-19730B75-15AF-4ede-9852-0392DEDD96A4.png',
// // 文件保存的路径,这里是由于本机服务器是windows的问题,真实项目需要统一处理路径问题
// path: 'uploads\\1591588463244-19730B75-15AF-4ede-9852-0392DEDD96A4.png',
// size: 107642
// }

前端代码:

1
<input type="file" @change="loadFile">
1
2
3
4
5
6
7
8
9
10
11
12
13
loadFile(event) {
const form = new FormData();
form.append('file', event.target.files[0])
axios({
method: 'POST',
url: 'http://localhost:3000/upload',
// request headers的content-type必须设置为multipart/form-data
headers: {
'Content-Type': 'multipart/form-data'
},
data: form
})
}

返回的data里会有path的属性,是中间件写上去的,是文件保存的路径

node实现绕过图片防盗链

发表于 2020-07-21

绕过图片防盗链的关键就是要伪造请求头的Referer,将Referer置为空即可

不要在html增加<meta name="referrer" content="never">的方法处理这类事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const express = require('express')
const app = express()
const port = 3000
const superagent = require('superagent')

function getQueryObject(url) {
url = url == null ? window.location.href : url
const search = url.substring(url.lastIndexOf('?') + 1)
const obj = {}
const reg = /([^?&=]+)=([^?&=]*)/g
search.replace(reg, (rs, $1, $2) => {
const name = decodeURIComponent($1)
let val = decodeURIComponent($2)
val = String(val)
obj[name] = val
return rs
})
return obj
}

// 前端无法修改请求头的Referer,只能通过接口修改,做一个转发请求
app.get('/imgBridge', async (req, res, next) => {
let url = req.query.url;
if (!url) {
res.send('');
return false;
}
// 微信的链接自带说明是什么格式,可以直接取,不用再额外分析文件的格式是什么
const wx_query = getQueryObject(url);

superagent.get(req.query.url)
.set('Referer', '')
.end(function(err, result) {
if (err) {
return false;
}
// 返回头必须写入对应的文件类型,直接在浏览器打开才会显示正确的图片而不是下载
res.set({
'Content-Type': `image/${query.wx_fmt}`
});
// 返回文件流
res.end(result.body);
return;
});
})

使用方法,直接赋值到img的src上或background的url上:

1
http://localhost:8080?url=https://mmbiz.qpic.cn/mmbiz_gif/iblvrvduDqxFibk6bianDue1Ygn2t0k1Cs8dmKjwcibxIesKWwDZwrib0aNxOxpZQyo4MxUPag0Cgz3dCrTMW6prHGA/640?wx_fmt=gif

即可显示出图片

git常用错误解决方案

发表于 2020-07-21

git常用错误解决方案

git被墙,需要设置代理

假如git push等操作出现Timed out等,无法对远端的代码进行操作,且访问github的时候无法访问,就要考虑是被墙的问题。浏览器可以通过SwitchyOmega进行代理,git同样需要进行配置代理才能够访问

我用的是ByWave,是V2ray的机场。注册购买登录后进入系统设置面板,主要看Socks5端口和HTTP端口,然后在需要进行配置的项目执行以下配置命令(不建议使用–global命令,不同项目的地址可能都不同,不需要所有都代理)

使用Socks5:(设配置定义的端口是1081)

git config http.proxy "socks5://127.0.0.1:1081"

git config https.proxy "socks5://127.0.0.1:1081"

使用HTTP:

git config http.proxy "http://127.0.0.1:1081"
git config https.proxy "http://127.0.0.1:1081"

然后开启代理,即可进行git push等操作

如果要取消代理配置,执行以下命令即可:

git config --unset http.proxy

git config --unset https.proxy

github连接报”ssh: connect to host github.com port 22: Connection timed out”错误

先测试是不是由于这个问题无法进行一系列的git操作:

在连接github时,执行”ssh -T git@github.com” 命令时,出现

1
ssh: connect to host github.com port 22: Connection timed out

解决方法:

首先设置好ssh key

然后找到git的安装目录,找到/etc/ssh/ssh_config文件,然后再把以下这段写入到文本即可

1
2
3
4
5
6
Host github.com // 或者使用默认的Host *
User YourEmail@163.com // 设置成自己ssh定义的email
Hostname ssh.github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
Port 443

Ubuntu就在根目录找到.ssh和直接找/etc路径即可,注意需要使用sudo权限去修改

记一次钉钉鉴权验证过程

发表于 2020-07-21

在使用jsapi中,出现以下错误的原因是:

1
{"errorMessage":"对应企业没有某域名微应用", "errorCode":"3"}
  • 微应用管理的基础信息,应用首页地址或PC端首页地址必须要和打开的地址匹配,不然就会报上面的错

  • 鉴权的时候也必须使用对应的corpId,公司ID一般是不变的,可以写死,在开发者后台的首页就能看到

  • 必须由最高管理员授权管理员对应的应用权限,才能进入应用拿到最新的AgentId,AppKey,AppSecret,不然有可能是旧的,就会造成鉴权错误

附:移动端调试钉钉详细步骤

  • 连续点击版本号6次
  • 进入系统和更新菜单,打开开发人员选项
  • 在开发者选项中,先把“仅充电模式下允许ADB调试”打开,然后再打开“USB调试”
  • 连接USB,选择充电模式,必须是充电模式
  • 自动会打开RSA验证菜单,确认
  • 下载钉钉android开发版,在开发者后台,工具与资源下载,pc和android都有
  • 进入手机dev钉钉版,设置-通用-开发者选项,勾选微应用调试
  • 应用稳定性管理增加开发负责人和APPOPS为自己
  • 手机连接到电脑,打开chrome,chrome://inspect 开始调试,必须翻墙
  • 如果无法翻墙,使用UC Devtools也可以进行inspect,如果UC无法inspcet,考虑进入设置,切换InspectorURI Resource为默认资源
  • 这时候无论是微应用还是在里面打开的h5页面,都能够监听调试,查看网络请求或错误信息
  • 开发一般会接入vConsole,就是微信开发的移动端调试工具,点击浮窗即可查看信息

Vue工作经验总结(3)

发表于 2019-08-26

v-model语法糖时,可以使用watch进行数据初始化

之前一直都是使用created+watch进行里面数据的初始化,其实可以直接使用watch就可以了:

1
2
3
4
5
6
value: {
immediate: true,
handler(val) {
this.currentValue = val;
}
}

elementUI里的table,template里不要使用ref

elementUI里的table,template里不要使用ref,引用的结果会与预期有差距,暂时无法找出什么原因

阅读全文 »

JS工作经验总结(3)

发表于 2019-08-26

JS工作经验总结(2)

JS拿到图片实际宽高方法

其实IE9+和其他浏览器都有原生的api可以取得到图片的实际宽高,用于做一些图片放大插件等有用处。如果api不兼容,可以使用图片onload后,取得到的宽高也是图片的实际宽高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (typeof el.naturalWidth == undefined) {
// LTE 8
var i = new Image();
var rw, rh;
i.src = el.src;
// 判断是否有缓存
if (i.complete) {
rw = i.width;
rh = i.height;
} else {
i.onload = function() {
rw = i.width;
rh = i.height;
}
}
} else {
// GTE IE9
rw = el.naturalWidth;
rh = el.naturalHeight;
}

关于使用formData传送的类型

formData可以直接传file,也可以通过Blob二进制类型转成二进制,然后放到formData里。一些压缩的原理就是经过转换后,存成二进制,然后发到后台,后台收到的仍然是文件类型

注意formData传图片文件的时候,是不能直接传base64的,因为它不是文件类型。

清空input文件的值

1
inputEl.files.value = null;

for-in循环时,使用数值作为键值遍历的问题

使用for in循环时,如果对象的键值为数值,会根据数值的大小顺序,从低到高进行遍历,遍历的顺序不可信,不要使用遍历对象然后直接push到数组,会有排序问题,需要额外的方法对数组进行排序

webpack打包体积过大,使用cdn引入依赖

由于我们常引用依赖的方法是import xx from ‘xxx’,但由于引用的cdn是通过全局变量进行引用的,如果想要使用cdn,同时使用import的方法,可以采用cdn和webpack的externals

阅读全文 »

微信工作经验总结

发表于 2019-05-27

记一次微信公众号/h5支付

公众号支付,https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

第三方H5支付,https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4

具体步骤:

  • 用户选择商品,提交订单,生成订单号,这时候金额什么的都会保存在后台算,不需要前端送任何关于钱的字段
  • 如果是公众号JSAPI支付,需要将这个订单号传到后台,生成一个签名,返回的字段里面包含公众号id,时间戳,随机字符串,签名方式,签名等
  • 使用这些签名的字段调用JSAPI
  • 在回调里处理成功和失败
  • 如果是h5支付,需要将订单号请求另一个接口,返回一个外部链接mwebUrl,然后拼接一个支付回调地址&redirect_url=,直接使用window.location.href将地址转到外部链接,就会h5就会进行支付了

注意:就算判断了支付成功,回调里也不能直接做成功的处理,到最后有可能由于各种原因失败,这时候一般都是判断支付成功后,跳到另一个页面,然后轮询这个订单的状况,那时候返回支付成功的时候,才算是真的成功,后台会得到各种信息,交易流水,交易时间等

注意:h5支付如果订单超时,一般时间比较短,比使用JSAPI的方法短很多,这时候mwebUrl是不会返回回来的

注意:已经生成订单的交易后台如果对交易金额进行修改,在继续交易用原订单号请求签名的时候,就会报错,走到了catch,不允许交易

注意:调用公众号JSAPI支付的时候,需要在平台里维护支付的url,不然会在调用JSAPI的时候走到失败的回调,各种报错会有错误提示信息显示,根据提示处理即可

<i class="fa fa-angle-left"></i>123…6<i class="fa fa-angle-right"></i>

56 日志
14 标签
GitHub
© 2023 Sellenite
由 Hexo 强力驱动
主题 - NexT.Mist