wangyuanqi

wangyuanqi 查看完整档案

杭州编辑温州大学  |  计算机技术 编辑深圳法本信息  |  web前端 编辑 blog.wangyuanqi.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

wangyuanqi 发布了文章 · 2020-09-27

前端Mac装机推荐软件(~附链接~)

日常必备

搜狗

https://pinyin.sogou.com/mac/

Chrome

https://www.google.cn/chrome/

eZip

https://ezip.awehunt.com/?loc...

效率工具

Alfred

https://github.com/zenorocha/...

思维导图

1、XMind8
https://www.xmind.cn/download...

2、MindMaster
http://www.edrawsoft.cn/mindm...

番茄土豆

(好用程度:3星;重点可以免费用)
https://pomotodo.com/

ProcessOn:在线绘图

https://www.processon.com/

开发工具

终端:iTerm 2

https://www.iterm2.com/downlo...

IDE:vsCode

https://code.visualstudio.com/

插件参考:

Chinese (Simplified) Language Pack for Visual Studio Code
CSS Formatter
HTML Snippets
Reactjs code snippets
TODO Highlight
View In Browser
vsc-commitizen

Homebrew

https://juemuren4449.com/arch...

SwitchHosts

通过Homebrew安装:brew cask install switchhosts

sublime (个人习惯的文档辅助工具)

http://www.sublimetext.com/

测试工具

接口调试:Postman

https://www.postman.com/products

抓包工具Charles

https://pan.baidu.com/s/1qXoC...

UI

Sketch

链接:https://pan.baidu.com/s/1CXrt... 密码:abpn

注:安装后执行:xattr -r -d com.apple.quarantine /Applications/Sketch.app/
查看原文

赞 0 收藏 0 评论 0

wangyuanqi 回答了问题 · 2020-09-24

解决求助下关于axios请求下载excel的问题

完整代码如下,命名的后缀注意和后端给的文件后缀一致;

exportData () {
        const form = this.getSearchForm() // 要发送到后台的数据
        axios({ // 用axios发送post请求
          method: 'post',
          url: '/user/12345', // 请求地址
          data: form, // 参数
          responseType: 'blob', // 表明返回服务器返回的数据类型
          responseType: 'arraybuffer' // 这样就不会让表格出现乱码现象
        })
          .then((res) => { // 处理返回的文件流
            const content = res
            const blob = new Blob([content], { type: 'application/vnd.ms-excel' })
            const fileName = '文件下载.xls'
            if ('download' in document.createElement('a')) { // 非IE下载
              const elink = document.createElement('a')
              elink.download = fileName
              elink.style.display = 'none'
              elink.href = URL.createObjectURL(blob)
              document.body.appendChild(elink)
              elink.click()
              URL.revokeObjectURL(elink.href) // 释放URL 对象
              document.body.removeChild(elink)
            } else { // IE10+下载
              navigator.msSaveBlob(blob, fileName)
            }
        })
      }

关注 4 回答 3

wangyuanqi 回答了问题 · 2020-09-16

解决vue axios 在设置了全局请求头下,单一请求更换不了请求头

可以封装一下axios,伪代码如下:

function request(path, param, method){
    const config = {
        ...param,
        headers: {'Content-Type' : 'multipart/form-data'}
    }
    if (//...是上传文件){
        config.headers = //...
    }
    return axios(config)
}

关注 4 回答 4

wangyuanqi 关注了用户 · 2020-06-16

九瑶 @sparkmorry

专注前端、专注WebAssembly在图形、视频领域的应用
淘系鹿班智能设计前端团队各种技术栈
图形学、计算机动画、音视频剪辑、AI设计…
WebAssembly、WebGL、C++、Javascript…
长期招人,有兴趣的疯狂私聊呀~~

关注 16

wangyuanqi 赞了文章 · 2020-06-11

10分钟白嫖我的常用的在线工具网站清单

大家好,我是 Guide 哥,一个三观比主角还正的技术人。

简单整理了一下自己日常经常使用的工具网站,分享给小伙伴们!其他推荐阅读:

  1. 完结撒花!JavaGuide面试突击版来啦!
  2. 「Java面试题精华集」Java基础知识篇(2020最新版)附PDF版 !
  3. 【Java后端面试经历】我和阿里面试官的“又”一次“邂逅”(附问题详解)

1.奶牛快传:用户体验更好的网盘工具。

https://cowtransfer.com/

最近开始使用的一款网盘工具,和百度网盘类似,不过没有下载速度的限制,并且可以支持自定义分享文件的下载次数(需要开会员)。

还有一点让我觉得比较舒服的是,你给别人分享文件,别人无需登录即可直接下载。

就传输速度和体验感来说,奶牛快传真的没话说。缺点也比较明显,免费用户的容量容量只有 5 g并且单次传输上限是 2g,所以,可能需要付费才能更好的使用。

2.docsmall:压缩工具

https://docsmall.com/

我经常用来压缩图片、GIF、PDF,你们平时看到我放的 Gif 图片都是在这里压缩过一波的。

并且,还支持PDF合并和分割。不得不说这个网站做的简直不要太美观,体验感和好感 Max!!!

3.创客贴:平面设计作图神器

https://www.chuangkit.com/

我的公众号首页封面图就是通过这个网站制作的。通过这个网站你可以制作好看的海报、简历、新媒体文章的首页图等等,这个网站甚至还有很多免费且好看的 PPT插件,简直是神器。

4.Dimmy.club:手机电脑等设备的展示模型

https://dimmy.club/

可以让你的图片放在电脑、手机、ipad等模型中展示,大大提升了图片的档次。

5.BrowserFrame:浏览器展示模型

https://browserframe.com/

正如你们所看到的,本文所有的图片都是他通过这个网站在线转换的,相比直接展示要更加美观一些,节省了很多自己手动调整图片的时间。

6.Flourish:数据可视化

https://flourish.studio/

通过这个网站,你可以快速地把表格数据转换为各种各样好看的图表,并且还支持动态可视化。

7.PDF派:20个免费好用的PDF在线工具

https://www.pdfpai.com/

PDF在线工具挺多的,PDF派是我最喜欢的一个,功能强大稳定。

8.小码短连接:简单易用的渠道短链接统计工具

https://xiaomark.com/

非常好用的长链接转短链接工具,能够让链接看起来更简洁。并且,转换为短链接之后,还能在后台监测访问数据,如访问次数、访问人数。

9.Kapwing:一个用于创建图像,视频和GIF的协作平台

https://www.kapwing.com/

神器网站!强烈推荐!

Kapwing 是一个在线视频编辑网站,集成了很多在线小工具,当有视频编辑的需求手头却没有什么趁手的小工具的话,不妨用来应应急。网站并没有复杂的操作界面,已经把常见的需求做成了单独的小功能,即使没有视频编辑经验的小白,也能三秒上手。

10.removebg:抠图神器

https://www.remove.bg/zh

抠图神器,绝对的神器!

11.今日热榜:你关心的热点

https://tophub.today/

今日热榜提供各站热榜聚合:微信、今日头条、百度、知乎、V2EX、微博、贴吧、豆瓣、天涯、虎扑、Github、抖音...。
追踪全网热点、简单高效阅读。

12.Apkpure:安卓安装包下载

https://apkpure.com/cn/

因为我的手机无法正常访问 Google Store,所以,很多安装包我都是通过这个网站来下载的。

13.crx4chrome:下载Chrome浏览器插件

https://www.crx4chrome.com/

如果你无法访问谷歌商店的话,可以通过这个网站来下载你想要的Chrome浏览器插件,毕竟没有插件的 Chrome浏览器 ,失去了很大一项乐趣。

14.ProcessOn:在线绘图

https://www.processon.com/

画图工具,支持流程图、思维导图、原型图、UML、网络拓扑图、组织结构图等。

15.draw.io:又一个画图工具

https://www.draw.io/

相比于 ProcessOn 我更喜欢这块画图工具,不光有在线版还有电脑版,并且可以将文件保存到多个位置。

16.其他

  1. Carbon :生成漂亮的代码图片。
  2. 图壳 :免费好用稳定的图床网站。
  3. 龙轩导航 :究极好用的导航网站。
  4. excalidraw :简洁大方的在线画图工具。
  5. mdnice :支持自定义样式的 Markdown 编辑器。支持微信公众号、知乎和稀土掘金的排版。
  6. ......

今天就分享到这里吧!后面再想起来其他的在线工具的话,我就直接补充在评论区了,也欢迎大家补充自己觉得不错的工具,不论是技术类还是非技术类都可以!

2020-05-25 23:57

查看原文

赞 71 收藏 51 评论 3

wangyuanqi 赞了文章 · 2020-05-27

React专题:react,redux以及react-redux常见一些面试题

面试中问框架,经常会问到一些原理性的东西,明明一直在用,也知道怎么用,
但面试时却答不上来,也是挺尴尬的,就干脆把react相关的问题查了下资料,再按自己的理解整理了下这些答案。

react生命周期有哪些

组件载入阶段:
componentWillMount:组件即将被装载、渲染到页面上,只调用1次
componentDidMount:组件真正在被装载之后,这里可以拿到真实DOM执行操作,只调用1次

运行中状态:
componentWillReceiveProps(nextProps):组件将要接收到新属性的时候调用,在这时setState不会触发额外的render,因为此时已经有一次来自父组件引发的render了。

shouldComponentUpdate:组件接受到新属性或者新状态的时候(返回 false,接收数据后不更新,阻止 render ,后面的函数不会继续执行)
componentWillUpdate:组件即将更新不能修改属性和状态
componentDidUpdate:组件已经更新

销毁阶段:
componentWillUnmount:组件即将销毁,这时候可以销毁绑定的事件监听或者定时器什么的。

有些好像把render也算进生命周期了:
render:组件在这里生成虚拟的 DOM 节点

react在哪个生命周期做优化

shouldComponentUpdate,这个方法用来判断是否需要调用 render 方法重绘 dom。
因为 dom 的描绘非常消耗性能,如果我们能在这个方法中能够写出更优化的 dom diff 算法,可以极大的提高性能。

react的diff算法是怎么完成的

1.把树形结构按照层级分解,只比较同级元素。
2.通过给列表结构的每个单元添加的唯一 key值进行区分同层次的子节点的比较。
3.React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)
4.合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.
    到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制。
5.选择性渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。

clipboard.png
图片源自:react精髓之一---diff算法

react虚拟DOM实现原理,以及为什么虚拟 dom 会提高性能

实现原理:

1. 用 js对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中。
2. 当状态变更的时候,重新构造一棵新的对象树。然后对比新旧虚拟DOM树,记录两棵树差异。
3. 把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。

原因:虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法减少了对真实DOM的操作次数,从而提高性能。

react怎么从虚拟dom中拿出真实dom

Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。
我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回。或者ref可以传字符串。

例如:<input ref=((input)=>{return this.name=input}) />, this.name.value取值
或者 <input ref="name" />,this.refs.name取值

React中的props和state的用法

state 是一种数据结构,用于组件挂载时所需数据的默认值。state 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。
Props则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的(immutable)

react组件之间如何通信

父子:父传子:props; 子传父:子调用父组件中的函数并传参;
兄弟:利用redux实现。
所有关系都通用的方法:利用PubSub.js订阅

react的setState的原理及用法

原理:当调用setState时,它并不会立即改变,而是会把要修改的状态放入一个任务队列,等到事件循环结束时,再合并指更新。
因此,setState有 异步,合并更新更新两个特性。

这里应该需要了解下Batch Update

clipboard.png

使用:
1.最常见的用法就是传入一个对象。

    this.setState({
        isLoading:false
    })

2.还可以接收一个函数

    this.setState((prevState,props)=>{
        // 要做的事件
        return {isLoading:false};
    })

3.因为setState是异步的,所以它还可以接收第二个参数,一个回调函数

    this.setState({count:2},()=>{
        isLoading:this.state.count===2 ? true : false
    })

setState为什么是异步的

参考链接:React 中 setState() 为什么是异步的?react的setstate原理

1.保证内部的一致性

因为props是要等到父组件渲染过后才能拿到,也就是不能同步更新,state出于统一性设成异步更新。

2.性能优化

举例说你正在一个聊天窗口输入,如果来了一条新消息又要render,那就会阻塞你的当前操作,导致延迟什么的。

3.支持state在幕后渲染

异步可以使state在幕后更新,而不影响你当前旧的页面的交互,提升用户体验。

详情可以点击上面的参考链接,写的很详细的。

另外:setstate在原生事件,setTimeout,setInterval,promise等异步操作中,state会同步更新

react的优势以及特点

优势:

1. 实现对虚拟DOM的操作,使得它速度快,提高了Web性能。
2. 组件化,模块化。react里每一个模块都是一个组件,组件化开发,可维护性高。
3. 单向数据流,比较有序,有便于管理,它随着React视图库的开发而被Facebook概念化。
4. 跨浏览器兼容:虚拟DOM帮助我们解决了跨浏览器问题,它为我们提供了标准化的API,甚至在IE8中都是没问题的。

不足:

1. react中只是MVC模式的View部分,要依赖引入很多其他模块开发。、
2. 当父组件进行重新渲染操作时,即使子组件的props或state没有做出任何改变,也会同样进行重新渲染。

特点: 

1. 声明式设计:React采用声明范式,可以轻松描述应用。
2. 高效:React通过对DOM的模拟,最大限度地减少与DOM的交互。
3. 灵活:React可以与已知的库或框架很好地配合。

React如何性能优化

讲一些项目中用到的小的点:

1. 充分利用shouldComponentUpdate函数,不过这需要你的组件尽量最小化,如果当前组件数据过于复杂,其实是很难优化的。
2. 给你的DOM遍历上加上唯一的key,注意尽量不要用index,因为如果你新DOM中删了某一个节点,它会重新排列index,
那跟原来同层级一比就都会完全不一样,而重新渲染了,所以最好使用id值什么的作key值。

3. 能用const声明的就用const。
4. DOM里少用箭头函数,当然其实要传参时也还是得用。再者,函数bind尽量写在constructor,避免每次render重新bind。
5. 减少对真实DOM的操作。
6. 如果是用webpack搭建环境的话,当一个包过大加载过慢时,可分打成多个包来优化。
react-perf性能查看工具,可自行了解下:react-perf

react与vue的对比

有些是个人意见,仅供参考。

相同点:

1. 都用虚拟DOM实现快速渲染
2. 我觉得父子,兄弟通信这些都挺像的,也都有自己的状态管理器:react=>redux, vue=>vuex
3. 都是轻量级框架
4. 现在vue也在渐渐吸收react中的一些语法,比如JSX语法,类式声明写法等

不同点:

1. React属于单向数据流——MVC模式,vue则属于双向——MVVM模式。
2. react兼容性比vue好,vue不兼容IE8.
3. react采用JSX语法,vue采用的则是html模板语法。
4. vue的css可以有组件的私有作用域,react则没有。
5. react比vue好的另一点是,它是团队维护,而vue属于个人,一般来说,大型项目更倾向于react,小型则用vue,当然这也不是绝对。

Redux的实现流程

用户页面行为触发一个Action,然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。Reducer 会返回新的 State 。每当state更新之后,view会根据state触发重新渲染。

react-redux的实现原理

Redux作为一个通用模块,主要还是用来处理应用中state的变更,通过react-redux做连接,可以在React+Redux的项目中将两者结合的更好。
react-redux是一个轻量级的封装库,它主要通过两个核心方法实现:

Provider:从最外部封装了整个应用,并向connect模块传递store。
Connect: 
    1、包装原组件,将state和action通过props的方式传入到原组件内部。
    2、监听store tree变化,使其包装的原组件可以响应state变化

redux中间件的理解,以及用过哪些中间件

理解:中间件就是要对redux的store.dispatch方法做一些改造,以实现其他的功能。

我用过redux-thunk,就拿它举例。

背景:Redux 的基本做法,是用户发出 Action,Reducer 函数立刻算出新的 State,View 重新渲染,但这是做同步。

而如果有异步请求时,那就不能知道什么时候获取的数据有存进store里面,因此此时需要在请求成功时返回一个标识或状态,并在此时再触发action给reducer传值。
因此,为了解决异步的问题,就引入了中间件的概念。

作用: redux-thunk 帮助你统一了异步和同步 action 的调用方式,把异步过程放在 action 级别解决,对 component 调用没有影响。

引入使用可参照:理解redux和redux的中间件redux-thunk的认识

redux-thunk VS redux-saga对比 异步处理方案中间件

原文链接:异步方案选型redux-saga 和 redux-thunk

redux-thunk
缺点:

(1).一个异步请求的action代码过于复杂,且异步操作太分散,相对比saga只要调用一个call方法就显得简单多了。
(2).action形式不统一,如果不一样的异步操作,就要写多个了。

优点:学习成本低

redux-saga:
优点:

(1)集中处理了所有的异步操作,异步接口部分一目了然(有提供自己的方法)
(2)action是普通对象,这跟redux同步的action一模一样({type:XXX})
(3)通过Effect,方便异步接口的测试
(4)通过worker和watcher可以实现非阻塞异步调用,并且同时可以实现非阻塞调用下的事件监听
(5) 异步操作的流程是可以控制的,可以随时取消相应的异步操作。

缺点:学习成本高。

比较redux和vuex的区别

原文链接不记得了(囧...)
相同点:

1.数据驱动视图,提供响应式的视图组件
2.都有virtual DOM, 组件化开发,通过props参数进行父子组件数据的传递,都实现webComponents规范
3.都支持服务端渲染  
4.都有native解决方案,reactnative(facebook团队) vs weex(阿里团队)

不同点:

1.vuex是一个针对VUE优化的状态管理系统,而redux仅是一个常规的状态管理系统(Redux)与React框架的结合版本。
2.开发模式:React本身,是严格的view层,MVC模式;Vue则是MVVM模式的一种方式实现
3.数据绑定:Vue借鉴了angular,采取双向数据绑定的方式;React,则采取单向数据流的方式
4.数据更新:Vue采取依赖追踪,默认是优化状态:按需更新;
    React在则有两种选择:
    1)手动添加shouldComponentUpdate,来避免冗余的vdom,re-render的情况
    2)Components 尽可能都用 pureRenderMixin,然后采用 redux 结构 + Immutable.js
5.社区:react相比来讲还是要大于vue,毕竟背后支撑团队不同。
    facebook vs 个人!当然目前vue的增长速度是高于react的增速,不知道未来的发展趋势是如何。

总之:期待构建一个大型应用程序——选择React,期待应用尽可能的小和快——选择Vue

react-router的实现原理

原理:实现URL与UI界面的同步。其中在react-router中,URL对应Location对象,
而UI是由react components来决定的,这样就转变成location与components之间的同步问题。

优点:

1.风格: 与React融为一体,专为react量身打造,编码风格与react保持一致,例如路由的配置可以通过component来实现
2.简单: 不需要手工维护路由state,使代码变得简单
3.强大: 强大的路由管理机制,体现在如下方面
4.路由配置: 可以通过组件、配置对象来进行路由的配置
5.路由切换: 可以通过<Link> Redirect进行路由的切换
6.路由加载: 可以同步记载,也可以异步加载,这样就可以实现按需加载
7.使用方式: 不仅可以在浏览器端的使用,而且可以在服务器端的使用

缺点:API不太稳定,在升级版本的时候需要进行代码变动。

react router3到4有什么改变

我只挑了一部分。
原文链接:https://blog.csdn.net/qq_3548...

1. V4不再使用V3里的{props.children}(代表所有路由-个人理解),而V4丢给 DOM 的是我们的应用程序本身.
2. V4还同时支持同时渲染多个路由,1和2都可参照下面代码,当访问 /user 时,两个组件都会被渲染。(V3可实现但过程复杂)
  <div className="primary-layout">
    <header>
      Our React Router 4 App
      <Route path="/user" component={UsersMenu} />
    </header>
    <main>
      <Route path="/" exact component={HomePage} />
      <Route path="/user" component={UsersPage} />
    </main>
  </div>;
3.获取路由的路径匹配,可以使用props.match.path获取,match上还有match.params,match.url等属性。
注:url是浏览器的url的一部分,path是给router写的路径
4.多了一个限制未登录的用户访问某些路由功能,可以在应用程序顶端中设置一个主入口,区别登录和未登录UI展示界面。

对webpack的理解:

参考链接:webpack配置整理

概念: webpack是一个预编译模块方案,它会分析你的项目结构将其打包成适合浏览器加载的模块。
打包原理:把所有依赖打包成一个bundle.js文件,通过代码分割成单元片段并按需加载。
核心思想:

1.一切皆模块,一个js,或者一个css文件也都看成一个模块,
2.按需加载,传统的模块打包工具(module bundlers)最终将所有的模块编译生成一个庞大的bundle.js文件。
    但是在真实的app里边,“bundle.js”文件可能有10M到15M之大可能会导致应用一直处于加载中状态。
    因此Webpack使用许多特性来分割代码然后生成多个“bundle”文件,而且异步加载部分代码以实现按需加载。

基础配置项:

1. entry:{} 入口,支持多入口
2. output 出口
3. resolve 处理依赖模块路径的解析
4. module 处理多种文件格式的loader
5. plugins 除了文件格式转化由loader来处理,其他大多数由plugin来处理
6. devServer 配置 webpack-dev-server
7. 搭配package.json配置环境变量,以及脚本配置。
"scripts": {
    "build": "webpack --mode production",
    "start": "webpack-dev-server --mode development"
}

"scripts": {
    "build_": "NODE_ENV=production webpack",
    "start_": "NODE_ENV=development webpack-dev-server"
}

react高阶组件

参考资料:浅谈React高阶组件
通俗理解 React 高阶函数
深入浅出React高阶组件
定义:js里的高阶函数的定义是接收一个函数作为参数,并返回一个函数。redux的connect就是一个高阶函数。
那react高阶组件就是指接收一个react组件作为入参,并返回一个新react组件的组件。

