Skip to content

JavaScript

什么是闭包?

在某个内层的函数,引用了函数外的变量或函数,当这个内层函数还被引用时,引用的函数外的变量或函数不会被销毁,从而形成闭包

闭包的作用可以实现私有属性、方法,模块化等功能,减低污染作用域;但是要注意内存泄露的问题,因为形成闭包的函数,没被毁坏时,里面引用的变量也不会被销毁

常见闭包场景

javascript
for (var i = 1; i <= 5; i++) {
   setTimeout(function test() {
        console.log(i) //>> 6 6 6 6 6
    }, i * 1000);
}

循环执行异步代码。比如循环执行 setTimeout,打印 i 的场景。最终所有的输出都是循环次数 + 1,因为 setTimeout 是异步执行,等 for 循环执行完后,索引值 i 等于循环次数 + 1,然后才继续执行 setTimeout 回调函数,这样因为作用域的关系,setTimeout 里面打印的 i 值,会按照作用域链一级级往上寻找,最终在全局作用域找到并打印

什么是作用域?

作用域是一套规则,规定在何处如何查找变量

JavaScript 使用的是词法作用域,词法作用域是定义在词法阶段,就是代码将变量和块作用域写在哪里决定(词法作用域由函数被声明时所在的位置所决定)

当要查询一个变量的时候,会先在当前的作用域查找,找不到就会一级级往上找,直到全局作用域为止;如果该变量没定义,就会抛出错误;找到到没定义,就返回 undefined

  1. 什么是原型?什么是原型链?

    1. 查看 prototype.md
  2. new 操作符的作用?

    1. 查看 prototype.md
  3. 基本类型

    1. Boolean
    2. Undefined
    3. Null
    4. Number
    5. String
    6. Symbol
  4. 引用类型

    1. Object
    2. Array
    3. Function
    4. RegExp
    5. Date
  5. 什么是安全的整数?

    在(-2^53, 2^53)范围内,双精度数表示和整数是一对一的

js 小数精度问题

资料

掘金文章

0.1 + 0.2 = 0.30000000000000004

js 的遵循 IEEE 754 标准,使用 64 位固定长度表示,也就是双精度浮点数,规则如下:

  • 符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
  • 指数位E:中间的 11 位存储指数(exponent),用来表示次方数
  • 尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零

当 0.1 + 0.2 时,两个数字会先转成二进制再相加;而浮点数的二进制表示是无穷的,根据 IEEE 754 的标准,小数部分最多支持 53 位二进制,当两个二进制相加后,再转成十进制,就会产生非 0.3 的结果

解决方式:

  1. 数据展示:

    javascript
    function strip(num, precision = 12) {
      return +parseFloat(num.toPrecision(precision));
    }

    为什么要用精度 12 ?一般情况精度确定到 12,能处理大部分情况,可以视情况调整精度

  2. 计算:

    把小数部分转成整数后再运算,最后再除以那个倍数

内存泄露

查看“基础总结”

说说 this 的理解

this 就是当前函数运行时所在的环境

说说对作用域的理解

作用域主要分为全局作用域、函数内的作用域、块作用域等

全局作用域是根据宿主环境,比如浏览器就是 window,nodejs 就是 global

而函数内的作用域和块作用域,根据执行时的上下文,去查找某个属性时,会在当前的作用域开始查找,如果找不到,就会从它的父级的作用域开始找,直到全局作用域,如果都没有,就会返回 undefined

JS EventLoop 原理

JS 是单线程,这里引用阮一峰老师文章的内容:

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

JS 里面有分同步和异步,同步任务直接在主线程执行,异步任务会加入到一个任务队列中,等主线程的任务完成了后,就会执行异步任务。JS 会不停循环这个过程。

  1. 将同步任务放到调用栈,将异步任务加到任务队列中,其中的异步任务会分为 task 和 mircotask,像 AJAX、setTimeout 这些是 task,而 Promise 则是 mircotask
  2. 首先执行同步任务
  3. 然后执行 mircotask queue 中的任务(promise)
  4. 然后执行其他 task(DOM 回调、AJAX、setTimout)
  5. 然后一直循环上面的顺序

css

rem 原理

