比如我定义了个变量,然后清除了控制台,再次定义这个变量会提示已定义。怎么样清空所有记录,实现就像新开了一个console的效果呢?
没有足够的数据
(゚∀゚ )
暂时没有任何数据
elnino 提出了问题 · 2020-01-06
比如我定义了个变量,然后清除了控制台,再次定义这个变量会提示已定义。怎么样清空所有记录,实现就像新开了一个console的效果呢?
比如我定义了个变量,然后清除了控制台,再次定义这个变量会提示已定义。怎么样清空所有记录,实现就像新开了一个console的效果呢?
关注 4 回答 3
elnino 赞了文章 · 2019-12-09
推荐下我自己的小册 React SSR 服务端渲染原理解析与实践
全网最完整的 React SSR 同构技术原理解析与实践,从零开始手把手带你打造自己的同构应用开发骨架,帮助大家彻底深入理解服务端渲染及底层实现原理,学完本课程,你也可以打造自己的同构框架。
现在面试文章已很多,更不觉得新鲜,本文可能有点不同,正因为“多”也就才有了本文的输出。
相信很多前端小伙伴也包括我在内,面试前都要做一些准备,而做做面试题是最平常不过的事儿了,然而每次面试前都要现找面试题,而且答案也不是现成的,其实这样重复的事情在不知不觉中浪费你很多时间。
也就因为发现了这个问题,才有了把面试题进行整理的想法,希望能对一些人有所帮助,节省一点时间吧。
这1个多月来一直在整理前端领域相关的面试题,阅读查看了将近100多篇的面试文章,文章大多来自技术社区,如掘金、思否等,由于很多文章的题目重复性较大,所以整理起来比较费事。
到目前为止已经收集了将近500道,包含问答题、代码题、高频基础、大厂题目等。
下面是部分问题:
第1题 谈谈变量提升?
第2题 说说bind、call、apply的 区别?
第3题 如何实现一个 bind 函数?
第4题 请实现一个 call 函数
第5题 如何实现一个 apply 函数?
第6题 简单说下原型链?
第7题 怎么判断对象类型?
第8题 说说箭头函数的特点
第9题 如何确定This指向
第10题 async、await 的优缺点
第11题 generator 原理
第12题 对Promise的理解
第13题 == 和 ===区别,什么情况用 ==
第14题 垃圾回收 新生代算法,老生代算法
第15题 说说你对闭包的理解
第16题 基本数据类型和引⽤类型在存储上的差别
第17题 浏览器 Eventloop 和 Node 中的有什么区别
第18题 怎样理解setTimeout 执行误差
第19题 说说函数节流和防抖
第20题 数组降维
第21题 请实现一个深拷贝
第22题 typeof 于 instanceof 区别
第23题 cookie和localSrorage、session、indexDB 的区别
第24题 怎么判断页面是否加载完成?
第25题 说说 jsonp 原理
第26题 说说你对Service worker的理解
第27题 说说浏览器缓存机制
第28题 怎样选择合适的缓存策略
第29题 说说重绘(Repaint)和回流(Reflow)
第30题 如何优化图片
第31题 页面首屏渲染性能优化方案有哪些
第32题 浏览器性能问题-使用 Webpack 优化项目
第33题 Babel 原理
第34题 介绍下React 生命周期
第35题 react setState 机制
第36题 Vue的 nextTick 原理
第37题 Vue 生命周期
第38题 Vue 双向绑定
第39题 v-model原理
第40题 watch 和 computed 的区别和运用的场景
第41题 Vue 的父子通信
第42题 简述路由原理
第43题 MVVM-脏数据检测
第44题 MVVM-数据劫持
第45题 React V16 生命周期函数用法
第46题 Vue 和 React 区别
第47题 介绍下虚拟 DOM,对虚拟 DOM 的理解
第48题 路由鉴权
第49题 TCP 3次握手
第50题 TCP 拥塞控制
第51题 慢开始算法
第52题 拥塞避免算法
第53题 tcp 快速重传
第54题 TCP New Ren 改进后的快恢复
第55题 HTTPS 握手
第56题 从输入 URL 到页面加载全过程
第57题 HTTP 常用状态码 301 302 304 403
第58题 常见排序-冒泡排序
第59题 常见排序-插入排序
第60题 常见排序-选择排序
第61题 常见排序-归并排序
第62题 常见排序-快排
第63题 常见排序-堆排序
第64题 常见排序-系统自带排序实现
第65题 介绍下设计模式-工厂模式
第66题 介绍下设计模式-单例模式
第67题 介绍下设计模式-适配器模式
第68题 介绍下设计模式-装饰模式
第69题 介绍下设计模式-代理模式
第70题 介绍下设计模式-发布-订阅模式
第71题 Vue 响应式原理
第72题 实现一个new操作符
第73题 实现一个JSON.stringify
第74题 实现一个JSON.parse
第75题 手写一个继承
第76题 实现一个JS函数柯里化
第77题 请手写一个Promise(中高级必考)
第78题 手写防抖(Debouncing)和节流(Throttling)
第79题 实现一个instanceOf
第80题 实现一个私有变量
第81题 使用setTimeout代替setInterval进行间歇调用
第82题 数组中的forEach和map的区别
第83题 for in和for of的区别
第84题 写一个发布订阅 EventEmitter方法
第85题 let、var、const区别
第86题 typeof和instanceof 区别
第87题 常见的继承的几种方法
第88题 常见的浏览器内核有哪些?
第89题 浏览器的主要组成部分是什么?
第90题 浏览器是如何渲染UI的?
第91题 浏览器如何解析css选择器?
第92题 DOM Tree是如何构建的?
第93题 重绘与重排的区别?
第94题 如何触发重排和重绘?
第95题 如何避免重绘或者重排?
第96题 前端如何实现即时通讯?
第97题 什么是浏览器同源策略?
第98题 怎样解决跨域问题?
第99题 时间格式化
第100题 说说对html 语义化的理解
第101题 说说常用的 meta 标签
第102题 说说两种盒模型以及区别
第103题 css reset 和 normalize.css 有什么区别
第104题 怎样让元素水平垂直居中
第105题 说说选择器的权重计算方式
第106题 清除浮动的方法
第107题 说说你对 BFC 的理解
第108题 import 和 link 区别
第109题 说下 [1, 2, 3].map(parseInt) 结果
第110题 介绍下浏览器事件委托
第111题 10w 条记录的数组,一次性渲染到页面上,如何处理可以不冻结UI?
第112题 如何实现一个左右固定,中间自适应的三栏布局
第113题 如何实现一个自适应的正方形
第114题 如何用css实现一个三角形
第115题 介绍下 positon 属性
第116题 说说渐进增强和优雅降级
第117题 defer和async区别
第118题 实现sleep函数
第119题 实现 lazyMan
第120题 获取元素的最终background-color
....
解析呢?请继续往下看。
为了方便管理和维护这些题目,同时为前端小伙伴儿提供更好看题体验,我简单做了一个题库网站-大前端面试题库,宗旨是充分利用大家的碎片时间,在上下班,地铁公交上刷刷题,日积月累,尽量避免临时抱佛脚的局面。
另外对面试题做了一个简单的类型划分,进行分类划分的重要性不言而喻,碎片化的信息会极大的消耗我们精力,同时也不利于我们记忆,人脑自然的更喜欢结构化的,有规则的信息,通过系统的只处理同一种类型的问题来降低大脑的损耗,加强我们对知识的理解和记忆。
分类大概为js、经典高频、编程题、大厂题、html、css、布局、浏览器、性能、前端框架、react、vue、web 安全、数据结构和算法、http、tcp、node、设计模式等。
这个网站是用的我自己的 react ssr 开发骨架做的,目前支持 pc 和移动端浏览。
看下页面效果
由于个人精力有限,一时无法写完所有的问题解析。目前仍在继续完善中,这个过程也是对自己一种提升和沉淀,我觉得这个价值很大。
不过为了方便大家校对答案,大部分问题的解析我单独做了链接跳转,指向了原文,可直接跳转到来源文章查看解析。
这个题库的价值不只是用来刷题,他的价值在于问题本身,问题本身比答案更重要。
通过问题来验证自己的知识技能,核对自己对知识的掌握程度,这完全可以当做一种学习方法来执行。
当我们遇到的问题越多,理解和解决的问题越多,相对的我们的能力就会越强。
这500+看上去确实挺多的了,覆盖面也挺广了,但是前端技术日新月异,新技术新思路层出不穷,相关的问题也会不断的更新迭代,所以这个题库还要继续更新,后面继续把更有针对性的问题收集进来。
以上一些观点有些属于本人自嗨,现在回归初心,收集题库并创建题库站的最终目的是希望帮一些人提升一点效率,节约一点时间,利用碎片时间,上下班地铁上刷个题,如果还能帮你提升技能,提升搞定面试的几率,那就最好不过了。
为了方便大家找到入口不迷路,题库入口绑定到了公众号的独立菜单。
我的公众号是-《前端技术江湖》,主要是个人的原创和一些工作心得的输出,可以及时的获得题库的更新通知,另外还有很多优质文章和前端学习资料,希望大家多多关注。
网站入口 - 大前端面试题库- http://bigerfe.com/,内有福利哦。
我正在打造一个纯技术交流群,面向初中级前端开发者,以学习、交流、思考、提升能力为目标,因为一个人学不如大家一起学,有了更多的交流才会进步的更快。
我理想的模式是,每期让一个人深入学习一个技术,然后自己再转述给大家听,类似一个分享课堂,这样可以成倍的提升学习效率。
或者可以按照题库的顺序依次进行,每人每天对对一个问题进行思考和总结性的输出,锻炼技术的同时提升表达能力。
在这个群里不用担心自己的能力不足,不用担心问题是否太小白而不敢说,大胆的说出问题, 让更多的人一起来分析,说错了也没关系。
有想加入请的关注公众号《前端技术江湖》,回复‘进群’,我拉你进群,另外还有各种学习资料和学习视频
希望本文可以给你带了一些帮助和便利,文中如有错误,欢迎在评论区指。
如果这篇文章帮助到了你,欢迎点赞和关注。
全网最完整的 React SSR 同构技术原理解析与实践,从零开始手把手带你打造自己的同构应用开发骨架,帮助大家彻底深入理解服务端渲染及底层实现原理,学完本课程,你也可以打造自己的同构框架。
赞 72 收藏 50 评论 6
elnino 赞了文章 · 2019-11-29
组件是 UI + 逻辑的复用,但逻辑复用能力等于 0。
一个 React 项目,是由无数个大大小小的组件组合而成的。在 React 的世界中,组件是一等公民。而我们平时拆分组件的依据无非是:尽量的复用代码。
组件是 UI + 逻辑的复用,你不能将 UI 和逻辑拆开。比如 Antd 的 Cascader 级联选择 组件,内置了样式和级联选择的逻辑,用户使用的时候相当于一个黑盒,只管用就行了。但是有一个很现实的问题,当该组件的样式不能满足我们需求的时候,我们需要从 0 重新实现一个组件,重写 UI + 逻辑,哪怕逻辑真的一模一样。组件的逻辑复用能力等于 0。我可以想到一个可怕的事实,社区上的同类组件,大部分的逻辑都是可以复用的,只是在样式上有差异,但逻辑共享在社区上并没有很流行。
HOC 与 Render Props 可以把逻辑抽出来复用,但并没有让逻辑复用流行起来。
当然,我上面说的不能复用有点夸张,React 提供了 HOC 与 Render Props 两种方式来解决逻辑复用的问题。比如下面的监听鼠标位置的逻辑,我们就可以通过 Render Props 来复用。
<Mouse>
(position)=> <OurComponent />
</Mouse>
同类逻辑包括监听 window size,监听 scroll 位置等等。但是我们一般很少用 render props 来封装逻辑,更少去和其它项目去共享逻辑。为什么呢?想想多个逻辑复用会怎么样,你就知道多可怕了。
<WindowSize>
(size)=> (
<Mouse>
(position)=> <OurComponent size={size} mouse={position}/>
</Mouse>
)
</WindowSize>
嵌套地狱的代码是我们不能忍受的,同时 HOC 也存在类似的问题,这可能是导致逻辑复用不能流行起来的一个重要原因。
React Hooks 很好的解决了逻辑复用的问题,同时社区中诞生了一批比较好的 React Hooks 库。
React Hooks 是今年 React 的一个重磅炸弹,在社区引起了激烈的回响。随着 Hooks 的诞生,我们可以通过 Custom Hooks 很方便的封装逻辑,逻辑共享也成为了潮流。比如上面的例子,我们就可以通过 react-use 很方便的实现。
import {useMouse, useWindowSize, useScroll} from 'react-use'
function Demo(){
const mousePosition = useMouse();
const windowSize = useWindowSize();
}
react-use 是社区中比较优秀的 Hooks 库,封装了很多常用的基础逻辑,在日常开发中必不可少。但是只用 react-use 就够了吗?显然不是。react-use 中的 Hooks 粒度比较小,类似于工具库。
而在中台产品中,有很多特定的场景逻辑,需要多个 Hooks 进行组合,或者定制特定的逻辑。在蚂蚁内部,有非常多的中台应用,开发者在各自项目中沉淀了各种好用的 custom Hooks,我们把这些好用的逻辑拿出来,创建了 @umijs/hooks ,定位为为中台场景服务的 Hooks 库。
@umijs/hooks 是面向中台应用场景的 Hooks 库,封装了中台常见场景的逻辑,让中台开发变得超级简单。@umijs/hooks 已经在蚂蚁金服多个产品中落地,口碑很好,提效明显。当然,你可能不信,口说无凭,那就用例子来说话。
中台开发中,table 页面应该算最多的一个了,我们一般会使用 Antd 的 Table 组件来搭建,但是其中还是有很多逻辑,我们是无法避免的。
上面的逻辑,我们在几乎所有的 table 页是必须要处理的,想想都可怕。useAntdTable 至少封装了上面 6 个逻辑,一行代码封装所有逻辑,列表页开发从未变得如此简单。
const { tableProps } = useAntdTable(asyncFn);
const columns = [];
return (
<Table columns={columns} rowKey="id" {...tableProps} />
)
常见的异步搜索场景,我们一般要处理:
现在一切变得如此简单:
const { data, loading, onChange } = useSearch(asyncFn);
<Select
onSearch={onChange}
loading={loading}
>
{data.map((item)=><Option />)}
</Select>
当然,我们还有更多极大提效的 Custom Hooks,你能想象不用写一行逻辑,就能实现异步 loadmore 功能吗?(useLoadMore)
你能想象不用写一行逻辑,就能实现动态增删,排序的表单吗?(useDynamicList)
各种常见场景,通通不用写逻辑,通通不用写逻辑。你可以在@umijs/hooks发现更多好用的 Hooks。
react-use 应该是目前发展最好的 Hooks 库,但是我们正在逐渐放弃它,最大的原因是版本升级太快,你能想象几个月之前我项目中用的是 v9,现在已经是 v13 了吗?我已经不知道怎么去升级了。
为了解决这个问题,@umijs/hooks 也沉淀了日常工作中频繁使用的基础 Hooks,包括常用的 useAsync,useDebounce,useBoolean,useMouse 等等,并且还在不断发展。也许只用 umi hooks 就够了。
umi hooks 让中台开发变得如此简单,我能想象,不久的将来,中台开发可以不用写一行逻辑,这也是我们为之奋斗的目标。
同时,希望更多的人参与进来,你可以提供好的 idea,也可以将日常封装的 Hooks 贡献上来,让 umi hooks 更加丰满,让更多的人收益。
如果你觉得不错,动动手指,点个 star,鼓励下我们,万分感谢!
查看原文一个 React 项目,是由无数个大大小小的组件组合而成的。在 React 的世界中,组件是一等公民。而我们平时拆分组件的依据无非是:尽量的复用代码。
赞 31 收藏 17 评论 2
elnino 赞了文章 · 2019-07-12
本文以CentOS 7/Mac 为例,介绍zsh的配置使用教程。
echo $SHELL
<!-- more -->
cat /etc/shells
yum install zsh # CentOS
brew install zsh # mac安装
zsh
设置为默认shellchsh -s /bin/zsh # CentOS
# Mac如下
# 在 /etc/shells 文件中加入如下一行
/usr/local/bin/zsh
# 接着运行
chsh -s /usr/local/bin/zsh
可以通过echo $SHELL
查看当前默认的shell,如果没有改为/bin/zsh
,那么需要重启shell。
配置zsh是一件麻烦的事儿,爱折腾的程序猿怎么可能忍受?!于是,oh-my-zsh出现了,有了这个东东,zsh配置起来就方便多了!
有若干安装方式,介绍三种:
1.自动安装
wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh
2.手动安装
git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
3.真-手动安装
~/.oh-my-zsh
目录。此处省略拷贝的操作步骤。cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
三选一即可,适合各种环境下的安装,然后需要source ~/.zshrc
将配置生效。以下修改了.zshrc
文件之后,都执行一下这个命令。
通过如下命令可以查看可用的Theme
:
# ls ~/.oh-my-zsh/themes
如何修改zsh主题呢?
编辑~/.zshrc
文件,将ZSH_THEME="candy"
,即将主题修改为candy
。我采用的steeef
。
在~/.zshrc
中找到plugins
关键字,就可以自定义启用的插件了,系统默认加载git
。
命令内容可以参考cat ~/.oh-my-zsh/plugins/git/git.plugin.zsh
。
常用的:
gapa git add --patch
gc! git commit -v --amend
gcl git clone --recursive
gclean git reset --hard && git clean -dfx
gcm git checkout master
gcmsg git commit -m
gco git checkout
gd git diff
gdca git diff --cached
gp git push
grbc git rebase --continue
gst git status
gup git pull --rebase
完整列表:https://github.com/robbyrussell/oh-my-zsh/wiki/Plugin:git
解压文件用的,所有的压缩文件,都可以直接x filename
,不用记忆参数
当然,如果你想要用tar
命令,可以使用tar -
加tab
键,zsh会列出参数的含义。
按照官方文档介绍,需要使用如下命令安装,而不是一些博客中的介绍:
yum install autojump-zsh # CentOS
brew install autojump # Mac
CentOS
安装好之后,需要在~/.zshrc
中配置一下,除了在plugins
中增加autojump
之外,还需要添加一行:
[[ -s ~/.autojump/etc/profile.d/autojump.sh ]] && . ~/.autojump/etc/profile.d/autojump.sh
安装好之后,记得source ~/.zshrc
,然后你就可以通过j+目录名
快速进行目录跳转。支持目录名的模糊匹配和自动补全。
j -stat
:可以查看历史路径库git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
在 ~/.zshrc
中配置
plugins=(其他的插件 zsh-autosuggestions)
因为箭头→
不太方便,在.zshrc
中自定义补全快捷键为逗号,但是又一次遇到了需要输入逗号的情况,所以,并不太推荐如下修改:
bindkey ',' autosuggest-accept
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
~/.zshrc
文件中配置:
plugins=(其他的插件 zsh-syntax-highlighting)
git-open插件可以在你git项目下打开远程仓库浏览项目。
git clone https://github.com/paulirish/git-open.git $ZSH_CUSTOM/plugins/git-open
bat
代替 cat
cat
某个文件,可以在终端直接输出文件内容,bat
相比 cat
增加了行号和颜色高亮 👍
brew install bat
命令历史记录
~/.zsh_history
文件中),方便再次运行之前的命令。可以按方向键↑和↓来查看之前执行过的命令r
来执行上一条命令ctrl-r
来搜索命令历史记录命令别名
.zshrc
中添加 alias shortcut='this is the origin command'
一行就相当于添加了别名alias
可以查看所有的命令别名cd
命令了。在你知道路径的情况下,比如 /usr/local/bin
你可以输入 cd /u/l/b
然后按进行补全快速输入 kill <tab>
就会列出所有的进程名和对应的进程号ls -l **/*.sh
,可以递归显示当前目录下的 shell 文件,文件少时可以代替 find
。使用 **/
来递归搜索setopt HIST_IGNORE_DUPS
可以消除重复记录,也可以利用 sort -t ";" -k 2 -u ~/.zsh_history | sort -o ~/.zsh_history
手动清除本文以CentOS 7/Mac 为例,介绍zsh的配置使用教程。 准备 查看当前环境shell {代码...} <!-- more --> 查看系统自带哪些shell {代码...} 安装zsh {代码...} 将zsh设置为默认shell {代码...} {代码...} 可以通过echo $SHELL查看当前默认的shell,如果没有改为/b...
赞 62 收藏 38 评论 2
elnino 赞了文章 · 2019-07-12
本文以CentOS 7/Mac 为例,介绍zsh的配置使用教程。
echo $SHELL
<!-- more -->
cat /etc/shells
yum install zsh # CentOS
brew install zsh # mac安装
zsh
设置为默认shellchsh -s /bin/zsh # CentOS
# Mac如下
# 在 /etc/shells 文件中加入如下一行
/usr/local/bin/zsh
# 接着运行
chsh -s /usr/local/bin/zsh
可以通过echo $SHELL
查看当前默认的shell,如果没有改为/bin/zsh
,那么需要重启shell。
配置zsh是一件麻烦的事儿,爱折腾的程序猿怎么可能忍受?!于是,oh-my-zsh出现了,有了这个东东,zsh配置起来就方便多了!
有若干安装方式,介绍三种:
1.自动安装
wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh
2.手动安装
git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
3.真-手动安装
~/.oh-my-zsh
目录。此处省略拷贝的操作步骤。cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
三选一即可,适合各种环境下的安装,然后需要source ~/.zshrc
将配置生效。以下修改了.zshrc
文件之后,都执行一下这个命令。
通过如下命令可以查看可用的Theme
:
# ls ~/.oh-my-zsh/themes
如何修改zsh主题呢?
编辑~/.zshrc
文件,将ZSH_THEME="candy"
,即将主题修改为candy
。我采用的steeef
。
在~/.zshrc
中找到plugins
关键字,就可以自定义启用的插件了,系统默认加载git
。
命令内容可以参考cat ~/.oh-my-zsh/plugins/git/git.plugin.zsh
。
常用的:
gapa git add --patch
gc! git commit -v --amend
gcl git clone --recursive
gclean git reset --hard && git clean -dfx
gcm git checkout master
gcmsg git commit -m
gco git checkout
gd git diff
gdca git diff --cached
gp git push
grbc git rebase --continue
gst git status
gup git pull --rebase
完整列表:https://github.com/robbyrussell/oh-my-zsh/wiki/Plugin:git
解压文件用的,所有的压缩文件,都可以直接x filename
,不用记忆参数
当然,如果你想要用tar
命令,可以使用tar -
加tab
键,zsh会列出参数的含义。
按照官方文档介绍,需要使用如下命令安装,而不是一些博客中的介绍:
yum install autojump-zsh # CentOS
brew install autojump # Mac
CentOS
安装好之后,需要在~/.zshrc
中配置一下,除了在plugins
中增加autojump
之外,还需要添加一行:
[[ -s ~/.autojump/etc/profile.d/autojump.sh ]] && . ~/.autojump/etc/profile.d/autojump.sh
安装好之后,记得source ~/.zshrc
,然后你就可以通过j+目录名
快速进行目录跳转。支持目录名的模糊匹配和自动补全。
j -stat
:可以查看历史路径库git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
在 ~/.zshrc
中配置
plugins=(其他的插件 zsh-autosuggestions)
因为箭头→
不太方便,在.zshrc
中自定义补全快捷键为逗号,但是又一次遇到了需要输入逗号的情况,所以,并不太推荐如下修改:
bindkey ',' autosuggest-accept
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
~/.zshrc
文件中配置:
plugins=(其他的插件 zsh-syntax-highlighting)
git-open插件可以在你git项目下打开远程仓库浏览项目。
git clone https://github.com/paulirish/git-open.git $ZSH_CUSTOM/plugins/git-open
bat
代替 cat
cat
某个文件,可以在终端直接输出文件内容,bat
相比 cat
增加了行号和颜色高亮 👍
brew install bat
命令历史记录
~/.zsh_history
文件中),方便再次运行之前的命令。可以按方向键↑和↓来查看之前执行过的命令r
来执行上一条命令ctrl-r
来搜索命令历史记录命令别名
.zshrc
中添加 alias shortcut='this is the origin command'
一行就相当于添加了别名alias
可以查看所有的命令别名cd
命令了。在你知道路径的情况下,比如 /usr/local/bin
你可以输入 cd /u/l/b
然后按进行补全快速输入 kill <tab>
就会列出所有的进程名和对应的进程号ls -l **/*.sh
,可以递归显示当前目录下的 shell 文件,文件少时可以代替 find
。使用 **/
来递归搜索setopt HIST_IGNORE_DUPS
可以消除重复记录,也可以利用 sort -t ";" -k 2 -u ~/.zsh_history | sort -o ~/.zsh_history
手动清除本文以CentOS 7/Mac 为例,介绍zsh的配置使用教程。 准备 查看当前环境shell {代码...} <!-- more --> 查看系统自带哪些shell {代码...} 安装zsh {代码...} 将zsh设置为默认shell {代码...} {代码...} 可以通过echo $SHELL查看当前默认的shell,如果没有改为/b...
赞 62 收藏 38 评论 2
elnino 提出了问题 · 2019-07-04
一段html字符串里有不定个包含a标签的p标签,要把他们全剔除掉,求大佬们帮忙写个正则
<p>
xxx
<a href='xxx'>a标签</a>
</p>
问题描述 一段html字符串里有不定个包含a标签的p标签,要把他们全剔除掉,求大佬们帮忙写个正则 {代码...}
关注 1 回答 1
elnino 评论了文章 · 2019-04-09
互联网寒冬之际,各大公司都缩减了HC,甚至是采取了“裁员”措施,在这样的大环境之下,想要获得一份更好的工作,必然需要付出更多的努力。
一年前,也许你搞清楚闭包,this,原型链,就能获得认可。但是现在,很显然是不行了。本文梳理出了一些面试中有一定难度的高频原生JS问题,部分知识点可能你之前从未关注过,或者看到了,却没有仔细研究,但是它们却非常重要。本文将以真实的面试题的形式来呈现知识点,大家在阅读时,建议不要先看我的答案,而是自己先思考一番。尽管,本文所有的答案,都是我在翻阅各种资料,思考并验证之后,才给出的(绝非复制粘贴而来)。但因水平有限,本人的答案未必是最优的,如果您有更好的答案,欢迎给我留言。
本文篇幅较长,但是满满的都是干货!并且还埋伏了可爱的表情包,希望小伙伴们能够坚持读完。
衷心的祝愿大家都能找到心仪的工作。
1. 原始类型有哪几种?null 是对象吗?原始数据类型和复杂数据类型存储有什么区别?
2. typeof 是否正确判断类型? instanceof呢? instanceof 的实现原理是什么?
首先 typeof 能够正确的判断基本数据类型,但是除了 null, typeof null输出的是对象。
但是对象来说,typeof 不能正确的判断其类型, typeof 一个函数可以输出 'function',而除此之外,输出的全是 object,这种情况下,我们无法准确的知道对象的类型。
instanceof可以准确的判断复杂数据类型,但是不能正确判断基本数据类型。(正确判断数据类型请戳:https://github.com/YvetteLau/...
instanceof 是通过原型链判断的,A instanceof B, 在A的原型链中层层查找,是否有原型等于B.prototype,如果一直找到A的原型链的顶端(null;即Object.prototype.__proto__
),仍然不等于B.prototype,那么返回false,否则返回true.
instanceof的实现代码:
// L instanceof R
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显式原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null) //已经找到顶层
return false;
if (O === L) //当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__; //继续向上一层原型链查找
}
}
3. for of , for in 和 forEach,map 的区别。
PS: Object.keys():返回给定对象所有可枚举属性的字符串数组。
关于forEach是否会改变原数组的问题,有些小伙伴提出了异议,为此我写了代码测试了下(注意数组项是复杂数据类型的情况)。
除了forEach之外,map等API,也有同样的问题。
let arry = [1, 2, 3, 4];
arry.forEach((item) => {
item *= 10;
});
console.log(arry); //[1, 2, 3, 4]
arry.forEach((item) => {
arry[1] = 10; //直接操作数组
});
console.log(arry); //[ 1, 10, 3, 4 ]
let arry2 = [
{ name: "Yve" },
{ age: 20 }
];
arry2.forEach((item) => {
item.name = 10;
});
console.log(arry2);//[ { name: 10 }, { age: 20, name: 10 } ]
如还不了解 iterator 接口或 for...of, 请先阅读ES6文档: Iterator 和 for...of 循环
更多细节请戳: https://github.com/YvetteLau/...
4. 如何判断一个变量是不是数组?
arr.constructor === Array
. (不准确,因为我们可以指定 obj.constructor = Array
)function fn() {
console.log(Array.isArray(arguments)); //false; 因为arguments是类数组,但不是数组
console.log(Array.isArray([1,2,3,4])); //true
console.log(arguments instanceof Array); //fasle
console.log([1,2,3,4] instanceof Array); //true
console.log(Object.prototype.toString.call(arguments)); //[object Arguments]
console.log(Object.prototype.toString.call([1,2,3,4])); //[object Array]
console.log(arguments.constructor === Array); //false
arguments.constructor = Array;
console.log(arguments.constructor === Array); //true
console.log(Array.isArray(arguments)); //false
}
fn(1,2,3,4);
5. 类数组和数组的区别是什么?
类数组:
1)拥有length属性,其它属性(索引)为非负整数(对象中的索引会被当做字符串来处理);
2)不具有数组所具有的方法;
类数组是一个普通对象,而真实的数组是Array类型。
常见的类数组有: 函数的参数 arugments, DOM 对象列表(比如通过 document.querySelectorAll 得到的列表), jQuery 对象 (比如 $("div")).
类数组可以转换为数组:
//第一种方法
Array.prototype.slice.call(arrayLike, start);
//第二种方法
[...arrayLike];
//第三种方法:
Array.from(arrayLike);
PS: 任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象。
6. == 和 === 有什么区别?
=== 不需要进行类型转换,只有类型相同并且值相等时,才返回 true.
== 如果两者类型不同,首先需要进行类型转换。具体流程如下:
let person1 = {
age: 25
}
let person2 = person1;
person2.gae = 20;
console.log(person1 === person2); //true,注意复杂数据类型,比较的是引用地址
思考: [] == ![]
我们来分析一下: [] == ![]
是true还是false?
![]
引用类型转换成布尔值都是true,因此![]
的是false7. ES6中的class和ES5的类有什么区别?
8. 数组的哪些API会改变原数组?
修改原数组的API有:
splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift
不修改原数组的API有:
slice/map/forEach/every/filter/reduce/entries/find
注: 数组的每一项是简单数据类型,且未直接操作数组的情况下。
9. let、const 以及 var 的区别是什么?
10. 在JS中什么是变量提升?什么是暂时性死区?
变量提升就是变量在声明之前就可以使用,值为undefined。
在代码块内,使用 let/const 命令声明变量之前,该变量都是不可用的(会抛出错误)。这在语法上,称为“暂时性死区”。暂时性死区也意味着 typeof 不再是一个百分百安全的操作。
typeof x; // ReferenceError(暂时性死区,抛错)
let x;
typeof y; // 值是undefined,不会报错
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
11. 如何正确的判断this? 箭头函数的this是什么?
this的绑定规则有四种:默认绑定,隐式绑定,显式绑定,new绑定.
测试下是否已经成功Get了此知识点(浏览器执行环境):
var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
如果this的知识点,您还不太懂,请戳: 嗨,你真的懂this吗?
12. 词法作用域和this的区别。
13. 谈谈你对JS执行上下文栈和作用域链的理解。
执行上下文就是当前 JavaScript 代码被解析和执行时所在环境, JS执行上下文栈可以认为是一个存储函数调用的栈结构,遵循先进后出的原则。
作用域链: 无论是 LHS 还是 RHS 查询,都会在当前的作用域开始查找,如果没有找到,就会向上级作用域继续查找目标标识符,每次上升一个作用域,一直到全局作用域为止。
题难不难?不难!继续挑战一下难!知道难,就更要继续了!
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方式就是在一个函数内部创建另一个函数。
闭包的作用有:
15. call、apply有什么区别?call,aplly和bind的内部是如何实现的?
call 和 apply 的功能相同,区别在于传参的方式不一样:
call核心:
Function.prototype.call = function (context) {
/** 如果第一个参数传入的是 null 或者是 undefined, 那么指向this指向 window/global */
/** 如果第一个参数传入的不是null或者是undefined, 那么必须是一个对象 */
if (!context) {
//context为null或者是undefined
context = typeof window === 'undefined' ? global : window;
}
context.fn = this; //this指向的是当前的函数(Function的实例)
let args = [...arguments].slice(1);//获取除了this指向对象以外的参数, 空数组slice后返回的仍然是空数组
let result = context.fn(...args); //隐式绑定,当前函数的this指向了context.
delete context.fn;
return result;
}
//测试代码
var foo = {
name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {
console.log(this.name);
console.log(job, age);
}
bar.call(foo, 'programmer', 20);
// Selina programmer 20
bar.call(null, 'teacher', 25);
// 浏览器环境: Chirs teacher 25; node 环境: undefined teacher 25
apply:
apply的实现和call很类似,但是需要注意他们的参数是不一样的,apply的第二个参数是数组或类数组.
Function.prototype.apply = function (context, rest) {
if (!context) {
//context为null或者是undefined时,设置默认值
context = typeof window === 'undefined' ? global : window;
}
context.fn = this;
let result;
if(rest === undefined || rest === null) {
//undefined 或者 是 null 不是 Iterator 对象,不能被 ...
result = context.fn(rest);
}else if(typeof rest === 'object') {
result = context.fn(...rest);
}
delete context.fn;
return result;
}
var foo = {
name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {
console.log(this.name);
console.log(job, age);
}
bar.apply(foo, ['programmer', 20]);
// Selina programmer 20
bar.apply(null, ['teacher', 25]);
// 浏览器环境: Chirs programmer 20; node 环境: undefined teacher 25
bind
bind 和 call/apply 有一个很重要的区别,一个函数被 call/apply 的时候,会直接调用,但是 bind 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
Function.prototype.my_bind = function(context) {
if(typeof this !== "function"){
throw new TypeError("not a function");
}
let self = this;
let args = [...arguments].slice(1);
function Fn() {};
Fn.prototype = this.prototype;
let bound = function() {
let res = [...args, ...arguments]; //bind传递的参数和函数调用时传递的参数拼接
context = this instanceof Fn ? this : context || this;
return self.apply(context, res);
}
//原型链
bound.prototype = new Fn();
return bound;
}
var name = 'Jack';
function person(age, job, gender){
console.log(this.name , age, job, gender);
}
var Yve = {name : 'Yvette'};
let result = person.my_bind(Yve, 22, 'enginner')('female');
16. new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?
new:
function new(func) {
lat target = {};
target.__proto__ = func.prototype;
let res = func.call(target);
if (typeof(res) == "object" || typeof(res) == "function") {
return res;
}
return target;
}
字面量创建对象,不会调用 Object构造函数, 简洁且性能更好;
new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。
通过对象字面量定义对象时,不会调用Object构造函数。
17. 谈谈你对原型的理解?
在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
18. 什么是原型链?【原型链解决的是什么问题?】
原型链解决的主要是继承问题。
每个对象拥有一个原型对象,通过 proto (读音: dunder proto) 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.__proto__
指向的是null)。这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。
构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(p.__proto__ === Parent.prototype)
19. prototype 和
__proto__
区别是什么?
prototype是构造函数的属性。
__proto__
是每个实例都有的属性,可以访问 [[prototype]] 属性。
实例的__proto__
与其构造函数的prototype指向的是同一个对象。
function Student(name) {
this.name = name;
}
Student.prototype.setAge = function(){
this.age=20;
}
let Jack = new Student('jack');
console.log(Jack.__proto__);
//console.log(Object.getPrototypeOf(Jack));;
console.log(Student.prototype);
console.log(Jack.__proto__ === Student.prototype);//true
20. 使用ES5实现一个继承?
组合继承(最常用的继承方式)
function SuperType() {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}
其它继承方式实现,可以参考《JavaScript高级程序设计》
21. 什么是深拷贝?深拷贝和浅拷贝有什么区别?
浅拷贝是指只复制第一层对象,但是当对象的属性是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
实现一个深拷贝:
function deepClone(obj) { //递归拷贝
if(obj === null) return null; //null 的情况
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
if(typeof obj !== 'object') {
//如果不是复杂数据类型,直接返回
return obj;
}
/**
* 如果obj是数组,那么 obj.constructor 是 [Function: Array]
* 如果obj是对象,那么 obj.constructor 是 [Function: Object]
*/
let t = new obj.constructor();
for(let key in obj) {
//如果 obj[key] 是复杂数据类型,递归
t[key] = deepClone(obj[key]);
}
return t;
}
看不下去了?别人的送分题会成为你的送命题
防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于设置的时间,防抖的情况下只会调用一次,而节流的情况会每隔一定时间调用一次函数。
防抖(debounce): n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
function debounce(func, wait, immediate=true) {
let timeout, context, args;
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空定时器
timeout = null
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {
func.apply(context, args);
context = args = null;
}
}, wait);
let debounced = function (...params) {
if (!timeout) {
timeout = later();
if (immediate) {
//立即执行
func.apply(this, params);
} else {
//闭包
context = this;
args = params;
}
} else {
clearTimeout(timeout);
timeout = later();
}
}
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
防抖的应用场景:
节流(throttle): 高频事件在规定时间内只会执行一次,执行一次后,只有大于设定的执行周期后才会执行第二次。
//underscore.js
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0 : Date.now() || new Date().getTime();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () {
var now = Date.now() || new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};
函数节流的应用场景有:
23. 取数组的最大值(ES5、ES6)
// ES5 的写法
Math.max.apply(null, [14, 3, 77, 30]);
// ES6 的写法
Math.max(...[14, 3, 77, 30]);
// reduce
[14,3,77,30].reduce((accumulator, currentValue)=>{
return accumulator = accumulator > currentValue ? accumulator : currentValue
});
24. ES6新的特性有哪些?
25. setTimeout倒计时为什么会出现误差?
setTimeout() 只是将事件插入了“任务队列”,必须等当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码消耗时间很长,也有可能要等很久,所以并没办法保证回调函数一定会在 setTimeout() 指定的时间执行。所以, setTimeout() 的第二个参数表示的是最少时间,并非是确切时间。
HTML5标准规定了 setTimeout() 的第二个参数的最小值不得小于4毫秒,如果低于这个值,则默认是4毫秒。在此之前。老版本的浏览器都将最短时间设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常是间隔16毫秒执行。这时使用 requestAnimationFrame() 的效果要好于 setTimeout();
26. 为什么 0.1 + 0.2 != 0.3 ?
0.1 + 0.2 != 0.3 是因为在进制转换和进阶运算的过程中出现精度损失。
下面是详细解释:
JavaScript使用 Number 类型表示数字(整数和浮点数),使用64位表示一个数字。
图片说明:
计算机无法直接对十进制的数字进行运算, 需要先对照 IEEE 754 规范转换成二进制,然后对阶运算。
1.进制转换
0.1和0.2转换成二进制后会无限循环
0.1 -> 0.0001100110011001...(无限循环)
0.2 -> 0.0011001100110011...(无限循环)
但是由于IEEE 754尾数位数限制,需要将后面多余的位截掉,这样在进制之间的转换中精度已经损失。
2.对阶运算
由于指数位数不相同,运算时需要对阶运算 这部分也可能产生精度损失。
按照上面两步运算(包括两步的精度损失),最后的结果是
0.0100110011001100110011001100110011001100110011001100
结果转换成十进制之后就是 0.30000000000000004。
27. promise 有几种状态, Promise 有什么优缺点 ?
promise有三种状态: fulfilled, rejected, pending.
Promise 的优点:
Promise 的缺点:
28. Promise构造函数是同步还是异步执行,then中的方法呢 ?promise如何实现then处理 ?
Promise的构造函数是同步执行的。then中的方法是异步执行的。
promise的then实现,详见: Promise源码实现
29. Promise和setTimeout的区别 ?
Promise 是微任务,setTimeout 是宏任务,同一个事件循环中,promise总是先于 setTimeout 执行。
30. 如何实现 Promise.all ?
要实现 Promise.all,首先我们需要知道 Promise.all 的功能:
promises 中所有的promise都“完成”时或参数中不包含 promise 时回调完成。
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let index = 0;
let result = [];
if (promises.length === 0) {
resolve(result);
} else {
setTimeout(() => {
function processValue(i, data) {
result[i] = data;
if (++index === promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
//promises[i] 可能是普通值
Promise.resolve(promises[i]).then((data) => {
processValue(i, data);
}, (err) => {
reject(err);
return;
});
}
})
}
});
}
如果想了解更多Promise的源码实现,可以参考我的另一篇文章:Promise的源码实现(完美符合Promise/A+规范)
31.如何实现 Promise.finally ?
不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.
Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}
32. 什么是函数柯里化?实现 sum(1)(2)(3) 返回结果是1,2,3之和
函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
function sum(a) {
return function(b) {
return function(c) {
return a+b+c;
}
}
}
console.log(sum(1)(2)(3)); // 6
引申:实现一个curry函数,将普通函数进行柯里化:
function curry(fn, args = []) {
return function(){
let rest = [...args, ...arguments];
if (rest.length < fn.length) {
return curry.call(this,fn,rest);
}else{
return fn.apply(this,rest);
}
}
}
//test
function sum(a,b,c) {
return a+b+c;
}
let sumFn = curry(sum);
console.log(sumFn(1)(2)(3)); //6
console.log(sumFn(1)(2, 3)); //6
如果您在面试中遇到了更多的原生JS问题,或者有一些本文未涉及到且有一定难度的JS知识,请给我留言。您的问题将会出现在后续文章中~
本文的写成耗费了非常多的时间,在这个过程中,我也学习到了很多知识,谢谢各位小伙伴愿意花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的肯定是我前进的最大动力。https://github.com/YvetteLau/...
后续写作计划
1.《寒冬求职季之你必须要懂的原生JS》(中)(下)
2.《寒冬求职季之你必须要知道的CSS》
3.《寒冬求职季之你必须要懂的前端安全》
4.《寒冬求职季之你必须要懂的一些浏览器知识》
5.《寒冬求职季之你必须要知道的性能优化》
针对React技术栈:
1.《寒冬求职季之你必须要懂的React》系列
2.《寒冬求职季之你必须要懂的ReactNative》系列
参考文章:
0.1 + 0.2 !== 0.3
此题答案大量使用了此篇文章的图文: https://juejin.im/post/5b90e0...推荐关注本人公众号:
一年前,也许你搞清楚闭包,this,原型链,就能获得认可。但是现在,很显然是不行了。本文梳理出了一些面试中有一定难度的高频原生JS问题,部分知识点可能你之前从未关注过,或者看到了,却没有仔细研究,但是它们却非常重要。本文将以真实的面试题的形式来呈现知识...
elnino 评论了文章 · 2019-04-09
互联网寒冬之际,各大公司都缩减了HC,甚至是采取了“裁员”措施,在这样的大环境之下,想要获得一份更好的工作,必然需要付出更多的努力。
一年前,也许你搞清楚闭包,this,原型链,就能获得认可。但是现在,很显然是不行了。本文梳理出了一些面试中有一定难度的高频原生JS问题,部分知识点可能你之前从未关注过,或者看到了,却没有仔细研究,但是它们却非常重要。本文将以真实的面试题的形式来呈现知识点,大家在阅读时,建议不要先看我的答案,而是自己先思考一番。尽管,本文所有的答案,都是我在翻阅各种资料,思考并验证之后,才给出的(绝非复制粘贴而来)。但因水平有限,本人的答案未必是最优的,如果您有更好的答案,欢迎给我留言。
本文篇幅较长,但是满满的都是干货!并且还埋伏了可爱的表情包,希望小伙伴们能够坚持读完。
衷心的祝愿大家都能找到心仪的工作。
1. 原始类型有哪几种?null 是对象吗?原始数据类型和复杂数据类型存储有什么区别?
2. typeof 是否正确判断类型? instanceof呢? instanceof 的实现原理是什么?
首先 typeof 能够正确的判断基本数据类型,但是除了 null, typeof null输出的是对象。
但是对象来说,typeof 不能正确的判断其类型, typeof 一个函数可以输出 'function',而除此之外,输出的全是 object,这种情况下,我们无法准确的知道对象的类型。
instanceof可以准确的判断复杂数据类型,但是不能正确判断基本数据类型。(正确判断数据类型请戳:https://github.com/YvetteLau/...
instanceof 是通过原型链判断的,A instanceof B, 在A的原型链中层层查找,是否有原型等于B.prototype,如果一直找到A的原型链的顶端(null;即Object.prototype.__proto__
),仍然不等于B.prototype,那么返回false,否则返回true.
instanceof的实现代码:
// L instanceof R
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显式原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null) //已经找到顶层
return false;
if (O === L) //当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__; //继续向上一层原型链查找
}
}
3. for of , for in 和 forEach,map 的区别。
PS: Object.keys():返回给定对象所有可枚举属性的字符串数组。
关于forEach是否会改变原数组的问题,有些小伙伴提出了异议,为此我写了代码测试了下(注意数组项是复杂数据类型的情况)。
除了forEach之外,map等API,也有同样的问题。
let arry = [1, 2, 3, 4];
arry.forEach((item) => {
item *= 10;
});
console.log(arry); //[1, 2, 3, 4]
arry.forEach((item) => {
arry[1] = 10; //直接操作数组
});
console.log(arry); //[ 1, 10, 3, 4 ]
let arry2 = [
{ name: "Yve" },
{ age: 20 }
];
arry2.forEach((item) => {
item.name = 10;
});
console.log(arry2);//[ { name: 10 }, { age: 20, name: 10 } ]
如还不了解 iterator 接口或 for...of, 请先阅读ES6文档: Iterator 和 for...of 循环
更多细节请戳: https://github.com/YvetteLau/...
4. 如何判断一个变量是不是数组?
arr.constructor === Array
. (不准确,因为我们可以指定 obj.constructor = Array
)function fn() {
console.log(Array.isArray(arguments)); //false; 因为arguments是类数组,但不是数组
console.log(Array.isArray([1,2,3,4])); //true
console.log(arguments instanceof Array); //fasle
console.log([1,2,3,4] instanceof Array); //true
console.log(Object.prototype.toString.call(arguments)); //[object Arguments]
console.log(Object.prototype.toString.call([1,2,3,4])); //[object Array]
console.log(arguments.constructor === Array); //false
arguments.constructor = Array;
console.log(arguments.constructor === Array); //true
console.log(Array.isArray(arguments)); //false
}
fn(1,2,3,4);
5. 类数组和数组的区别是什么?
类数组:
1)拥有length属性,其它属性(索引)为非负整数(对象中的索引会被当做字符串来处理);
2)不具有数组所具有的方法;
类数组是一个普通对象,而真实的数组是Array类型。
常见的类数组有: 函数的参数 arugments, DOM 对象列表(比如通过 document.querySelectorAll 得到的列表), jQuery 对象 (比如 $("div")).
类数组可以转换为数组:
//第一种方法
Array.prototype.slice.call(arrayLike, start);
//第二种方法
[...arrayLike];
//第三种方法:
Array.from(arrayLike);
PS: 任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象。
6. == 和 === 有什么区别?
=== 不需要进行类型转换,只有类型相同并且值相等时,才返回 true.
== 如果两者类型不同,首先需要进行类型转换。具体流程如下:
let person1 = {
age: 25
}
let person2 = person1;
person2.gae = 20;
console.log(person1 === person2); //true,注意复杂数据类型,比较的是引用地址
思考: [] == ![]
我们来分析一下: [] == ![]
是true还是false?
![]
引用类型转换成布尔值都是true,因此![]
的是false7. ES6中的class和ES5的类有什么区别?
8. 数组的哪些API会改变原数组?
修改原数组的API有:
splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift
不修改原数组的API有:
slice/map/forEach/every/filter/reduce/entries/find
注: 数组的每一项是简单数据类型,且未直接操作数组的情况下。
9. let、const 以及 var 的区别是什么?
10. 在JS中什么是变量提升?什么是暂时性死区?
变量提升就是变量在声明之前就可以使用,值为undefined。
在代码块内,使用 let/const 命令声明变量之前,该变量都是不可用的(会抛出错误)。这在语法上,称为“暂时性死区”。暂时性死区也意味着 typeof 不再是一个百分百安全的操作。
typeof x; // ReferenceError(暂时性死区,抛错)
let x;
typeof y; // 值是undefined,不会报错
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
11. 如何正确的判断this? 箭头函数的this是什么?
this的绑定规则有四种:默认绑定,隐式绑定,显式绑定,new绑定.
测试下是否已经成功Get了此知识点(浏览器执行环境):
var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
如果this的知识点,您还不太懂,请戳: 嗨,你真的懂this吗?
12. 词法作用域和this的区别。
13. 谈谈你对JS执行上下文栈和作用域链的理解。
执行上下文就是当前 JavaScript 代码被解析和执行时所在环境, JS执行上下文栈可以认为是一个存储函数调用的栈结构,遵循先进后出的原则。
作用域链: 无论是 LHS 还是 RHS 查询,都会在当前的作用域开始查找,如果没有找到,就会向上级作用域继续查找目标标识符,每次上升一个作用域,一直到全局作用域为止。
题难不难?不难!继续挑战一下难!知道难,就更要继续了!
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方式就是在一个函数内部创建另一个函数。
闭包的作用有:
15. call、apply有什么区别?call,aplly和bind的内部是如何实现的?
call 和 apply 的功能相同,区别在于传参的方式不一样:
call核心:
Function.prototype.call = function (context) {
/** 如果第一个参数传入的是 null 或者是 undefined, 那么指向this指向 window/global */
/** 如果第一个参数传入的不是null或者是undefined, 那么必须是一个对象 */
if (!context) {
//context为null或者是undefined
context = typeof window === 'undefined' ? global : window;
}
context.fn = this; //this指向的是当前的函数(Function的实例)
let args = [...arguments].slice(1);//获取除了this指向对象以外的参数, 空数组slice后返回的仍然是空数组
let result = context.fn(...args); //隐式绑定,当前函数的this指向了context.
delete context.fn;
return result;
}
//测试代码
var foo = {
name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {
console.log(this.name);
console.log(job, age);
}
bar.call(foo, 'programmer', 20);
// Selina programmer 20
bar.call(null, 'teacher', 25);
// 浏览器环境: Chirs teacher 25; node 环境: undefined teacher 25
apply:
apply的实现和call很类似,但是需要注意他们的参数是不一样的,apply的第二个参数是数组或类数组.
Function.prototype.apply = function (context, rest) {
if (!context) {
//context为null或者是undefined时,设置默认值
context = typeof window === 'undefined' ? global : window;
}
context.fn = this;
let result;
if(rest === undefined || rest === null) {
//undefined 或者 是 null 不是 Iterator 对象,不能被 ...
result = context.fn(rest);
}else if(typeof rest === 'object') {
result = context.fn(...rest);
}
delete context.fn;
return result;
}
var foo = {
name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {
console.log(this.name);
console.log(job, age);
}
bar.apply(foo, ['programmer', 20]);
// Selina programmer 20
bar.apply(null, ['teacher', 25]);
// 浏览器环境: Chirs programmer 20; node 环境: undefined teacher 25
bind
bind 和 call/apply 有一个很重要的区别,一个函数被 call/apply 的时候,会直接调用,但是 bind 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
Function.prototype.my_bind = function(context) {
if(typeof this !== "function"){
throw new TypeError("not a function");
}
let self = this;
let args = [...arguments].slice(1);
function Fn() {};
Fn.prototype = this.prototype;
let bound = function() {
let res = [...args, ...arguments]; //bind传递的参数和函数调用时传递的参数拼接
context = this instanceof Fn ? this : context || this;
return self.apply(context, res);
}
//原型链
bound.prototype = new Fn();
return bound;
}
var name = 'Jack';
function person(age, job, gender){
console.log(this.name , age, job, gender);
}
var Yve = {name : 'Yvette'};
let result = person.my_bind(Yve, 22, 'enginner')('female');
16. new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?
new:
function new(func) {
lat target = {};
target.__proto__ = func.prototype;
let res = func.call(target);
if (typeof(res) == "object" || typeof(res) == "function") {
return res;
}
return target;
}
字面量创建对象,不会调用 Object构造函数, 简洁且性能更好;
new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。
通过对象字面量定义对象时,不会调用Object构造函数。
17. 谈谈你对原型的理解?
在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
18. 什么是原型链?【原型链解决的是什么问题?】
原型链解决的主要是继承问题。
每个对象拥有一个原型对象,通过 proto (读音: dunder proto) 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.__proto__
指向的是null)。这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。
构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(p.__proto__ === Parent.prototype)
19. prototype 和
__proto__
区别是什么?
prototype是构造函数的属性。
__proto__
是每个实例都有的属性,可以访问 [[prototype]] 属性。
实例的__proto__
与其构造函数的prototype指向的是同一个对象。
function Student(name) {
this.name = name;
}
Student.prototype.setAge = function(){
this.age=20;
}
let Jack = new Student('jack');
console.log(Jack.__proto__);
//console.log(Object.getPrototypeOf(Jack));;
console.log(Student.prototype);
console.log(Jack.__proto__ === Student.prototype);//true
20. 使用ES5实现一个继承?
组合继承(最常用的继承方式)
function SuperType() {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}
其它继承方式实现,可以参考《JavaScript高级程序设计》
21. 什么是深拷贝?深拷贝和浅拷贝有什么区别?
浅拷贝是指只复制第一层对象,但是当对象的属性是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
实现一个深拷贝:
function deepClone(obj) { //递归拷贝
if(obj === null) return null; //null 的情况
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
if(typeof obj !== 'object') {
//如果不是复杂数据类型,直接返回
return obj;
}
/**
* 如果obj是数组,那么 obj.constructor 是 [Function: Array]
* 如果obj是对象,那么 obj.constructor 是 [Function: Object]
*/
let t = new obj.constructor();
for(let key in obj) {
//如果 obj[key] 是复杂数据类型,递归
t[key] = deepClone(obj[key]);
}
return t;
}
看不下去了?别人的送分题会成为你的送命题
防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于设置的时间,防抖的情况下只会调用一次,而节流的情况会每隔一定时间调用一次函数。
防抖(debounce): n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
function debounce(func, wait, immediate=true) {
let timeout, context, args;
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空定时器
timeout = null
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {
func.apply(context, args);
context = args = null;
}
}, wait);
let debounced = function (...params) {
if (!timeout) {
timeout = later();
if (immediate) {
//立即执行
func.apply(this, params);
} else {
//闭包
context = this;
args = params;
}
} else {
clearTimeout(timeout);
timeout = later();
}
}
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
防抖的应用场景:
节流(throttle): 高频事件在规定时间内只会执行一次,执行一次后,只有大于设定的执行周期后才会执行第二次。
//underscore.js
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0 : Date.now() || new Date().getTime();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () {
var now = Date.now() || new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};
函数节流的应用场景有:
23. 取数组的最大值(ES5、ES6)
// ES5 的写法
Math.max.apply(null, [14, 3, 77, 30]);
// ES6 的写法
Math.max(...[14, 3, 77, 30]);
// reduce
[14,3,77,30].reduce((accumulator, currentValue)=>{
return accumulator = accumulator > currentValue ? accumulator : currentValue
});
24. ES6新的特性有哪些?
25. setTimeout倒计时为什么会出现误差?
setTimeout() 只是将事件插入了“任务队列”,必须等当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码消耗时间很长,也有可能要等很久,所以并没办法保证回调函数一定会在 setTimeout() 指定的时间执行。所以, setTimeout() 的第二个参数表示的是最少时间,并非是确切时间。
HTML5标准规定了 setTimeout() 的第二个参数的最小值不得小于4毫秒,如果低于这个值,则默认是4毫秒。在此之前。老版本的浏览器都将最短时间设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常是间隔16毫秒执行。这时使用 requestAnimationFrame() 的效果要好于 setTimeout();
26. 为什么 0.1 + 0.2 != 0.3 ?
0.1 + 0.2 != 0.3 是因为在进制转换和进阶运算的过程中出现精度损失。
下面是详细解释:
JavaScript使用 Number 类型表示数字(整数和浮点数),使用64位表示一个数字。
图片说明:
计算机无法直接对十进制的数字进行运算, 需要先对照 IEEE 754 规范转换成二进制,然后对阶运算。
1.进制转换
0.1和0.2转换成二进制后会无限循环
0.1 -> 0.0001100110011001...(无限循环)
0.2 -> 0.0011001100110011...(无限循环)
但是由于IEEE 754尾数位数限制,需要将后面多余的位截掉,这样在进制之间的转换中精度已经损失。
2.对阶运算
由于指数位数不相同,运算时需要对阶运算 这部分也可能产生精度损失。
按照上面两步运算(包括两步的精度损失),最后的结果是
0.0100110011001100110011001100110011001100110011001100
结果转换成十进制之后就是 0.30000000000000004。
27. promise 有几种状态, Promise 有什么优缺点 ?
promise有三种状态: fulfilled, rejected, pending.
Promise 的优点:
Promise 的缺点:
28. Promise构造函数是同步还是异步执行,then中的方法呢 ?promise如何实现then处理 ?
Promise的构造函数是同步执行的。then中的方法是异步执行的。
promise的then实现,详见: Promise源码实现
29. Promise和setTimeout的区别 ?
Promise 是微任务,setTimeout 是宏任务,同一个事件循环中,promise总是先于 setTimeout 执行。
30. 如何实现 Promise.all ?
要实现 Promise.all,首先我们需要知道 Promise.all 的功能:
promises 中所有的promise都“完成”时或参数中不包含 promise 时回调完成。
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let index = 0;
let result = [];
if (promises.length === 0) {
resolve(result);
} else {
setTimeout(() => {
function processValue(i, data) {
result[i] = data;
if (++index === promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
//promises[i] 可能是普通值
Promise.resolve(promises[i]).then((data) => {
processValue(i, data);
}, (err) => {
reject(err);
return;
});
}
})
}
});
}
如果想了解更多Promise的源码实现,可以参考我的另一篇文章:Promise的源码实现(完美符合Promise/A+规范)
31.如何实现 Promise.finally ?
不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.
Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}
32. 什么是函数柯里化?实现 sum(1)(2)(3) 返回结果是1,2,3之和
函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
function sum(a) {
return function(b) {
return function(c) {
return a+b+c;
}
}
}
console.log(sum(1)(2)(3)); // 6
引申:实现一个curry函数,将普通函数进行柯里化:
function curry(fn, args = []) {
return function(){
let rest = [...args, ...arguments];
if (rest.length < fn.length) {
return curry.call(this,fn,rest);
}else{
return fn.apply(this,rest);
}
}
}
//test
function sum(a,b,c) {
return a+b+c;
}
let sumFn = curry(sum);
console.log(sumFn(1)(2)(3)); //6
console.log(sumFn(1)(2, 3)); //6
如果您在面试中遇到了更多的原生JS问题,或者有一些本文未涉及到且有一定难度的JS知识,请给我留言。您的问题将会出现在后续文章中~
本文的写成耗费了非常多的时间,在这个过程中,我也学习到了很多知识,谢谢各位小伙伴愿意花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的肯定是我前进的最大动力。https://github.com/YvetteLau/...
后续写作计划
1.《寒冬求职季之你必须要懂的原生JS》(中)(下)
2.《寒冬求职季之你必须要知道的CSS》
3.《寒冬求职季之你必须要懂的前端安全》
4.《寒冬求职季之你必须要懂的一些浏览器知识》
5.《寒冬求职季之你必须要知道的性能优化》
针对React技术栈:
1.《寒冬求职季之你必须要懂的React》系列
2.《寒冬求职季之你必须要懂的ReactNative》系列
参考文章:
0.1 + 0.2 !== 0.3
此题答案大量使用了此篇文章的图文: https://juejin.im/post/5b90e0...推荐关注本人公众号:
一年前,也许你搞清楚闭包,this,原型链,就能获得认可。但是现在,很显然是不行了。本文梳理出了一些面试中有一定难度的高频原生JS问题,部分知识点可能你之前从未关注过,或者看到了,却没有仔细研究,但是它们却非常重要。本文将以真实的面试题的形式来呈现知识...
elnino 赞了回答 · 2019-03-27
如果你用到了constructor
就必须写super()
,是用来初始化this
的,可以绑定事件到this
上;
如果你在constructor中
要使用this.props
,就必须给super加参数:super(props)
;
(无论有没有constructor
,在render
中this.props
都是可以使用的,这是React自动附带的;)
如果没用到constructor
,是可以不写的,直接:
class HelloMessage extends React.Component{
render (){
return (
<div>nice to meet you! {this.props.name}</div>
);
}
}
//不过这种只是用render的情况,使用一般的ES6函数写会更简便:
const HelloMessage = (props)=>(
<div>nice to meet you! {this.props.name}</div>
)
如果你用到了constructor就必须写super(),是用来初始化this的,可以绑定事件到this上;如果你在constructor中要使用this.props,就必须给super加参数:super(props);(无论有没有constructor,在render中this.props都是可以使用的,这是React自动附带的;)如果没用到...
关注 15 回答 3
elnino 评论了文章 · 2019-03-25
Logoly.Pro 是一个在线的 PornHub 风格 Logo 生成工具,可以帮助你快速生成类似 PornHub 风格的 Logo
目前项目已经上线:https://logoly.pro/
代码也已开源:https://github.com/bestony/lo...
欢迎各位前来试用 && 求 Star !
昨晚,我花了 5 个小时,在肝一个项目,如今,让他成功上线,我便向大家介绍一下他。
我自己平时经常要做一些 Side Project ,在做 Side Project 的时候,就涉及到了要做 Logo ,但是作为一个没有设计感的程序员,在做 Logo 时总是会做出一些很丑的 Logo ,于是痛定思痛,想想有没有什么有用的工具可以帮助我生成好看的 Logo。对于我来说,也不需要太过复杂,能够满足我自己的要求就行。
那么这就要求这个 Logo 有一些特点
经过一番筛选,PornHub 的 Logo 进入到我的视线。
在开发之前,我先进行了产品方面的考虑,看看我需要做哪些功能,哪些不做,最终得到了这样一个清单:
要做的项目
不做的项目
后续迭代实现的
在完成了产品的功能,我又进行了布局的设计,这次我用的是 Adobe XD,最近很喜欢用这个工具来设计产品的界面,非常的方便。最终设计完成的版本如下:
设计完成后,就要开始准备开始编码了。
一开始,我考虑使用一些 UI Framework ,不过,由于一开始没有引入 UI Framework, 快写完了才发现基本不需要组件库,干脆将错就错,这样用了。
在完成了基本的界面后,就是涉及到的一些库的使用了,这里要感谢前端生态圈的繁荣,我从 Picas 和 Carbon 的源码里找到了我想要用的库。
其他我用到的库还有
在完成了开发后,将代码上传到 Github,准备部署。
在前面提到,我考虑用 Netlify 进行部署,这里非常方便,在 Netlify 上直接创建项目,选择你的项目,然后填入命令即可。
并配置一下域名,将自己的域名设置为主域名
稍等一会,就会自动为你的域名签注 Let's Encrypt 的证书。
关于这个项目的故事,我已经说完了所有我能想到的了,接下来,就是你的提问时间了,欢迎你针对项目对我提问,无论是产品、设计、编码,都可以~
希望大家能够给这个项目一个 Star: https://github.com/bestony/lo...
查看原文TL;DR Logoly.Pro 是一个在线的 PornHub 风格 Logo 生成工具,可以帮助你快速生成类似 PornHub 风格的 Logo 目前项目已经上线:[链接]代码也已开源:[链接] 欢迎各位前来试用 && 求 Star ! 昨晚,我花了 5 个小时,在肝一个项目,如今,让他成功上线,我便...
查看全部 个人动态 →
(゚∀゚ )
暂时没有
注册于 2017-02-28
个人主页被 507 人浏览
推荐关注