好处:它不用关心组件从哪来,也就是不用自己去引入很多个组件了。
一个简单的高阶组件:(写法不是唯一)

    export default function withHeader(WrappedComponent){
        return class HOC extends component{
            return (
                <div className="wrap">
                    <div>这是一段普通的文字</div>
                    <WrappedComponent {...this.props} />
                </div>
            )
        }
    }

直接引入:import withHeader from 'withHeader'

高阶组件部分还有待补充。

查看原文

赞 60 收藏 46 评论 2

wangyuanqi 关注了用户 · 2020-05-15

leexiaoran @li1076629390

关注 541

wangyuanqi 赞了文章 · 2019-12-23

「万字整理 」这里有一份Node.js入门指南和实践,请注意查收 ❤️

前言

什么是 Node.js 呢 ?

JS 是脚本语言,脚本语言都需要一个解析器才能运行。对于写在 HTML 页面里的 JS,浏览器充当了解析器的角色。而对于需要独立运行的 JS,NodeJS 就是一个解析器。

解析器需要运行引擎才能对 JavaScript 进行解析,Node.js 采用了 V8 引擎,Google 开源的 JavaScript 引擎。

所以,Node.js 就是一个基于 Chrome V8 引擎的 JavaScript 运行环境。

Node.js 事件驱动机制 + 异步 IO + 高性能 V8 引擎 ,也让它成为编写高性能 Web 服务一个非常好的选择。

Node.js 能做什么呢 ?

马上 2020 年了,距离 2009 年 Node.js 开源以来,已经 10 个年头了。

这么长时间的迭代,Node.js 生态圈已经非常成熟,有了很多优秀的实践和轮子,比如 express,koa 等 web 开发框架。

Node.js 无疑也带动了前端生态的发展,比如前端工程化领域。

说 Node.js 能做什么,不如说说我用 Node.js 做了什么吧。

工作中:

  • 基于 express 做了一个活动页生成工具
  • 基于 koa + sequelize 做了一个监控系统平台
  • 用 Node.js 撸了一些自动化脚本,优化重复性劳作

工作之余:

null-cli 来啦 , 一行命令提高你的效率

5 个有趣的 Node.js 库,带你走进 彩色 Node.js 世界

nodejs + docker + github pages 定制自己的 「今日头条」

说了这么多废话,我要干嘛呢~

如果你最近刚好想要了解,学习 Node.js,那希望这篇文章能帮到你~

本文通过了解 Node.js 13 个 基础核心模块 和 一个基于 原生 Node.js 的 TodoList 实践 ,带你上手 Node.js !

13 个基础核心模块

1. 事件触发器 events 模块

2. 本地路径 path 模块

3. 文件操作系统 fs 模块

4. 全局对象 process 进程

5. http 模块

6. 统一资源定位符 url 模块

7. 压缩 zlib 模块

8. 流 stream 模块

9. 逐行读取 readline 模块

10. 查询字符串 querystring 模块

11. module 模块

12. 缓冲器 Buffer 模块

13. 域名服务器 dns 模块

Node.js 内置模块远不止 13 个,入门阶段我们了解一些常用的基础核心模块,就可以上手 实践啦~

如果不想看通篇长文,我在github 博客 将 13 个模块拆分成了 13 个小节,方便阅读,每个模块的 demo 代码也能在博客中找到~

TodoList 实现了什么?

为了对 Node.js 核心模块进一步加深理解,这个 demo 采用原生 api 实现,脱离 express,koa 等一些 web 框架和库 。

  • RESTful API 实践
  • 静态资源映射及 gzip 压缩
  • 后端路由 Router 简易实现
  • Node.js 核心模块方法实践

实现了一个简单的任务管理,前端采用的是 vue + element-ui ,

TodoList
└───app             // 前端代码
│   │   ...
└───controllers     // 控制器
│   │   list.js     // api 逻辑实现
└───router
│   │   index.js    // 注册路由
│   │   router.js   // 路由实现
└───utils           // 工具类
    │   index.js
|   data.json       // 数据存放
│   index.js        // 工程入口

实现没有借助任何库,不用安装任何依赖

node index.js

就可以启动服务,自己想要开发或者调试的话,这里推荐使用nodemon,它实现了热更新,可以自动重启.

npm install -g nodemon

nodemon
#or
nodemon index.js

TodoList 代码地址

实现效果如下:

todolist

<h2 id="1"> 1. 事件触发器 events 模块</h2>

Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。

大多数 Node.js 核心 API 都采用惯用的事件驱动架构,其中某些类型的对象(触发器)会周期性地触发命名事件来调用函数对象(监听器),那么 Node.js 是如何实现事件驱动的呢?

events 模块是 Node.js 实现事件驱动的核心,在 node 中大部分的模块的实现都继承了 Events 类。比如 fs 的 readstream,net 的 server 模块。

events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装,EventEmitter 本质上是一个观察者模式的实现。

所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上。 事件的命名通常是驼峰式的字符串,但也可以使用任何有效的 JavaScript 属性键。

EventEmitter 对象使用 eventEmitter.emit()触发事件,当 EventEmitter 对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用。 被调用的监听器返回的任何值都将会被忽略并丢弃。

下面我们通过几个简单的例子来学习 events 模块

1. 基础例子

注册 Application 实例,继承 EventEmitter 类,通过继承而来的 eventEmitter.on() 函数监听事件,eventEmitter.emit()触发事件

const EventEmitter = require('events')
/**
 * Expose `Application` class.
 * Inherits from `EventEmitter.prototype`.
 */
class Application extends EventEmitter {}
const app = new Application()
//  监听hello事件
app.on('hello', data => {
  console.log(data) // hello nodeJs
})
//  触发hello事件
app.emit('hello', 'hello nodeJs')

2. 多个事件监听器及 this 指向

绑定多个事件监听器时,事件监听器按照注册的顺序执行。

当监听器函数被调用时, this 关键词会被指向监听器所绑定的 EventEmitter 实例。也可以使用 ES6 的箭头函数作为监听器,但 this 关键词不会指向 EventEmitter 实例。

const EventEmitter = require('events')

class Person extends EventEmitter {
  constructor() {
    super()
  }
}
const mrNull = new Person()
//  监听play事件
mrNull.on('play', function(data) {
  console.log(this)
  // Person {
  //   _events:
  //   [Object: null prototype] { play: [[Function], [Function]] },
  //   _eventsCount: 1,
  //     _maxListeners: undefined
  // }
  console.log(`play`)
})
//  监听play事件
mrNull.on('play', data => {
  console.log(this) // {}
  console.log(`play again`)
})
//  触发play事件
mrNull.emit('play', 'hello nodeJs')

3. 同步 VS 异步

EventEmitter 以注册的顺序同步地调用所有监听器。

const EventEmitter = require('events')

class Person extends EventEmitter {
  constructor() {
    super()
  }
}
const mrNull = new Person()
mrNull.on('play', function(data) {
  console.log(data)
})

mrNull.emit('play', 'hello nodeJs')

console.log(`hello MrNull`)

// hello nodeJs
// hello MrNull

监听器函数可以使用 setImmediate() 和 process.nextTick() 方法切换到异步的操作模式

const developer = new Person()
developer.on('dev', function(data) {
  setImmediate(() => {
    console.log(data)
  })
})
developer.on('dev', function(data) {
  process.nextTick(() => {
    console.log(data)
  })
})
developer.emit('dev', 'hello nodeJs')

console.log(`hello developer`)

// hello developer
// hello nodeJs
// hello nodeJs

4. 只调用一次的事件监听器

使用 eventEmitter.once() 可以注册最多可调用一次的监听器。 当事件被触发时,监听器会被注销,然后再调用。

const EventEmitter = require('events')

class Person extends EventEmitter {
  constructor() {
    super()
  }
}
const mrNull = new Person()
mrNull.once('play', () => {
  console.log('play !')
})

mrNull.emit('play')
mrNull.emit('play')

// play ! 只输出一次

5. 事件触发顺序

在注册事件前,触发该事件,不会被触发 !!

const EventEmitter = require('events')

class Person extends EventEmitter {
  constructor() {
    super()
  }
}
const mrNull = new Person()

mrNull.emit('play')

mrNull.on('play', () => {
  console.log('play !')
})

// 无任何输出

6. 移除事件监听器

const EventEmitter = require('events')

class Person extends EventEmitter {
  constructor() {
    super()
  }
}
const mrNull = new Person()

function play() {
  console.log('play !')
}
mrNull.on('play', play)

mrNull.emit('play')

// mrNull.off("play", play); v10.0.0版本新增,emitter.removeListener() 的别名。
//  or
mrNull.removeListener('play', play)

mrNull.emit('play')

// play !  移除后不再触发

<h2 id="2"> 2. 本地路径 path 模块</h2>

Node.js 提供了 path 模块,用于处理文件路径和目录路径 . 不同操作系统 表现有所差异 !

1. 获取路径的目录名

const path = require('path')

path.dirname('/path/example/index.js') // /path/example

2. 获取路径的扩展名

const path = require('path')

path.extname('/path/example/index.js') // .js

3. 是否是绝对路径

const path = require('path')

path.isAbsolute('/path/example/index.js') // true

path.isAbsolute('.') // false

4. 拼接路径片段

path.join('/path', 'example', './index.js') // /path/example/index.js

5. 将路径或路径片段的序列解析为绝对路径。

path.resolve('/foo/bar', './baz')
// 返回: '/foo/bar/baz'

path.resolve('/foo/bar', '/tmp/file/')
// 返回: '/tmp/file'

path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
// 如果当前工作目录是 /home/myself/node,
// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'

6. 规范化路径

path.normalize('/path///example/index.js') //  /path/example/index.js

7. 解析路径

path.parse('/path/example/index.js')

/*
 { root: '/',
  dir: '/path/example',
  base: 'index.js',
  ext: '.js',
  name: 'index' }
*/

8. 序列化路径

path.format({
  root: '/',
  dir: '/path/example',
  base: 'index.js',
  ext: '.js',
  name: 'index'
}) // /path/example/index.js

9. 获取 from 到 to 的相对路径

path.relative('/path/example/index.js', '/path') // ../..

<h2 id="3">3 .文件操作系统 fs 模块</h2>

在一些场景下,我们需要对文件进行 增删改查等操作, Nodejs 提供了 fs 模块,让我们对文件进行操作.

下面我们来介绍几个经常用的 API

1. 读取文件

const fs = require('fs')
const fs = require('fs')

// 异步读取
fs.readFile('./index.txt', 'utf8', (err, data) => {
  console.log(data) //  Hello Nodejs
})

// 同步读取
const data = fs.readFileSync('./index.txt', 'utf8')

console.log(data) //  Hello Nodejs

// 创建读取流
const stream = fs.createReadStream('./index.txt', 'utf8')

// 这里可以看到fs.createReadStream用到了我们前面介绍的events eventEmitter.on() 方法来监听事件
stream.on('data', data => {
  console.log(data) // Hello Nodejs
})

2. 写入/修改文件

写入文件时,如果文件不存在,则会创建并写入,如果文件存在,会覆盖文件内容.

const fs = require('fs')
// 异步写入
fs.writeFile('./write.txt', 'Hello Nodejs', 'utf8', err => {
  if (err) throw err
})
// 同步写入
fs.writeFileSync('./writeSync.txt', 'Hello Nodejs')
// 文件流写入
const ws = fs.createWriteStream('./writeStream.txt', 'utf8')
ws.write('Hello Nodejs')
ws.end()

3. 删除文件/文件夹

  • 删除文件
// 异步删除文件
fs.unlink('./delete.txt', err => {
  if (err) throw err
})

// 同步删除文件
fs.unlinkSync('./deleteSync.txt')
  • 删除文件夹
// 异步删除文件夹
fs.rmdir('./rmdir', err => {
  if (err) throw err
})

// 同步删除文件夹
fs.rmdirSync('./rmdirSync')

4. 创建文件夹

// 异步创建文件夹
fs.mkdir('./mkdir', err => {
  if (err) throw err
})

// 同步创建文件夹
fs.mkdirSync('./mkdirSync')

5. 重命名文件/文件夹

const fs = require('fs')

// 异步重命名文件
fs.rename('./rename.txt', './rename-r.txt', err => {
  if (err) throw err
})

// 同步重命名文件夹
fs.renameSync('./renameSync', './renameSync-r')

6. 复制文件/文件夹

const fs = require('fs')

// 异步复制文件
fs.copyFile('./copy.txt', './copy-c.txt', (err, copyFiles) => {
  if (err) throw err
})

// 同步复制文件夹
fs.copyFileSync('./null', 'null-c')

7. 文件夹状态- 文件/文件夹

const fs = require('fs')

// 异步获取文件状态
fs.stat('./dir', (err, stats) => {
  if (err) throw err
  // 是否是文件类型
  console.log(stats.isFile()) // false
  // 是否是文件夹类型
  console.log(stats.isDirectory()) // true
})

// 同步获取文件状态
const stats = fs.statSync('./stats.txt')

// 是否是文件类型
console.log(stats.isFile()) // true
// 是否是文件夹类型
console.log(stats.isDirectory()) // false

在一些复杂的操作场景下,fs 模块要做很多判断与处理 ,这里我推荐大家使用 fs-extra,它在 fs 的基础上扩展了一些方法,让一些复杂操作更简便!

<h2 id="4">4. 全局对象 process 进程</h2>

process 对象是一个 Global 全局对象,你可以在任何地方使用它,而无需 require。process 是 EventEmitter 的一个实例,所以 process 中也有相关事件的监听。使用 process 对象,可以方便处理进程相关操作。

process 常用属性

进程命令行参数: process.argv

process.argv 是一个当前执行进程折参数组,第一个参数是 node,第二个参数是当前执行的.js 文件名,之后是执行时设置的参数列表。

node index.js --tips="hello nodejs"

/*
[ '/usr/local/bin/node',
  'xxx/process/index.js',
  '--tips=hello nodejs' ]
*/

Node 的命令行参数数组:process.execArgv

process.execArgv 属性会返回 Node 的命令行参数数组。

node --harmony index.js --version

console.log(process.execArgv);  // [ '--harmony' ]

console.log(process.argv);

/*
[ '/usr/local/bin/node',
  'xxx/process/index.js',
  '--version' ]
*/

Node 编译时的版本: process.version

process.version 属性会返回 Node 编译时的版本号,版本号保存于 Node 的内置变量 NODE_VERSION 中。

console.log(process.version) // v10.15.3

当前进程的 PID process.pid

process.pid 属性会返回当前进程的 PID。

console.log('process PID: %d', process.pid)

//process PID: 10086

process 常用方法

当前工作目录 process.cwd()

process.cwd()方法返回进程当前的工作目录

console.log(process.cwd()) // /Users/null/nodejs/process

终止当前进程:process.exit([code])

process.exit()方法终止当前进程,此方法可接收一个退出状态的可选参数 code,不传入时,会返回表示成功的状态码 0。

process.on('exit', function(code) {
  console.log('进程退出码是:%d', code) // 进程退出码是:886
})

process.exit(886)

nodejs 微任务: process.nextTick()

process.nextTick()方法用于延迟回调函数的执行, nextTick 方法会将 callback 中的回调函数延迟到事件循环的下一次循环中,与 setTimeout(fn, 0)相比 nextTick 方法效率高很多,该方法能在任何 I/O 之前调用我们的回调函数。

console.log('start')
process.nextTick(() => {
  console.log('nextTick cb')
})
console.log('end')

// start
// end
// nextTick cb

process 标准流对象

process 中有三个标准备流的操作,与 其他 streams 流操作不同的是,process 中流操作是同步写,阻塞的。

标准错误流: process.stderr

process.stderr 是一个指向标准错误流的可写流 Writable Stream。console.error 就是通过 process.stderr 实现的。

标准输入流:process.stdin

process.stdin 是一个指向标准输入流的可读流 Readable Stream。

process.stdin.setEncoding('utf8')

process.stdin.on('readable', () => {
  let chunk
  // 使用循环确保我们读取所有的可用数据。
  while ((chunk = process.stdin.read()) !== null) {
    if (chunk === '\n') {
      process.stdin.emit('end')
      return
    }
    process.stdout.write(`收到数据: ${chunk}`)
  }
})

process.stdin.on('end', () => {
  process.stdout.write('结束监听')
})

process-stdin

标准输出流:process.stdout

process.stdout 是一个指向标准输出流的可写流 Writable Stream。console.log 就是通过 process.stdout 实现的

console.log = function(d) {
  process.stdout.write(d + '\n')
}

console.log('Hello Nodejs') // Hello Nodejs

<h2 id="5">5. http 模块</h2>

http 模块是 Node.js 中非常重要的一个核心模块。通过 http 模块,你可以使用其 http.createServer 方法创建一个 http 服务器,也可以使用其 http.request 方法创建一个 http 客户端。(本文先不说),Node 对 HTTP 协议及相关 API 的封装比较底层,其仅能处理流和消息,对于消息的处理,也仅解析成报文头和报文体,但是不解析实际的报文头和报文体内容。这样不仅解决了 HTTP 原本比较难用的特性,也可以支持更多的 HTTP 应用.

http.IncomingMessage 对象

IncomingMessage 对象是由 http.Server 或 http.ClientRequest 创建的,并作为第一参数分别传递给 http.Server 的'request'事件和 http.ClientRequest 的'response'事件。

它也可以用来访问应答的状态、头文件和数据等。 IncomingMessage 对象实现了 Readable Stream 接口,对象中还有一些事件,方法和属性。

在 http.Server 或 http.ClientRequest 中略有不同。

http.createServer([requestListener])创建 HTTP 服务器

实现 HTTP 服务端功能,要通过 http.createServer 方法创建一个服务端对象 http.Server。

这个方法接收一个可选传入参数 requestListener,该参数是一个函数,传入后将做为 http.Server 的 request 事件监听。不传入时,则需要通过在 http.Server 对象的 request 事件中单独添加。

var http = require('http')

// 创建server对象,并添加request事件监听器
var server = http.createServer(function(req, res) {
  res.writeHeader(200, { 'Content-Type': 'text/plain' })
  res.end('Hello Nodejs')
})

// 创建server对象,通过server对象的request事件添加事件事件监听器
var server = new http.Server()
server.on('request', function(req, res) {
  res.writeHeader(200, { 'Content-Type': 'text/plain' })
  res.end('Hello Nodejs')
})

http.Server 服务器对象

http.Server 对象是一个事件发射器 EventEmitter,会发射:request、connection、close、checkContinue、connect、upgrade、clientError 事件。

其中 request 事件监听函数为 function (request, response) { },该方法有两个参数:request 是一个 http.IncomingMessage 实例,response 是一个 http.ServerResponse 实例。

http.Server 对象中还有一些方法,调用 server.listen 后 http.Server 就可以接收客户端传入连接。

http.ServerResponse

http.ServerResponse 对象用于响应处理客户端请求。

http.ServerResponse 是 HTTP 服务器(http.Server)内部创建的对象,作为第二个参数传递给 'request'事件的监听函数。

http.ServerResponse 实现了 Writable Stream 接口,其对于客户端的响应,本质上是对这个可写流的操作。它还是一个 EventEmitter,包含:close、finish 事件。

创建一个 http.Server

创建 http.Server 使用 http.createServer()方法,为了处理客户端请求,需要在服务端监听来自客户的'request'事件。

'request'事件的回调函数中,会返回一个 http.IncomingMessage 实例和一个 http.ServerResponse。

const http = require('http')
/**
 * @param {Object} req 是一个http.IncomingMessag实例
 * @param {Object} res 是一个http.ServerResponse实例
 */
const server = http.createServer((req, res) => {
  console.log(req.headers)
  res.end(`Hello Nodejs`)
})

server.listen(3000)

http.ServerResponse 实例是一个可写流,所以可以将一个文件流转接到 res 响应流中。下面示例就是将一张图片流传送到 HTTP 响应中:

const http = require('http')
/**
 * @param {Object} req 是一个http.IncomingMessag实例
 * @param {Object} res 是一个http.ServerResponse实例
 */
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'image/jpg' })
  const r = require('fs').createReadStream('./kobe.jpg')
  r.pipe(res)
})

server.listen(3000)

<h2 id="6">6. 统一资源定位符 url 模块</h2>

Node.js 提供了 url 模块,用于处理与解析 URL。

1. URL 对象都有哪些属性 ?

const { URL } = require("url");

const myURL = new URL("https://github.com/webfansplz#hello");
console.log(myURL);
{
  href: 'https://github.com/webfansplz#hello',  // 序列化的 URL
  origin: 'https://github.com', // 序列化的 URL 的 origin
  protocol: 'https:', // URL 的协议
  username: '', // URL 的用户名
  password: '', //  URL 的密码
  host: 'github.com', // URL 的主机
  hostname: 'github.com',   // URL 的主机名
  port: '',  // URL 的端口
  pathname: '/webfansplz',  // URL 的路径
  search: '', // URL 的序列化查询参数
  searchParams: URLSearchParams {}, //  URL 查询参数的 URLSearchParams 对象
  hash: '#hello'  // URL 的片段
}

URL 对象属性 除了 origin 和 searchParams 是只读的,其他都是可写的.

2. 序列化 URL

const { URL } = require('url')

const myURL = new URL('https://github.com/webfansplz#hello')

console.log(myURL.href) //  https://github.com/webfansplz#hello

console.log(myURL.toString()) // https://github.com/webfansplz#hello

console.log(myURL.toJSON()) //  https://github.com/webfansplz#hello

<h2 id="7">7. 压缩 zlib 模块</h2>

在流传输过程中,为减少传输数据加快传输速度,往往会对流进行压缩。

HTTP 流就是如此,为提高网站响应速度,会在服务端进行压缩,客户端收到数据后再进行相应的解压。

Node.js 中的 Zlib 模块提供了流压缩与解压缩功能,Zlib 模块提供了对 Gzip/Gunzip、Deflate/Inflate、DeflateRaw/InflateRaw 类的绑定,这些类可以实现对可读流/可写流的压缩与解压。

<!-- 做过 web 性能优化的同学,应该对 gzip 神器很熟悉. -->

关于 gzip 与 deflate