rem 单位兼容 IE9 以上,基本上移动端的浏览器都支持。它是根据 HTML 的字体大小而变化。1rem 就等于多少字体大小。而移动端的 rem 布局原理,就是根据浏览器的宽度、一个基准值而改变 HTML 的字体大小,再添加阿里的 flexible 方案,从而形成动态缩放的效果。

设置一个基准值,flexible 这个库就会根据当前浏览器的宽度,重新计算 HTML 的字体大小。比如设置基准值是 75,设计稿的宽度是 750px 的

在 750 宽的浏览器,HTML 的字体大小就是 75px,1rem 就等于 75px 。而我写某个样式时,直接就可以根据设计稿的大小 / 基准值,就是所要的 rem 值。再配上自动转换工具,可以直接写上 px。

  1. link 可以多个同时加载;@import 需要等页面加载完成后再加载
  2. link 没有兼容问题;@import 低版本 ie 不支持(ie5)
  3. link 可以通过 js 动态插入,@import 不行

border 三角形原理

每个位置的边框,其实是有三角形形成的

原文

什么是 BFC

处于 BFC 的子级,与外部的元素互不干扰,是一个完全独立的区域,例如 margin,如果不是 BFC 状态,margin 的效果会影响到外部元素;计算 BFC 的高度时,浮动子元素也会算上

有什么方法可以触发 BFC

  1. overflow 不是 visible
  2. position 不是 static 和 relative
  3. display 是 flex 和 inline-block
  4. float 不是 none

margin 边界叠加问题

当两个 block 元素,在垂直方向有同一个方向的 margin 重叠,会两者取最大的进行渲染;比如上方元素的 margin-bottm 是 30px,下方的元素的 margin-top 是 100px,那最终渲染的结果是两个元素的距离是 100px

解决方式:

  1. 改用 padding
  2. 单方面加 margin

如果某个元素是嵌套元素,可以使用 overflow: hidden 处理,将该元素变成 bfc 状态,不影响外部元素

重绘重排问题

重绘:重新绘制该元素的一些属性,例如颜色、背景、transfrom 等不会影响布局的属性

重排:重排是在重绘的基础上,从新排计算排列布局属性数值等,而且会一层层往上级计算,直到没有影响为止

优化方式:

  1. 实现动画的时候,尽量使用 transfrom、opacity,而不是 top right bottom left,display 这些属性去做位移、显示隐藏等操作;因为 transform 和 opacity 不会影响到外部的元素
  2. 尽量少一个个样式改,可以的情况下,去改变标签的 className
  3. 要插入节点的时候,可以使用 DocumentFragment 一次性出入所有的节点

NodeJs

为什么要用 NodeJs

NodeJs 是一个单线程、异步、非阻塞io 的语言,这些特性适合高并发访问的项目;而且 NodeJs 是基于 JavaScript,这更利于前端可以快速学习、过渡,比如实现中间层、同构等

怎么理解异步

我们先说下同步,比如有三行代码,第二行代码是要发起一个同步 http 请求的,需要大量的时间,所以第三行代码要等第二行代码执行完才能执行

而如果我们使用异步,这时候第二行代码发起一个异步的 http 请求,执行后,就会先先不理,接着执行第三行代码;第二行代码等 http 请求响应后再继续

框架

vue

为什么使用 react

react 上手也是很快,单项数据流,数据流自上而下,显示清晰,debug 方便。而且 react 生态很火热,在 github 上有各种各样的功能库,足够满足很多项目需要的功能

vuex 和 redux 的区别

  1. vuex 和 redux 都是基于一个状态树进行维护数据
  2. redux 的 store 是不可变的,只能通过 reducer 得到的 state 进行替换;而 vuex 的 store 通过 mutation 对某个变量进行修改,而不是整个替换
  3. vuex 的异步处理,有自带的 action;而 redux 可以选择不同的第三方库配合 middleware 进行处理

单一状态树的好处:能够直接地定位任一特定的状态片段,也能很容易地获取整块数据的快照

vuex

  1. 要修改 store 里面的数据,要显示调用 mutation 才可以,这是为了可以追踪每一次状态的变化
  2. action 类似于 mutation,它可以调用 mutation,但不能直接修改状态值,在 action 可以包含任意异步操作
  3. 为什么 mutation 必须同步函数?这是作者的一种约定,mutation 是同步函数,才能让开发工具捕捉到每次的状态变更。因为 mutation 触发后, devtool 不知道回调函数什么时候才被调用,所以无法追踪数据变化。

