问题
在执行自己编译的cli时出现:
1 | env: node\r: No such file or directory |
原因
在windows电脑发布代码, 其单行结束符为\r\n
。然后mac执行时仅将\n
视为换行符。因此程序试图去找node\r
这个程序。当然是找不到的了。
如何处理该问题?
1 | npx crlf --set=LF ./**/*.js |
可以在发布脚本执行前执行一下以确保End of Line
的正确。
相关库
在执行自己编译的cli时出现:
1 | env: node\r: No such file or directory |
在windows电脑发布代码, 其单行结束符为\r\n
。然后mac执行时仅将\n
视为换行符。因此程序试图去找node\r
这个程序。当然是找不到的了。
1 | npx crlf --set=LF ./**/*.js |
可以在发布脚本执行前执行一下以确保End of Line
的正确。
相关库
1 | <html> |
main.js:
1 | require.config({ |
控制台会出现报错Error: Mismatched anonymous define() module: function(){return y}
, 但是有时候白屏有时候正常。
可知白屏是因为标记为main logic
的代码没有正常工作导致的。
分析下来可得如下结论
当出现错误时代码执行顺序为:
1 | -> require.min.js |
当出现正确时代码执行顺序为:
1 | -> require.min.js |
易得是代码执行时序的问题。那么问题来了, 为什么会出现这种情况?
我们可以看一下requirejs
的源码: https://github.com/requirejs/requirejs/blob/HEAD/require.js
当我们执行sha1.min.js
时, sha1.min.js
检测到当前有amd环境,调用define
将自己注入到requirejs
的运行时中, 因为没有调用require
相关方法, 因此requirejs
将其定义推入自身的globalDefQueue
中。(相关代码: L2061)。
当我们执行main.js
时, 调用require.config
时, requirejs
会尝试消费所有的globalDefQueue
, 此时在queue中的参数为[null, [], function(){…}], 因为第一个参数(name)为null, 则会抛出异常Mismatched anonymous define() module...
(相关代码: L1244)
requirejs
会通过调用自身的onError
方法抛出异常, 如果没有手动覆盖onError
的话会调用内置的defaultOnError
方法(相关代码: L1870), 而defaultOnError
的实现很简单:
1 | function defaultOnError(err) { |
直接向顶层抛出异常, 导致整个script的运行时中断,后续的代码当然无法执行。
可以看见原来的实现是有onError的覆写的, 只不过因为在require.config
之后执行导致没有执行。
最佳的解决方案是尽可能早的覆写requirejs.onError
方法。
如果看源码的话。可以看到requirejs
写明了允许匿名模块
Allow for anonymous modules
但是实际使用中却会报错。而且默认的报错是直接向顶层抛出。
这种情况就是A做错了,但是却导致B无法正常执行。这种场景非常难debug
今天凌晨4点多, 噩耗传来。小时候一直疼爱我的外公结束了自己的一生。自然死亡, 寿终正寝。
心里有说不出的难受,又一位长辈永远离开了我。昨天见了外公的最后一眼,面对躺在床上无法与人交流,生活不能自理的老人,纵有千言万语我却说不出一句话,而现在,我已经永远失去了这个机会。而逐渐的,令人恐惧而不得不面对的是,迟早会轮到我的父母。而我无疑是爱他们的。我不害怕死亡,但我害怕离别。
有时候我就会在想,宇宙广阔而浩瀚,人如浮游生物一般,终生被重力束缚在大地上。而短短这一生百年,大多数人也无法对人类史留下什么令人称道的痕迹。令人舒适的事在于所有的一切都依据之前的惯性前进,昨天、今天、明天,每天都按照一样的痕迹走下去,今天的自己比昨天的自己更加优秀一点,而明天的自己再比今天的自己更加优秀一点,太阳照常升起,照常落下。而在这惯性之中,难免生活会时不时来打破这种惯性:生老病死,自然天灾,人心莫测。有时候我会想,也许人生就是一场游戏,为了不让这个游戏太过无聊,设计者会在这场游戏中增加一些随机的事件,也许是好事、也许是坏事。但往往总是违背人们本身的意愿去发生。
有的时候,人生平静如水,让人觉得厌烦与无聊。而有的时候,意外却接踵而至,这时候,人又开始向往原来平静的生活了。人本身就是这样矛盾对立统一的综合体。
而拓展到整个宇宙的视角中,一个人内心不论是怎么样的波涛汹涌,在宇宙的尺度下都会显得无比渺小。对面这浩瀚空间,也许很多人用一生为之奋斗的东西,其实毫无意义。但也不能说是毫无意义,每个独立的个体都没有义务向他人负责,自我实现就是最大的负责。
然而, 生活还要继续, 人生还要前行。做好自己,行出不后悔,即可。
一些胡言乱语罢了。
互联网正在逐步走向越来越安全的趋势, Chrome 90
将默认使用https
。而但凡对这方面有一定了解的都会知道https
: 证书, 校验, 签发机构…等等等等。
而这些我们所熟知的东西, 即一般我们所说的通过ssl层进行传输与校验的, 一般指的是服务端证书。而我们今天要说说客户端安全证书。
客户端安全证书一般不常见, 只出现在对安全有一定需求的内部系统中。他的作用是规定哪些人可以访问: 客户端根据服务端配置的证书签发来下的子证书来对服务端的资源进行访问, 而服务端会对其进行校验 —— 校验不通过则不允许访问。
服务端证书恰恰相反: 他是客户端来校验服务端是否是一个正确的, 没有被篡改过的服务端。
以下是一个客户端进行TLS握手授权的示例:
ClientHello
消息到服务端ServerHello
消息Certificate
消息ServerKeyExchange
消息以用于交换秘钥CertificateRequest
来请求客户端发送他的证书ServerHelloDone
消息表示服务端已经完成了协商消息的发送Certificate
消息, 其中包含了客户端的证书ClientKeyExchange
消息, 其中包含了公钥或者公钥加密的 PreMasterSecret
CertificateVerify
消息, 这是使用客户机证书的私钥对先前握手消息的签名。可以使用客户端证书的公钥来验证此签名。这让服务器知道客户端可以访问证书的私钥,以确保客户端是合法的。ChangeCipherSpec
记录来告知服务器: 所有的信息都将进行身份验证ChangeCipherSpec
关于项目:
常规问题:
webpack 打包细节
Task 与 Microtask 的区别, 以及时序
详细说明可以见: https://html.spec.whatwg.org/multipage/webappapis.html#queuing-tasks
简单的说就是每次执行task
之前会把microtask
都处理掉。在处理过程中加入的microtask
也会按照顺序处理掉
闭包问题:
1 | for (var i = 0; i < 5; i++) { |
结果是什么? (我回答了5个4
, 其实是5个5
..扶额)
然后问如何让其输出 0 1 2 3 4
解法一:
1 | for (var i = 0; i < 5; i++) { |
解法二:
1 | for (let i = 0; i < 5; i++) { |
我没有说出解法二, 想了半天想出了一个解法三。。
1 | for (var i = 0; i < 5; i++) { |
中文数字转阿拉伯数字
1 | // 五千三百万零五百零一 |
一面过后第二天 HR 就打电话过来约二面,就效率方面来说还是非常迅速的
二面没有考察具体代码,主要是问了问项目方面的问题。
比如公司项目的架构,公司团队协作方面是如何协作的,除了平时工作之外有没有做什么其他的事情,有自己的项目么,自己的项目由什么亮点难点这种比较抽象的问题。
其中可能也有混入价值观方面的问题。总之需要注意一下。
然后我这面被刷了,第三天收到了感谢面试的信。至少有个反馈,从这点来看字节做的还是非常不错的。
又被别的部门捞起来了, 之前是飞书, 现在是抖音部门, 就再战一次呗。
路径总和:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
1 | class TreeNode { |
number
, boolean
, string
, object
, undefined
, symbol
string
, number
, bigint
, boolean
, undefined
, symbol
, null
. (Reference: https://developer.mozilla.org/en-US/docs/Glossary/Primitive)1 | 1. 监听所有的用户事件 |
二面算法题难度急速升高, 反正我都没做过。
1 | 用 Javascript 对象模拟 DOM 树,并实现它的 render 方法,通过调用该方法可以转成真正的 DOM 节点,例如我们已经实现了 element.js,通过 require('element.js'),我们可以定义 ul, 如下: |
我的算法:
1 | function el(tag, props, children) { |
1 | 给你一副类似于如下的地图: |
我的算法:
1 | const land = [ |
面试官上来就说,之前面了很多技术问题,我们来聊一聊项目吧。
大概问了问项目经历,工作经历,职业规划,如何协作,如何codereview,最近在学什么新技术等等看上去很随意的问题。
回头跟朋友们复盘了一下,其实是一道情商题,只有情商高的人才能答对,而我情商。。不高,就是一个憨憨。
如何面对这种软刀子题:
记住以下几点:
又换了个团队继续,直接从二面开始
webpack
打包原理么?你们的项目是用什么打包的lock
文件么,升级单个依赖时会遇到什么坑cypress
, 你是如何解决cypress
的下载问题的题:
1 | 设计一个组件,用 React 编写,写出伪代码,有三个要求 |
我的解法:
1 | import React, {useState, useCallback} from 'react'; |
其实还有就是使用jquery
时代的代码来进行处理,然后外面包一层react代码
但是我觉得不够react
因此没有选择这个方案。
杂七杂八的聊了聊,大部分都忘了,就记录一下记得的
一道题:
1 | 实现 tom().eat('apple').play('football').sleep(5).eat('apple').play('football') |
我的写法, 其实不对。主要是一个sleep函数不太会写。求大佬给出正解:
1 | function tom() { |
题:
实现一个LRU:
我的回答:
1 | class LRU { |
我的不是一个优解。
建议正确的答案参考:
又被教育部门捞起来了,反正我来者不拒继续面呗。
1 | inner = 'window'; |
做算法题:
实现16进制加法,不能将两数直接转成十进制相加再转回十六进制
1 | var map = { |
最后还是没有成功拿到offer, 很遗憾,原因是【不太适合团队协作】(当然也有可能只是单纯的敷衍)。面试者除了提升自己的硬实力,还应当提升一下自己在职场的软实力,比如朋友推荐的这本书 软技能
被飞书部门又拉起来了,继续
算法题
没做出来,关键词 padding-bottom
百分比
实现: https://blog.csdn.net/weixin_39357177/article/details/81186498
没做出来
框架
1 | class MiniStack { |
原题: https://leetcode-cn.com/problems/min-stack/
做题:
1 | // 消除字符串中全部的y和连续的xz. |
1 | // 实现一个功能, 发送5个请求,当请求数 |
该问题可以使用Promise.allSettled
进行简化
问的和二面差不多,只是更加细一点
算法题在面试官的要求下不做公开
反正基本不挂人,主要是谈薪的问题。有一个比较难受的点就是字节的薪资是基于你上家薪资来的,或者说大部分大厂的薪资都是基于上家来的。所以比较好的做法是可以先去别的厂然后再来字节996。我就是基础薪资比较低的那种,最后也没感觉有太大的竞争力,比较菜。
万万没想到,我最终还是拿到了Offer。虽然经历比较坎坷,但总不算完全浪费时间。总得来说跳槽这事三分靠运气七分靠实力,但绝对不要被眼前的利益限制住了自己的发展,主要还是要看这次跳槽在自己的职业生涯中能收获什么,而不要为跳而跳。
本文主要是阐述与总结现代浏览器的跨域问题
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
如果两个 URL 的 protocol、port (en-US) (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。
下表给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html | 同源 | 只有路径不同 |
https://store.company.com/secure.html | 失败 | 协议不同 |
http://store.company.com:81/dir/etc.html | 失败 | 端口不同 (http:// 默认端口是80) |
http://news.company.com/dir/other.html | 失败 | 主机不同 |
你可以使用XMLHttpRequest
或Fetch
发起一个跨域请求
你可以在网站http://foo.com
发起一个对http://bar.com
的请求,如果对方网站许可,那么便能拿到对应的响应,否则则失败。
预检请求是一个OPTIONS 请求,在跨域时预先发送到服务端以获取该服务器对跨域访问控制的一些配置,以决定接下来的请求是否会被发送。一般以Header
头的形式返回, 相关配置一般以Access-Control-*
作为开头
简单请求不会触发CORS 预检请求
若请求满足所有下述条件,则该请求可视为”简单请求”:
- 使用下列方法之一:
- GET
- HEAD
- POST
- 除了被用户代理自动设置的首部字段(例如
Connection
,User-Agent
)和在Fetch
规范中定义为禁用首部名称
的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:
- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
Content-Type
的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
- 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
- 请求中没有使用 ReadableStream 对象。
除了简单请求以外的所有请求都被称为复杂请求,复杂请求在进行跨域访问前会发送一个 CORS 预检请求
1 | fetch('http://bar.com', { |
credentials
含义:
- “omit”: Excludes credentials from this request, and causes any credentials sent back in the response to be ignored.
- “same-origin”: Include credentials with requests made to same-origin URLs, and use any credentials sent back in responses from same-origin URLs.
- “include”: Always includes credentials with this request, and always use any credentials sent back in the response.
或
1 | const req = new XMLHttpRequest(); |
1 | Access-Control-Allow-Credentials: true |
注意此时Access-Control-Allow-Origin
不能为 “\“*
注意Chrome 80 后将默认值从原来的None
改为Lax
, 相关影响可以看如下文章
ETag
(如: 50b1c1d4f775c61:df3
):ETag
返回资源计算出的实体值,在再次发送请求时浏览器会带上一个If-None-Match
(如W / "50b1c1d4f775c61:df3"
)请求头,服务端会对这个请求与这个资源的实体值进行对比,如果相同则直接返回304Last-Modified
(如: Fri , 12 May 2006 18:53:33 GMT
):ETag
返回资源最后更新时间,在再次发送请求时浏览器会带上一个If-Modified-Since
请求头,服务端会对这个请求与这个资源的最后更新时间进行对比,如果服务端资源的最后更新时间
>=If-Modified-Since
则返回304Expires
/Cache-Control
:当前时间
<过期时间
则不会发送请求。该过程不需要服务端介入,是浏览器本身的缓存行为。可以通过首次请求资源后服务端返回的响应头来被服务端进行控制。Expires
来源于http/1.0
Cache-Control
来源于http/1.1
max-age
单位为秒Cache-Control
与Expires
同时存在,Cache-Control
生效用户操作 | Expires/Cache-Control | Last-Modified/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进后退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl+F5强制刷新 | 无效 | 无效 |
TRPG Engine 是我一直坚持维护的一个开源项目。正如所有的开源项目都起源于开发者的兴趣,TRPG Engine
也是我对于跑团这一小众领域的兴趣。
我在没有支援没有赞助,单凭个人爱好独立坚持开发3年以上不间断。前端代码累计提交2700+, 后端代码累计提交1200+,风雨无阻。
谨以此文,分享一下我维护这个开源项目的经历。并与千千万万独立坚持的开源人共勉。
所有的个人项目都起源于想做。因为我想做,因此我建立了这样的项目。最初的目的很单纯,就是作为我学习React的一个实践。
最初选用的技术栈是React
+ Redux
。之所以用React
是为了可以共享一部分的代码到React Native
, 这样的搭配会很大的减少重复逻辑的开发与后期的维护,以减少成本。
而事实是我在两者共存与中间代码上花费了很多的精力,写了很多中间脚本使网页版与RN版能够相互协作。
一个项目,如果要走的长远,必须明确你的产品定位与目标用户。比如同样作为即时通讯应用, 钉钉瞄准的用户群是上班族,slack的用户群是开发者,discord的目标用户是游戏玩家,而qq的用户群是小学生。以此为原点,钉钉开发出了一系列日历,考勤,协同,已读未读,ding等功能。slack只做了大量机器人,discord专注于语音会话与大用户群组聊天,而qq则整出了很多花样。
明确自己的定位,其目标一定是解决某种痛点与缺陷,做出任何的抉择都需要考虑是不是符合自己的定位。一个明确的定位可以帮助你不会花费无意义的时间在没有收益的功能上。
时刻记住,项目是为自己做的。它不是一种谋生工具,而是一种兴趣爱好。它最初应当起源于你对现状的小小不满,终止于对此兴趣的终止。不妨增加一些筹码:比如它可以作为你的游戏场,比如他可以作为你谋求更高事业渠道的凭证,比如他可以是你与你伴侣之间的小小私人空间,比如你可以这样告诉自己,你的每一行代码都有可能帮助到素未蒙面的陌生人,而他会感谢你的无私付出。这些会帮助你时刻保持热情与动力,而不至于让这个项目半途而废。
如果一个项目想要宣传,一个门面则非常重要。这可能是一个README, 一片文章,一个官网。但是如果没有任何说明,告诉别人这是什么,你在做什么。又能期待谁来阅读你的源码呢?
对于个人项目来说,自己才是自己产品最忠实的用户,只有这样才能不断发掘产品的不足,并不断改善。如果自己都不想用自己的产品,那么这个产品还有存在的必要么?
回到技术细节:
因为技术在不断更新。而有的库又逐步步入生命周期的终结,很多情况不得不花费时间去迁移。
而TRPG Engine
经历过几次大的迁移/迭代:
TRPG Engine
的后端是使用的node-orm2. 然后这个库被弃用了。因此花了很大经历将其迁移到sequelize,包括代码的修改与数据库的变更。这个没什么好分享的,纯粹是当时的选择有限,而库的生命周期比较短。TRPG Engine
使用的是用纯粹的js写的,但是随着业务的不断增多,代码复杂度的变高,我选择使用Typescript对我的项目进行重构。从长远来看这一步是非常值得的,TS的类型系统长远的帮助到了一个项目的健康发展。(其实有点小遗憾,从目前的角度来看flow在一些细节做得比ts更加好)。React ClassComponent
到 React Hooks
: hooks无疑拥有更高的抽象性,使用Hooks能抛弃HOC这种很难被typescript支持的写法。同时更方便代码的复用,特别是对于React和React Native代码并存的项目来说。Discord
设计的新版页面,并且颜色也从原来的亮色变成保护眼睛的暗色。为了一个项目的长远发展,测试用例与持续集成都是必要的。特别是单元测试,在很多时候能帮助到项目的提前预知问题。坚持每个bug都有一个对应的单元测试用例,防止再次出现类似的问题。
TRPG Engine
虽然开始写测试用例比较晚,但是一旦有机会就会补充一些测试用例,以防止出现一些边缘问题。
目前TRPG Engine
前端有223个测试用例,后端有284个测试用例,还远远不够一个项目的健康发展。
为了方便数据库的升级,自己fork了一个sequelize-auto-migrations库用于生成数据库的迁移脚本, 因为原作者已经不维护了。
数据库迁移脚本可以保证在任意环境下都能生成正确的数据库格式。这对于关系型数据库来说非常重要。
与商业项目不同的是,个人项目可以把一切自己感兴趣、想做的东西都想办法以某种形式糅合到自己的项目中。
TRPG Engine
就拥有这些有意思的系统:
为了维护与运营这个项目,唯一的支出就是在服务器和OSS上了,零零散散也花费了几千元在上面了。一台2核4G的主服务器, 在主服务器上运行Mysql, Redis与Mysql。另外还有一台1核1G的小型服务器作为语音服务器与测试服务器。前端的代码都是本地编译好后提交到第三方对象存储上,这样用户的访问会更加迅速与快捷。
曾经也考虑过要不要和别人一样放出二维码,看看能不能获得一些打赏,后来想了想还是算了。一方面没有多少的关注,我本人也比较低调不善于宣传。另一方面感觉,掺杂了利益后的开源项目,总感觉失去了纯粹性。爱好只能是爱好,如果成为一种牟利方式,那么可能就不像当初那么纯粹了。
最后感谢所有开源作者的无私奉献,正是你们存在才能让这个互联网世界变得更加精彩。