deflate(RFC1951)是一种压缩算法,使用 LZ77 和哈弗曼进行编码。gzip(RFC1952)一种压缩格式,是对 deflate 的简单封装,gzip = gzip 头(10 字节) + deflate 编码的实际内容 + gzip 尾(8 字节)。在 HTTP 传输中,gzip 是一种常用的压缩算法,使用 gzip 压缩的 HTTP 数据流,会在 HTTP 头中使用 Content-Encoding:gzip 进行标识。

HTTP Request Header 中 Accept-Encoding 是浏览器发给服务器,声明浏览器支持的解压类型

Accept-Encoding: gzip, deflate, br

HTTP Response Header 中 Content-Encoding 是服务器告诉浏览器 使用了哪种压缩类型

Content-Encoding: gzip

对 web 性能优化有所了解的同学,相信对 gzip 都不陌生,我们就通过 gzip 来了解 zlib 模块.

1. 文件压缩/解压

文件压缩

const zlib = require('zlib')
const fs = require('fs')
const gzip = zlib.createGzip()
const inp = fs.createReadStream('zlib.txt')
const out = fs.createWriteStream('zlib.txt.gz')
inp.pipe(gzip).pipe(out)

文件解压

const zlib = require('zlib')
const fs = require('fs')
const gunzip = zlib.createGunzip()
const inp = fs.createReadStream('./un-zlib.txt.gz')
const out = fs.createWriteStream('un-zlib.txt')
inp.pipe(gunzip).pipe(out)

2. 服务端 gzip 压缩

const fs = require('fs')
const http = require('http')
const zlib = require('zlib')
const filepath = './index.html'

const server = http.createServer((req, res) => {
  const acceptEncoding = req.headers['accept-encoding']
  if (acceptEncoding.includes('gzip')) {
    const gzip = zlib.createGzip()
    res.writeHead(200, {
      'Content-Encoding': 'gzip'
    })
    fs.createReadStream(filepath)
      .pipe(gzip)
      .pipe(res)
  } else {
    fs.createReadStream(filepath).pipe(res)
  }
})

server.listen(4396)

<h2 id="8">8. 流 stream 模块</h2>

流(stream)是 Node.js 中处理流式数据的抽象接口。 stream 模块用于构建实现了流接口的对象。

Node.js 提供了多种流对象。 例如,HTTP 服务器的请求和 process.stdout 都是流的实例。

流可以是可读的、可写的、或者可读可写的。 所有的流都是 EventEmitter 的实例。

尽管理解流的工作方式很重要,但是 stream 模块主要用于开发者创建新类型的流实例。 对于以消费流对象为主的开发者,极少需要直接使用 stream 模块。

stream 类型

Node.js 中有四种基本的流类型:

  • Writable - 可写入数据的流(例如 fs.createWriteStream())。
  • Readable - 可读取数据的流(例如 fs.createReadStream())。
  • Duplex - 可读又可写的流(例如 net.Socket)。
  • Transform - 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate())。

用于消费流的 API

const http = require('http')

const server = http.createServer((req, res) => {
  // req 是一个 http.IncomingMessage 实例,它是可读流。
  // res 是一个 http.ServerResponse 实例,它是可写流。

  let body = ''
  // 接收数据为 utf8 字符串,
  // 如果没有设置字符编码,则会接收到 Buffer 对象。
  req.setEncoding('utf8')

  // 如果添加了监听器,则可读流会触发 'data' 事件。
  req.on('data', chunk => {
    body += chunk
  })

  // 'end' 事件表明整个请求体已被接收。
  req.on('end', () => {
    try {
      const data = JSON.parse(body)
      // 响应信息给用户。
      res.write(typeof data)
      res.end()
    } catch (er) {
      // json 解析失败。
      res.statusCode = 400
      return res.end(`错误: ${er.message}`)
    }
  })
})

server.listen(1337)

// curl localhost:1337 -d "{}"
// object
// curl localhost:1337 -d "\"foo\""
// string
// curl localhost:1337 -d "not json"
// 错误: Unexpected token o in JSON at position 1

当数据可以从流读取时,可读流会使用 EventEmitter API 来通知应用程序 (比如例子中的 req data 事件)。 从流读取数据的方式有很多种。

可写流(比如例子中的 res)会暴露了一些方法,比如 write() 和 end() 用于写入数据到流。

可写流和可读流都通过多种方式使用 EventEmitter API 来通讯流的当前状态。Duplex 流和 Transform 流都是可写又可读的。

对于只需写入数据到流或从流消费数据的应用程序,并不需要直接实现流的接口,通常也不需要调用 require('stream')。

对于大部分的 nodejs 开发者来说,平常并不会直接用到 stream 模块,但是理解 stream 流的运行机制却是尤其重要的.

<h2 id="9">9. 逐行读取 readline 模块</h2>

readline 模块是一个流内容的逐行读取模块,通过 require('readline')引用模块。你可以用 readline 模块来读取 stdin,可以用来逐行读取文件流,也可用它来在控制台和用户进行一些交互。

const readline = require('readline')

const rl = readline.createInterface({
  //  监听的可读流
  input: process.stdin,
  //  逐行读取(Readline)数据要写入的可写流
  output: process.stdout
})

rl.question('你如何看待 null-cli ?', answer => {
  console.log(`感谢您的宝贵意见:${answer}`)
  rl.close()
})

readline

很多有趣的 CLI 工具是基于 readline 造的哦,有兴趣的同学也可以尝试~

<h2 id="10">10. 查询字符串 querystring 模块</h2>

querystring 模块是 Node.js 中的工具模块之一,用于处理 URL 中的查询字符串,即:querystring 部分。查询字符串指:URL 字符串中,从问号"?"(不包括?)开始到锚点"#"或者到 URL 字符串的结束(存在#,则到#结束,不存在则到 URL 字符串结束)的部分叫做查询字符串。querystring 模块可将 URL 查询字符串解析为对象,或将对象序列化为查询字符串。

1. 对象序列化为查询字符串

querystring.stringify(obj, sep[, options])

const querystring = require('querystring')

const obj = {
  url: 'github.com/webfansplz',
  name: 'null'
}

console.log(querystring.stringify(obj)) // url=github.com%2Fwebfansplz&name=null

2. 查询字符串解析为对象

const querystring = require('querystring')

const o = querystring.parse(`url=github.com%2Fwebfansplz&name=null`)

console.log(o.url) // github.com/webfansplz

3. 编码查询字符串中的参数

querystring.escape 方法会对查询字符串进行编码,在使用 querystring.stringify 方法时可能会用到.

const str = querystring.escape(`url=github.com%2Fwebfansplz&name=null`)

console.log(str) // url%3Dgithub.com%252Fwebfansplz%26name%3Dnull

4. 解码查询字符串中的参数

querystring.unescape 方法是和 querystring.escape 相逆的方法,在使用 querystring.parse 方法时可能会用到。

const str = querystring.escape(`url=github.com%2Fwebfansplz&name=null`)

console.log(querystring.parse(str)) // { 'url=github.com%2Fwebfansplz&name=null': '' } ✖️

console.log(querystring.parse(querystring.unescape(str))) // { url: 'github.com/webfansplz', name: 'null' }

<h2 id="11">11. module 模块</h2>

Node.js 实现了一个简单的模块加载系统。在 Node.js 中,文件和模块是一一对应的关系,可以理解为一个文件就是一个模块。其模块系统的实现主要依赖于全局对象 module,其中实现了 exports(导出)、require()(加载)等机制。

1. 模块加载

Node.js 中一个文件就是一个模块。如,在 index.js 中加载同目录下的 circle.js:

// circle.js
const PI = Math.PI

exports.area = r => PI * r * r

exports.circumference = r => 2 * PI * r
// index.js
const circle = require('./circle.js')

console.log(`半径为 4 的圆面积为 ${circle.area(4)}`) // 半径为 4 的圆面积为 50.26548245743669

circle.js 中通过 exports 导出了 area()和 circumference 两个方法,这两个方法可以其它模块中调用。

exports 与 module.exports

exports 是对 module.exports 的一个简单引用。如果你需要将模块导出为一个函数(如:构造函数),或者想导出一个完整的出口对象而不是做为属性导出,这时应该使用 module.exports。

// square.js

module.exports = width => {
  return {
    area: () => width * width
  }
}
// index.js

const square = require('./square.js')
const mySquare = square(2)
console.log(`The area of my square is ${mySquare.area()}`) // The area of my square is 4

2. 访问主模块

当 Node.js 直接运行一个文件时,require.main 属性会被设置为 module 本身。这样,就可通过这个属性判断模块是否被直接运行:

require.main === module

比如,对于上面例子的 index.js 来说, node index.js 上面值就是 true, 而通过 require('./index')时, 值却是 false.

module 提供了一个 filename 属性,其值通常等于__filename。 所以,当前程序的入口点可以通过 require.main.filename 来获取。

console.log(require.main.filename === __filename) // true

3. 解析模块路径

使用 require.resolve()函数,可以获取 require 加载的模块的确切文件名,此操作只返回解析后的文件名,不会加载该模块。

console.log(require.resolve('./square.js')) // /Users/null/meet-nodejs/module/square.js

require.resolve 的工作过程:

require(X) from module at path Y
1. If X is a core module,
   a. return the core module
   b. STOP
2. If X begins with './' or '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. THROW "not found"

LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text.  STOP
2. If X.js is a file, load X.js as JavaScript text.  STOP
3. If X.json is a file, parse X.json to a JavaScript Object.  STOP
4. If X.node is a file, load X.node as binary addon.  STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
   a. Parse X/package.json, and look for "main" field.
   b. let M = X + (json main field)
   c. LOAD_AS_FILE(M)
2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
4. If X/index.node is a file, load X/index.node as binary addon.  STOP

LOAD_NODE_MODULES(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
   a. LOAD_AS_FILE(DIR/X)
   b. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
   a. if PARTS[I] = "node_modules" CONTINUE
   c. DIR = path join(PARTS[0 .. I] + "node_modules")
   b. DIRS = DIRS + DIR
   c. let I = I - 1
5. return DIRS

4. 模块缓存

模块在第一次加载后会被缓存到 require.cache 对象中, 从此对象中删除键值对将会导致下一次 require 重新加载被删除的模块。

多次调用 require('index'),未必会导致模块中代码的多次执行。这是一个重要的功能,借助这一功能,可以返回部分完成的对象;这样,传递依赖也能被加载,即使它们可能导致循环依赖。

如果你希望一个模块多次执行,那么就应该输出一个函数,然后调用这个函数。

模块缓存的注意事项

模块的基于其解析后的文件名进行缓存。由于调用的位置不同,可能会解析到不同的文件(如,需要从 node_modules 文件夹加载的情况)。所以,当解析到其它文件时,就不能保证 require('index')总是会返回确切的同一对象。

另外,在不区分大小写的文件系统或系统中,不同的文件名可能解析到相同的文件,但缓存仍会将它们视为不同的模块,会多次加载文件。如:require('./index')和 require('./INDEX')会返回两个不同的对象,无论'./index'和'./INDEX'是否是同一个文件。

5. 循环依赖

当 require()存在循环调用时,模块在返回时可能并不会被执行。

// a.js
console.log('a starting')
exports.done = false
const b = require('./b.js')
console.log('in a, b.done = %j', b.done)
exports.done = true
console.log('a done')
// b.js
console.log('b starting')
exports.done = false
const a = require('./a.js')
console.log('in b, a.done = %j', a.done)
exports.done = true
console.log('b done')
// main.js
console.log('main starting')
const a = require('./a.js')
const b = require('./b.js')
console.log('in main, a.done=%j, b.done=%j', a.done, b.done)

首先 main.js 会加载 a.js,接着 a.js 又会加载 b.js。这时,b.js 又会尝试去加载 a.js。

为了防止无限的循环,a.js 会返回一个 unfinished copy 给 b.js。然后 b.js 就会停止加载,并将其 exports 对象返回给 a.js 模块。

这样 main.js 就完成了 a.js、b.js 两个文件的加载。输出如下:

$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true

6. 文件模块

当加载文件模块时,如果按文件名查找未找到。那么 Node.js 会尝试添加.js 和.json 的扩展名,并再次尝试查找。如果仍未找到,那么会添加.node 扩展名再次尝试查找。

对于.js 文件,会将其解析为 JavaScript 文本文件;而.json 会解析为 JOSN 文件文件;.node 会尝试解析为编译后的插件文件,并由 dlopen 进行加载。

路径解析

当加载的文件模块使用'/'前缀时,则表示绝对路径。如,require('/home/null/index.js')会加载/home/null/index.js 文件。

而使用'./'前缀时,表示相对路径。如,在 index.js 中 require('./circle')引用时,circle.js 必须在相同的目录下才能加载成功。

当没有'/'或'./'前缀时,所引用的模块必须是“核心模块”或是 node_modules 中的模块。

如果所加载的模块不存在,require()会抛出一个 code 属性为'MODULE_NOT_FOUND'的错误。

7. __dirname

当前模块的目录名。 与 __filename 的 path.dirname() 相同。

console.log(__dirname) // /Users/null/meet-nodejs/module

console.log(require('path').dirname(__filename)) // /Users/null/meet-nodejs/module

console.log(__dirname === require('path').dirname(__filename)) // true

8. module 对象

module 在每个模块中表示对当前模块的引用。 而 module.exports 又可以通过全局对象 exports 来引用。module 并不是一个全局对象,而更像一个模块内部对象。

module.children

这个模块引入的所有模块对象

module.exports

module.exports 通过模块系统创建。有时它的工作方式与我们所想的并不一致,有时我们希望模块是一些类的实例。因此,要将导出对象赋值给 module.exports,但是导出所需的对象将分配绑定本地导出变量,这可能不是我们想要的结果。

// a.js

const EventEmitter = require('events')

module.exports = new EventEmitter()

// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(() => {
  module.exports.emit('ready')
}, 1000)
const a = require('./a')
a.on('ready', () => {
  console.log('module a is ready')
})

需要注意,分配给 module.exports 的导出值必须能立刻获取到,当使用回调时其不能正常执行。

exports 别名

exports 可以做为 module.exports 的一个引用。和任何变量一样,如果为它分配新值,其旧值将会失效:

function require(...) {
  // ...
  ((module, exports) => {
    // Your module code here
    exports = some_func;        // re-assigns exports, exports is no longer
                                // a shortcut, and nothing is exported.
    module.exports = some_func; // makes your module export 0
  })(module, module.exports);
  return module;
}
  • module.filename - 模块解析后的完整文件名
  • module.id - 用于区别模块的标识符,通常是完全解析后的文件名。
  • module.loaded - 模块是否加载完毕
  • module.parent - 父模块,即:引入这个模块的模块
  • module.require(id)
  • module.require 提供了类似 require()的功能,可以从最初的模块加载一个模块

<h2 id="12">12. 缓冲器 Buffer 模块</h2>

在引入 TypedArray 之前,JavaScript 语言没有用于读取或操作二进制数据流的机制。 Buffer 类是作为 Node.js API 的一部分引入的,用于在 TCP 流、文件系统操作、以及其他上下文中与八位字节流进行交互。

创建缓冲区

console.log(Buffer.from([1, 2, 3, 4, 5])) // <Buffer 01 02 03 04 05>

console.log(Buffer.from(new ArrayBuffer(8))) // <Buffer 00 00 00 00 00 00 00 00>

console.log(Buffer.from('Hello world')) // <Buffer 48 65 6c 6c 6f 20 77 6f 72 6c 64>

Buffer 与字符编码

当字符串数据被存储入 Buffer 实例或从 Buffer 实例中被提取时,可以指定一个字符编码。

// 缓冲区转换为 UTF-8 格式的字符串

const buffer = Buffer.from('Hello world')

console.log(buffer.toString()) // Hello world
// 缓冲区数据转换为base64格式字符串

const buffer = Buffer.from('Hello world')

console.log(buffer.toString('base64')) // SGVsbG8gd29ybGQ=
// 将base64编码的字符串,转换为UTF-8编码

const buffer = Buffer.from('Hello world')

const base64Str = buffer.toString('base64')

const buf = Buffer.from(base64Str, 'base64')

console.log(buf.toString('utf8')) // Hello world

<h2 id="13">13. 域名服务器 dns 模块</h2>

DNS(Domain Name System,域名系统),DNS 协议运行在 UDP 协议之上,使用端口号 53。DNS 是因特网上作为域名和 IP 地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。简单的说,就是把域名(网址)解析成对应的 IP 地址。Node.js 的 dns 模块,提供了 DNS 解析功能。当使用 dns 模块中的 net.connect(80, 'github.com/webfansplz')方法 或 http 模块的 http.get({ host: 'github.com/webfansplz' })方法时,在其底层会使用 dns 模块中的 dns.lookup 方法进行域名解析。

dns 模块的两种域名解析方式

1.使用操作系统底层的 DNS 服务解析

使用操作系统底层的 DNS 服务进行域名解析时,不需要连接到网络仅使用系统自带 DNS 解析功能。这个功能由 dns.lookup()方法实现。

dns.lookup(hostname[, options], callback):将一个域名(如:'www.baidu.com')解析为第一个找到的 A 记录(IPv4)或 AAAA 记录(IPv6)

hostname 表示要解析的域名。

options 可以是一个对象或整数。如果没有提供 options 参数,则 IP v4 和 v6 地址都可以。如果 options 是整数,则必须是 4 或 6。如果 options 是对象时,会包含以下两个可选参数:

  • family:可选,IP 版本。如果提供,必须是 4 或 6。不提供则,IP v4 和 v6 地址都可以
  • hints:可选。如果提供,可以是一个或者多个 getaddrinfo 标志。若不提供,则没有标志会传给 getaddrinfo。

callback 回调函数,参数包含(err, address, family)。出错时,参数 err 是 Error 对象。address 参数表示 IP v4 或 v6 地址。family 参数是 4 或 6,表示 address 协议版本。

const dns = require('dns')

dns.lookup(`www.github.com`, (err, address, family) => {
  if (err) throw err
  console.log('地址: %j 地址族: IPv%s', address, family) // 地址: "13.229.188.59" 地址族: IPv4
})

2.连接到 DNS 服务器解析域名

在 dns 模块中,除 dns.lookup()方法外都是使用 DNS 服务器进行域名解析,解析时需要连接到网络。

dns.resolve(hostname[, rrtype], callback):将一个域名(如 'www.baidu.com')解析为一个 rrtype 指定类型的数组

hostname 表示要解析的域名。

rrtype 有以下可用值:

rrtyperecords 包含结果的类型快捷方法
'A'IPv4 地址 (默认)stringdns.resolve4()
'AAAA'IPv6 地址stringdns.resolve6()
'ANY'任何记录Objectdns.resolveAny()
'CNAME'规范名称记录stringdns.resolveCname()
'MX'邮件交换记录Objectdns.resolveMx()
'NAPTR'名称权限指针记录Objectdns.resolveNaptr()
'NS'名称服务器记录stringdns.resolveNs()
'PTR'指针记录stringdns.resolvePtr()
'SOA'开始授权记录Objectdns.resolveSoa()
'SRV'服务记录Objectdns.resolveSrv()
'TXT'文本记录string[]dns.resolveTxt()

callback 回调函数,参数包含(err, addresses)。出错时,参数 err 是 Error 对象。addresses 根据记录类型的不同返回值也不同。

const dns = require('dns')

dns.resolve('www.baidu.com', 'A', (err, addresses) => {
  if (err) throw err
  console.log(`IP地址 : ${JSON.stringify(addresses)}`) // IP地址 : ["163.177.151.110","163.177.151.109"]
})

// or

dns.resolve4('www.baidu.com', (err, addresses) => {
  if (err) throw err
  console.log(`IP地址 : ${JSON.stringify(addresses)}`) // IP地址 : ["163.177.151.110","163.177.151.109"]
})

反向 DNS 查询

将 IPv4 或 IPv6 地址解析为主机名数组。

使用 getnameinfo 方法将传入的地址和端口解析为域名和服务

dns.reverse(ip, callback)

ip 表示要反向解析的 IP 地址。

callback 回调函数,参数包含(err, domains)。出错时,参数 err 是 Error 对象。domains 解析后的域名数组。

dns.reverse('8.8.8.8', (err, domains) => {
  if (err) throw err
  console.log(domains) // [ 'dns.google' ]
})

dns.lookupService(address, port, callback)

address 表示要解析的 IP 地址字符串。

port 表示要解析的端口号。

callback 回调函数,参数包含(err, hostname, service)。出错时,参数 err 是 Error 对象。

dns.lookupService('127.0.0.1', 80, function(err, hostname, service) {
  if (err) throw err
  console.log('主机名:%s,服务类型:%s', hostname, service) // 主机名:localhost,服务类型:http
})

参考

Node.js 中文网

IT 笔录

后记

如果你和我一样喜欢前端,也爱动手折腾,欢迎关注我一起玩耍啊~ ❤️

博客

我的博客

公众号

前端时刻

公众号

查看原文

赞 52 收藏 42 评论 0

wangyuanqi 发布了文章 · 2019-12-13

tweenJs的使用及源码分析

xmind

tweenJs与css动画相比

优势

  • 更加灵活(链式补间...)
  • 可以定义多个动画,循环调用
  • 应用场景更广阔

弊端

  • 动画的的更新需要主动调用更新方法(依赖定时器或者动画主循环函数)
  • 性能没css更优

CSS3中transition和animation的属性

1) transition(过渡动画)
示例

  • 用法:transition: property duration timing-function delay
属性含义描述
transition-property指定哪个CSS属性需要应用到transition效果
transition-duration指定transition效果的持续时间
transition-timing-function指定transition效果的速度曲线
transition-delay指定transition效果的延迟时间

2) animation(关键帧动画)
示例

  • 用法:animation: name duration timing-function delay iteration-count direction fill-mode play-state