vuex 概念:vuex 是专门配合 vue 的一个状态管理库,由一个状态树维护数据,它有几个核心的概念: state 就是一个状态树,保存着全部的状态;getter 用于封装业务逻辑从而获取数据的方法;mutation 是唯一能修改 state 的同步方法;action 用于处理异步操作的方法,不能直接修改数据,但能调用 mutation 去修改;module 划分不同的模块。其实整个状态管理库并不复杂,用 state 保存全部数据,只能通过 mutation 去修改 state 的值,当 state 修改后,就会通知 view 层进行更新操作。

redux

  1. dispatch ,触发 action 传到 store,而 action 是 store 数据的唯一来源。官方的约定是 action 是一个 Object 的数据结构, type 字段是声明要执行的动作类型,其余是要传入的数据字段。action 只是描述有什么事情发生

  2. reducer 是响应 dispatch 传入的 action,进行处理后再发送到 store 的纯函数。reducer 是描述如何更新 state 值。reducer 接收两个参数:state 和 action。官方约定 reducer 是纯函数,不能有

    1. 修改传入参数
    2. 执行有副作用的操作(请求 api 和路由跳转)
    3. 调用非纯函数(Date.now()) ??

    reducer 里面不要修改 state 值,而是通过生成新的属性,例如用 Object.assign

redux 概念:单向数据流,维护唯一的、不可变的 store,state 的值是不可直接修改的,要通过触发 action 后,state 再去响应对应的 reducer,最后替换 state 值 。

什么是不可变数据:

不可变数据就是你不能直接修改它的值,而是通过复制它的值,并且产生一个新对象的方式来得到一个新的数据,包含里要修改的部分

不可变数据的优势:

带来更高的性能,更简单的调试,不会改变的数据比可以随意更改的数据更容易调试。

redux 的异步处理

像请求 API ,大概分为三个状态,start,success,fail

最简单的处理

首先在 component 里面,开始请求 api,随即修改请求状态为:start;当收到响应后,如果是成功,修改状态为:success,否则修改状态为:fail

存在问题:

  1. 每次写请求的代码,都需要重复这些代码
  2. 没有做竟态处理,当某个请求还没收到响应时,其他请求收到了响应,导致数据渲染有误
异步 action

工具

webpack

构建流程

  1. 解析webpack配置参数。
  2. 注册所有配置的插件,好让插件监听webpack构建生命周期的事件节点,以做出对应的反应。
  3. 从配置的entry入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去。
  4. 在解析文件递归的过程中根据文件类型和loader配置找出合适的loader用来对文件进行转换。
  5. 而 plugin 可以扩展功能,比如打包优化、资源管理等
  6. 递归完后得到每个文件的最终结果,根据entry配置生成代码块chunk
  7. 根据 output 的配置,输出所有chunk到文件系统。

工具链

在网上看文章的时候,看到这个名词,具体意思就是在整个开发流程中所使用到的工具。这里我大概列下:

前端

  1. 原型、设计稿
    1. Axure,蓝湖,sketch,photoshop
  2. 代码管理
    1. git,gitlab
  3. 创建项目模板
    1. 自研一个脚手架,通过执行 npx ,根据要求输入内容和选择,生成一个项目模板
  4. 开发
    1. 处理资源、包管理工具
      1. webpack
    2. 框架
      1. Vue,React
  5. 项目发布
    1. Jenkins

后端

网络

GET 和 POST 请求的区别

  1. 通常 GET 请求传入的参数是放在 URL 上;而 POST 是放在 body; URL 有长度限制;大概 2000 个字符左右;而 body 无限制;
  2. GET 和 POST 要传参,其实放 URL 或 BODY 都是可以的,没什么区别;上面的限制,只是浏览器的限制;两者更多的是语义上和规范上的区别
  3. 两种请求的安全性其实都一样,因为这个都是 HTTP 协议的,而 HTTP 协议是明文的,所以一样不安全;而参数存放的位置,body 相对比 url 安全而已
  4. GET 请求会产生一个 TCP 数据包;POST 会产生两个
    1. GET 请求会把 http header 和 data 一起发送
    2. POST 请求会先发送 header;等服务器响应 100 continue ,再发送 data

跨域

同源策略是浏览器的一个限制,同源是值“协议+域名+端口”一致,否则非同源。

