大家好,我是编程小6,很高兴遇见你,有问题可以及时留言哦。
Create by jsliang on 2019-2-11 15:30:34
Recently revised in 2019-3-17 21:30:36
Hello 小伙伴们,如果觉得本文还不错,记得给个 star , 小伙伴们的 star 是我持续更新的动力!GitHub 地址
【2019-08-16】Hello 小伙伴们,由于 jsliang 对文档库进行了重构,这篇文章中的一些链接可能失效,而 jsliang 缺乏精力维护掘金这边的旧文章,对此深感抱歉。请需要获取最新文章的小伙伴,点击上面的 GitHub 地址,去文档库查看调整后的文章。
并不是只有特定的季节才能跑路,只因为人跑得多了,这条路就定下来了。
金三银四跳槽季,jsliang 于 2019年2月11日 写下了这篇文章,开始准备自己的面试之旅。
至 2019年3月17日 为止,jsliang 搭建出个人的面试知识体系,海量地翻阅了一些前辈留下的资料,结合个人需求进行了几场面试,从而进一步完善该文章并进行了发表,希望对准备跳槽或者正在跳槽中的小伙伴有所帮助。
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
2.1 自我介绍 |
2.2 跳槽原委 |
2.3 进击目标 |
2.4 开篇点题 |
三 HTML |
3.1 HTML 学习推荐 |
3.2 HTML 语义化 |
3.3 HTML5 新标签 |
3.4 常见浏览器及其内核 |
3.5 cookies、session、sessionStorage、localStorage |
四 CSS |
4.1 CSS 学习推荐 |
4.2 CSS reset |
4.3 CSS 盒模型 |
4.4 CSS 单位 |
4.5 CSS 选择器 |
4.6 CSS 常见布局 |
4.7 CSS3 新特性 |
4.8 BFC |
4.9 行内元素和块级元素 |
4.10 行内样式、内嵌式、链接式以及导入式 |
4.11 水平垂直居中 |
五 JavaScript |
5.1 JS 学习推荐 |
5.2 JS 引用方式 |
5.3 原型与原型链 |
5.4 作用域与闭包 |
5.5 浅拷贝与深拷贝 |
5.6 模块化与组件化 |
5.7 面向对象与面向过程 |
5.8 防抖与节流 |
5.9 ES6 |
5.10 数组操作 |
六 Vue |
6.1 MVVM |
6.2 生命周期 |
6.3 双向数据绑定 |
6.4 Virtual DOM |
6.5 template 编译 |
6.6 key |
6.7 nextTick |
6.8 父子组件通讯 |
七 微信小程序 |
7.1 文件主要目录及文件作用 |
7.2 微信小程序生命周期 |
7.3 如何封装数据请求 |
7.4 页面数据传递 |
7.5 加载性能优化的方法 |
7.6 微信小程序与原生 APP、Vue、H5 差异 |
7.7 微信小程序原理 |
八 浏览器 |
8.1 浏览器解析 URL |
8.2 重绘与回流 |
8.3 数据存储 |
8.4 内存管理与垃圾回收 |
8.5 内存泄漏 |
九 网络协议 |
9.1 网络分层 |
9.2 HTTP/HTTPS |
9.3 HTTP 状态码 |
9.4 TCP 三次握手与四次挥手 |
十 性能优化 |
10.1 HTML 优化 |
10.2 CSS 优化 |
10.3 JavaScript 优化 |
十一 算法 |
十二 其他 |
十三 总结 |
十四 参考文献 |
14.1 关于面试 |
14.2 关于 HTML |
14.3 关于 CSS |
14.4 关于 JS |
14.5 关于其他 |
十五 网友反馈 |
返回目录
请时刻准备好自己的简历,不管是互联网经济不佳面临裁员,还是因为公司内部斗争严重心乱如麻,还是因为厌倦了目前的一切……只有随时更新自己,把自己的简历准备好,你才知道哪一刻跑路是最佳选择。
返回目录
Hello 小伙伴们好,我叫梁峻荣,网名叫 jsliang,由于崇拜前端大佬 技术胖(jspang)的原因,又因为自己学的是前端(JavaScript),所以给自己取了个 jsliang(JavaScriptLiang) 的网名,希望自己能通过建立自己的前端知识体系,从而在前端路上走得更远。并将自己的经验分享给小伙伴,携手小伙伴们一起前行。
下面是讲讲个人故事:
首先,jsliang 高考后的暑期就听大学师兄的建议,开始学习编程,那时候学习了 C 语言,觉得世界上最神奇的事情不过如此,敲两下键盘,按下回车,电脑就会一闪一闪地响应我们!于是在大学的时候,陆陆续续学过 C、C#、.Net 等……。
-_-|| 由于学得都是基础,又都还给老师了,在这里就不多累述了。
然后,在大二就开始接触 HTML,那时候选修了个《网页设计基础》,跟着老师做了个只有几个页面的静态网站。在大三的时候,参加了学校的特训班,分角色按流程从头到尾做了个包含角色管理、购物等功能的网站。同时,由于在特训班的时候,看到后端使用 ThinkPHP(简称 TP),觉得蛮不错的,于是自己捣鼓,使用 TP 3.2.3 + Bootstrap 3 + MySQL 打造了自己的博客(已下线)。
接着,由于选修了门 Node.js 的课,所以也跟着大佬的步伐接触了下 Vue、Koa 这些,那时候对 npm 等诸多不懂,为了折腾这个,我的前端世界自此打开了个大门。
最后,我在自己的毕业设计中使用 Node.js + Vue + ElementUI + MongoDB 打造了个校园外卖、快递代拿等功能的社区单页应用。
在 2018 年 5 月的时候,家里催促,于是直接出来面试。不像其他大佬的毕业等于失业,很幸运地 jsliang 面试第一家就给了 offer,于是就进了这家公司,那时候感觉自己前面的大学生活白过了,只懂 ES5、jQuery、HTML/HTML5、CSS/CSS3 的皮毛。
在熟悉了三个月的业务,公司给的任务能顺利完成后,我觉得自己不够努力:外面的前端翻天覆地,我的技术却只是 jQuery is all!
于是 2018 年 8 月,jsliang 开始写 Markdown,将 5 月份到 8 月份记录到 Word 文档上的笔记整理成了 jsliang 的文档库,并在 jsliang 的掘金 上发表了第一篇文章。
18 年 8 月至今,jsliang 大致经历了以下这些:
以上,就是 jsliang 的编程生涯。
今儿,在这里写写 jsliang 为了跳槽,根据个人想法进行的一些前端面试资料整理,小伙伴们觉得不错的点个赞或者去 GitHub 点个 star,觉得有误请指出,谢谢~
返回目录
马老板曾经说过,跳槽有两个原因:
首先,如果非要给 jsliang 我自己的跳槽定个位,那肯定是钱没给到位,劳动与产出不成正比。在我 2018 年 5 月入职前,与人事的交谈中了解到每年的 8 月和 2 月可以提薪,当初的技术栈是:HTML、CSS、ES5。
然后,2018 年 6 月到 2019 年 1 月,学习并应用到工作中的技术有:
其中 2018 年 8 月刚转正,也不敢说自己技术进步很大,也不敢说自己项目贡献很大,为公司谋了多大利益,所以没有提薪想法。
2019 年 1 月底感觉自己项目也做了,凌晨 4/5/6 点的体育西路也看过了,技术也提升了,于是跟人事交谈,期望 2 月能加薪,人事表示年终述职演讲得好的给提薪,2 月开工的时候表示提薪名单没我份……
你没看错,提薪全靠 PPT。PPT 里提高了不给,没提就是没有。
当初想法很简单,你随便加个 5/600 我也满足了。
最后,jsliang 曾跟项目总监私下谈话,建议可以发展一些新产品,这样公司或许能获取一些新收入,我也可以进一步挑战我的技术。但是,由于我司是个老牌子公司,并且大部分依赖于接手电信项目进行扩张……
enm...所以心也委屈了。
在 2018 的努力下,GitHub 破 600 近 700 star,掘金破 10 万阅读量,3000 粉丝:
GitHub 见证:点击查看
掘金见证:点击查看
返回目录
1. 熟悉 HTML/HTML5、CSS/CSS3、ES5/ES6。
2. 了解 OOP 概念,并尝试在工作中使用过 OOP 技巧。
3. 对 MVC/MVVM 架构有一定了解,如有 Vue/React/Angular 或者 微信小程序开发经验更佳。
4. 使用过 Bootstrap 或者 ElementUI 等 UI 库,并对前端 UI 库有一定的个人理解。
5. 了解 Git、Webpack 等工具。
6. 对 Java、Node.js 等后端编程有一定了解。
7. 一年及以上工作经验。
该分析数据来自 Boss 直聘
返回目录
本文的知识点将涉及 HTML、CSS、JS、HTTP、Vue、Webpack、打包工具、性能优化等,没有前置条件,看得懂可以瞅瞅复习下,看不懂可以瞅瞅学习下。
关于面试,在这记下慕课网视频看到的,个人非常认同的三个问答:
然后在复习面试题的过程中,个人有些小看法:
当编写业务代码中,碰到某个业务 bug 时,我会习惯性地百度这个业务 bug,看看网友是怎么解决的。但是,学霸级的程序猿,会多走一步,他们会思考产生这个业务 bug 的底层原因是什么,下次碰到类似的是如何应用该技术解决。所以,日积月累,我的确比不上人家了。
way 1:面试成功,跟自己公司递辞呈,走流程,同时跟对面 hr 申请一个月后入职。
way 2:面试成功,跟自己公司递辞呈,询问能不能快速离职,收到回复跟对面 hr 确认时间。【推荐】
way 3:先递辞呈,同时面试,面试成功的,一律申请走完原公司一个月的流程之后的日子入职。
jsliang 于 2 月底拿到 offer 并递交辞呈,3 月 - 4 月进入一个月倒计时,4 月第一周才能拿到离职证明。
最后在这里祝各位小伙伴能找到称心如意的工作~
返回目录
HTML 属于结构层,负责描绘出内容的结构。
CSS 属于表示层,负责如何显示有关内容。
JavaScript 属于行为层,负责内容应如何对事件做出反应。
返回目录
返回目录
语义化的含义就是用正确的标签做正确的事情,HTML 语义化就是让页面的内容结构化,它有如下优点:
简单来说,能用 <header>
、<footer>
等 H5 新标签的就不用 <div class="header">
,不要使用 <div>
来存放段落等……
返回目录
HTML5 中新增标签大致有:<header>
、<footer>
、<aside>
、<nav>
、<video>
、<audio>
、<canvas>
等等。
返回目录
Chrome | Firefox | Safari | IE | Opera | |
---|---|---|---|---|---|
排版引擎 | Blink | Gecko | Webkit | Trident | Blink |
JS 引擎 | V8 | SpiderMonkey | Nitro | Chakra | V8 |
国内一些浏览器使用较多的是 Webkit 内核。
<!--[if IE]><![endif]-->
<!--[if !IE]><![endif]-->
/* 设置文字不可选取 */
* {
-moz-user-select: none; /* 火狐 浏览器 */
-webkit-user-select: none; /* Webkit 浏览器 */
-o-user-select: none; /* Opera 浏览器 */
-ms-user-select: none; /* IE10 浏览器 */
-khtml-user-select: none; /* 早期浏览器 */
user-select: none; /* 默认 */
}
返回目录
cookies:存储于浏览器端的数据。可以设置 cookies 的到期时间,如果不设置时间,则在浏览器关闭窗口的时候会消失。
session:存储于服务器端的数据。session 存储特定用户会话所需的属性和配置信息。
cookies 和 session 的区别在于:
sessionStorage:生命周期存在于标签页或窗口,用于本地存储一个会话(session)中的数据,这些数据会随着窗口或者标签页的关闭而被清空。
localStorage:生命周期是永久的,除非用户主动清除浏览器上存储的 localStorage 信息,否则它将会永久存在。
sessionStorage 和 localStorage 操作方法:setItem
、getItem
以及 removeItem
。
以 localStorage 为例:
localStorage.getItem('name'); // 获取 name 的值
localStorage.setItem('name', 'jsliang'); // 设置 name 的值为 jsliang
localStorage.removeItem('name'); // 删除 name 的值
参考 1:《前端分享之cookie的使用及单点登录》
参考 2:《Cookie、session和localStorage、以及sessionStorage之间的区别》
返回目录
HTML 属于结构层,负责描绘出内容的结构。
CSS 属于表示层,负责如何显示有关内容。
JavaScript 属于行为层,负责内容应如何对事件做出反应。
返回目录
返回目录
在工作的过程中,会发现各式各样的浏览器对某个标签有自己独特的样式。
但是在前端开发中,如果不采用统一标准,那么会产生千奇百怪的 bug。所以为了减少后期 bug 的出现,前端开发人员会重置一遍 CSS 样式,尽可能地使开发的网页在各个浏览器相差不大。
下面是 jsliang 在使用的样式重置,当然如果小伙伴有不同的想法,可以去 百度/必应/google 搜索并使用其他版本的样式重置:
返回目录
在工作的过程中,也许小伙伴需要 div 块的总宽度为 100px,然后发现总是被 margin 撑高,这是因为盒模型定义的问题:
CSS 中有个属性叫 box-sizing
。
box-sizing: border-box
box-sizing: content-box
border-box
中,整个 div
的宽、高,包括 margin
、padding
、border
。content-box
中,整个 div
的宽、高,则不包括上面元素。如上图,如果一个 div
,你的代码如下:
div {
box-sizing: border-box;
margin: 10px;
width: 100px;
height: 100px;
padding: 10px;
}
那么,你的整个宽高还是 100px
。
但是,如果你的代码如下:
div {
box-sizing: content-box;
margin: 10px;
width: 100px;
height: 100px;
padding: 10px;
}
那么,你的整个盒子宽高是 120px
。
如果你在设计页面中,发现内容区被撑爆了,那么,请检查下现在的 border-box
是什么,最好在引用 reset.css
的时候,就对 border-box
进行统一设置,方便管理。
返回目录
在 CSS 中,除了我们常用的 px
,还有其他单位小伙伴们可以了解一下:
单位 | 描述 |
---|---|
% | 百分比 |
px | 像素。计算机屏幕上的一个点为 1px 。 |
em | 相对单位。相对于父元素计算,假如某个 p 元素为 font-size: 12px ,在它内部有个 span 标签,设置 font-size: 2em ,那么,这时候的 span 字体大小为:12 * 2 = 24px |
rem | 相对单位。相对于根元素 html 的 font-size ,假如 html 为 font-size: 12px ,那么,在其当中的 div 设置为 font-size: 2rem ,就是当中的 div 为 24px 。 |
rpx | 微信小程序相对单位。1rpx = 屏幕宽度 / 750 px。在 750px 的设计稿上,1rpx = 1px。 |
除此之外还有 pt、ex 等单位,但由于不太好换算,故在此不提。
返回目录
选择器是匹配元素的一种模式。
HTML 经过解析生成 DOM Tree;而在 CSS 解析完毕后,需要将解析的结果与 DOM Tree 的内容一起进行分析建立一棵 Render Tree,最终用来进行绘图。
Render Tree 中的元素与 DOM 元素相对应,但非一一对应:一个 DOM 元素可能会对应多个 renderer,如文本折行后,不同的「行」会成为 render tree 种不同的 renderer。也有的 DOM 元素被 Render Tree 完全无视,比如 display:none 的元素。
在建立 Render Tree 时,浏览器就要为每个 DOM Tree 中的元素根据 CSS 的解析结果来确定生成怎样的 renderer。对于每个 DOM 元素,必须在所有 Style Rules 中找到符合的 selector 并将对应的规则进行合并。选择器的「解析」实际是在这里执行的,在遍历 DOM Tree 时,从 Style Rules 中去寻找对应的 selector。
在 CSS 的选择器中,它会按照优先级 从右向左解析,因为这样匹配元素的时候,能尽量少地查找,所以选择器最好写地简洁一点。
*
#ID
.class
p
、a
等……p span
、div a
等……a:hover
等……input[type="text"]
等……li:firth-child
、p:nth-child(1)
等……!important -> 行内样式 -> #id -> .class -> 元素和伪元素 -> * -> 继承 -> 默认
返回目录
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Two Column Layout</title>
<style> .container { display: flex; } .child-one { width: 300px; height: 300px; background: red; } .child-two { width: 100%; height: 300px; background: deepskyblue; } </style>
</head>
<body>
<div class="container">
<div class="child-one"></div>
<div class="child-two"></div>
</div>
</body>
</html>
<div>
块而已。当然,小伙伴们可能会说:jsliang 你要考虑 flex
的兼容性啊!enm...支持所有最新版本的浏览器!请更新你的浏览器哦亲~避免被寄刀片,附上
float
布局:《css常见布局》
返回目录
经典:CSS3 相关属性你了解吗,说说都有哪些?能说说你工作中常用的一些 CSS3 属性吗?
那么,CSS3 新特性都有哪些呢?
为了方便记忆,咱将它们扔到同一个 HTML 文件上,小伙伴拷贝到本地上打开,可以看到一个拥有渐变的小球,做着横向运动,如果你鼠标移动到它上面,它的宽度会放大,并且进行倾斜。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS3 新特性</title>
<style> div { width: 100px; height: 100px; border-radius: 50px; background: linear-gradient(red, orange); box-shadow: 10px 10px 5px #888888; position: relative; transition: width 2s; animation: mymove 5s infinite; } div:hover { width:300px; transform: rotate(7deg); } @keyframes mymove { from { left: 0px; } to { left: 200px; } } </style>
</head>
<body>
<div></div>
</body>
</html>
参考 1:《CSS3 圆角》
参考 2:《CSS3 渐变(Gradients)》
参考 3:《CSS3 transition 属性》
参考 4:《CSS3 transform 属性》
参考 5:《CSS3 animation(动画) 属性》
参考 6:《CSS3 box-shadow 属性》
参考 7:《个人总结(css3新特性)》
返回目录
BFC 就是 块级格式上下文,它是一个独立的渲染区域,让处于 BFC 内部的元素和外部的元素相互隔离,使内外元素的定位不会相互影响。
一定的 CSS 声明可以生成 BFC,浏览器对生成的 BFC 有一系列的渲染规则,利用这些渲染规则可以达到一定的布局效果。
参考文献:《我对BFC的理解》
返回目录
返回目录
行内元素:宽度和高度由内容决定,与其他元素共占一行的元素,我们将其叫行内元素。例如:<span>
、<i>
、<a>
等……
块级元素:默认宽度由父容器决定,默认高度由内容决定,独占一行并且可以设置宽高的元素,我们将其叫做块级元素。例如:<p>
、<div>
、<ul>
等……
在日常开发中,我们经常使用 CSS 的 display
属性来打破两者的壁垒:display: inline-block
,使它们拥有更多的状态。
返回目录
在引用 CSS 上,分为四种形式:行内样式、内嵌式、链接式以及导入式,下面介绍这四种模式。
直接对 HTML 的标记使用 style 属性,然后将 CSS 代码直接写进去:
<p style="color: #fff; backgournd: deepskyblue;"></p>
将 CSS 写 <head>
与 </head>
之间,并且用 <style>
和 </style>
标记进行声明:
<head>
<style> p { color: #fff; background: deepskyblue; } </style>
</head>
通过将 <style>
上的 CSS 提起到指定的 CSS 文件上,然后通过 <link>
的方式在 HTML 上链接起来。
<head>
<link href="reset.css" type="text/css" rel="stylesheet">
</head>
<head>
<style> @import url(reset.css); </style>
</head>
在优先级上,行内样式 > 链接式 > 内嵌式 > @import 导入式。
返回目录
Flex 是 Flexible Box 的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。
/* 设置 Flex 模式 */
display: flex;
/* 决定元素是横排还是竖着排,要不要倒序 */
flex-direction: column;
/* 决定元素换行格式,一行排不下的时候如何排 */
flex-wrap: wrap;
/* flex-flow = flex-direction + flex-wrap */
flex-flow: column wrap;
/* 同一排下对齐方式,空格如何隔开各个元素 */
justify-content: space-between;
/* 同一排下元素如何对齐,顶部对齐、中部对齐还是其他 */
align-items: center;
/* 多行对齐方式 */
align-content: space-between;
HTML
<div class="container">
<div class="child"></div>
</div>
CSS
.container {
margin: 0 auto;
width: 300px;
height: 200px;
background: deepskyblue;
display: flex;
/* 实现元素水平居中 */
justify-content: center;
/* 实现元素垂直居中 */
align-items: center;
}
.child {
width: 100px;
height: 100px;
background: #fff;
}
HTML
<div class="container">
<div class="child"></div>
</div>
CSS
.container {
position: relative;
width: 300px;
height: 200px;
background: pink;
margin: 0 auto;
}
.child {
position: absolute;
width: 100px;
height: 100px;
top: 50%;
left: 50%;
/* 下面两种方式均可 */
/* margin-top: -50px; margin-left: -50px; */
transform: translate(-50%, -50%);
background: #fff;
}
水平居中:
display: inline-block; text-align: center;
margin: 0 auto;
display: flex; justify-content: center;
垂直居中:
line-height: height
display: flex; align-items: center;
参考文献:
① 《CSS实现垂直居中的常用方法》
② 《CSS 用 position: absolute 与 transform 来居中块级元素的问题》
返回目录
HTML 属于结构层,负责描绘出内容的结构。
CSS 属于表示层,负责如何显示有关内容。
JavaScript 属于行为层,负责内容应如何对事件做出反应。
返回目录
返回目录
<body>
<input type="button" onclick="alert('行内引入')" value="按钮"/>
<button onclick="alert(123)">点击我</button>
</body>
<script> window.onload = function() { alert("js 内部引入!"); } </script>
<body>
<div></div>
<script type="text/javascript" src="./js/index.js"></script>
</body>
注意:
<script>
,因为浏览器解析顺序缘故,如果解析到死循环之类的 JS 代码,会卡住页面。返回目录
关于 prototype
、__proto__
、new
、call()
、apply()
、bind()
、this
这些的知识点,由于篇幅太长,jsliang 已经抽离了出来,并做了简洁详细讲解,详见:
下面放出相关知识点:
__proto__
属性(原型)等于其构造函数的 prototype
属性。返回目录
在 JS 中,最容易混淆的就是作用域的情况。
在传统的后端语言(例如 C 语言)中,一对花括号 {}
就是一个块级作用域,作用域内变量不会相互影响,但是在 JS 中,像 if 条件语句的 {}
就不算一个独立的作用域:
var x = 1;
console.log(x); // 1
if(true) {
var x = 2;
console.log(x); // 2
}
console.log(x); // 2
所以有时候我们就需要变通,通过自执行函数创建临时作用域:
function foo() {
var x = 1;
console.log(x); // 1
if(x) {
(function(x) {
console.log(x); // 1
var x = 2;
console.log(x); // 2
})(x)
}
console.log(x); // 1
}
foo();
说到创建临时作用域,我们就不得不谈一下闭包。
那么,什么是闭包呢?
闭包简单定义:函数 A 里面包含了 函数 B,而 函数 B 里面使用了 函数 A 的变量,那么 函数 B 被称为闭包。
又或者:闭包就是能够读取其他函数内部变量的函数
function A() {
var a = 1;
function B() {
console.log(a);
}
return B();
}
for(var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
请问这段代码输出什么?
答案:3 个 3。
解析:首先,for
循环是同步代码,先执行三遍for
,i 变成了 3;然后,再执行异步代码setTimeout
,这时候输出的 i,只能是 3 个 3 了。
for(let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
在这里,每个 let 和代码块结合起来形成块级作用域,当 setTimeout() 打印时,会寻找最近的块级作用域中的 i,所以依次打印出 0 1 2。
如果这样讲不明白,我们可以执行下下面这段代码:
for(let i = 0; i < 3; i++) {
console.log("定时器外部:" + i);
setTimeout(function() {
console.log(i);
}, 1000);
}
此时浏览器依次输出的是:
定时器外部:0
定时器外部:1
定时器外部:2
0
1
2
即代码还是先执行 for
循环,但是当 for
结束执行到了 setTimeout
的时候,它会做个标记,这样到了 console.log(i)
中,i 就能找到这个块中最近的变量定义。
for(let i = 0; i < 3; i++) {
(function(i){
setTimeout(function() {
console.log(i);
}, 1000);
})(i)
}
以上,我们就讲解完了闭包及解决闭包的方式。
观点 1:有些资料表示闭包中产生的大量局部变量,会造成内存消耗过大,从而造成网页的性能问题。
观点 2:有些资料表示目前浏览器引擎都基于 V8,而 V8 引擎有个 gc 回收机制,不用太过担心变量不会被回收。
提示:所以,如果你觉得不够保险,那就在退出函数之前,将不使用的局部变量全部删除。
返回目录
简单来说,有两个对象 A 和 B,B = A,当你修改 A 时,B 的值也跟着发生了变化,这时候就叫浅拷贝。如果不发生变化,就叫深拷贝。
let a = 1; let b = a; a = 2; console.log(b)
。当我们尝试这样子写时,b 在栈内存中开辟了一个新内存,所以 b 的值不会改变,仍是 1.let a = [1, 2, 3], b = a; a[0] = 3; console.log(b)
。当我们尝试这样子写时,b 会偷懒,引用跟 a 同一块的内存地址,从而 a 的修改会影响 b,使得 b 变成 [3, 1, 3]。function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if(obj && typeof obj === "object") {
for(key in obj) {
if(obj.hasOwnProperty(key)) {
// 判断 obj 子元素是否为对象,如果是,递归复制
if(obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone(obj[key]);
} else {
// 如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a = [1, 2, 3, 4];
let b = deepClone(a);
a[0] = 2;
console.log(a, b);
// Console
// a = [2, 2, 3, 4];
// b = [1, 2, 3, 4];
注意:采用 JSON 进行的深拷贝,无法拷贝到 undefined、function、symbol 这类数据,它是有小 bug 的深拷贝。
function deepClone(obj) {
let _obj = JSON.stringify(obj);
let objClone = JSON.parse(_obj);
return objClone
}
let a = [0, 1, [2, 3], 4];
let b = deepClone(a);
a[0] = 1;
a[2][0] = 1;
console.log(a, b);
// Console
// a = [1, 1, [1, 3], 4];
// b = [0, 1, [2, 3], 4];
返回目录
在前端发展中,随着前后端分离,前端社区的不断壮大,前端能做的事情越来越多,承受的任务越来越重,代码也就越来越长了。就好比 jsliang 个人使用 jQuery 开发的时候,动不动就上千行代码,这在一个编辑器上看起来就有点乱了。如果碰上没有代码折叠的编辑器,你就更加难受了。
有的小伙伴的编辑器不是 VS Code,也不能进行代码折叠
所以,面对越来越多的代码,我们就急需将这些代码分门别类,将代码按功能划分,将同一功能的代码整合在一起,于是就有了模块化开发:一个文件就是一个模块,当我们需要某个文件的时候,我们只需要引用这个模块即可……
首先,是 CommonJS 的提出,在 Node.js 以及 Webpack 都支持 CommonJS,它规定了一个文件就是一个模块,文件内部定义的变量属于这个模块,不会对外暴露从而污染全局变量的规则。在 CommonJS 中,通过 exports 或者 module.exports 进行导出,通过 require 进行 同步加载 所需要依赖的模块。由于它是同步加载模块的形式,所以比较通用于服务器端。
然后,根据 CommonJS 只能同步加载的问题,AMD 根据浏览器的特性,进行了非同步加载模块的提出。同时,AMD 有个问题,就是在使用 require.js 的时候,必须提前加载所有模块。
接着,根据 AMD 的问题,CMD 提出来了:通过按需加载的形式,哪里需要就调用哪里,而不用等到所有的模块都加载了再解析。
最后,ECMA 国际推出了 ES6 的 modules。在 ES6 中,通过 export 关键字导出模块,通过 import 关键字引用代码。当然,由于浏览器厂商诸多,ES6 在浏览器的尚不支持,目前主流做法是先将 ES6 通过 babel 编译成 require。
当然,JS 都进行模块化了,jsliang 想起自己项目中的那一坨 CSS,真心没有回顾的想法!所以我们还需要知道为了方便管理 CSS,大佬们还是有做事儿的:Less 以及 Sass,这两者使 CSS 的编写更有组织性和目的性了。
说起模块化,我们又可以顺带提及组件化了,一开始为了区分这两者,jsliang 也是百度了大量文章,最后成功把自己整蒙了,还是说说感觉可以的解释:
组件化更关注的是 UI 部分:弹出框、头部,内容区、按钮等,都可以编写成组件,然后在适用的地方进行引用。而模块化更侧重于功能或者数据的封装,比如全局的 JSON 配置文件,比如通用的验证方法,比如规范时间戳等。
所以,说到这里,我们就可以提到前端工程化:将整个开发流程就行工程规划,从而提高整个团队的开发效率。
在前端工程化中,最重要的就是提高整个团队在 编码 -> 测试 -> 维护 这三个阶段的生产效率。团队的协调至关重要,将每个任务细分给各个成员,从而获取极致的工作效率,是管理者最喜欢看到的。而在上面的模块化和组件化的应用,就属于前端工程化中的一部分,其目的就是在一些复杂的项目中,方便团队进行合作开发,提高生产效率。
参考文献:
① 《到底什么是前端工程化、模块化、组件化》
② 《【前端工程化系列】简谈前端模块化开发与开发规范》
③ 《个人关于模块化的理解》
④ 《组件化开发和模块化开发概念辨析》
⑤ 《JavaScript模块化 --- Commonjs、AMD、CMD、es6 modules》
⑥ 《浅谈什么是前端工程化》
返回目录
简单来说,就是增加代码的可复用性,减少咱们的工作,使代码更加流畅。
function Person(name, phone) {
this.name = name;
this.phone = phone;
this.eat = function() {
console.log(name + " 吃饭");
}
return this;
}
let p1 = new Person("jsliang", "18818881888");
console.log(p1.name); // jsliang
p1.eat(); // jsliang 吃饭
当然,jsliang 只能写到这里了,再写下去就是设计模式等知识点了。
所以希望小伙伴们还是了解下面向对象思想,有助于进一步提升自己。
返回目录
关于 防抖与节流,jsliang 特意将资料结合起来:
小伙伴们可以前往 《面试知识点 - JS 防抖与节流》 查看。
返回目录
ES6 是个大知识点,如果你面试的公司不是 “饱经沧桑” 的那种,那么一定会出点 ES6 问题,例如:
因为 jsliang 感觉自己连 ES6 的门还没进,所以在这里就不 自作聪明,推荐下阮一峰大佬的教程:
希望小伙伴们看完能有所收获,并在工作中大量使用。
返回目录
在 JavaScript 中,用得较多的之一无疑是数组操作,这里过一遍数组的一些用法:
map
: 遍历数组,返回回调返回值组成的新数组forEach
: 无法break,可以用try/catch中throw new Error来停止filter
: 过滤some
: 有一项返回true,则整体为trueevery
: 有一项返回false,则整体为falsejoin
: 通过指定连接符生成字符串push / pop
: 末尾推入和弹出,改变原数组, 返回推入/弹出项【有误】unshift / shift
: 头部推入和弹出,改变原数组,返回操作项【有误】sort(fn) / reverse
: 排序与反转,改变原数组concat
: 连接数组,不影响原数组, 浅拷贝slice(start, end)
: 返回截断后的新数组,不改变原数组splice(start, number, value...)
: 返回删除元素组成的数组,value 为插入项,改变原数组indexOf / lastIndexOf(value, fromIndex)
: 查找数组项,返回对应的下标reduce / reduceRight(fn(prev, cur), defaultPrev)
: 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)相信小伙伴在工作中耍的已经是一套一套的了,或者像 jsliang 一样只会简单的使用 push
、map
这几个,感兴趣的小伙伴可以 百度/bing/google 找找一些 奇技淫巧,说不定对工作效率有很大提升~
返回目录
推荐:
返回目录
在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互时双向的,因此 View 数据会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需要关注业务逻辑,不需要手动操作 DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
返回目录
$el
和数据对象 data 以及事件还未初始化。在 created 阶段,Vue 实例的数据对象 data 以及方法的运算有了,$el
还没有。render
函数首次被调用,Vue 实例的 $el 和 data 都初始化了,但还是挂载在虚拟的 DOM 节点上。在 mounted 阶段,Vue 实例挂载到实际的 DOM 操作完成,一般在该过程进行 Ajax 交互。Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载 DOM -> 渲染、更新 -> 渲染、销毁等一系列过程,称之为 Vue 的生命周期。
8 个,创建前/创建后、挂载前/挂载后、更新前/更新后、销毁前/销毁后。Vue 生命周期的作用是方便我们通过它的生命周期,在业务代码中更好地操作数据,实现相关功能。
会触发 4 个生命钩子:创建前/创建后、挂载前/挂载后
在 beforeMounted
时它执行了 render
函数,对 $el 和 data 进行了初始化,但此时还是挂载到虚拟的 DOM 节点,然后它在 mounted
时就完成了 DOM 渲染,这时候我们一般还进行 Ajax 交互。
返回目录
Vue 采用 数据劫持 结合 发布者-订阅者 模式的方式,通过 Object.defineProperty()
来劫持各个属性的 setter 以及 getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
dep.notice()
通知时,能调用自身的 update()
方法,并触发 Compile 中绑定的回调,则功成身退。js 实现简单的双向绑定
<body>
<div id="app">
<input type="text" id="txt">
<p id="show"></p>
</div>
<script> window.onload = function() { let obj = {}; Object.defineProperty(obj, "txt", { get: function() { return obj; }, set: function(newValue) { document.getElementById("txt").value = newValue; document.getElementById("show").innerHTML = newValue; } }) document.addEventListener("keyup", function(e) { obj.txt = e.target.value; }) } </script>
</body>
Object.defineProperty 接收三个参数:对象,属性名,配置对象
这里使用的是 Object.defineProperty,这是 Vue 2.0 进行双向数据绑定的写法。在 Vue 3.0 中,它使用 Proxy 进行数据劫持。
参考自《实现双向绑定Proxy比defineproperty优劣如何》
返回目录
Vue 在 render
中 createElement
的时候,并不是产生真实的 DOM 元素,实际上 createElement
描述为 createNodeDescription
,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点。
因此,我们将这样的节点描述为 “虚拟节点”(Virtual Node),简称 VNode。“虚拟 DOM” 是我们对由 Vue 组件树建立的整个 VNode 树的称呼。
作为一枚切图仔,很荣幸地跟小伙伴说:“其实我也不懂 Virtual DOM!”
但是,总会有些面试场合会提到的,所以这里找了几篇资料,小伙伴们可以进一步学习:
其他的就需要小伙伴自己寻找了,如果觉得有不错的解析 Virtual DOM 的文档/视频,小伙伴也可以推荐过来哈~
返回目录
Vue 中 template 就是先转化成 AST 树,再得到 render 函数返回 VNode(Vue 的虚拟 DOM 节点)。
返回目录
key 的作用就是在更新组件时判断两个节点是否相同。相同就复用,不相同就删除旧的创建新的。
对于 diff 过程来说 key 是起不到提速作用的,详见:key 的作用
返回目录
用法:Vue.nextTick( [callback, context] )
参数:
{Function} [callback]
{Object} [context]
说明:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
案例:
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick().then(function () {
// DOM 更新了
})
关于 nextTick 的更多理解,jsliang 就不献丑了,需要学习的小伙伴可以查看:
或者自行查找更优秀的资源。
返回目录
关于 Vue 中的父子组件通讯,相信经常开发 Vue 的小伙伴比 jsliang 知道的多很多。
没怎么使用 Vue 的小伙伴可以看下下面的文章,并尝试自己写一写:
下面咱讲下使用 bus.js 实现非父子组件通讯:
假设在工作中,有三个 .vue 文件:A.vue、B.vue、C.vue。A.vue 是主页面,B.vue 和 C.vue 类似于头部导航条和底部导航栏。现在,B.vue 点击会切换路由,C.vue 需要获取 B.vue 传递的信息。
A.vue
<template>
<div>
<top-nav></top-nav>
<div class="container">
<router-view></router-view>
</div>
<bottom-nav></bottom-nav>
</div>
</template>
bus.js
import Vue from 'vue';
// 使用 Event Bus
const bus = new Vue();
export default bus;
B.vue
<template>
<div class="bottom-nav">
<div class="nav-one" @click="goToPage({path: '/HomeIndex', meta:'首页'})">
<i class="icon-home"></i>
<span>首页</span>
</div>
</div>
</template>
<script> import bus from '../utils/bus' export default { methods: { goToPage(route) { this.$router.push(route.path); bus.$emit('meta', route.meta); } } } </script>
C.vue
<template>
<div class="top-nav">
<span class="title">{{title}}</span>
</div>
</template>
<script> import bus from '../utils/bus' export default { data() { return { title: '' } }, created() { bus.$on('meta', msg=> { this.title = msg; }) } } </script>
返回目录
微信小程序,简称小程序,英文名 Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。
返回目录
- component —————————————————— 组件文件夹
- navBar —— 底部组件
- navBar.js —— 底部组件的 JS 代码
- navBar.json —— 底部组件的配置文件
- navBar.wxml —— 底部组件的 HTML 代码
- navBar.wxss —— 底部组件的 CSS 代码
- pages ————————————————————— 页面文件夹
- index —— 首页
- index.js —— 首页的 JS 代码
- index.json —— 首页的配置文件
- index.wxml —— 首页的 HTML 代码
- index.wxss —— 首页的 CSS 代码
- public ————————————————————— 图片文件夹
- utils —————————————————————— 工具文件夹
- api.js —— 控制 API 的文件
- md5.js —— 工具 - MD5 加密文件
- timestamp.js —— 工具 - 时间戳文件
- app.json ——————————————————— 设置全局的基础数据等
- app.wxss ——————————————————— 公共样式,可通过 import 导入更多
- project.config.json ———————— 项目配置文件
返回目录
onLoad()
:页面加载时触发。onShow()
:页面显示/切入前台时触发。onReady()
:页面初次渲染完成时触发。onHide()
:页面隐藏/切入后台时触发。onUnload()
:页面卸载时触发。返回目录
项目/utils/api.js
// 将请求进行 Promise 封装
const fetch = ({url, data}) => {
// 打印接口请求的信息
console.log(`【step 1】API 接口:${url}`);
console.log("【step 2】data 传参:");
console.log(data);
// 返回 Promise
return new Promise((resolve, reject) => {
wx.request({
url: getApp().globalData.api + url,
data: data,
success: res => {
// 成功时的处理
if (res.data.code == 0) {
console.log("【step 3】请求成功:");
console.log(res.data);
return resolve(res.data);
} else {
wx.showModal({
title: '请求失败',
content: res.data.message,
showCancel: false
});
}
},
fail: err => {
// 失败时的处理
console.log(err);
return reject(err);
}
})
})
}
/** * code 换取 openId * @data { * jsCode - wx.login() 返回的 code * } */
export const wxLogin = data => {
return fetch({
url: "tbcUser/getWechatOpenId",
data: data
})
}
项目/pages/login/login.js
import {
wxLogin,
} from '../../utils/api.js'
项目/pages/login/login.js
wxLogin({
jsCode: this.data.code
}).then(
res => {
console.log("【step 4】返回成功处理:");
console.log(res.data);
},
err => {
console.log("【step 4】返回失败处理:");
console.log(err);
}
)
返回目录
onLoad()
中通过 options
获取 url 上的参数:代码演示
<navigator url="../index/index?userId={{userId}}"></navigator>
<!-- 这两段是分别在 HTML 和 JS 中的代码 -->
onLoad: function(options) {
console.log(options.userId);
}
wx.setStorageSync('userId', 'jsliang');
wx.getStorageSync('userId');
login.wxml
<text bindtap="clickText" data-labelId="{{userId}}">点击传递数据到 JS</text>
login.js
clickText(e) {
console.log(e.currentTarget.labelid)
}
组件接收数据:component-tag-name
Component({
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
}
})
使用组件的页面定义 json
{
"usingComponents": {
"component-tag-name": "../component/component"
}
}
使用组件的页面 HTML 代码
<view>
<!-- 以下是对一个自定义组件的引用 -->
<component-tag-name inner-text="Some text"></component-tag-name>
</view>
返回目录
this.$preload()
预加载用户可能点击的第二个页面。返回目录
优势:
劣势:
微信小程序有着低开发成本、低获客成本、无需下载的优势。
微信小程序看似就是阉割版的 Vue。
返回目录
返回目录
返回目录
关于 浏览器解析 URL,jsliang 特意将资料结合起来:
小伙伴们可以前往 《面试知识点 - JS 防抖与节流》 查看。
返回目录
关于 重绘与回流,jsliang 特意将资料结合起来:
小伙伴们可以前往 《面试知识点 - JS 防抖与节流》 查看。
返回目录
返回目录
V8 将内存分为两类:新生代内存空间和老生代内存空间。
这两者通过不同的算法,对内存进行管理操作。
返回目录
意外的全局变量:无法被回收。
定时器:未被正确关闭,导致所引用的外部变量无法被释放。
事件监听:没有正确销毁(低版本浏览器可能出现)。
闭包:会导致父级中的变量无法被释放。
DOM 引用:DOM 被删除时,内存中的引用未被正确清空。
如何查看内存变化情况?
使用 Chrome 的 Timeline(新版本 Performance)进行内存标记,可视化查看内存的变化情况,找出异常点。
返回目录
返回目录
目前网络分层可分为两种:OSI 模型和 TCP/IP 模型。
更多详情可以查看下面这篇文章,里面讲得非常详细:
返回目录
返回目录
首先,我们大致区分下状态码:
然后,常见的状态码:
最后,小伙伴们如果想要了解更多,还是需要自行查找资料的。
返回目录
关于 TCP 三次握手与四次挥手,jsliang 特意将资料结合起来:
小伙伴们可以前往 《面试知识点 - JS 防抖与节流》 查看。
返回目录
通过优化从而提高页面的加载速度。
返回目录
返回目录
.c {}
而不是 .a .b .c {}
。padding-left: 10px
而不是 padding: 0 0 0 10px
。.a .b * {}
这样的选择器,根据从右到左的解析顺序在解析过程中遇到通配符 * {}
会遍历整个 DOM,性能大大损耗。float
在渲染时计算量比较大,可以使用 flex 布局。返回目录
<script>
标签放在 body
之后,避免 JS 的执行卡住 DOM 的渲染,最大程度保证页面尽快地展示出来。display: none
来隐藏,按需显示。返回目录
在算法这块,jsliang 觉得自己还是比较薄弱的,如果小伙伴们跟 jsliang 一样,也想丰富下这方面知识,欢迎一起刷 LeetCode 共同进步:
返回目录
在 【其他】 这章,原本 jsliang 想谈谈面试中的一些小技巧,例如谈薪;或者讲讲 HR 面需要询问的问题,例如工作时长、加班机制、调薪机制等……
但是,最终看来,jsliang 的经历还是有所欠缺,所经历的面试不够 “盛大”,所以说出的话可能就是 “胡言乱语”、“误导观众”,故在此就不献丑了,如果小伙伴们想知道更多,可以通过 QQ 群:798961601
找到我。
☆ 目前 jsliang 通过 3 天的请假,去了 5 场面试,收获了 3 份 offer。
☆ 如果小伙伴不知道简历该怎么写、面试总是镇静不下来、总感觉面试没谱,可以先找 jsliang 聊聊,我会讲讲个人的面试经历,以及听到的其他小伙伴的经历~
返回目录
在观看这篇文章的过程中,小伙伴可能会有这些疑问:
回答:
系列套餐你值得拥有!
回答:
每个人的学习经历是不同的,所拥有的技术、知识点以及工作经验等都是不同的。
所以 jsliang 的目的是通过这篇文章充实自己的同时,顺带挖掘自己的不足,例如面向对象造轮子、算法问题等让 jsliang 想进一步折腾,并应用到工作中。
因此,小伙伴应该根据自己实际去扩展补充属于自己的知识点。
毕竟了解自己的,只有自己!
回答:
每个人的目的都是不同的,不可能一篇文章写完所有知识点,同时有些知识点可能 jsliang 也不感兴趣、或者 jsliang 的层次不够,接触不到。
并且每个面试官都可能有自己的一套面试题,如果 jsliang 能将所有的面试题都写出来,那还需要面试官做啥,大家都像考国家证书一样直接电脑考试吧~(我也期待!!!)
如果小伙伴对文章存有疑问,想快速得到回复。
或者小伙伴对 jsliang 个人的前端文档库感兴趣,也想将自己的前端知识整理出来。
或者小伙伴对文章后续的更新感兴趣,掌握更多的面试技巧。
欢迎加 QQ 群一起探讨:798961601
。
返回目录
本文中的许多内容,也许小伙伴看了会觉得眼熟,因为它们大部分是 jsliang 参考大量文献,再经过刷选整理,最后根据自己理解后的一些阐述。
下面是个人觉得非常优秀的文章。
返回目录
返回目录
返回目录
返回目录
返回目录
返回目录
查看了下掘金评论区,感谢各位大大的反馈,由于本人将于 2019年4月1日 入职,故将一些个人觉得不错的自己没有察觉的知识点记录下来,区分于原文,更为了猴年马月后的下一次跳槽进一步完善。
意思就是,jsliang 这货懒得改原文了,小伙伴们看着这里进行知识点补充
函数 A 里面包含了 函数 B,而 函数 B 里面使用了 函数 A 的变量,函数 B 被 return 了出去,那么 函数 B 被称为闭包。
box-sizing
属性:当值为 border-box
时,宽度 width = content + padding + border
,包含内边距与边框。
当值为 content-box
时,宽度 width = content
,不包含内边距与边框。
em
是一个相对的大小,这里的相对于元素父元素的 font-size
。
Side Project 对应的中文就是副业、业余项目或者小项目。
感兴趣的小伙伴可以去了解一下。
push
与 shift
系列:这里原文已备注是有误的,只是一时没空,没有修改。
原文:!important -> 行内样式 -> #id -> .class -> 元素和伪元素 -> * -> 继承 -> 默认
网友:“应该是最后的优先级最高。”
这里最后的优先级最高应该是指同等级优先级覆盖。浏览器通过 CSSParser 将 CSS 解析成 CSS Rule Tree 的时候,没错的话应该是按照原文中的排序先加载,然后同等级的时候,后面的属性覆盖前面的属性。
对于 HTML5 的语义化,ARIA 的意思是 Accessible Rich Internet Application,aria-*
的作用就是描述这个 Tag 在可视化的情境中的具体信息。例如:
aria-label
:为组件指定内置的文本标签,用来替代开发者没有使用 <label>
标签aria-labelledby
:会读取与此具有相同的 id
名的值详情可参考张鑫旭的 《WAI-ARIA无障碍网页应用属性完全展示》
文章描述不够详细。
child-tow
中设置 width: 100%
的时候 child-one
的宽度会随机而变,设置 flex: 1
就不会。所以看个人需求进行设置。float
进行相关的布局。可参考文章:《深拷贝的终极探索(90%的人不知道)》
Promise
与 async
/await
:文章描述不够详细。
本来打算写的,后面没时间,给我删了这块,评论区有篇文献参考:
《九种跨域方式实现原理(完整版)》
以上,即为目前评论区的补充,感谢各位小伙伴的点赞支持。
jsliang 广告推送:
也许小伙伴想了解下云服务器
或者小伙伴想买一台云服务器
或者小伙伴需要续费云服务器
欢迎点击 云服务器推广 查看!
jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/LiangJunron…上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。