属性含义描述
animation-name指定要绑定到选择器的关键帧的名称
animation-duration指定动画的持续时间
animation-timing-function指定动画的速度曲线
animation-delay指定动画的延迟时间
animation-iteration-count指定动画的播放次数
animation-direction指定是否应该轮流反向播放动画
animation-fill-mode规定当动画不播放时(当动画完成时,或当动画有一个延迟未开始播放时),要应用到元素的样式
animation-play-state指定动画是否正在运行或已暂停

tweenjs介绍

tweenjs 是使用 JavaScript 的一个简单的补间动画库,支持数字、对象的属性和 CSS 样式属性的赋值。

tweenjs 以 <mark>平滑</mark> 的方式修改元素的属性值,需要传递给 tween 要修改的值、动画结束时的最终值和动画花费时间(duration),之后 tween 引擎就可以计算从开始动画点到结束动画点之间值,从而产生平滑的动画效果。

示例

var box = document.createElement('div');
box.style.setProperty('background-color', '#008800');
box.style.setProperty('width', '100px');
box.style.setProperty('height', '100px');
document.body.appendChild(box);

function animate() {
    requestAnimationFrame(animate);
    TWEEN.update();
}
requestAnimationFrame(animate);

var coords = { x: 0, y: 0 };
var tween = new TWEEN.Tween(coords) 
    .to({ x: 300, y: 200 }, 1000) 
    .easing(TWEEN.Easing.Quadratic.Out) 
    .onUpdate(function() { 
      box.style.setProperty('transform','translate('+coords.x+'px,'+ coords.y+'px)');
    })
    .start(); 

demo

示例说明

1、 假设有一个对象 position ,它的坐标为 x 和 y

    var position = { x: 100, y: 0 }

2、 假设有一个对象 position ,它的坐标为 x 和 y

    var tween = new TWEEN.Tween(position)
    tween.to({x: 200}, 1000)

3、 创建 tween 对象后,激活它,从而让它开始动画

    tween.start();

4、 为了平滑的动画效果,需要在同一个循环动画中调用 TWEEN.update 方法

    animate();
    function animate(){
        requestAnimationFrame(animate);
        TWEEN.update();
    }

这个动作将会更新所有被激活的 tweens ,在 1s 内 position.x 将变为 200 。

5、 可以使用 onUpdate 回调函数将结果打印到控制台上

    tween.onUpdate(function(){
        console.log( this.x );
    })

这个函数在每次 tween 被更新时都会被调用

tweenjs 动画

Tween.js 本身不会运行,你需要通过 update 方法明确告诉它什么时候开始运行,推荐在动画主循环中使用该动画,可以调用 requestAnimationFrame 方法来获得良好的图像性能。

深入理解 requestAnimationFrame

使用无参数的调用方法,update 将明确当前时间。也可以为 update 方法法明确一个时间。

    TWEEN.update(100);

意思是"更新时间 = 100 毫秒"。你可以使用它来确保代码中的所有时间相关函数都使用相同的时间值。例如,假设你有一个播放器,并希望同步运行补间。

控制 tween 动画

方法名功能参数
Tween.start控制动画开始time (延迟一段时间后触发;可选)
Tween.stop控制动画结束-
TWEEN.update手动执行动画的更新time (更新动画位于time时间点;可选)
  • chain ==> 制作多个动画,例如一个动画在另一个动画结束后开始,可以通过 chain 来实现

示例

    tweenA.chain(tweenB);  //tweenB 在 tweenA 之后开始动画,故可以制作一个无线循环的动画
    tweenB.chain(tweenA);
  • repeat ==> 制作循环动画,优于 chain,接收一个用于描述循环次数的参数
    tween.repeat(10);
    tween.repeat(infinity);
  • delay ==> 用于控制动画之间的延迟
    tween.delay(1000);
    tween.start()
  • to ==> 用于控制动画之间的延迟
/** Tween传入参数可以读取当前属性值并应用相对值来找出新的最终值 **/
    // 绝对值
    Tween(absoluteObj).to({ x: 100 });
    // 相对值
    Tween(relativeObj).to({ x: "+100" });
    // 数组
    Tween(relativeObj).to({ x: [0, -100, 100] });

回调函数

可以在每次 tween 循环周期的指定时间点调用自定义的函数

  • onStart ==> tween 动画开始前的回调函数
  • onStop ==> tween 动画结束后的回调函数
  • onUpdate ==> 在 tween 动画每次更新后执行
  • onComplete ==> 在 tween 动画全部结束后执行
    var tween = new TWEEN.Tween({
    
    }).to({
    
    }).onStart(function(){
    
    }).onUpdate(function(){
    
    })

easing函数

函数示例

函数名效果
Linear线性匀速运动效果
Quadratic二次方的缓动(t^2)
Cubic三次方的缓动
Quartic四次方的缓动
Sinusoidal正弦曲线的缓动
Exponential指数曲线的缓动
Circular圆形曲线的缓动
Elastic指数衰减的正弦曲线缓动
Back超过范围的三次方的缓动
Bounce指数衰减的反弹缓动

easing类型

  • easeIn(In) ==> 加速,先慢后快
  • easeOut(Out) ==> 减速,先快后慢
  • easeInOut(InOut) ==> 前半段加速,后半段减速

使用公式

    .easing(TWEEN.Easing.easing函数.easing类型)

tweenjs 源码分析

源码地址

控制方法

缓动函数

插值函数

应用场景总结

动画类型应用场景
CSS动画或过度动画动画需求非常简单
Tween.js动画需要涉及复杂的布局,如:需要将多个补间同步到一起,在完成一些动作之后,循环多次等等

使用它们来计算平滑曲线作为输入数据

tip

保持你的onUpdate回调非常轻量级,因为这个函数每秒钟会被调用很多次,所以如果每次更新都要花费很多的代价,那么你可能会阻塞主线程并导致可怕的结果。

查看原文

赞 0 收藏 0 评论 0

wangyuanqi 赞了回答 · 2019-12-04

解决构造的File对象,内容不正确, 最明显的就是文件大小只有几个字节

是谁教你这种写法的?你想传入一个 URL 然后获得这个 URL 所代表的 File 对象?那你方向完全是错的。

new File(['第一行', '第二行', '第三行'], '文件名');

这么构建出来的只能是一个文本文件(可以理解为 .txt),其中第一个参数表示这个文本文件每一行的内容。

你那个 size: 9 实际就是你传进去的 abff 变量的文本值字节长度。

关注 2 回答 3

wangyuanqi 提出了问题 · 2019-12-04

解决构造的File对象,内容不正确, 最明显的就是文件大小只有几个字节

js代码:
var url = 'http://cdn.wangyuanqi.com/ava...'

// 构造新File对象
var aafile = new File([url], "aa.jpeg");

1501813931_798800.png

关注 2 回答 3

wangyuanqi 收藏了文章 · 2019-11-28

中高级前端面试秘籍,助你直通大厂(一)

引言

又是一年寒冬季,只身前往沿海工作,也是我第一次感受到沿海城市冬天的寒冷。刚过完金九银十,经过一场惨烈的江湖厮杀后,相信有很多小伙伴儿已经找到了自己心仪的工作,也有的正在找工作的途中。考虑到年后必定又是一场不可避免的厮杀,这里提前记录一下自己平时遇到和总结的一些知识点,自己巩固复习加强基础的同时也希望能在你的江湖路上对你有所帮助。笔者在入职最近这家公司之前也曾有过长达3个月的闭关修炼期,期间查阅资料无数,阅读过很多文章,但总结下来真正让你印象深刻的,不是那些前沿充满神秘感的新技术,也不是为了提升代码逼格的奇淫巧技,而是那些我们经常由于项目周期紧而容易忽略的基础知识。所谓万丈高楼平地起,只有你的地基打得足够牢固,你才有搭建万丈高楼的底气,你才能在你的前端人生路上越走越远

这篇主要是先总结一下CSS相关的知识点,可能某些部分不会涉及到太多具体的细节,主要是对知识点做一下汇总,如果有兴趣或者有疑惑的话可以自行百度查阅下相关资料或者在下方评论区留言讨论,后续文章再继续总结JS和其他方面相关的知识点,如有不对的地方还请指出。

1. CSS盒模型

CSS盒模型就是在网页设计中经常用到的CSS技术所使用的一种思维模型。CSS 假定所有的HTML 文档元素都生成了一个描述该元素在HTML文档布局中所占空间的矩形元素框,可以形象地将其看作是一个盒子。CSS 围绕这些盒子产生了一种“盒子模型”概念,通过定义一系列与盒子相关的属性,可以极大地丰富和促进各个盒子乃至整个HTML文档的表现效果和布局结构。

CSS盒模型可以看成是由从内到外的四个部分构成,即内容区(content)、内边距(padding)、边框(border)和外边距(margin)。内容区是盒子模型的中心,呈现盒子的主要信息内容;内边距是内容区和边框之间的空间;边框是环绕内容区和内边距的边界;外边距位于盒子的最外围,是添加在边框外周围的空间。

根据计算宽高的区域我们可以将其分为IE盒模型W3C标准盒模型,可以通过box-sizing来进行设置:

  • content-box:W3C标准盒模型
  • border-box:IE盒模型

区别:
W3C标准盒模型:width(宽度) = content(内容宽度)
IE盒模型:width(宽度) = content(内容宽度) + padding(内边距) + border(边框)

2. BFC

BFC即Block Fromatting Context(块级格式化上下文),它是页面中的一块独立的渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。具有BFC特性的元素可以看成是一个隔离的独立容器,让处于BFC内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

IE浏览器下为hasLayout,一般可以通过zoom:(除normal外任意值)来触发,hasLayout是IE浏览器渲染引擎的一个内部组成部分。在IE浏览器中,一个元素要么自己对自身的内容进行计算大小和组织,要么依赖于父元素来计算尺寸和和组织内容。为了调节这两个不同的概念,渲染引擎采用了hasLayout的属性,属性值可以为true或false。当一个元素的hasLayout属性为true时,我们就说这个元素有一个布局(Layout)。当拥有布局后,它会负责对自己和可能的子孙元素进行尺寸计算和定位,而不是依赖于祖先元素来完成这些工作。

2.1 触发条件

  • 根元素(<html>)
  • 浮动元素(元素的float不是none)
  • 绝对定位元素(元素的positionabsolutefixed)
  • 行内块元素(元素的displayinline-block)
  • 表格单元格(元素的displaytable-cell,HTML表格单元格默认为该值)
  • 表格标题(元素的displaytable-caption,HTML表格标题默认为该值)
  • display值为flow-root的元素
  • overflow属性的值不为visible
  • 弹性元素(displayflexinline-flex元素的直接子元素)
  • 网格元素(displaygrid或者inline-grid元素的直接子元素)

2.2 布局规则

普通文档流布局规则

  • 浮动的元素是不会被父级计算高度的
  • 非浮动元素会覆盖浮动元素的位置
  • margin会传递给父级
  • 两个相邻元素上下margin会发生重叠

BFC布局规则

  • 浮动的元素会被父级计算高度(父级触发了BFC)
  • 非浮动元素不会覆盖浮动元素的位置(非浮动元素触发了BFC)
  • margin不会传递给父级(父级触发了BFC)
  • 两个相邻元素上下margin不会发生重叠(给其中一个元素增加一个父级,并让它的父级触发BFC)

2.3 应用

  • 防止margin重叠
  • 清除内部浮动(原理是父级计算高度时,浮动的子元素也会参与计算)
  • 自适应两栏布局
  • 防止元素被浮动元素所覆盖

3. 层叠上下文

层叠上下文(stacking context),是HTML中一个三维的概念。在CSS2.1规范中,每个盒模型的位置都是三维的,分别是平面画布上的X轴Y轴以及表示层叠的Z轴。一般情况下,元素在页面上沿X轴Y轴平铺,我们察觉不到它们在Z轴上的层叠关系。而一旦元素发生堆叠,这时就能发现某个元素可能覆盖了另一个元素或者被另一个元素覆盖。


如果一个元素含有层叠上下文,我们就可以理解为这个元素在Z轴上就"高人一等",最终表现就是它离屏幕观察者更近。

你可以把层叠上下文理解为该元素当了官,而其他非层叠上下文元素则可以理解为普通群众。凡是"当了官的元素"就比普通元素等级要高,也就是说元素在Z轴上更靠上,更靠近观察者。

3.1 触发条件

  • 根层叠上下文(<html>)
  • position属性为非static值并设置z-index为具体数值
  • CSS3中的属性也可以产生层叠上下文

    • flex
    • transform
    • opacity
    • filter
    • will-change
    • -webkit-overflow-scrolling

3.2 层叠等级

层叠等级(stacking level),又叫"层叠级别"或者"层叠水平"。

  • 在同一个层叠上下文中,它描述定义的是该层叠上下文中的层叠上下文元素在Z轴上的上下顺序
  • 在其他普通元素中,它描述定义的是这些普通元素在Z轴上的上下顺序

注意:

  1. 普通元素的层叠等级优先由其所在的层叠上下文决定。
  2. 层叠等级的比较只有在当前层叠上下文元素中才有意义,不同层叠上下文中比较层叠等级是没有意义的。


根据以上的层叠等级图,我们在比较层叠等级时可以按照以下的思路来顺序比较:

  • 首先判定两个要比较的元素是否处于同一个层叠上下文中
  • 如果处于同一个层叠上下文中,则谁的层叠等级大,谁最靠上
  • 如果处于不同的层叠上下文中,则先比较他们所处的层叠上下文的层叠等级
  • 当两个元素层叠等级相同,层叠顺序相同时,在DOM结构中后面的元素层叠等级在前面元素之上

4. CSS3中新增的选择器以及属性

  • 属性选择器:
属性选择器含义描述
E[attr^="val"]属性attr的值以"val"开头的元素
E[attr$="val"]属性attr的值以"val"结尾的元素
E[attr*="val"]属性attr的值包含"val"子字符串的元素
  • 结构伪类选择器
选择器含义描述
E:root匹配元素所在文档的根元素,对于HTML文档,根元素始终是<html>
E:nth-child(n)匹配其父元素的第n个子元素,第一个编号为1
E:nth-last-child(n)匹配其父元素的倒数第n个子元素,第一个编号为1
E:nth-of-type(n)与:nth-child()作用类似,但是仅匹配使用同种标签的元素
E:nth-last-of-type(n)与:nth-last-child() 作用类似,但是仅匹配使用同种标签的元素
E:last-child匹配父元素的最后一个子元素,等同于:nth-last-child(1)
E:first-of-type匹配父元素下使用同种标签的第一个子元素,等同于:nth-of-type(1)
E:last-of-type匹配父元素下使用同种标签的最后一个子元素,等同于:nth-last-of-type(1)
E:only-child匹配父元素下仅有的一个子元素,等同于:first-child:last-child或 :nth-child(1):nth-last-child(1)
E:only-of-type匹配父元素下使用同种标签的唯一一个子元素,等同于:first-of-type:last-of-type或 :nth-of-type(1):nth-last-of-type(1)
E:empty匹配一个不包含任何子元素的元素,文本节点也被看作子元素
E:not(selector)匹配不符合当前选择器的任何元素
  • CSS3新增属性
属性含义描述
transition过渡效果
transform变换效果(移动(translate)、缩放(scale)、旋转(rotate)、倾斜(skew))
transform-origin设置旋转元素的基点位置
animation动画效果
border-color为边框设置多种颜色
border-radius圆角边框
box-shadow边框阴影
border-image边框图片
background-size规定背景图片的尺寸
background-origin规定背景图片的定位区域
background-clip规定背景图片从什么位置开始裁切
text-shadow文本阴影
text-overflow文本截断
word-wrap对长单词进行拆分,并换行到下一行
opacity不透明度
box-sizing控制盒模型的组成模式
rgba基于r,g,b三个颜色通道来设置颜色值,通过a来设置透明度

5. CSS3中transition和animation的属性

1) transition(过渡动画)

用法:transition: property duration timing-function delay

属性含义描述
transition-property指定哪个CSS属性需要应用到transition效果
transition-duration指定transition效果的持续时间
transition-timing-function指定transition效果的速度曲线
transition-delay指定transition效果的延迟时间

2) animation(关键帧动画)

用法:animation: name duration timing-function delay iteration-count direction fill-mode play-state

属性含义描述
animation-name指定要绑定到选择器的关键帧的名称
animation-duration指定动画的持续时间
animation-timing-function指定动画的速度曲线
animation-delay指定动画的延迟时间
animation-iteration-count指定动画的播放次数
animation-direction指定是否应该轮流反向播放动画
animation-fill-mode规定当动画不播放时(当动画完成时,或当动画有一个延迟未开始播放时),要应用到元素的样式
animation-play-state指定动画是否正在运行或已暂停

6. 清除浮动的方式以及各自的优缺点

  • 额外标签法(在最后一个浮动元素的后面新加一个标签如<div class="clear"></div>,并在其CSS样式中设置clear: both;)
优点:简单,通俗易懂,写少量代码,兼容性好
缺点:额外增加无语义html元素,代码语义化差,后期维护成本大
  • 给父级设置高度
优点:简单,写少量代码,容易掌握
缺点:不够灵活,只适用于高度固定的布局
  • 触发父级BFC(如给父元素设置overflow:hidden,特别注意的是:在IE6中还需要触发hasLayout,例如给父元素设置zoom:1。原理是触发父级BFC后,父元素在计算高度时,浮动的子元素也会参与计算)
优点:简单,代码简洁
缺点:设置overflow:hidden容易造成不会自动换行导致超出的尺寸被隐藏掉,无法显示要溢出的元素
  • 使用after伪元素,常见的写法如下:
 .clearfix::after {
    content: ".";
    display: block;
    height: 0;
    line-height: 0;
    clear: both;
    visibility:hidden;
    font-size: 0;
 }
 
 .clearfix {
    // 注意此处是为了兼容IE6和IE7浏览器,即触发hasLayout
    zoom: 1;
 }
优点:符合闭合浮动思想,结构语义化正确
缺点:代码量多,因为IE6-7下不支持after伪元素,需要额外写zoom:1来触发hasLayout

7. 居中布局的方式

水平居中

  • 若是行内元素,则直接给其父元素设置text-align: center即可
  • 若是块级元素,则直接给该元素设置margin: 0 auto即可
  • 若子元素包含浮动元素,则给父元素设置width:fit-content并且配合margin
.parent {
    width: -webkit-fit-content;
    width: -moz-fit-content;
    width: fit-content;
    margin: 0 auto;
}
  • 使用flex布局的方式,可以轻松实现水平居中,即使子元素中存在浮动元素也同样适用
// flex 2012年版本写法
.parent {
    display: flex;
    flex-direction: row;
    justify-content: center;
}

// flex 2009年版本写法
.parent {
    display: box;
    box-orient: horizontal;
    box-pack: center;
}
  • 使用绝对定位的方式,再配合CSS3新增的transform属性
.child {
    position: absolute;
    left: 50%;
    transform: translate(-50%, 0);
}
  • 使用绝对定位的方式,再配合负值的margin-left(此方法需要固定宽度)
.child {
    position: absolute;
    left: 50%;
    width: 200px; // 假定宽度为200px
    margin-left: -100px; // 负值的绝对值为宽度的一半
}
  • 使用绝对定位的方式,再配合left:0;right:0;margin:0 auto;(此方法需要固定宽度)
.child {
    position: absolute;
    left: 0;
    right: 0;
    margin: 0 auto;
    width: 200px; // 假定宽度为200px
}

垂直居中

  • 若元素是单行文本,则直接给该元素设置line-height等于其父元素的高度
  • 若元素是行内块级元素,可以配合使用display:inline-block;vertical-align:middle和一个伪元素来让内容块居中
.parent::after, .child {
    display: inline-block;
    vertical-align: middle;
}

.parent::after {
    content: "";
    height: 100%;
}
  • 使用vertical-align属性并且配合使用display:tabledisplay:table-cell来让内容块居中
.parent {
    display: table;
}

.child {
    display: table-cell;
    vertical-align: middle;
}
  • 使用flex布局的方式,可以轻松实现垂直居中,即使子元素中存在浮动元素也同样适用
// flex 2012年版本写法
.parent {
    display: flex;
    align-items: center;
}

// flex 2009年版本写法
.parent {
    display: box;
    box-orient: vertical;
    box-pack: center;
}
  • 使用绝对定位的方式,再配合CSS3新增的transform属性
.child {
    position: absolute;
    top: 50%;
    transform: translate(0, -50%);
}
  • 使用绝对定位的方式,再配合负值的margin-top(此方法需要固定高度)
.child {
    position: absolute;
    top: 50%;
    height: 200px; // 假定高度为200px
    margin-top: -100px; // 负值的绝对值为高度的一半
}
  • 使用绝对定位的方式,再配合top:0;bottom:0;margin:auto 0;(此方法需要固定高度)
.child {
    position: absolute;
    top: 0;
    bottom: 0;
    margin: auto 0;
    height: 200px; // 假定高度为200px
}

水平垂直居中

  • 使用flex布局的方式同样可以轻松实现水平垂直居中
// flex 2012年版本写法
.parent {
    display: flex;
    justify-content: center;
    align-items: center;
}

// flex 2009年版本写法
.parent {
    display: box;
    box-pack: center;
    box-align: center;
}
  • 使用绝对定位的方式,再配合CSS3新增的transform属性
.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}
  • 使用绝对定位的方式,再配合使用负值的margin-top和负值的margin-left(此方法需要同时固定宽度和高度)
.child {
    position: absolute;
    left: 50%;
    top: 50%;
    margin-top: -50px; // 负值的绝对值为高度的一半
    margin-left: -100px; // 负值的绝对值为宽度的一半
    width: 200px; // 假定宽度为200px
    height: 100px; // 假定高度为100px
}

8. CSS的优先级和权重