解决跨域问题

  1. JSONP
  2. CORS,后端在 HTTP 请求的时候添加跨域头字段
  3. postMessage,解决多窗口之间,嵌套的 iframe,页面与新打开的窗口的消息传递
  4. 后端代理,后端服务通过请求对应的地址,将响应返回给前端,由后端实现
  5. nginx 反向代理,类似后端代理,使用 nginx 转发请求

HTTPS 能做什么?有什么用

HTTPS 就是在 HTTP 基础上多了一层 SSL,SSL 协议拥有加密、CA 的功能,可以建立一个信息安全通道,确认内容的真实性

使用 HTTPS 是代替 HTTP,HTTP 有一些不足的地方

  1. 通信使用明文,内容会被窃取

    HTTP 是基于 TCP/IP 的,通信内容有可能被窥视(为啥 TCP 传输内容会被窥视?)

  2. 不验证通信方的身份,通信会被遭遇伪装

    1. HTTP 协议的请求和响应,不会对通信方进行确认(通信的对方,是否你要指定的主机)
    2. 任何人都可以对某个主机发起请求
  3. 无法证明报文的完整性,内容会被篡改

    1. 无法证明信息的完整性,内容可能会在传输中,被攻击者篡改

而 HTTPS 解决上面的问题

  1. 建立安全的通信通道,内容无法被窃取、篡改,保证完整性
  2. 服务器和客户端某一方拥有 CA 的话,即可判断通信方的真实存在

HTTP2.0

HTTP2.0 能显著提高性能

  1. 多路复用(Multiplexing),能允许同时通过单一的 HTTP/2 连接发起多重的请求- 响应消息,在同一个连接上进行交互数据;而HTTP1.x 版本,会对同一域名有限制的发送请求。所以减少了服务端的链路压力,连接吞吐量更大
  2. 首部压缩,传说的体积更少
  3. 服务端推送

基础

什么是 base64

  1. 阮一峰
  2. wiki
  3. mdn

base64 是由52个英文字符 + 10个数字字符 + 两个特殊字符(+, /)组成的,最后可能会有 = 进行补位;base64 用来表示二进制数据

base64 编码后的数据会比原本的长,为原来的 4 / 3 ;

原理:

  1. 将一段文字转为 ASCII 编码值,再转为二进制,并在前面加 0 ,补到 8 位数
  2. 把所有值拼合起来
  3. 然后每6位就转为十进制的值,得到索引值,这索引值是 base64 表的索引表
  4. 因为每 6 位进行裁剪,所以最大值也就 63 而已
  5. 然后根据得到的索引值,最后得到 base64 的字符串
  6. 最后会有可能出现余数,用 = 进行补位

而浏览器的 base64,像图片这些多媒体,还会在前面加上前缀:

编程原则

单一职责原则 - Single Responsibility Principle

一个类,最好只负责一件事,只有一个引起它变化的原因

我的理解:对封装的函数、类、组件等,根据功能或需求,按一定的粒度进行切分,降低该模块的复杂度,方便调试、测试和迭代

在我们平时开发时,比较常见的像原子类、一些工具函数(防抖、去重函数)等,都是按照单一职责原则,它们只有单一功能。对于这些工具函数,我个人觉得可以按照最小粒度进行切分

而组件这些相对比较大的模块,就要看具体需求进行切分以 UI 组件为例,Button 组件功能比较单一,而且很多其他组件可能会用到,所以单独切分;而像下拉组件的选择项、表格组件的列,这些是否要再单独封装出来?我觉得还是要看需求,想 Element-ui,Ant-design 这些 UI 框架,它们面向的是大众,会有不同的需求,所以他们会做到切分的越细越好,让你更方便个性化。而我们在做业务需求时,需求要的功能不是很复杂,但很多地方要用到,这时候我更倾向粒度更大得封装,让使用者可以用更少的代码去使用组件

总结来说,单一职责原则要根据对应的需求,从而确定切分的粒度

接口隔离原则 - Interface Segregation Principle

开放封闭原则

不应该强行要求客户端依赖于它们不用的接口

类之间的依赖应该建立在最小的接口上面

在不修改源码的基础上,提供可扩展模块的方式