选择器(优先级从高到低)示例特殊性值
!important(重要性标识)div { color: #fff !important; }无,但为了方便记忆,可将其表示为1,0,0,0,0
行内样式<div style="color: #fff;"></div>1,0,0,0
id选择器#id0,1,0,0
类,伪类和属性选择器.content, :first-child, [type="text"]0,0,1,0
标签和伪元素选择器h1, ::after0,0,0,1
通配符、子选择器、相邻选择器*, div > p, p + p0,0,0,0
继承span { color: inherit; }
浏览器默认值浏览器开发者工具右侧的Styles面板中会显示user agent stylesheet字样

9. 移动端1px物理像素边框

我们知道,在移动端存在物理像素(physical pixel)设备独立像素(density-independent pixel)的概念。物理像素也称为设备像素,它是显示设备中一个最微小的物理部件,每个像素可以根据操作系统设置自己的颜色和亮度。设备独立像素也称为密度无关像素,可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用的虚拟像素(比如CSS像素),然后由相关系统转换为物理像素。根据物理像素和设备独立像素也衍生出了设备像素比(device pixel ratio)的概念,简称为dpr,其定义了物理像素和设备独立像素的对应关系,其计算公式为设备像素比 = 物理像素 / 设备独立像素。因为视网膜(Retina)屏幕的出现,使得一个物理像素并不能和一个设备独立像素完全对等,如下图所示:

在上图中,在普通屏幕下1个CSS像素对应1个物理像素,而在Retina屏幕下,1个CSS像素却对应4个物理像素,即在Retina屏幕下会有不同的dpr值。为了追求在移动端网页中更好的显示质量,因此我们需要做各种各样的适配处理,最经典的莫过于1px物理像素边框问题,我们需要根据移动端不同的dpr值来对边框进行处理。在JavaScript中,可以通过window.devicePixelRatio来获取当前设备的dpr,在CSS中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和-webkit-max-device-pixel-ratio来进行媒体查询,从而针对不同的设备,来做一些样式适配。这里对于1px像素的边框问题,给出一种最常见的写法:

.border-1px {
    position: relative;
}

.border-1px::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 1px;
    background-color: #000;
    -webkit-transform: scaleY(.5);
    transform: scaleY(.5);
}

@media only screen and (-webkit-min-device-pixel-ratio: 2.0), (min-device-pixel-ratio: 2.0) {
    .border-1px::after {
        -webkit-transform: scaleY(.5);
        transform: scaleY(.5);
    }
}

@media only screen and (-webkit-min-device-pixel-ratio: 3.0), (min-device-pixel-ratio: 3.0) {
    .border-1px::after {
        -webkit-transform: scaleY(.33);
        transform: scaleY(.33);
    }
}

10. 实现三栏布局的方式有哪些

三栏布局,顾名思义就是分为左中右三个模块进行布局,并且左右两边固定,中间模块根据浏览器的窗口变化进行自适应,效果图如下:

这里给出四种实现三栏布局的方式:

  • 使用绝对定位的方式
.container {
    position: relative;
    height: 200px;
    line-height: 200px;
    text-align: center;
    font-size: 20px;
    color: #fff;
}

.left {
    position: absolute;
    left: 0;
    top: 0;
    width: 150px;
    background: red;
}

.main {
    margin-left: 160px;
    margin-right: 110px;
    background: green;
}

.right {
    position: absolute;
    right: 0;
    top: 0;
    width: 100px;
    background: blue;
}

<div class="container">
    <div class="left">左</div>
    <div class="main">中</div>
    <div class="right">右</div>
</div>
优点:方便快捷,简单实用,不容易出现问题,而且还可以将<div class="main"></div>元素放到最前面,使得主要内容被优先加载。
缺点:元素脱离了文档流,可能会造成元素的重叠。
  • 使用flex布局的方式
.container {
    display: flex;        
    height: 200px;
    line-height: 200px;
    text-align: center;
    font-size: 20px;
    color: #fff;
}

.left {
    width: 150px;
    background: red;
}

.main {
    margin: 0 10px;
    flex: 1;
    background: green;
}

.right {
    width: 100px;
    background: blue;
}

<div class="container">
    <div class="left">左</div>
    <div class="main">中</div>
    <div class="right">右</div>
</div>
优点:简单实用,是现在比较流行的方案,特别是在移动端,大多数布局都采用的这种方式,是目前比较完美的一个。
缺点:需要考虑到浏览器的兼容性,根据不同的浏览器厂商需要添加相应的前缀。
  • 双飞翼布局
.content {
    float: left;
    width: 100%;
}

.main,
.left,
.right {
    height: 200px;
    line-height: 200px;
    text-align: center;
    font-size: 20px;
    color: #fff;
}

.main {
    margin-left: 160px;
    margin-right: 110px;
    background: green;
}

.left {
    float: left;
    margin-left: -100%;
    width: 150px;
    background: red;
}

.right {
    float: right;
    margin-left: -100px;
    width: 100px;
    background: blue;
}

<div class="content">
    <div class="main">中</div>
</div>
<div class="left">左</div>
<div class="right">右</div>
优点:比较经典的一种方式,通用性强,没有兼容性问题,而且支持主要内容优先加载。
缺点:元素脱离了文档流,要注意清除浮动,防止高度塌陷,同时额外增加了一层DOM结构,即增加了渲染树生成的计算量。
  • 圣杯布局
.container {
    margin-left: 160px;
    margin-right: 110px;
}

.left,
.main,
.right {
    height: 200px;
    line-height: 200px;
    text-align: center;
    font-size: 20px;
    color: #fff;    
}

.main {
    float: left;
    width: 100%;
    background: green;        
}

.left {
    position: relative;
    left: -160px;
    margin-left:  -100%;
    float: left;
    width: 150px;
    background: red;
}

.right {
    position: relative;
    right: -110px;
    margin-left:  -100px;
    float: left;
    width: 100px;
    background: blue;
}

<div class="container">
    <div class="main">中</div>
    <div class="left">左</div>
    <div class="right">右</div>
</div>
优点:相比于双飞翼布局,结构更加简单,没有多余的DOM结构层,同样支持主要内容优先加载。
缺点:元素同样脱离了文档流,要注意清除浮动,防止高度塌陷。

11. 实现等高布局的方式有哪些

等高布局,顾名思义就是在同一个父容器中,子元素高度相等的布局。从等高布局的实现方式来说,可以分为两种,分别是伪等高真等高伪等高是指子元素的高度差依然存在,只是视觉上给人的感觉就是等高,真等高是指子元素的高度真实相等。效果图如下:

这里给出五种实现等高布局的方式:

伪等高

  • 使用padding-bottom和负的margin-bottom来实现
.container {
    position: relative;
    overflow: hidden;
}
    
.left,
.main,
.right {
    padding-bottom: 100%;
    margin-bottom: -100%;
    float: left;
    color: #fff;
}

.left {
    width: 20%;
    background: red;
}

.main {
    width: 60%;
    background: green;
}

.right {
    width: 20%;
    background: blue;
}

<div class="container">
    <div class="left">左侧内容</div>
    <div class="main">
        <p>中间内容</p>
        <p>中间内容</p>
        <p>中间内容</p>
    </div>
    <div class="right">右侧内容</div>
</div>

真等高

  • 使用flex布局的方式
.container {
    display: flex;
}

.left,
.main,
.right {
    flex: 1;
    color: #fff;
}

.left {
    background: red;
}

.main {
    background: green;
}

.right {
    background: blue;
}

<div class="container">
    <div class="left">左侧内容</div>
    <div class="main">
        <p>中间内容</p>
        <p>中间内容</p>
        <p>中间内容</p>
    </div>
    <div class="right">右侧内容</div>
</div>
  • 使用绝对定位的方式
.container {
  position: relative;
  height: 200px;
}

.left,
.main,
.right {
    position: absolute;
    top: 0;
    bottom: 0;
    color: #fff;
}

.left {
    left: 0;
    width: 20%;
    background: red;
}

.main {
    left: 20%;
    right: 20%;
    background: green;
}

.right {
    right: 0;
    width: 20%;
    background: blue;
}

<div class="container">
    <div class="left">左侧内容</div>
    <div class="main">
        <p>中间内容</p>
        <p>中间内容</p>
        <p>中间内容</p>
    </div>
    <div class="right">右侧内容</div>
</div>
  • 使用table布局的方式
.container {
    width: 100%;
    display: table;
}

.left,
.main,
.right {
    display: table-cell;
    color: #fff;
}

.left {
    width: 20%;
    background: red;
}

.main {
    width: 60%;
    background: green;
}

.right {
    width: 20%;
    background: blue;
}

<div class="container">
    <div class="left">左侧内容</div>
    <div class="main">
        <p>中间内容</p>
        <p>中间内容</p>
        <p>中间内容</p>
    </div>
    <div class="right">右侧内容</div>
</div>
  • 使用grid网格布局的方式
.container {
    display: grid;
    width: 100%;
    grid-template-columns: 1fr 1fr 1fr;
    color: #fff;
}

.left {
    background: red;
}

.main {
    background: green;
}

.right {
    background: blue;
}

<div class="container">
    <div class="left">左侧内容</div>
    <div class="main">
        <p>中间内容</p>
        <p>中间内容</p>
        <p>中间内容</p>
    </div>
    <div class="right">右侧内容</div>
</div>

12. CSS实现三角形的原理

工作中我们经常会遇到需要三角形图标的应用场景,例如内容展开收起、左右箭头点击切换轮播,点击某条列表数据查看详情等。三角形图标的应用范围之广,使得我们有必要了解一下它的实现原理。
1) 首先我们来实现一个最基础的边框效果

.content {
    width: 50px;
    height: 50px;
    border: 2px solid;
    border-color:#ff9600 #3366ff #12ad2a #f0eb7a;
}

效果如下:

2) 然后我们尝试将border值放大10倍

.content {
    width: 50px;
    height: 50px;
    border: 20px solid;
    border-color: #ff9600 #3366ff #12ad2a #f0eb7a;
}

效果如下:

上图中我们可以很清楚地看到,在绘制border的时候并不是矩形区域,而是梯形区域,那么此时如果我们将widthheight值设置为0,看会发生什么:

.content {
    width: 0;
    height: 0;
    border: 20px solid;
    border-color: #ff9600 #3366ff #12ad2a #f0eb7a;
}

效果如下:

此时会看到一个由四个三角形拼装而成的矩形区域,即由上下左右四个边框组合而成。因此不难想象,如果我们想得到某一个方向的三角形,我们只需要让其他方向的边框不可见就行了,例如我们想得到一个朝左的三角形:

.content {
    width: 0;
    height: 0;
    border: 20px solid;
    border-color: transparent #3366ff transparent transparent;
}

效果如下:

这样就得到了一个很完美的三角形图标,是不是很简单?

13. link与@import的区别

  • 从属关系区别
@import是CSS提供的语法规则,只有导入样式表的作用;link是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS,Rel连接属性,设置浏览器资源提示符preload、prefetch等。
  • 加载顺序区别
HTML文档在解析的过程当中,如果遇到link标签,则会立即发起获取CSS文件资源的请求;@import引入的CSS将在页面加载完毕后才会被加载。
  • 兼容性区别
@import是CSS2.1才有的语法,因此需要IE5以上才能识别;link标签作为HTML元素,不存在兼容性问题。
  • DOM可控性区别
link标签可以通过JS来动态引入,而@import无法通过JS来插入样式
const loadStyle = (url) => {
    const link = document.createElement('link');
    link.setAttribute('type', 'text/css');
    link.setAttribute('rel', 'stylesheet');
    link.setAttribute('href', url);
    
    document.head.appendChild(link);
}

14. 浏览器是怎样解析CSS选择器的

CSS选择器的解析是从右向左解析的。若从左向右地匹配,发现不符合规则,需要进行回溯,会损失很多性能。若从右向左匹配,先找到所有的最右节点,对于每一个节点,向上寻找其父节点直到找到根元素或满足条件的匹配规则,则结束这个分支的遍历。两种匹配规则的性能差别很大,是因为从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点),而从左向右的匹配规则的性能都浪费在了失败的查找上面。而在CSS解析完毕后,需要将解析的结果与DOM Tree的内容一起进行分析建立一棵 Render Tree,最终用来进行绘图。在建立Render Tree时浏览器就要为每个DOM Tree中的元素根据CSS的解析结果(Style Rules)来确定生成怎样的Render Tree。

15. CSS的性能优化方案

  • 层级尽量扁平,避免嵌套过多层级的选择器;
  • 使用特定的选择器,避免解析器过多层级的查找;
  • 减少使用通配符与属性选择器;
  • 减少不必要的多余属性;
  • 避免使用!important标识,可以选择其他选择器;
  • 实现动画时优先使用CSS3的动画属性,动画时脱离文档流,开启硬件加速;
  • 使用link标签代替@import;
  • 将渲染首屏内容所需的关键CSS内联到HTML中;
  • 使用资源预加载指令preload让浏览器提前加载CSS资源并缓存;
  • 使用Gulp,Webpack等构建工具对CSS文件进行压缩处理;

推荐阅读

记一次大厂的面试过程
装饰你的敲门砖,离大厂更近一步

交流

终于接近尾声了,居然花费掉了我一整个周末的时间,不过这篇主要是先总结一下CSS相关的知识点,当然还有很多地方没有总结到,只是列出了个人觉得比较容易考察的点,如果你有其他补充的,欢迎在下方留言区讨论哦,也欢迎关注我的公众号[前端之境],关注后我可以拉你加入微信前端交流群,我们一起互相交流学习,共同进步。
后续会陆续总结出JS方面、浏览器视角、算法基础和框架方面的内容,希望你能够喜欢!

文章已同步更新至Github博客,若觉文章尚可,欢迎前往star!

你的一个点赞,值得让我付出更多的努力!

逆境中成长,只有不断地学习,才能成为更好的自己,与君共勉!

查看原文

wangyuanqi 收藏了文章 · 2019-11-25

万字长文,20-50K前端工程师部分面试题集锦 - 附答案

​ 现在20-50K的招聘,我们先看看是什么要求?

蚂蚁金服招聘要求:

虾皮招聘:

腾讯:

明源云:

毫无疑问,这些公司都是招聘的大前端技术栈的职位,之前文章提到过2020年大前端最理想的技术栈,其实真的弄得很明白那些,出去面试基本上不会有什么问题。
小提示:如果发现小公司面试套你的技术和架构,迅速结束,开出天价薪资走人

下面正式公布部分面试题,以及答案

  • 出于对各个公司的尊重,不公布是哪家公司的面试题,以及面试技巧。只公布部分面试题和答案,以及分析问题的角度,学习方向,面试中考察的不仅仅技术深度,还有广度,每个人不可能技术面面俱到,前端学习的东西太多,忘掉一部分也是正常。记住核心就是关键,这些都是一些基础面试题,比较通用。
  • 一般面试都会要做题,据我经验看,一般都是6页,三张纸。考察的大部分是前端技术栈,原生Javascript的内容,当然,有的外企的面试体验更棒,技术一面规定是半个小时,国内公司可能有5轮,甚至6、7轮。
  • 面试题我会归纳成原生JavaScript、Node.js、React、Vue、通信协议、运维部署、CI自动化部署、Docker、性能优化、前端架构设计、后端常见的架构等来分开写

原生JavaScript篇

以下代码跟我写的有点不一样,但是大致差不多,最终都是在纸上手写实现

手写一个深拷贝:

此处省略了一些其他类型的处理,可以在题目旁注释、

手写一个reduce:

Array.isArray的原理:

手写一个的防抖函数:

手写一个Promise:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
​
function MyPromise(fn) {
    const self = this;
    self.value = null;
    self.error = null;
    self.status = PENDING;
    self.onFulfilledCallbacks = [];
    self.onRejectedCallbacks = [];
​
    function resolve(value) {
        if (value instanceof MyPromise) {
            return value.then(resolve, reject);
        }
        if (self.status === PENDING) {
            setTimeout(() => {
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
            }, 0)
        }
    }
​
    function reject(error) {
        if (self.status === PENDING) {
            setTimeout(function() {
                self.status = REJECTED;
                self.error = error;
                self.onRejectedCallbacks.forEach((callback) => callback(self.error));
            }, 0)
        }
    }
    try {
        fn(resolve, reject);
    } catch (e) {
        reject(e);
    }
}
​
function resolvePromise(bridgepromise, x, resolve, reject) {
    if (bridgepromise === x) {
        return reject(new TypeError('Circular reference'));
    }
​
    let called = false;
    if (x instanceof MyPromise) {
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(bridgepromise, y, resolve, reject);
            }, error => {
                reject(error);
            });
        } else {
            x.then(resolve, reject);
        }
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(bridgepromise, y, resolve, reject);
                }, error => {
                    if (called) return;
                    called = true;
                    reject(error);
                })
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}
​
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    let bridgePromise;
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
    if (self.status === FULFILLED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0);
        })
    }
    if (self.status === REJECTED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(self.error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0);
        });
    }
    if (self.status === PENDING) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            self.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            self.onRejectedCallbacks.push((error) => {
                try {
                    let x = onRejected(error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
}
MyPromise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}
​
MyPromise.deferred = function() {
    let defer = {};
    defer.promise = new MyPromise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}
try {
    module.exports = MyPromise
} catch (e) {}
promisify原理:

promisify = function(fn) {
  return function() {
    var args = Array.from(arguments);
    return new MyPromise(function(resolve, reject) {
      fn.apply(
        null,
        args.concat(function(err) {
          err ? reject(err) : resolve(arguments[1]);
        })
      );
    });
  };
};
Redux核心源码解析:

bindActionCreator源码解析:


export default function bindActionCreator(actions, dispatch) {
    let newActions = {};
    for (let key in actions) {
        newActions[key] = () => dispatch(actions[key].apply(null, arguments));
    }
    return newActions;
}

核心:将多个action和dispatch传入,合并成一个全新的actions对象

combineReducers源码:

export default combineReducers = reducers => (state = {}, action) => Object.keys(reducers).reduce((currentState, key) => {
    currentState[key] = reducers[key](state[key], action);
    return currentState;
}, {});

核心:跟上面有点类似,遍历生成一个全新的state,将多个state合并成一个state

createStore源码结合applyMiddleware讲述如何实现处理多个中间件:


export default function createStore(reducer, enhancer) {
    if (typeof enhancer !== 'undefined') {
        return enhancer(createStore)(reducer)
    }
    let state = null
    const listeners = []
    const subscribe = (listener) => {
        listeners.push(listener)
    }
    const getState = () => state
    const dispatch = (action) => {
        state = reducer(state, action)
        listeners.forEach((listener) => listener())
    }
    dispatch({})
    return { getState, dispatch, subscribe }
}
applyMiddleware:
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer) => {
        const store = createStore(reducer)
        let dispatch = store.dispatch
        let chain = []
​
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
​
        return {
            ...store,
            dispatch
        }
    }
}

核心:当发现传入createStore的第二个或者第三个参数存在时候(这里没有像原生redux支持SSR代码注水,不支持第二个参数initState),就去返回它的调用结果

整个Redux这里是最绕的,这里不做过分的源码讲解,其实核心就是一点:

实现多个中间件原理,就是将dispatch当作最后一个函数传入,利用compose这个工具函数,最终实现多个中间件同时起作用,当你源码看得比较多的时候会发现,大多数的源码是跟redux相似

compose工具函数实现:
export default function compose(...funcs) {
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

核心:其实就是一个reduce函数实现,每次返回一个新的函数,再将新的参数传入

redux下次会专门出个文章讲解,它的源码太重要了~

原生JavaScript考察点比较多,这里只列出一部分,还有像结合TypeScript一起问的,组合继承,对象创建模式、设计模式等,但是那些本次不做讲解

Node.js篇幅:

简述Node.js的EventLoop:

现场出题,项目里有下面这段代码,输出是什么,稳定吗,说明原因:
setTimeout(() => {
  console.log(1);
});
//----若干代码逻辑
new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log(2);
});

答案:先输出2,再输出1,但是不稳定。因为定时器的执行时间不确定,node.js的轮询相当于一个定时器,一直从上往下6个阶段轮询,此时如果中间代码比较耗时,还没运行到Promise时候,已经轮询到第一阶段,定时器的回调就会被触发。

Node.js为什么处理异步IO快?

答:Node 底层采用线程池的原理管理异步 IO,所以我们通常所的 单线程是指 Node 中 JavaScript 的执行是单线程的,但 Node 本身是多线程的。Node.js 中异步 IO 是通过事件循环的方式实现的,异步 IO 事件主要来源于网络请求和文件 IO。但是正因为如此,Node.js处理很多计算密集型的任务,就比较吃力,当然有多进程方式可以解决这个问题。(自己给自己挖坑)

以前听过一个很形象的回答:Java是一百个服务员对应一百个用餐客人,Node是一个服务员对应一百个用餐客人(因为客人不需要分分钟服务,可能只要三分钟----好像,东哥?)

Node.js有cluster、fork两种模式多进程,那么这两种情况下,主进程负责TCP通信,怎样才可以让子进程共享用户的Socket对象?

答案:cluster模式,多实例、自动共享端口链接、自动实现负载均衡。fork模式实现的多进程,单实例、多进程,可以通过手动分发socket对象给不同子进程进行定制化处理、实现负载均衡

Node.js多进程维护,以及通信方式:

答案:原生的cluster和fork模式都有API封装好的进行通信。如果是execfile这样形式调起第三方插件形式,想要与第三方插件进行通信,可以自己封装一个类似promisyfy形式进行通信,维护这块,子进程可以监听到异常,一旦发现异常,立刻通知主进程,杀死这个异常的子进程,然后重新开启一个子进程~

简单谈谈,Node.js搭建TCP、restful、websocket、UDP服务器,遇到过哪些问题,怎么解决的

答案:这里涉及的问题比较多,考察全方位的通信协议知识,需要出个专题后期进行编写

看你简历上写,对koa源码系统学习过,请简述核心洋葱圈的实现:

答案:洋葱圈的实现,有点类似Promise中的then实现,每次通过use方法定义中间件函数时候,就会把这个函数存入一个队列中,全局维护一个ctx对象,每次调用next(),就会调用队列的下一个任务函数。伪代码实现~:


use (fn) {
    // this.fn = fn 改成:
    this.middlewares.push(fn) // 每次use,把当前回调函数存进数组
}
compose(middlewares, ctx){ // 简化版的compose,接收中间件数组、ctx对象作为参数
    function dispatch(index){ // 利用递归函数将各中间件串联起来依次调用
        if(index === middlewares.length) return // 最后一次next不能执行,不然会报错
        let middleware = middlewares[index] // 取当前应该被调用的函数
        middleware(ctx, () => dispatch(index + 1)) // 调用并传入ctx和下一个将被调用的函数,用户next()时执行该函数
    }
    dispatch(0)
}
​

所以这里说,源码看多了会发现,其实大都差不多,都是你抄我的,我抄你的,轮子上搭积木

你对TCP系统学习过,请你简述下SYN flood攻击:

小提示:我的TCP是跟张师傅学习的,在某金的小册上有卖。~推荐购买

答案:攻击方伪造源地址发送SYN报文,服务端此时回复syn+ack,但是真正的IP地址收到这个包之后,有可能直接回复了RST包,但是如果不回复RST包,那就更严重了,可能服务端会在几十秒后才关闭这个socket链接(时间根据每个系统不一样)

抓包可见~:

TCP可以快速握手吗?

答案:可以 -- 内容来自 张师傅的小册

TCP链接和UDP的区别,什么时候选择使用UDP链接?

http://www.xiuchuang.com/ques...

总结就是:TCP面向链接,UDP面向消息,TCP的ACK作用是确认已经收到上一个包,UDP只管发送,一些无人机的操作,就用UDP链接,每个无人机就是一个服务器,跟地面通讯。

通信协议还是要系统学习,通信这里也问了大概半个小时,包括密钥交换等

看你简历上有写自己实现了一个mini-react,请简述实现原理,以及diff算法实现

仓库地址:https://github.com/JinJieTan/...

答案:利用了babel,将虚拟dom转换成了我想要的对象格式,然后实现了异步setState、component diff 、 element diff 、props 更新等。类似PReact的将真实dom和虚拟dom对比的方式进行diff,这里结合代码讲了大概半个小时~ 大家可以看源码,这个对于学习React是非常好的资料,当时我花了半个多月学习

看你对Vue的源码有系统学习过,请简述下Vue2.x版本的数据绑定:

答案:Vue里面的{{}}写法, 会用正则匹配后,拿到数据跟data里的做对比-解析指令,观察数据变化是利用defineProperty来实现,因为监听不到数组的变化,所以尤大大只重写了6个数组API。源码解析,后面就是拼细节,主要讲一些核心点的实现。

为什么Vue的nextTick不稳定?

答案:Vue的nextTick原理是:

优雅降级:

首选promise.then

然后是setImmediate

然后是一个浏览器目前支持不好的API

最后是setTimeout

dom真正更新渲染好的时间,不能真正确定,不论是框架还是原生,都存在这个问题。所以用nextTick并不能保证拿到最新的dom

谈谈你对微前端的看法,以及实践:

答案:将Vue和React一起开发,其实一点都不难,只要自己能造出Redux这样的轮子,熟悉两个框架原理,就能一起开发,难的是将这些在一个合适的场景中使用。之前看到网上有微前端的实践,但是并不是那么完美,当然,类似Electron这样的应用,混合开发很正常,微前端并不是只单单多个框架混合开发,更多是多个框架引入后解决了什么问题、带来的问题怎么解决?毕竟5G还没完全普及,数据传输还是不那么快。过大的包容易带来客户端的过长白屏时间(自己给自己挖坑)

你有提到白屏时间,有什么办法可以减少吗?都是什么原理

答案: GZIP,SSR同构、PWA应用、预渲染、localStorage缓存js文件等、

下面就是细分拆解答案,无限的连带问题,这里非常耗时,这些内容大都网上能搜到,我这里就不详细说

其中有问到PWA的原理,我的回答是:

Service Worker 有一套自己的声明周期,当安装并且处于激活状态时候,网站在https或者localhost的协议时候,可以拦截过滤发出的请求,会先把请求克隆一份(请求是流,消费就没有了),然后判断请求的资源是否在 Service Worker 缓存中,如果存在那么可以直接从 Service Worker 缓存中取出,如果不存在,那么就真正的发出这个请求。

看你的技术栈对Electron比较熟悉,有使用过React-native,请你谈谈使用的感受?

答案:React-native的坑还是比较多,但是目前也算拥有成熟的生态了,开发简单的APP可以使用它。但是复杂的应用还是原生比较好,Electron目前非常受欢迎,它基本上可以完成桌面应用的大部分需求,重型应用开发也是完全没问题的,可以配合大量C# C++插件等。

Node.js的消息队列应用场景是什么?原理是什么

答案:我们公司之前用的kafka,消息队列的核心概念,异步,提供者,消费者。例如IM应用,每天都会有高峰期,但是我们不可能为了高峰期配置那么多服务器,那样就是浪费,所以使用消息队列,在多长时间内流量达到多少,就控制消费频率,例如客户端是流的提供者,有一个中间件消费队列,我们的服务器是消费者,每次消费一个任务就回复一个ACK给消费队列,消费频率由我们控制,这样任务不会丢失,服务器也不会挂。 还有一个异步问题,一个用户下单购买一件商品,可能要更新库存,已购数量,支付,下单等任务。不可能同步进行,这时候需要异步并行,事务方式处理。这样既不耽误时间,也能确保所有的任务成功才算成功,不然没有支付成功,但是已购数量增长了就有问题。

此处省略、、、一万字

用户就是要上传10个G的文件,服务器存储允许的情况下,你会怎么处理保证整体架构顺畅,不影响其他用户?

答案:我会准备两个服务器上传接口,前端或者原生客户端上传文件可以拿到文件大小,根据文件大小,分发不同的对应服务器接口处理上传,大文件可以进行断点续传,原理是md5生成唯一的hash值,将分片的hash数组先上传到后端,然后将文件分片上传,对比hash值,相同的则丢弃。不一致的话,根据数组内容进行buffer拼接生成文件。

关于服务器性能,大文件上传的服务器允许被阻塞,小文件的服务器不会被阻塞。

谈谈你对前端、客户端架构的认识?

答案:前端的架构,首先明确项目的兼容性,面向浏览器编程,是否做成PC、移动端的响应式布局。根据项目规模、后期可能迭代的需求制定技术方案,如果比较重型的应用应该选用原生开发,尽量少使用第三方库。

客户端架构:是否跨平台,明确兼容系统,例如是否兼容XP ,如果兼容XP就选择nw.js,再然后根据项目复杂度招聘相应技术梯度人员,安排系统学习相关内容,招聘人员或者购买定制开发相关原生插件内容。

虽然说只是谈谈,但是感觉面试的职位越高级、轮数越往后,越考验你的架构能力,前面考察基础,后面考察你的技术广度以及逻辑思维,能否在复杂的应用中保持清醒头脑,定位性能这类型的细节能力。很多人基础面试面得很好,但是拿不到offer,原因就是没有这种架构能力,只能自己写代码,不能带领大家学习、写代码。这也是我在面试时偶然听到某个大公司HR之间的对话,原话是:他面试还可以,看起来是很老实(某个之前的面试者),但是他对之前项目整体流程并不是那么清楚,连自己做的项目,前后端流程都不清楚,感觉不合适。

介绍一下Redis,为什么快,怎么做持久化存储,什么叫缓存击穿?

答案:Redis将数据存储在内存中,key-value形式存储,所以获取也快。支持的key格式相对于memorycache更多,而且支持RDB快照形式、AOF

1.RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。RDB是Redis默认的持久化方式,会在对应的目录下生产一个dump.rdb文件,重启会通过加载dump.rdb文件恢复数据。

2、优点

1)只有一个文件dump.rdb,方便持久化;

2) 容灾性好,一个文件可以保存到安全的磁盘;

3) 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化(使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能) ;

4)如果数据集偏大,RDB的启动效率会比AOF更高。

1)数据安全性低。(RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不是特别严格的时候)

2)由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,文件中可以看到详细的操作记录。她的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

2、优点

1)数据安全性更高,AOF持久化可以配置appendfsync属性,其中always,每进行一次命令操作就记录到AOF文件中一次。

2)通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。

3)AOF机制的rewrite模式。(AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall))

3、缺点

1)AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比rdb启动效率低。

2)根据同步策略的不同,AOF在运行效率上往往会慢于RDB。

具体可看、

https://baijiahao.baidu.com/s...

Redis可以配合session等做服务端持久化存储、还介绍了下session的场景,

介绍下缓存击穿和穿透:

缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

介绍下你会用的自动化构建的方式:

答案

Jenkins自动化构建

自己搭建Node.js服务器,实现Jenkins

Docker配合Travis CI实现自动化构建

细化答案:

Jenkins自动化构建:

配置,自动同步某个分支代码,打包构建。

自己搭建Node.js服务器,实现Jenkins:

自己搭建Node.js的服务器,在GitLab上指定webhook地址,分支代码更新触发事件,服务器接受到post请求,里面附带分支的信息,执行自己的shell脚本命令,指定文件夹,构建打包。

服务器上使用Docker-compose指定镜像,每次代码推送到gitHub,通过自己编写的yml和dockerfile文件构建打包,服务器自动拉取最新镜像并且发布到正式环境

代码实现:

.travis.yml

language: node_js
node_js:

  • '12'

services:

  • docker


before_install:

  • npm install
  • npm install -g parcel-bundler


script:

  • parcel build ./index.js
  • echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
  • docker build -t jinjietan/mini-react:latest .
  • docker push jinjietan/mini-react:latest

dockerfile:

FROM nginx
COPY ./index.html /usr/share/nginx/html/
COPY ./dist /usr/share/nginx/html/dist
EXPOSE 80

问完Redis,肯定会问数据库,mysql mongodb sqlite都问了,这里就暂时不写了

数据库需要系统的学习,特别是mysql,我这里就不班门弄斧了,推荐某金上面的小孩子的小册。零蛋学mysql(本文没有收取任何广告费,自己买了看完才推荐给大家)

附带的一些问题:

Linux常见操作

云端部署

等。

当然有人会问20-50K的问题怎么这么简单,因为每个问题都是串起来的,需要你的知识面足够广,才能一路面下去,直到拿到offer。而且每个问题都是有坑,例如pm2 start 如果不指定参数到底会启动多少个进程? 在云端和自己的电脑上是不一样的,这些都需要你去有实际操作经验才能应对。

本文的初衷,不为了面试而背面试题,只是让大家知道应该学习什么方向。纯粹为了面试去背诵面试题,是很容易被识破,只有不断积累学习,你才会轻易拿下offer。
座右铭:你想要在外人看来毫不费力,那你就要平时不断努力~

如果觉得写得不错,欢迎点个在看,关注一下小编的公众号:前端巅峰,赞赏一下更好啦。我们有大前端交流群,里面妹子多多~大牛多多,欢迎加入~ 微信公众号发送加群:即可加入~

文章里面有写得不对的地方,可以在下面指出,我的答案也不一定对,上面部分答案由于太长,网上复制了一部分,但是大部分是自己写的~

查看原文

wangyuanqi 赞了文章 · 2019-11-25

万字长文,20-50K前端工程师部分面试题集锦 - 附答案

​ 现在20-50K的招聘,我们先看看是什么要求?

蚂蚁金服招聘要求:

虾皮招聘:

腾讯:

明源云:

毫无疑问,这些公司都是招聘的大前端技术栈的职位,之前文章提到过2020年大前端最理想的技术栈,其实真的弄得很明白那些,出去面试基本上不会有什么问题。
小提示:如果发现小公司面试套你的技术和架构,迅速结束,开出天价薪资走人

下面正式公布部分面试题,以及答案

  • 出于对各个公司的尊重,不公布是哪家公司的面试题,以及面试技巧。只公布部分面试题和答案,以及分析问题的角度,学习方向,面试中考察的不仅仅技术深度,还有广度,每个人不可能技术面面俱到,前端学习的东西太多,忘掉一部分也是正常。记住核心就是关键,这些都是一些基础面试题,比较通用。
  • 一般面试都会要做题,据我经验看,一般都是6页,三张纸。考察的大部分是前端技术栈,原生Javascript的内容,当然,有的外企的面试体验更棒,技术一面规定是半个小时,国内公司可能有5轮,甚至6、7轮。
  • 面试题我会归纳成原生JavaScript、Node.js、React、Vue、通信协议、运维部署、CI自动化部署、Docker、性能优化、前端架构设计、后端常见的架构等来分开写

原生JavaScript篇

以下代码跟我写的有点不一样,但是大致差不多,最终都是在纸上手写实现

手写一个深拷贝:

此处省略了一些其他类型的处理,可以在题目旁注释、

手写一个reduce:

Array.isArray的原理:

手写一个的防抖函数:

手写一个Promise:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
​
function MyPromise(fn) {
    const self = this;
    self.value = null;
    self.error = null;
    self.status = PENDING;
    self.onFulfilledCallbacks = [];
    self.onRejectedCallbacks = [];
​
    function resolve(value) {
        if (value instanceof MyPromise) {
            return value.then(resolve, reject);
        }
        if (self.status === PENDING) {
            setTimeout(() => {
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
            }, 0)
        }
    }
​
    function reject(error) {
        if (self.status === PENDING) {
            setTimeout(function() {
                self.status = REJECTED;
                self.error = error;
                self.onRejectedCallbacks.forEach((callback) => callback(self.error));
            }, 0)
        }
    }
    try {
        fn(resolve, reject);
    } catch (e) {
        reject(e);
    }
}
​
function resolvePromise(bridgepromise, x, resolve, reject) {
    if (bridgepromise === x) {
        return reject(new TypeError('Circular reference'));
    }
​
    let called = false;
    if (x instanceof MyPromise) {
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(bridgepromise, y, resolve, reject);
            }, error => {
                reject(error);
            });
        } else {
            x.then(resolve, reject);
        }
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(bridgepromise, y, resolve, reject);
                }, error => {
                    if (called) return;
                    called = true;
                    reject(error);
                })
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}
​
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    let bridgePromise;
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
    if (self.status === FULFILLED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0);
        })
    }
    if (self.status === REJECTED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(self.error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0);
        });
    }
    if (self.status === PENDING) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            self.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            self.onRejectedCallbacks.push((error) => {
                try {
                    let x = onRejected(error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
}
MyPromise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}
​
MyPromise.deferred = function() {
    let defer = {};
    defer.promise = new MyPromise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}
try {
    module.exports = MyPromise
} catch (e) {}
promisify原理:

promisify = function(fn) {
  return function() {
    var args = Array.from(arguments);
    return new MyPromise(function(resolve, reject) {
      fn.apply(
        null,
        args.concat(function(err) {
          err ? reject(err) : resolve(arguments[1]);
        })
      );
    });
  };
};
Redux核心源码解析:

bindActionCreator源码解析:


export default function bindActionCreator(actions, dispatch) {
    let newActions = {};
    for (let key in actions) {
        newActions[key] = () => dispatch(actions[key].apply(null, arguments));
    }
    return newActions;
}

核心:将多个action和dispatch传入,合并成一个全新的actions对象

combineReducers源码:

export default combineReducers = reducers => (state = {}, action) => Object.keys(reducers).reduce((currentState, key) => {
    currentState[key] = reducers[key](state[key], action);
    return currentState;
}, {});

核心:跟上面有点类似,遍历生成一个全新的state,将多个state合并成一个state

createStore源码结合applyMiddleware讲述如何实现处理多个中间件:


export default function createStore(reducer, enhancer) {
    if (typeof enhancer !== 'undefined') {
        return enhancer(createStore)(reducer)
    }
    let state = null
    const listeners = []
    const subscribe = (listener) => {
        listeners.push(listener)
    }
    const getState = () => state
    const dispatch = (action) => {
        state = reducer(state, action)
        listeners.forEach((listener) => listener())
    }
    dispatch({})
    return { getState, dispatch, subscribe }
}
applyMiddleware:
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer) => {
        const store = createStore(reducer)
        let dispatch = store.dispatch
        let chain = []
​
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
​
        return {
            ...store,
            dispatch
        }
    }
}

核心:当发现传入createStore的第二个或者第三个参数存在时候(这里没有像原生redux支持SSR代码注水,不支持第二个参数initState),就去返回它的调用结果

整个Redux这里是最绕的,这里不做过分的源码讲解,其实核心就是一点:

实现多个中间件原理,就是将dispatch当作最后一个函数传入,利用compose这个工具函数,最终实现多个中间件同时起作用,当你源码看得比较多的时候会发现,大多数的源码是跟redux相似

compose工具函数实现:
export default function compose(...funcs) {
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

核心:其实就是一个reduce函数实现,每次返回一个新的函数,再将新的参数传入

redux下次会专门出个文章讲解,它的源码太重要了~

原生JavaScript考察点比较多,这里只列出一部分,还有像结合TypeScript一起问的,组合继承,对象创建模式、设计模式等,但是那些本次不做讲解

Node.js篇幅:

简述Node.js的EventLoop:

现场出题,项目里有下面这段代码,输出是什么,稳定吗,说明原因:
setTimeout(() => {
  console.log(1);
});
//----若干代码逻辑
new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log(2);
});

答案:先输出2,再输出1,但是不稳定。因为定时器的执行时间不确定,node.js的轮询相当于一个定时器,一直从上往下6个阶段轮询,此时如果中间代码比较耗时,还没运行到Promise时候,已经轮询到第一阶段,定时器的回调就会被触发。

Node.js为什么处理异步IO快?

答:Node 底层采用线程池的原理管理异步 IO,所以我们通常所的 单线程是指 Node 中 JavaScript 的执行是单线程的,但 Node 本身是多线程的。Node.js 中异步 IO 是通过事件循环的方式实现的,异步 IO 事件主要来源于网络请求和文件 IO。但是正因为如此,Node.js处理很多计算密集型的任务,就比较吃力,当然有多进程方式可以解决这个问题。(自己给自己挖坑)

以前听过一个很形象的回答:Java是一百个服务员对应一百个用餐客人,Node是一个服务员对应一百个用餐客人(因为客人不需要分分钟服务,可能只要三分钟----好像,东哥?)

Node.js有cluster、fork两种模式多进程,那么这两种情况下,主进程负责TCP通信,怎样才可以让子进程共享用户的Socket对象?

答案:cluster模式,多实例、自动共享端口链接、自动实现负载均衡。fork模式实现的多进程,单实例、多进程,可以通过手动分发socket对象给不同子进程进行定制化处理、实现负载均衡

Node.js多进程维护,以及通信方式:

答案:原生的cluster和fork模式都有API封装好的进行通信。如果是execfile这样形式调起第三方插件形式,想要与第三方插件进行通信,可以自己封装一个类似promisyfy形式进行通信,维护这块,子进程可以监听到异常,一旦发现异常,立刻通知主进程,杀死这个异常的子进程,然后重新开启一个子进程~

简单谈谈,Node.js搭建TCP、restful、websocket、UDP服务器,遇到过哪些问题,怎么解决的

答案:这里涉及的问题比较多,考察全方位的通信协议知识,需要出个专题后期进行编写

看你简历上写,对koa源码系统学习过,请简述核心洋葱圈的实现:

答案:洋葱圈的实现,有点类似Promise中的then实现,每次通过use方法定义中间件函数时候,就会把这个函数存入一个队列中,全局维护一个ctx对象,每次调用next(),就会调用队列的下一个任务函数。伪代码实现~:


use (fn) {
    // this.fn = fn 改成:
    this.middlewares.push(fn) // 每次use,把当前回调函数存进数组
}
compose(middlewares, ctx){ // 简化版的compose,接收中间件数组、ctx对象作为参数
    function dispatch(index){ // 利用递归函数将各中间件串联起来依次调用
        if(index === middlewares.length) return // 最后一次next不能执行,不然会报错
        let middleware = middlewares[index] // 取当前应该被调用的函数
        middleware(ctx, () => dispatch(index + 1)) // 调用并传入ctx和下一个将被调用的函数,用户next()时执行该函数
    }
    dispatch(0)
}
​

所以这里说,源码看多了会发现,其实大都差不多,都是你抄我的,我抄你的,轮子上搭积木

你对TCP系统学习过,请你简述下SYN flood攻击:

小提示:我的TCP是跟张师傅学习的,在某金的小册上有卖。~推荐购买

答案:攻击方伪造源地址发送SYN报文,服务端此时回复syn+ack,但是真正的IP地址收到这个包之后,有可能直接回复了RST包,但是如果不回复RST包,那就更严重了,可能服务端会在几十秒后才关闭这个socket链接(时间根据每个系统不一样)

抓包可见~:

TCP可以快速握手吗?

答案:可以 -- 内容来自 张师傅的小册

TCP链接和UDP的区别,什么时候选择使用UDP链接?

http://www.xiuchuang.com/ques...

总结就是:TCP面向链接,UDP面向消息,TCP的ACK作用是确认已经收到上一个包,UDP只管发送,一些无人机的操作,就用UDP链接,每个无人机就是一个服务器,跟地面通讯。

通信协议还是要系统学习,通信这里也问了大概半个小时,包括密钥交换等

看你简历上有写自己实现了一个mini-react,请简述实现原理,以及diff算法实现

仓库地址:https://github.com/JinJieTan/...

答案:利用了babel,将虚拟dom转换成了我想要的对象格式,然后实现了异步setState、component diff 、 element diff 、props 更新等。类似PReact的将真实dom和虚拟dom对比的方式进行diff,这里结合代码讲了大概半个小时~ 大家可以看源码,这个对于学习React是非常好的资料,当时我花了半个多月学习

看你对Vue的源码有系统学习过,请简述下Vue2.x版本的数据绑定:

答案:Vue里面的{{}}写法, 会用正则匹配后,拿到数据跟data里的做对比-解析指令,观察数据变化是利用defineProperty来实现,因为监听不到数组的变化,所以尤大大只重写了6个数组API。源码解析,后面就是拼细节,主要讲一些核心点的实现。

为什么Vue的nextTick不稳定?

答案:Vue的nextTick原理是:

优雅降级:

首选promise.then

然后是setImmediate

然后是一个浏览器目前支持不好的API