比如封装了一个请求模块,里面要根据业务码判断请求是否成功、正常。但这个模块是通用型的,每个前端项目都要求用这个请求模块进行请求,这时候就可能会遇到后端返回的成功状态业务码会不一样。如果符合开放封闭原则的话,应该会有一个配置或方法,动态修改该请求模块成功状态码,而不需要修改该模块的源码

里氏替换原则

分两种情况,如果父类是可实例化的,继承该父类的子类,不能重新定义父类的方法;如果要覆盖,则父类不能实例化,只能是抽象类

依赖倒置原则

平时项目开发遇到什么问题?怎么解决?

使用 keep-alive 组件,解决后退页面时,保存之前的状态

项目需求,在 A 页面点击某个按钮跳去 B 页面时,再回到 A 页面时,要保存之前的状态,而其他情况进入该页面要刷新的状态。使用 keep-alive 组件和 keep-alive 的 include 属性,判断哪些页面组件需要缓存。再加上 vuex 去动态的控制哪些页面在什么时候需要缓存,在 activateddeactivated 的钩子函数中,去判断是否要缓存页面。还有根据 route 的 meta 属性也可以。

原本的思路是,全部组件都用 keep-alive 的组件,然后需要销毁组件的时候,在 deactivated 的钩子函数中,使用 destory 方法。但页面组件使用了该方法后,后面的路由会混乱,比如后退时,去到的页面组件并不是实际要去到的。

在做管理后台的项目时,根据业务需求,对 element-ui 的组件做二次封装

因为管理后台用的是 第三方的 UI 组件,比如 element-ui,而需求使用定制不一样的样式,所以把用到的 element-ui 组件做二次封装,定制样式。比如 Dialog 样式,顶部的样式、底部的按钮组。还有 Table 组件,添加默认样式和默认配置,普通的栏直接用传入配置和数据即可,无需再写 html,特殊的栏可以用 slot-scope。还有其他的 Select 组件、Checkbox 组件、分页组件等等。

使用 easy-mock 模拟数据,可以不用等后端有接口才开始写逻辑,加快开发速度

我之前开发的项目很多都是前后端分离的,前端切完图之后,就要等后端有接口和文档,才能继续开发。然后我们就产品原型出了之后,后端就会尽快出接口文档,定好返回值的 key 值和类型。我们后端用的是 swagger 生成文档的,swagger 可以导出一份 json 文件,再放到 easy-mock (一个网站)上,就可以直接拿来用,会返回假的数据。

使用脚手架,为模板项目快速开发;开始一些小工具,提升开发效率

有很多个项目的时候,如何很好地维护前端组件

之前公司在初期陆续有新的管理系统要进行开发,到现在,大概有 10 余个管理系统。这些管理系统外层框架的样式、交互,登录页,错误页,都是基本一致的,但这部分的功能,之前都是先通过复制粘贴的形式去开发,这样后期如果有迭代更新会很麻烦。

所以我们这边开始开发一个前端的业务组件库,将公共的组件抽离出去,通过 npm 包进行版本管理和使用。每次有问题修复或者功能迭代,只需更新该组件库,然后各个系统进行相关的依赖升级即可

开发前端工具库、组件库,调试问题

我会负责开发一些前端的公共的工具库和组件库,使用 npm 包进行管理,这些库之间有可能是相互调用的,在开发测试的时候,比如 A 工具库会使用 B 工具库,当 B 工具库更新后,要进行打包、发布版本到 npm 上,然后 A 工具库要进行更新依赖的操作。一旦工具库多起来后,这样地操作会非常麻烦

那时候我就在找一些工具,去管理这些库,用了一个叫 lernajs 的工具,它会对这些库在本地进行一个软链接,当 B 工具库修改代码后,无需发版,A 工具库那边会自动引用本地的代码,大大减少操作

其他

自我介绍

大家好,我从事前端工作两年半有多,主要使用 Vue 进行开发,负责的项目很多都是移动端 H5 和管理后台系统。也有负责前端组的公共组件开发,给到各个不同的管理后台系统进行使用。其中有一些小型项目的后端,也有用过 NodeJs 开发的经验

问公司的问题

  1. 贵公司的主要核心、盈利业务是什么?
  2. 开发团队、部门有多少人,各是什么类型
  3. 前端部门主要用的技术栈是什么?
  4. 一个项目的开发,前端是怎么分配任务?怎么协调合作的?
  5. 公司福利等