最后是setTimeout

dom真正更新渲染好的时间,不能真正确定,不论是框架还是原生,都存在这个问题。所以用nextTick并不能保证拿到最新的dom

谈谈你对微前端的看法,以及实践:

答案:将Vue和React一起开发,其实一点都不难,只要自己能造出Redux这样的轮子,熟悉两个框架原理,就能一起开发,难的是将这些在一个合适的场景中使用。之前看到网上有微前端的实践,但是并不是那么完美,当然,类似Electron这样的应用,混合开发很正常,微前端并不是只单单多个框架混合开发,更多是多个框架引入后解决了什么问题、带来的问题怎么解决?毕竟5G还没完全普及,数据传输还是不那么快。过大的包容易带来客户端的过长白屏时间(自己给自己挖坑)

你有提到白屏时间,有什么办法可以减少吗?都是什么原理

答案: GZIP,SSR同构、PWA应用、预渲染、localStorage缓存js文件等、

下面就是细分拆解答案,无限的连带问题,这里非常耗时,这些内容大都网上能搜到,我这里就不详细说

其中有问到PWA的原理,我的回答是:

Service Worker 有一套自己的声明周期,当安装并且处于激活状态时候,网站在https或者localhost的协议时候,可以拦截过滤发出的请求,会先把请求克隆一份(请求是流,消费就没有了),然后判断请求的资源是否在 Service Worker 缓存中,如果存在那么可以直接从 Service Worker 缓存中取出,如果不存在,那么就真正的发出这个请求。

看你的技术栈对Electron比较熟悉,有使用过React-native,请你谈谈使用的感受?

答案:React-native的坑还是比较多,但是目前也算拥有成熟的生态了,开发简单的APP可以使用它。但是复杂的应用还是原生比较好,Electron目前非常受欢迎,它基本上可以完成桌面应用的大部分需求,重型应用开发也是完全没问题的,可以配合大量C# C++插件等。

Node.js的消息队列应用场景是什么?原理是什么

答案:我们公司之前用的kafka,消息队列的核心概念,异步,提供者,消费者。例如IM应用,每天都会有高峰期,但是我们不可能为了高峰期配置那么多服务器,那样就是浪费,所以使用消息队列,在多长时间内流量达到多少,就控制消费频率,例如客户端是流的提供者,有一个中间件消费队列,我们的服务器是消费者,每次消费一个任务就回复一个ACK给消费队列,消费频率由我们控制,这样任务不会丢失,服务器也不会挂。 还有一个异步问题,一个用户下单购买一件商品,可能要更新库存,已购数量,支付,下单等任务。不可能同步进行,这时候需要异步并行,事务方式处理。这样既不耽误时间,也能确保所有的任务成功才算成功,不然没有支付成功,但是已购数量增长了就有问题。

此处省略、、、一万字

用户就是要上传10个G的文件,服务器存储允许的情况下,你会怎么处理保证整体架构顺畅,不影响其他用户?

答案:我会准备两个服务器上传接口,前端或者原生客户端上传文件可以拿到文件大小,根据文件大小,分发不同的对应服务器接口处理上传,大文件可以进行断点续传,原理是md5生成唯一的hash值,将分片的hash数组先上传到后端,然后将文件分片上传,对比hash值,相同的则丢弃。不一致的话,根据数组内容进行buffer拼接生成文件。

关于服务器性能,大文件上传的服务器允许被阻塞,小文件的服务器不会被阻塞。

谈谈你对前端、客户端架构的认识?

答案:前端的架构,首先明确项目的兼容性,面向浏览器编程,是否做成PC、移动端的响应式布局。根据项目规模、后期可能迭代的需求制定技术方案,如果比较重型的应用应该选用原生开发,尽量少使用第三方库。

客户端架构:是否跨平台,明确兼容系统,例如是否兼容XP ,如果兼容XP就选择nw.js,再然后根据项目复杂度招聘相应技术梯度人员,安排系统学习相关内容,招聘人员或者购买定制开发相关原生插件内容。

虽然说只是谈谈,但是感觉面试的职位越高级、轮数越往后,越考验你的架构能力,前面考察基础,后面考察你的技术广度以及逻辑思维,能否在复杂的应用中保持清醒头脑,定位性能这类型的细节能力。很多人基础面试面得很好,但是拿不到offer,原因就是没有这种架构能力,只能自己写代码,不能带领大家学习、写代码。这也是我在面试时偶然听到某个大公司HR之间的对话,原话是:他面试还可以,看起来是很老实(某个之前的面试者),但是他对之前项目整体流程并不是那么清楚,连自己做的项目,前后端流程都不清楚,感觉不合适。

介绍一下Redis,为什么快,怎么做持久化存储,什么叫缓存击穿?

答案:Redis将数据存储在内存中,key-value形式存储,所以获取也快。支持的key格式相对于memorycache更多,而且支持RDB快照形式、AOF

1.RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。RDB是Redis默认的持久化方式,会在对应的目录下生产一个dump.rdb文件,重启会通过加载dump.rdb文件恢复数据。

2、优点

1)只有一个文件dump.rdb,方便持久化;

2) 容灾性好,一个文件可以保存到安全的磁盘;

3) 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化(使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能) ;

4)如果数据集偏大,RDB的启动效率会比AOF更高。

1)数据安全性低。(RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不是特别严格的时候)

2)由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,文件中可以看到详细的操作记录。她的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

2、优点

1)数据安全性更高,AOF持久化可以配置appendfsync属性,其中always,每进行一次命令操作就记录到AOF文件中一次。

2)通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。

3)AOF机制的rewrite模式。(AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall))

3、缺点

1)AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比rdb启动效率低。

2)根据同步策略的不同,AOF在运行效率上往往会慢于RDB。

具体可看、

https://baijiahao.baidu.com/s...

Redis可以配合session等做服务端持久化存储、还介绍了下session的场景,

介绍下缓存击穿和穿透:

缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

介绍下你会用的自动化构建的方式:

答案

Jenkins自动化构建

自己搭建Node.js服务器,实现Jenkins

Docker配合Travis CI实现自动化构建

细化答案:

Jenkins自动化构建:

配置,自动同步某个分支代码,打包构建。

自己搭建Node.js服务器,实现Jenkins:

自己搭建Node.js的服务器,在GitLab上指定webhook地址,分支代码更新触发事件,服务器接受到post请求,里面附带分支的信息,执行自己的shell脚本命令,指定文件夹,构建打包。

服务器上使用Docker-compose指定镜像,每次代码推送到gitHub,通过自己编写的yml和dockerfile文件构建打包,服务器自动拉取最新镜像并且发布到正式环境

代码实现:

.travis.yml

language: node_js
node_js:

  • '12'

services:

  • docker


before_install:

  • npm install
  • npm install -g parcel-bundler


script:

  • parcel build ./index.js
  • echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
  • docker build -t jinjietan/mini-react:latest .
  • docker push jinjietan/mini-react:latest

dockerfile:

FROM nginx
COPY ./index.html /usr/share/nginx/html/
COPY ./dist /usr/share/nginx/html/dist
EXPOSE 80

问完Redis,肯定会问数据库,mysql mongodb sqlite都问了,这里就暂时不写了

数据库需要系统的学习,特别是mysql,我这里就不班门弄斧了,推荐某金上面的小孩子的小册。零蛋学mysql(本文没有收取任何广告费,自己买了看完才推荐给大家)

附带的一些问题:

Linux常见操作

云端部署

等。

当然有人会问20-50K的问题怎么这么简单,因为每个问题都是串起来的,需要你的知识面足够广,才能一路面下去,直到拿到offer。而且每个问题都是有坑,例如pm2 start 如果不指定参数到底会启动多少个进程? 在云端和自己的电脑上是不一样的,这些都需要你去有实际操作经验才能应对。

本文的初衷,不为了面试而背面试题,只是让大家知道应该学习什么方向。纯粹为了面试去背诵面试题,是很容易被识破,只有不断积累学习,你才会轻易拿下offer。
座右铭:你想要在外人看来毫不费力,那你就要平时不断努力~

如果觉得写得不错,欢迎点个在看,关注一下小编的公众号:前端巅峰,赞赏一下更好啦。我们有大前端交流群,里面妹子多多~大牛多多,欢迎加入~ 微信公众号发送加群:即可加入~

文章里面有写得不对的地方,可以在下面指出,我的答案也不一定对,上面部分答案由于太长,网上复制了一部分,但是大部分是自己写的~

查看原文

赞 189 收藏 145 评论 9

wangyuanqi 赞了文章 · 2019-11-25

网站性能优化实战篇

阅读理由:经验之谈,从理论到实践。

前情摘要

网站优化是前端开发的重中之重,但是优化细节却十分繁杂,没有好的思路,优化很难高效的开展。

本文将以实际网站来做参考,手把手教你如何一步步做好网站优化。

这不是一篇 基础网站优化 文章,继续下文前,请确定已经做了如下基本优化:

  1. 图片压缩、合并
  2. 代码精简、混淆
  3. 减少 iframe 使用
  4. 避免图片 src 为空
  5. 减少 HTTP 请求数
  6. 避免重定向
  7. 样式表放页头、脚本放底部

优化的意义

我们可以从两个角度来看这个问题:

  1. 用户角度

    网站优化能够让页面加载得更快,响应更加及时,极大提升用户体验。

  2. 服务商角度

    优化会减少页面资源请求数,减小请求资源所占带宽大小,从而节省可观的带宽资源。

网站优化的目标是:减少网站加载时间,提高响应速度。

那么网站加载速度和用户体验又有着怎样的关系呢?我们来看下面这张图:

性能优化

Google 和亚马逊的研究表明,Google 页面加载的时间从 0.4 秒提升到 0.9 秒导致丢失了 20% 流量和广告收入,对于亚马逊,页面加载时间每增加 100ms 就意味着 1% 的销售额损失。

可见,页面的加载速度对于用户有着至关重要的影响。

一个好的交互效果可能是这样的:

性能优化

分析网站性能瓶颈

1. 打包文件大小

2. 打包文件目录

.
├── favicon.ico
├── index.html
├── manifest.json
├── static
│   ├── DIN-Medium.1bbe3460.otf
│   ├── DIN-Regular.799221d7.otf
│   └── logo.c57d38d0.png
├── umi.css
├── umi.css.map
├── umi.js
└── umi.js.map
需要注意:生产环境不要开启 SOURCEMAP

3. 静态资源加载时间

4. 资源瀑布 Waterfall

waterfall

TTFB 全称 Time To First Byte:是指网络请求被发起到从服务器接收到第一个字节的这段时间,它包含了 TCP 连接时间、发送 HTTP 请求时间和获得响应消息第一个字节的时间。

Content Download:即下载内容所需要的时间。

页面一接口情况:

wrong

页面二接口情况:

history

用户下载内容所需要的时间,受限于服务器的资源、资源的大小以及用户的网络速度。因此,我们暂时不讨论这方面的内容。

5. 分析工具

通过 webpack 打包,分析一下大文件构成。

6.YSlow 或者 PageSpeed

我们可以通过 Google PageSpeed Insights API Extension 来对网站整体性能 做一下评估,按照建议去做一些高效优化。

加载时间概况:

speed1

影响网站加载因素:

缓存策略问题:

speed3

DOM 节点:

关键路径:

speed5

主线程情况:

通过策略解决问题

1. favicon.ico 404 问题 ✓

2. 去除调试工具代码 eruda,线上环境是不需要的 ✓

3. 图片合并或者多个 svg

建议使用 webpack-spritesmith,简单使用如下:

plugins: [
  new SpritesmithPlugin({
    src: {
        cwd: path.resolve(__dirname, 'src/ico'),
        glob: '*.png'
    },
    target: {
        image: path.resolve(__dirname, 'src/spritesmith-generated/jartto.png'),
        css: path.resolve(__dirname, 'src/spritesmith-generated/jartto.styl')
    },
    apiOptions: {
        cssImageRef: "~jartto.png"
    }
  })
]

4. 大文件拆分 ✓

从上图分析得出,大文件主要包含:dist.jslottie.jslodash.jsloading.json 等文件。所以我们从这几个文件入手,逐个优化:

  • moment.js - 配置 moment 忽略本地化,可减少 70kb
ignoreMomentLocale: true
  • dist.js - 在给单页应用做按需加载优化时,一般采用以下原则:

    1. 把整个网站划分成一个个小功能,再按照每个功能的相关程度把它们分成几类。
    2. 把每一类合并为一个 Chunk,按需加载对应的 Chunk
    3. 对于用户首次打开你的网站时需要看到的画面所对应的功能,不要对它们做按需加载,而是放到执行入口所在的 Chunk 中,以降低用户能感知的网页加载时间。
    4. 对于个别依赖大量代码的功能点,例如依赖 Chart.js 去画图表、依赖 flv.js 去播放视频的功能点,可再对其进行按需加载。
  • lottie.js - 分离减少 60kb

    externals: {
        lottie : 'react-lottie',
    }
  • lodash.js

    externals: {
    lodash : {
        commonjs: 'lodash',
        amd: 'lodash',
        root: '_' // indicates global variable
    }
    }
  • 动态导入以及文件拆分

    dynamicImport: {
        webpackChunkName: true,
        loadingComponent: './components/Loading/jartto.js',
    }

按照上面我们一步步处理后,重新打包分析一下文件构成:

这里为什么没有继续拆分 dist.js,是因为目前阶段没有好的方案,需要对代码做很多调整,所以暂且保留。相关信息可以在 Ant-Design IssuseSvg icons make bunlde size too large 中查看解决方案。

5. 存放 CDN

  • loading.json 大小54kb ✓
  • svg 替换 2 倍图 ✓
  • 删除项目冗余图片 ✓

6. 优化 TTFB

  • 减少 DNS 查询
  • 使用 CDN
  • 提早 Flush
  • 添加周期头

7. 移除阻塞渲染的资源

  • css 预加载 preload

    <link rel="preload">
  • 异步加载第三方资源

    <script async data-original="https://cdn.jartto.wang/fastclick.js"></script>

    没有 async 属性,script 将立即获取(下载)并执行,期间阻塞了浏览器的后续处理。
    如果有 async 属性,那么 script 将被异步下载并执行,同时浏览器继续后续的处理。

8. 确保文本在网页字体加载期间保持可见状态

利用 font-display 这项 CSS 功能,确保文本在网页字体加载期间始终对用户可见。

@font-face {
    font-family: 'Arvo';
    font-display: auto;
    src:local('Arvo'),url(https://fonts.jartto.wang/fonts/temp.woff2)format('woff2');
}

9.采用高效的缓存策略提供静态资源

延长缓存期限可加快重访网页的速度。

DNS TTL(Time-To-Live) 简单的说:它表示一条域名解析记录在 DNS 服务器上的缓存时间。

  • 当各地的 DNS 服务器接受到解析请求时,就会向域名指定的 DNS 服务器发出解析请求从而获得解析记录。
  • 在获得这个记录之后,记录会在 DNS 服务器中保存一段时间,这段时间内如果再接到这个域名的解析请求,DNS 服务器将不再向 DNS 服务器发出请求,而是直接返回刚才获得的记录。
这个记录在 DNS 服务器上保留的时间,就是 TTL 值。

所以一般更新域名解析的步骤如下:

  • 先查看域名当前的 TTL 值。
  • 修改 TTL 值为可设定的最小值,建议为 60 秒。
  • 等待一天,保证各地的 DNS 服务器缓存都过期并更新了记录。
  • 设置修改 DNS 解析到新的记录,这个时候各地的 DNS 就能以最快的速度更新到新的记录。
  • 确认各地的 DNS 已经更新完成后,再 TTL 值设置成常用的值(如: TTL=86400)。

如下图,TTL 值设置的最佳实践,可供参考:

后文我们会详细介绍 DNS 相关内容,欢迎各位童鞋关注。

10. 避免 DOM 规模过大

  • 网页包含的 DOM 节点最好少于 1500 个左右。
  • 理想状况是,树深度少于 32 个元素,且少于 60 个子/父元素。
  • 大型 DOM 可能会增加内存使用量、导致样式计算用时延长并产生高昂的布局重排费用。

11. 最大限度地缩短关键请求深度

关键请求链 显示了以高优先级加载的资源。

我们可以通过:缩短链长、缩减资源的下载文件大小,或者推迟下载不必要的资源,从而提高网页加载速度。

HTML 解析过程中遇到一个 script 标记时,它会暂停 DOM 构建,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复 DOM 构建。

也就是说,执行内联的 JavaScript 会阻塞页面的首次渲染。

在关键渲染路径中,我们通常要关注三个点:

  • 页面首次渲染需要的关键资源数量。
  • 关键资源的大小。
  • 关键渲染路径的往返次数(Roundtrip)。
我们的策略也非常简单,就是减少关键资源数量,降低资源大小,减少关键路径的往返次数。

优化关键渲染路径的常规步骤如下:

  1. 对关键路径进行分析和特性描述:资源数、字节数、长度。
  2. 最大限度减少关键资源的数量:删除它们,延迟它们的下载,将它们标记为异步等。
  3. 优化关键字节数以缩短下载时间(往返次数)。
  4. 优化其余关键资源的加载顺序:您需要尽早下载所有关键资源,以缩短关键路径长度。

更多详情,请参考前端性能优化—关键渲染路径

12. 最大限度地减少主线程工作

考虑减少为解析、编译和执行 JS 而花费的时间。我们可以提供较小的 JS 负载来实现此目标。

13. 最优配置 nginx

  1. gzip 配置
gzip  on;
gzip_min_length  1k;
gzip_buffers     4 8k;
gzip_http_version 1.1;
gzip_comp_level 4;
gzip_types text/plain text/css application/json image/png image/x-icon application/javascript application/x-javascript text/javascript text/xml application/xml application/xml+rss text/cache-manifest application/octet-stream;
gzip_vary on;
  1. nginx 开启缓存

如果你对浏览器缓存还不太清楚,欢迎移步聊一聊浏览器缓存机制。

location ~.*\.(html|htm|js|css|gif|jpg|jpeg|png|bmp|swf|ico|json|otf)$ {
  root /var/www/jartto_web/;
  index index.html;
  expires 1d;
}
Nginx 能非常有效地直接处理静态内容。在静态文件和 Nginx 在同一主机的情况下,这种特性尤为有用。

效果如何?

优化前:网站评分 27 ,首次内容绘制 6.9 秒

网站评分:

网站评分

加载概况:

优化后:网站评分 70 ,首次内容绘制 1.6 秒

网站评分:

加载概况:

当然,优化还可以做更多,我们尽量让网站的评分接近 100 分,譬如:

网站评分:

加载概况:

总结

我们从头优化下来,做了不少代码改动,也达到了不错的效果。但是有几点还是需要注意:

1. 尽可能减少白屏出现时间

骨架图解决 webview 加载页面过长的白屏过程。

2. 关注整站性能,如 TTFB

服务端接口也需要同步优化,而不要仅仅依赖前端单方面优化。

3. 按照使用情况加载优先使用的资源

  • css 预加载
  • font 预加载
  • js 预加载
  • 图片懒加载

4. 高效利用 DNSCDN

  • 增加缓存时间
  • DNS 预解析

网站优化从来不是一蹴而就,需要不断的去优化细节,不断的摸索尝试。从我的角度来看,其实优化更像是在网站性能和加载速度之间找到一个平衡点。譬如,文中我们为了优化文件打包大小,进行了大文件拆分。随之而来的问题就是拆分后的文件可能还会对某些文件有依赖,那么就影响到了关键渲染路径。

所以,优化不存在什么奇技淫巧,不断的去尝试,找到这个最佳优化点,这才是根本。

转载自: Jartto's blog

作者:jartto

查看原文

赞 32 收藏 23 评论 0

wangyuanqi 赞了文章 · 2019-11-21

27 个提升开发幸福度的 VsCode 插件

作者:Jsmanifest
译者:前端小智
来源:medium

个人专栏 ES6 深入浅出已上线,深入ES6 ,通过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

Visual Studio Code(也称为VSCode)是一种轻量级但功能强大的跨平台源代码编辑器, 借助对TypeScriptChrome调试器等开发工具的内置支持,越来越多的开发都都喜欢使用它。

如果你正在寻找更多的好用的 VsCode 工具,那么这篇或许能够帮助你。以下是 2019年为 JS 开发者提供的26个不可思议的VSCode工具。

1. Project Snippets (代码片段)

project snippets,这是我最喜欢的一个工具,它来自于 VSCode 中内置的原始用户代码片段。
该特性允许你创建自己的代码段,以便在整个项目中重用。

但是“重用”它们到底意味着什么?

如果咱们经常要重复写下面这样的样板文件:

import { useReducer } from 'react'

const initialState = {
  //
}

const reducer = (state, action) => {
  switch (action.type) {
    default:
      return state
  }
}

const useSomeHook = () => {
  const [state, dispatch] = useReducer(reducer, initialState)
  return {
    ...state,
  }
}

export default useSomeHook

实际上,咱们可以直接将上面的代码放到的用户代码片段中,因此不必写出(或复制和粘贴)整个代码片段,只需键入一个自定义前缀来生成配置的代码片段即可。

打开 VsCode,然后选择 文件 >首选项 > 用户代码片段,则可以选择通过单击 '新建全局代码片段文件'来创建新的全局代码片段。

例如,要为 TypeScript React 项目创建自己的代码片段文件,可以单击新建全局代码片段文件,输入 入typescriptreact.json。它将引导咱们访问一个新创建的.json文件,可以使用该文件来构建使用TypeScript 的 React 应用程序。

例如,要从上面的代码示例创建一个用户片段,可以这样做:

{
  "const initialState = {}; const reducer = (state, action)": {
    "prefix": "rsr",
    "body": [
      "const initialState = {",
      "  //$1",
      "}",
      "",
      "const reducer = (state, action) => {",
      "  switch (action.type) {",
      "    default:",
      "      return state",
      "  }",
      "}"
    ]
  }
}

有了它,咱们可以创建一个以.tsx结尾的新TypeScript文件,在新创建的文件输入rsr,然后按回车或 tab 键 Vscode 就会帮咱们生成代码片段内容。

const initialState = {
  //
}

const reducer = (state, action) => {
  switch (action.type) {
    default:
      return state
  }
}

全局用户代码片段的问题是,它将贯穿咱们所有项目(在某些情况下,这对于一般的代码片段来说是非常强大的)。

一些项目将以不同的方式配置,当需要区分特定的用例时,用于配置代码片段的全局文件就成了一个问题。

例如,当每个项目的项目结构不同时

{
  "import Link from components/common/Link": {
    "prefix": "gcl",
    "body": "import Link from 'components/common/Link'"
  },
  "border test": {
    "prefix": "b1",
    "body": "border: '1px solid red',"
  },
  "border test2": {
    "prefix": "b2",
    "body": "border: '1px solid green',"
  },
  "border test3": {
    "prefix": "b3",
    "body": "border: '1px solid magenta',"
  }
}

这对于具有特定file/folder结构的项目可能就足够了,但是如果咱们正在处理另一个项目,其中Link 组件具有类似components/Link的路径,该怎么办?

请注意这三个border tests是如何将它们的值用单引号括起来的:border: '1px solid red'

这在 JS 中是完全有效的,但是如果使用 styled-components作为项目的样式解决方案呢?该语法不再适用于该工作区,因为 styled components使用普通的CSS语法

这就是 project snippets 的亮点所在。

Project snippets使咱们可以声明项目/工作区级别的代码段,让当前项目代码段不会与其它项目冲突也不会污染其他项目。

2. Better Comments(更加人性化的注释)

如果喜欢在代码中编写注释,那么有时你可能会发现搜索您以前编写的特定注释的位置是令人沮丧的,因为代码可能会变得有些拥挤。

有了Better Comments,可以通过引入彩色注释使注释更加明显。

clipboard.png

3. Bracket Pair Colorizer (标签匹配 括号匹配插件)

第一次看到Bracket Pair Colorizer的屏幕截图时,我第一时间入安装使用了。

clipboard.png

4. Material Theme

Material Theme是一个史诗主题,可以直接安装到VSCode中,安装后代码看起来像这样:

clipboard.png

5. @typescript-eslint/parser

如果你是一个TypeScript用户,应该开始考虑将你的 TSLint 配置转移到ESLint + TypeScript上,TSLint 背后的支持者已经宣布计划在今年的某个时候弃用 TSLint

项目正逐步采用@typescript-eslint/parser和相关包,以确保其项目的前瞻性设置。

咱们仍然能够利用大部分ESLint的规则和兼容性与更漂亮使用新的设置。

6. Stylelint

对我来说,出于以下几个原因,stylelint 在我所有的项目中都是必须的:

  1. 它有助于避免错误。
  2. 它加强了CSS中的样式约定。
  3. 它与Prettier支持并驾齐驱。
  4. 它支持 CSS/SCSS/Sass/Less。
  5. 它支持社区编写的插件。

clipboard.png

7. Markdownlint + docsify

markdown 爱好者一定要试试 vscode 上的 markdownlint 扩展,会用绿色波浪线给你提示出 N 多不符合书写规范的地方,比如:

  • 标题下面必须是个空行
  • 代码段必须加上类型
  • 文中不能出现<br>这种html标号
  • URL必须用< >扩起来

同时也可以配合安装 docsify,因为它支持Markdown和每个项目的其他增强。

8. TODO Highlight

如果习惯在应用程序代码中编写待办事项的开发者,可以安装 TODO Highlight 这样的扩展名对于突出显示整个项目中设置的待办事项非常有用。

clipboard.png

9. Import Cost

Import Cost 可以显示咱们在VS代码编辑器中导入的程序包的大小。

图片描述

10. Highlight Matching Tag

有时,试图匹配标签的结束地方会令人沮丧,这时 Highlight Matching Tag 就派上用场了

图片描述

11. vscode-spotify

程序员经常边听歌边打代码,有时候写到一半,歌太难听,想切换,得切到音乐播放器,然后在回到 VsCdoe 界面,有点麻烦。

这就 是vscode-spotify 用武功之地,因为它可以在VSCode内直接使用音乐播放器。

有了这个扩展,各位就可以在状态栏中看到当前播放的歌曲,可以通过热键在歌曲之间切换,也可以点击按钮来控制音乐播放器等等。

clipboard.png

12. GraphQL for VSCode

GraphQL一直在发展,咱们经常可以在 JS 社区中看到它的身影。因此,最好开始考虑在 VSCode中安装 GraphQL for VSCode

clipboard.png

13. Indent-Rainbow

Indent-Rainbow 会给缩进添加一种颜色,让你更加直观的看到代码层次。

clipboard.png

14. Color Highlight

Color Highlight 可以在代码中突出显示颜色,如下所示:

clipboard.png

15. Color Picker

Color Picker 是一个 VSCode 扩展,它为咱们提供了一个图形用户界面,用来选择和生成颜色代码,如 CSS 颜色符号。

图片描述

16. REST Client

第一次看到 REST Client 并尝试它时,与现有的软件(如Postman)相比,它似乎不是一个非常有用的工具。

但是,对 REST Client 扩展的用法了解越多,就会意识到它对开发工具的影响有多大,尤其是在测试API 时。

只需要创建一个新文件写入下面这一行:

https://google.com

然后转到命令面板(CTRL + SHIFT + P),单击Rest Client: Send request,它会在一瞬间弹出一个包含请求响应详细信息的新选项卡,非常有用:

clipboard.png

甚至还可以传递参数,或将请求体数据请求到POST,而下面仅需几行代码:

POST https://test.someapi.com/v1/account/user/login/
Content-Type: application/json

{ "email": "someemail@gmail.com", "password": 1 }

就会发送POST请求,参数为 { "email": "someemail@gmail.com", "password": 1 }

17. Settings Sync

vscode上有各种各样不同的插件,如果要在不同的电脑上使用 vscode 配置是件比较麻烦的事情,使用 Settings Syncvscode 配置备份起来,当需要在其他电脑使用 vscode 时只需下载备份的配置就可以了。

咱们只需要一个 GitHub 帐户,并且每次要保存配置(包括按键绑定,代码片段,扩展名等)时,只需按SHIFT + ALT + U将私有设置上传到 GitHub 帐户即可。 然后,下次登录或重新格式化为另一台计算机时,可以按SHIFT + ALT + D组合键立即下载配置。

18. Todo Tree

Todo Tree 将帮助咱们找到在整个应用程序代码中创建的所有待办事项。它将把它们放到一个单独的树中,还可以在面板的左侧同时查看它们

clipboard.png

19. Toggle Quotes

Toggle Quotes是一个有趣的实用工具扩展,它允许咱们在引号之间进行切换。当需要在使用字符串插入时切换到反引号时,它就派上用场了。

图片描述

20. Better Align

Better Align 对齐赋值符号和注释。要使用它,将光标放在想要对齐的代码中,使用CTRL + SHIFT + P打开命令面板(或者使用自定义快捷方式打开命令面板),然后调用Align 命令。

图片描述

21. Auto Close Tag

Auto Close Tag自动闭合html标签。

图片描述

22. Sort Lines

Sort lines 可以帮助咱们排序选中行。

图片描述

23. VSCode Google Translate

如果是参与多语言开发的项目,VSCode Google Translate 可以帮助咱们快速切换语言。

图片描述

24. Prettier

Prettier是VSCode的扩展,可自动格式化JavaScript/TypeScript 等等,使代码更漂亮。

clipboard.png

25. Material Icon Theme

与其他图标主题相比,我更喜欢 Material Icon Theme,因为文件类型更为明显,尤其是在使用深色主题。

clipboard.png

26. IntelliSense for CSS Class Names in HTML

IntelliSense for CSS Class Names in HTML,基于在工作空间中找到的定义性,并提供了CSS 类名补全。

图片描述

27. Path Intellisense

Path Intellisense 自动路劲补全。

图片描述


原文:https://dev.to/jsmanifest/26-...

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug


交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq449245884/xiaozhi

因为篇幅的限制,今天的分享只到这里。如果大家想了解更多的内容的话,可以去扫一扫每篇文章最下面的二维码,然后关注咱们的微信公众号,了解更多的资讯和有价值的内容。

clipboard.png

每次整理文章,一般都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励

查看原文

赞 77 收藏 52 评论 2

wangyuanqi 收藏了文章 · 2019-10-12

稍微整理了几个经常在H5移动端开发遇到的东西😢

不用说我也知道,此类文章太多太多了,常见的譬如:

viewport强制浏览器全屏IOS的Web APP模式可点击元素出现阴影(这个我觉得真没必要去掉,用户点击是需要反馈的,而这个背景色刚刚好提供了一种反馈)等等,太多啦,这些相信大家百度一下就可以查到很多资料😂

本篇文章主要是讲一些其他的或者优化手段。内容不多😂

1. 弹出数字键盘

<!-- 有"#" "*"符号输入 -->
<input type="tel">

<!-- 纯数字 -->
<input pattern="\d*">

安卓IOS的表现形式应该不一样,大家可以自己试试。当运用了正则pattern后,就不用关注input的类型了😂

2. 调用系统的某些功能

<!-- 拨号 -->
<a href="tel:10086">打电话给: 10086</a>

<!-- 发送短信 -->
<a href="sms:10086">发短信给: 10086</a>

<!-- 发送邮件 -->
<a href="mailto:839626987@qq.com">发邮件给:839626987@qq.com</a>

<!-- 选择照片或者拍摄照片 -->
<input type="file" accept="image/*">

<!-- 选择视频或者拍摄视频 -->
<input type="file" accept="video/*">

<!-- 多选 -->
<input type="file" multiple>

同上🤦‍♂️

3. 打开原生应用

<a href="weixin://">打开微信</a>
<a href="alipays://">打开支付宝</a>
<a href="alipays://platformapi/startapp?saId=10000007">打开支付宝的扫一扫功能</a>
<a href="alipays://platformapi/startapp?appId=60000002">打开支付宝的蚂蚁森林</a>

这种方式叫做URL Scheme,是一种协议,一般用来访问APP或者APP中的某个功能/页面(如唤醒APP后打开指定页面或者使用某些功能)😒

URL Scheme的基本格式如下:

     行为(应用的某个功能/页面)    
            |
scheme://[path][?query]
   |               |
应用标识       功能需要的参数

一般是由APP开发者自己定义,比如规定一些参数或者路径让其他开发者来访问,就像上面的例子🍤

注意事项:

  • 唤醒APP的条件是你的手机已经安装了该APP
  • 某些浏览器会禁用此协议,比如微信内部浏览器(除非开了白名单)

后面应该会专门写一篇文章用来探讨唤醒APP😒

4. 解决active伪类失效

<body ontouchstart></body>

body注册一个空事件即可😂

5. 忽略自动识别

<!-- 忽略浏览器自动识别数字为电话号码 -->
<meta name="format-detection" content="telephone=no">

<!-- 忽略浏览器自动识别邮箱账号 -->
<meta name="format-detection" content="email=no">

当页面上的内容包含了手机号/邮箱等,会自动转换成可点击的链接😁

比如你有如下代码:

<p>13192733603</P>

但是有些浏览器会识别为手机,并且可以点击拨号😒

6. 解决input失焦后页面没有回弹

一般出现在IOS设备中的微信内部浏览器,出现的条件为:

  • 页面高度过小
  • 聚焦时,页面需要往上移动的时候

所以一般input在页面上方或者顶部都不会出现无法回弹🤣

解决办法为,在聚焦时,获取当前滚动条高度,然后失焦时,赋值之前获取的高度:

<template>
  <input type="text" @focus="focus" @blur="blur">
</template>

<script>
  export default {
    data() {
      return {
        scrollTop: 0
      }
    },
    
    methods: {
      focus() {
        this.scrollTop = document.scrollingElement.scrollTop;
      },
      
      blur() {
        document.scrollingElement.scrollTo(0, this.scrollTop);
      }
    }
  }
</script>

6. 禁止长按

以上行为可以总结成这几个(每个手机以及浏览器的表现形式不一样):长按图片保存长按选择文字长按链接/手机号/邮箱时呼出菜单

想要禁止这些浏览器的默认行为,可以使用以下CSS

// 禁止长按图片保存
img {
  -webkit-touch-callout: none;
  pointer-events: none; // 像微信浏览器还是无法禁止,加上这行样式即可
}

// 禁止长按选择文字
div {
  -webkit-user-select: none;
}

// 禁止长按呼出菜单
div {
  -webkit-touch-callout: none;
}

7. 滑动不顺畅,粘手

一般出现在IOS设备中,自定义盒子使用了overflow: auto || scroll后出现的情况。

优化代码:

div {
  -webkit-overflow-scrolling: touch;
}

8. 屏幕旋转为横屏时,字体大小会变

具体出现的情况不明😒,有时候有有时候没有,欢迎指出。

优化代码:

* {
  -webkit-text-size-adjust: 100%;
}

9. 最简单的rem自适应

大家都知道,rem的值是根据根元素的字体大小相对计算的,但是我们每个设备的大小不一样,所以根元素的字体大小要动态设置😂

html {
 font-size: calc(100vw / 3.75);
}

body {
  font-size: .14rem;
}

效果如下:

像我一般的话,直接搞lib-flexiblepostcss-pxtorem就完事啦!

10. 滑动穿透

当你想在出现遮罩的时候,锁住用户的滚动行为,你可以这么做。

假设HTML结构如下:

<div class="mask">
  <div class="content">我是弹框</div>
</div>

CSS样式如下:

.mask {
  position: fixed;
  top: 0;
  left: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background-color: rgba($color: #333, $alpha: .6);

  .content {
    padding: 20px;
    background-color: #fff;
    width: 300px;
  }
}

效果如下:

可以看到,当在遮罩上滑动的时候,是会穿透到父节点的,最简单的办法就是阻住默认行为:

document.querySelector(".mask").addEventListener("touchmove", event => {
  event.preventDefault();
});

如果在vue中,你可以这么写:

<div class="mask" @touchumove.prevent></div>

如果.content也有滚动条,那么只要阻止遮罩本身就行:

document.querySelector(".mask").addEventListener("touchmove", event => {
  if (event.target.classList.contains("mask")) event.preventDefault();
});

或者:

<div class="mask" @touchumove.self.prevent></div>

这样,当出现遮罩的时候用户的滑动就会被锁住啦👌

最后

1px边框唤醒APP后面会单独出文章,因为涉及的内容比较多。

文章是有点水,请轻喷🤦‍♂️

如果你觉得这篇文章不错,请别忘记点个关注哦~😊

交流

微信公众号「前端宇宙情报局」,将不定时更新最新、实用的前端技巧/技术性文章,对了偶尔还会有互联网中的趣事趣闻🍻

关注公众号,回复"1"获取群聊二维码,一起学习、一起交流、一起摸鱼🌊

查看原文

wangyuanqi 收藏了文章 · 2019-09-30

🚆新手前端不要慌! 给你✊10根救命稻草🍃

🧨放假了特意给大家坐火车打发时间写了这篇工具收集类的小文, 让大家轻轻松松学知识, 😊"铁皮饭盒"祝大家十一快乐, 吃开心玩开心!

新手阶段的前端面临的最大问题就是: "😥这个咋做?", 这个阶段的前端自己实现交互功能基本是不可能的, 那怎么体现价值呢?

我给个建议: "不会写还不会用吗?", 其实js发展这么多年,常见的功能在github上都是可以找到的, 下面我就给大家拿出我收藏多年的"救命插件"!

copy-to-clipboard (剪贴板)

var clipboard = new ClipboardJS('.btn');

https://github.com/zenorocha/...

FileSaver (文件另存为)

https://github.com/eligrey/Fi...

var FileSaver = require('file-saver');
var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "hello world.txt");

excel-js (xlsx转换)

var workbook = XLSX.utils.table_to_book(document.getElementById('table'));

https://github.com/SheetJS/js...

jsPDF (生成pdf)

var doc = new jsPDF();
doc.text('Hello world!', 10, 10);
doc.save('a4.pdf');

https://github.com/MrRio/jsPDF

fileApi (上传, 支持进度/分段)

var uploadButton = document.getElementById('uploadButton');
FileAPI.event.on(uploadButton, 'change', function (evt){

}

https://github.com/mailru/Fil...

swipe(轮播)

<div class="swiper-container">
    <div class="swiper-wrapper">
        <div class="swiper-slide">第一页</div>
        <div class="swiper-slide">第二页</div>
        <div class="swiper-slide">第三页</div>
    </div>
</div>
new Swiper ('#my-swiper');

https://github.com/nolimits4w...

qrcodejs(二维码生成器)

var qrcode = new QRCode(document.getElementById("qrcode"), {
    text: "你好js!",
    width: 128,
    height: 128,
    colorDark : "#000000",
    colorLight : "#ffffff",
    correctLevel : QRCode.CorrectLevel.H
});

https://github.com/davidshimj...

autosize (textara高度自适应文字)

autosize(document.querySelectorAll('textarea'));

https://github.com/jackmoore/...

shake.js (监听手机震动)

var myShakeEvent = new Shake({
    threshold: 15,
    timeout: 1000
});

window.addEventListener('shake', ()=>{
    // 摇晃触发
}, false);

https://github.com/alexgibson...
提示: 适合用来做"摇一摇"的活动页面活游戏

dayjs (时间格式转换)

dayjs('2018').fromNow(); // 1年前

https://github.com/iamkun/dayjs

progressbar (进度条)

var bar = new ProgressBar.Circle(container, {
  strokeWidth: 6,
  easing: 'easeInOut',
  duration: 1400,
  color: '#FFEA82',
  trailColor: '#eee',
  trailWidth: 1,
  svgStyle: null
});

bar.animate(1.0);  

https://github.com/kimmobrunf...

最后

😃 感谢大家阅读, 最后推下自己的小工具, 作为工作了8,9年的老菜鸟插件用多了自己也造了几个小工具, 大家看看有没有需要的.

any-rule

🔥 支持vscode版本, 查证则再也不用"百度"了. 😃

vscode中搜索"any-rule"进行安装.

https://github.com/any86/any-...

any-touch

👋一个手势库, 支持tap(点击) / press(按) / pan(拖拽) / swipe(划) / pinch(捏合) / rotate(旋转) 6大类手势, 同时支持鼠标和触屏.

在线演示

import AnyTouch from "any-touch";
const el = doucument.getElementById("box");
const at = new AnyTouch(el);

at.on("pan", ev => {
  // 拖拽触发.
});

tap(点击)

用来解决移动端"click的300ms延迟问题", 同时通过设置支持"双击"事件.

press(按)

用来触发自定义菜单.

pan(拖拽)

这应该是组件库中最常用的手势, carousel(轮播) / drawer(抽屉) / scroll(滑动) / tabs(标签页)等都需要"拖拽识别"

swipe(滑)

carousel/tabs的切换下一幅, scroll的快速滑动等.

pinch(捏合) / rotate(旋转)

pinch用来缩放商品图片, rotate一般用在高级定制化功能呢, 比如对图片(商品)刻字后旋转文字.

🚀 更多说明: https://github.com/any86/any-touch

vue-create-root

🍭 不到1kb的小工具, 把vue组件变成this.$xxx这样的命令.

// main.js
Vue.use(createRoot);

// xxx.vue
import UCom from '../UCom.vue';
{
    mounted(){
        // 默认组件被插入到<body>尾部
        this.$createRoot(UCom, {props: {value:'hello vue!'}});
        // 或者简写为:
        this.$createRoot(UCom, {value:'hello vue!'});
    }
}

🚀 更多说明: https://github.com/any86/vue-create-root

微信群

感谢大家的阅读, 如有疑问可以加我微信, 我拉你进入微信群(由于腾讯对微信群的100人限制, 超过100人后必须由我拉进去)

查看原文

wangyuanqi 回答了问题 · 2019-09-27

关于Select问题 antd

把Select组件的mode属性改成combobox就没有问题了,亲测有效。
clipboard.png

关注 7 回答 5

wangyuanqi 收藏了文章 · 2019-09-26

超全面的前端监控sdk

项目地址

https://github.com/bombayjs/b...

喜欢请star!!!

喜欢请star!!!

喜欢请star!!!

您的支持是我不断前进的动力(^▽^)

功能

  • 上报pv uv
  • 捕获error
  • 上报性能performance
  • 上报用户轨迹
  • 支持单页面
  • hack ajax fetch
  • 上报加载的资源
  • hack console
  • hack onpopstate
  • 暴露全局变量__bb
  • 埋点 sum avg msg

build

npm run build

example

参考example目录

用法

token在bombayjs-admin后台申请

方法一

  <script data-original='../dist/bombay.js'></script>
  <script>
    new Bombay({
      token: 'xxx',
      reportUrl: 'http://127.0.0.1:7002/api/v1/report/web'
    })
  </script>

方法二

npm i bombayjs -S
new Bombay({
  token: 'xxxx',
  reportUrl: 'http://127.0.0.1:7002/api/v1/report/web'
})

配置

{
  // 上报地址
  reportUrl: 'http://localhost:10000',
  // 提交参数
  token: '',
  // app版本
  appVersion: '1.0.0',
  // 环境
  environment: 'production',
  // 脚本延迟上报时间
  outtime: 300,
  // 开启单页面?
  enableSPA: true,
  // 是否自动上报pv
  autoSendPv: true,
  // 是否上报页面性能数据
  isPage: true,
  // 是否上报ajax性能数据
  isAjax: true,
  // 是否上报页面资源数据
  isResource: true,
  // 是否上报错误信息
  isError: true,
  // 是否录屏
  isRecord: true,
  // 是否上报行为
  isBehavior: true,
  ignore: {
    ignoreErrors: [],
    ignoreUrls: [],
    ignoreApis: ['/api/v1/report/web', 'livereload.js?snipver=1', '/sockjs-node/info'],
  },
  behavior: {
    console: true,
    click: true,
  },
  // 最长上报数据长度
  maxLength: 1000,
}
查看原文