Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面试题总结 #17

Open
XJIANBIN opened this issue May 22, 2019 · 0 comments
Open

面试题总结 #17

XJIANBIN opened this issue May 22, 2019 · 0 comments

Comments

@XJIANBIN
Copy link
Owner

XJIANBIN commented May 22, 2019

JS 七种内置数据类型

String Number Boolean Null undefined symbol (基本数据类型) Object 复杂数据类型

判断数据类型的方法

// 1, typeof 可以判断出基本数据类型,对象的只能打印出object,包括null
typeof null --> object
// 2, instanceof ---> a instanceof Array ---> true or false
new Number(1) instanceof Number 才是true
// 3, constructor ---> a constructor Array  ----> true or false
function User(){}
var u = new User;
console.log(u.constructor===User )
// 4, Object.prototype.toString.call(a) === ‘[object String]’ -------> true;

Object.prototype.toString.call() 、 instanceof 以及 Array.isArray() 之间的区别和优劣
利用对象上面的 toString 方法,返回类型的字符串格式,基本对于所有的数据类型都能使用

instancof 的内部机制是通过判断对象原型链中是不是能找到类型的 prototype 。但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。

Array.isArray() es5 当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes
instanceof 不能检测来自 iframe 的数组。就是官方文档上,window.frames[xx].Array 构造出来的数组。

性能应该 instanaceof 比较好

线程和进程各自有什么区别和优劣呢?

1 进程是资源分配的最小单位,线程是程序执行的最小单位
2 进程有自己的独立地址空间,而线程共享进程中的数据,使用相同的地址空间。所以创建一个线程的开销比进程小很多。
3 线程之间的通信方便,同一进程下的线程共享全局变量静态变量等数据。进程之间的通信需要以通信的方式(IPC)进行。
4 多进程之间不会相互影响。多线程只要一个线程 down 掉,整个进程也跟着 down 掉。

五层因特网协议栈

应用层 --> 传输层 --> 网络层 --> 数据链路层 --> 物理层
发送 http 请求 --> 三次握手简历 tcp/ip 连接 --> ip 寻址 --> 封装成帧 --> 物理介质传输

传输层: TCP:提供面向连接的,可靠的数据传输服务,UDP,提供无连接服务,不保证数据的可靠性

常见请求头

Accept
Accept-Encoding
Accept-Language
Cache-Control: 请求所应该遵循的缓存机制
If-None-Match
expires
max-age
cookie
reffer
user-agent

host

响应头部

access-control-allow-headers
access-control-allow-methods
access-control-allow-origin
ETag
Cache-Control
content-type
Date
set-cookie
Status
server 返回服务器相关信息
vary: 根据 http 标准,vary 的值表明需要哪些 request header 去决定一个 response 是否是 fresh 的,缓存服务器是否可以不用重新确认就使用一个 reponse。
还有一些特殊的命中 CDN 的头部

常见请求方法 (http 1.1)

get
post
delete
put
option
head
trace
connect

HTTPS

HTTP 加上加密处理和认证以及完整性保护后
HTTPS 是基于 SSL 安全连接的 HTTP 协议

SSL 和 TLS 是应用在不同层的协议

SSL 协议实现的安全机制包含:

l 传输数据的机密性:利用对称密钥算法对传输的数据进行加密。

l 身份验证机制:基于证书利用数字签名方法对 server 和 client 进行身份验证,当中 client 的身份验证是可选的。

l 消息完整性验证:消息传输过程中使用 MAC 算法来检验消息的完整性。

HTTPS 采用混合加密机制,因为共享加密不安全,而公开秘钥加密处理速度比较慢,所以采用混合加密的机制,发挥两种加密的优势。
1 使用公开密钥加密方式安全地交互在稍后的共享密钥要使用的密钥 (非对称)
2 确保交换的密钥是安全的前提下,使用共享密钥加密方式进行通信。(对称)

SSL 是 Netscape 开发的专门用户保护 Web 通讯的,目前版本为 3.0。最新版本的 TLS 1.0 是 IETF(工程任务组)制定的一种新的协议,
它建立在 SSL 3.0 协议规范之上,是 SSL 3.0 的后续版本。两者差别极小,可以理解为 SSL 3.1,它是写入了 RFC 的。

为什么需要使用证书认证?
因为公开秘钥加密方式还是存在公开证书被攻击的情况。所以使用数字证书认证机构发布的公开秘钥证书来解决此问题。

抓包工具为什么可以进行中间人攻击,因为一般我们需要安装它的证书。

HTTPs 的握手过程包含四次通信

1 发出请求告诉服务端要进行加密通信
包含自己支持的协议版本,客户端生成的随机数,支持的加密方法和压缩方法
2 服务端发出回应
确定加密通信协议的版本比如TLS1.2,和服务端生成的一个随机数,还有使用的加密方法和服务器证书
3 客户端收到后先验证证书,如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。如果没有问题,就取出服务器的公钥,然后发送回应
包含一个用公钥加密的随机数和编码改变通知和握手结束通知(给服务端校验的前面所有内容的hash值)
这个阶段的随机数称"pre-master key"。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把"会话密钥"。
4 服务端收到随机数,计算密钥,然后发出通知
编码改变通知和握手结束通知(也是前面所有内容的hash)

缓存

1.强缓存(200 from cache)
(http1.1)Cache-Control/Max-Age
(http1.0)Pragma/Expires expiress 有个致命缺点,他是以服务端的时间为准,如果客户端的时间和服务端的时间不一致,那就可能导致资源失效。
max-age 优先于 expires

2.协商缓存(304)
(http1.1)If-None-Match/E-tag 浏览器的头部是 If-None-Match,而服务端的是 E-tag,如果 If-None-Match 和 E-tag 匹配,则代表内容未变,通知浏览器使用本地缓存,
E-tag:相当于一种 hash ,如果文件变化就相应改变
(http1.0)If-Modified-Since/Last-Modified
E-tag 和 Last-Modified,服务端会优先检查 E-tag

浏览器渲染过程

1 拿到 html,然后构建 dom 树
2 解析 css, 构建 css 规则树
3 合并 dom 树和 cssom 树,生成 render tree
4 layout render tree 并且绘制
5 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成(composite),显示在屏幕上。

DOM 树,cssOM 树 过程
Bytes → characters → tokens → nodes → DOM
将字节转码成单个字符 -> 然后将字符转换成对应的对象

重绘回流的优化

1 把更改样式通过类名的形式更改,从而达到合并更改样式的操作。
2 避免多次获取位置信息的调用,例如 offset 这些。或者缓存到变量
3 使用一些 css 属性(定位,transform),使元素脱离文档流(如果你为太多元素使用 css3 硬件加速,会导致内存占用较大,会有性能问题。
在 GPU 渲染字体会导致抗锯齿无效。这是因为 GPU 和 CPU 的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊)

简单图层和复合图层

1 默认情况下元素都是在简单图层下绘制。
2 通过某些 css 属性可以把元素提升到复合图层
3 复合图层之间的绘制互不干扰,由 GPU 直接控制
4 简单图层中,即使通过绝对定位方式可以不造成回流,但是仍然会影响到绘制,所以动画建议使用硬件加速(即提升到复合层)

将Layer Tree上的某些节点进一步提升与合并,生成生成Graphics Layer,使用硬件加速

video、canvas元素,flash插件
拥有perspective、CSS3D变形的元素
backface-visibility 为 hidden 隐藏旋转元素的背面
对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition
设置了will-change属性的元素
层之间的层叠遮盖

DOMContentLoaded 事件触发时,仅当 DOM 加载完成,不包括样式表,图片(譬如如果有 async 加载的脚本就不一定完成)
load 事件触发时,页面上所有的 DOM,样式表,脚本,图片都已经加载完成了

BFC 和 IFC

BFC ,块级格式化上下文,定义了元素内部的渲染规则。

内部 box 在垂直方向,一个接一个的放置
box 的垂直方向由 margin 决定,属于同一个 BFC 的两个 box 间的 margin 会重叠
BFC 区域不会与 float box 重叠(可用于排版)
BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此
计算 BFC 的高度时,浮动元素也参与计算(不会浮动坍塌)

触发方式
根元素,
float 不为 none,
position 为 absolute 或 fixed,
display 为 inline-block, flex, inline-flex,table,table-cell,table-caption,
overflow 不为 visible

IFC,行内格式化上下文
在行内格式化上下文中,框(boxes)一个接一个地水平排列,起点是包含块的顶部。
水平方向上的 margin,border 和 padding 在框之间得到保留。框在垂直方向上可以以不同的方式对齐:它们的顶部或底部对齐,
或根据其中文字的基线对齐。包含那些框的长方形区域,会形成一行,叫做行框。

JS 引擎

引擎对 JS 的处理过程可以简述如下:
读取代码,进行词法分析(Lexical analysis),然后将代码分解成词元(token)
对词元进行语法分析(parsing),整理成语法树(syntax tree)
使用翻译器(translator),将代码转为字节码(bytecode)
使用字节码解释器(bytecode interpreter),将字节码转为机器码

正式执行 JS 前,还会有一个预处理阶段(你不知道的 javascript 也把这个过程定义为编译过程)

(譬如变量提升,分号补全等)

执行流程包含概念

执行上下文,执行堆栈概念

执行上下文: 每当控制器执行到可执行代码的时候,就会进入一个执行上下文,执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。
javascript 用栈的方式来处理它们,
这个我们称之为函数调用栈,栈底永远是全局上下文。

JS 有执行上下文,当浏览器首次载入脚本,将创建全局执行上下文,并压入执行栈栈顶。
然后每次进入其他函数作用域的时候就创建对应的执行上下文,并压入执行栈的顶部,当执行上下文执行完毕,就从栈顶弹出,并且上下文控制权交给当前的的栈,然后这样依次执行。
每个执行上下文都有三个重要属性,分别是变量对象,作用域链,this.

VO (变量对象) AO(活动对象)
未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。
这样,如果再面试的时候被问到变量对象和活动对象有什么区别,就又可以自如的应答了,他们其实都是同一个对象,只是处于执行上下文的不同生命周期。不过只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。

作用域与执行上下文是完全不同的两个概念。我知道很多人会混淆他们,但是一定要仔细区分。

JavaScript 代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,
这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

作用域链 每进入一个新的执行环境,都会创建一条用于搜索变量和函数的作用域链,作用域链是函数被创建的作用域中对象的集合。

作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,
一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

this 机制

闭包

闭包是一种特殊的对象。

它由两部分组成。执行上下文(代号 A),以及在该执行上下文中创建的函数(代号 B)。

当 B 执行时,如果访问了 A 中变量对象中的值,那么闭包就会产生。

通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。比如在上面的例子中,我们在函数 B 的执行环境中访问到了函数 A 的 a 变量

在大多数理解中,包括许多著名的书籍,文章里都以函数 B 的名字代指这里生成的闭包。而在 chrome 中,则以执行上下文 A 的函数名代指闭包。

应用场景
1 模块化 (封装私有变量和延长局部变量的寿命) 比如说全局的状态管理模块,我们暴露出去的是状态管理的方法,然后模块内部维护者状态变量
2 柯里化
柯里化是指这样一个函数(假设叫做 createCurry),他接收函数 A 作为参数,运行后能够返回一个新的函数。并且这
个新的函数能够处理函数 A 的剩余参数。

柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

function _add(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}
_add(1)(2)(3);

提升

在 ES6 之前,JavaScript 没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域
1 变量提升 变量声明提升到它所在作用域的最开始
2 函数提升 只有函数声明才存在函数提升

函数先于变量被提升

函数声明的三种方式

 1 function a (){} // 函数声明
 2 var a = function (){} // 函数表达式,函数字面量
 3 var sum3=new Function('n1','n2','return n1+n2'); // 函数构造法

new

创建一个新对象,并把 this 引用改对象,并继承该函数的原型,
属性和方法加入到该 this 引用的对象中,
新创建的对象由 this 所引用,并且最后隐式的返回 this

vue3 为什么使用 Proxy 取代 Object.defineProperty

Object.defineProperty 从性能/体验的性价比考虑,弃用了这个特性
Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。

Proxy 的优点
可以劫持整个对象,并返回一个新对象
有 13 种劫持操作
get,get,has,deleteProperty,ownKeys,getownPropertyDescriptor,defindProperty,
preventExtensions,getprototypeOf,isExtensible,setPrototypeOf,apply,construct

浏览器缓存位置

Service Worker、Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
Memory Cache、内存缓存在缓存资源时并不关心返回资源的 HTTP 缓存头 Cache-Control 是什么值,同时资源的匹配也并非仅仅是对 URL 做匹配,还可能会对 Content-Type,CORS 等其他特征做校验。
Disk Cache 命中强缓存
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂

Virtual DOM 真的比操作原生 DOM 快吗?谈谈你的想法

这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。
没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。框架给你的保证是,
你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。

// 第一种
var arr0 = ["A1", "A2", "B1", "B2", "C1", "C2", "D1", "D2"],
  arr1 = ["A", "B", "C", "D"];
var handle = (a, b) => {
  var res = b.map((item, index) => {
    return [a[index * 2], a[index * 2 + 1], item];
  });
  // res 是map 方法内每一个return 返回的值
  return [].concat(...res);
};
// 第二种
// 利用charAt(0)来判断
// 第三种:

for 循环输出 i

1 let
2
 ((i) => {
    setTimeout(() => {
      console.log(i);
    }, 1000)
 })(i)
3
for (var i = 0; i< 10; i++){
  setTimeout(((i) => {
	console.log(i);
    })(i), 1000)
}
4 setTimeout(console.log(i), 1000,i)
5
for (var i = 0; i < 10; i++) {
  try {
    throw i;
  } catch ( i) {
    setTimeout(() => {
      console.log(i);
    }, 1000)
  }
}
7
利用 eval 或者 new Function 执行字符串,然后执行过程同方法四
for (var i = 0; i < 10; i++) {
  setTimeout(eval('console.log(i)'), 1000)
}
for (var i = 0; i < 10; i++) {
  setTimeout(new Function('console.log(i)')(), 1000)
}

尾递归

ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
函数调用自身,称为递归,如果尾调用自身,就称为尾递归。

一般函数调用过程
1 调用开始前,调用方会往栈上压相关的数据,参数,返回地址,局部变量等;
2 然后执行函数;
3 清理栈上相关的数据,返回。

原理:每一个函数在调用下一个函数之前,都能做到先把当前自己占用的栈给先释放了,尾递归的调用链上可以做到只有一个函数在使用栈,因此可以无限地调用!

MVC 模式和 MVVM 模式

MVC 将应用划分为 view 视图层,Modal 层, Control 层,当用户在 View 上进行一系列操作后,发送到 control 层去处理这个动作,
修改 modal,然后 modal 把新的数据反馈的 view 层

MVVM 是把应用拆分成 view, modal, viewmodal 三层,MVVM 的核心就是提供对 View 和 ViewModal 的双向数据绑定,使得 ViewModal
状态改变可以自动传给 View,即所谓的数据双向绑定。

MVVM

MVVM 是 Model-View-ViewModel 的缩写。mvvm 是一种设计思想。Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑;View 代表 UI 组件,
它负责将数据模型转化成 UI 展现出来,ViewModel 是一个同步 View 和 Model 的对象。在 MVVM 架构下,View 和 Model 之间并没有直接的联系,
而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM,
不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

MVVM 指双向数据流,即 View-Model 之间的双向通信,由 ViewModel 作桥接
而单向数据流则去除了 View -> Model 这一步,需要由用户手动绑定。

###mvvm 和 mvc 区别?
mvc 和 mvvm 其实区别并不大。都是一种设计思想。主要就是 mvc 中 Controller 演变成 mvvm 中的 viewModel。mvvm 主要解决了 mvc 中
大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View 。

###vue method computed watch
methods用来定义方法,computed 定义的是计算属性,
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。所以,对于任何复杂逻辑,你都应当使用计算属性。
我们通过method 也可以实现computed 的作用,但是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
而watch 可以赋予我们更高的自由度,我们可以在回调函数里面执行操作,比如一些数据变化时执行异步或开销较大的操作。

vuex 的 store 特性是什么

vuex 就是一个仓库,仓库里放了很多对象。其中 state 就是数据源存放地,对应于一般 vue 对象里面的 datastate 里面存放的数据是响应式的,
vue 组件从 store 读取数据,若是 store 中的数据发生改变,依赖这相数据的组件也会发生更新它通过 mapState 把全局的 state 和 getters 映射到当前组件的 computed 计算属性

不用 vuex 会带来什么问题

可维护性会下降,你要修改数据,你得维护 3 个地方
可读性下降,因为一个组件里的数据,你根本就看不出来是从哪里来的
增加耦合,大量的上传派发,会让耦合性大大的增加,
本来 Vue 用 Component 就是为了减少耦合,现在这么用,和组件化的初衷相背
知乎

bind apply call

bind 不会立即调用,其他两个会立即调用。多次 bind 只能改变一次

var a = { b: 1 },
  c = { b: 2 };
function d() {
  console.log(this.b);
}
d.bind(a).bind(c)();

bind 会创建一个新函数,并且改变把传入的第一个参数作为 this, 其余参数一并传入函数。
apply 第一个参数为绑定给 this 的值,第二个参数是一个数组或者类数组对象(一个以数字为 key,并且包含 length 这个 key 的对象)
call, 第一个参数为绑定给 this 的值,后面是参数列表

异步方案

1 callBack 缺点:回调地狱
2 Promise es6 并发处理 Promise.all() 缺点:错误不能被 try catch,并且也没有从根本上解决回调问题,只是换了种写法
3 Generator es6 缺点:比较复杂
4 async/await es7 async 其实就是一个语法糖,它的实现就是将 Generator 函数和自动执行器(co),包装在一个函数中
5 观察者模式

//并发
let a = getFoo(),
    b = getBar(),
    foo = await a,
    bar = await b;
// 或者
await Promise.all([]);
async 函数的返回值是Promise,generator的返回值是iterator 遍历器

Generator

function* gen() {
  let a = yield 111;
  console.log(a);
  let b = yield 222;
  console.log(b);
  let c = yield 333;
  console.log(c);
  let d = yield 444;
  console.log(d);
}
let t = gen();
//next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
t.next(1); //第一次调用next函数时,传递的参数无效
t.next(2); //a输出2;
t.next(3); //b输出3;
t.next(4); //c输出4;
t.next(5); //d输出5;

异步编程方案,有一种叫做“协程”的方案,多个线程互相协助,完成异步任务。
而 generator 的核心就是 yield 命令,它表示执行到此处,将执行权交给其他协程,等到执行权返回,在从暂停的地方继续执行。

Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

1 基于 Promise

function setTime(x) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(x);
    }, x * 1000);
  });
}
function* gen() {
  var r1 = yield setTime(1);
  console.log(r1);
  var r2 = yield setTime(2);
  console.log(r2);
}
var g = gen();
g.next().value.then(function(data) {
  g.next(data).value.then(function(data) {
    g.next(data);
  });
});

2 基于 thunk 函数
Thunk 函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数

var Thunk = function(fileName) {
  return function(callback) {
    return fa.readFile(fileName, callback);
  };
};

3 自己实现一个 co 模块
co 模块支持多个请求并发,通过数组或者是对象的形式

function my_co(fun) {
  return new Promise((rsolve, reject) => {
    function next(data) {
      let { value, done } = fun.next(data);
      if (!done) {
        if (typeof value == "object") {
          // Promise
          value.then(val => {
            next(val);
          });
        } else {
          // thunk函数
          value(val => {
            next(val);
          });
        }
      } else {
        resolve(value);
      }
    }
    next();
  });
}

订阅/观察者模式

const fs = require("fs");
let pubhub = {
  arr: [],
  emit() {
    this.arr.forEach(fn => fn());
  },
  on(fn) {
    this.arry.push(fn);
  }
};
let data = [];
pubhub.on(() => {
  if (data.length === 3) {
    console.log(data);
  }
});
fs.readFile("./JS/Async/data/age.txt", "utf-8", (err, value) => {
  data.push(value);
  pubhub.emit();
});
fs.readFile("./JS/Async/data/name.txt", "utf-8", (err, value) => {
  data.push(value);
  pubhub.emit();
});
fs.readFile("./JS/Async/data/job.txt", "utf-8", (err, value) => {
  data.push(value);
  pubhub.emit();
});

ES6 常用的功能

1 let const
2 模版字面量
3 解构
4 展开操作符
5 箭头函数
6 类

###模块化开发的价值
1 避免命名冲突
当新增一个函数
(1)在原来的函数上面去增加功能,这样就违背了工具函数单一性原则,并且还得理解原函数的的逻辑
(2)新增一个函数,这样子就需要人工全局检查名字冲突,如果项目比较大效率就很难保证。
2 便于依赖管理
浏览器按照从上往下加载,如果存在两个相邻的 script 标签,就先加载并执行第一个之后再解析下一个,所以在模块化出现之前,处理依赖加载的方式都是通过控制标签顺序来实现。这样随着项目复杂度的提升,维护难度不断提高。通过模块化的规范解决错综复杂的依赖管理问题,降低开发难度和维护难度,让开发者将更多的经历投入到业务逻辑中。
3 利于性能优化
(1)通过模块化规范的依赖管理让按需加载的模块更易于管理
(2)使用模块化构建工具将模块进行打包,可以减少客户端的请求,提高 web 的解析速度
4 提高可维护性

5 利于代码复用

模块化开发

无模块时代
1 全局变量灾难
2 函数命名冲突
3 依赖关系不好处理

模块化面临问题

如何安全的包装一个模块的代码?(不污染模块外的任何代码)
如何唯一标识一个模块?
如何优雅的把模块的 API 暴漏出去?(不能增加全局变量)
如何方便的使用所依赖的模块

发展历史

1 一开始是在服务端的 CommonJS ,它定义了全局函数 require,通过传入模块标识来引入其他模块,如果被引入的模块又依赖了其他模块,
那么会依次加载这些模块;
通过 module.exports 向外部暴露 API,以便其他的模块引入。由于 CommonJS 加载模块是同步的,即只有加载完成才能进行接下来的操作,在服务器端,文件都是保存在硬盘上,
所以同步加载没有问题,但是对于浏览器端,需要将文件从服务器端请求过来,那么同步加载就不适用了,所以,CommonJS 是不适用于浏览器端的。因此当应用于浏览器端时会受到网速的限制。
2 之后出现了 AMD 和 CMD 浏览器端的模块开发规范,可以实现异步模块加载。
分别对应 requirejs 和 seajs
AMD 推崇依赖前置,提前执行,CMD`推崇就近依赖,延迟执行。
两种都是提前下载
3 ES6 中给出了 import export 这样的方案,目前为止我们都是通过 babel 将 ES6 代码转为 ES5,import 转为了
require,export 转为了 module.exports,即 commonJS。

requirejs 导入模块的方式实际就是创建脚本标签,并且监听 onload 事件

资料 1
JavaScript 模块的循环加载

commonjs 加载原理

CommonJS 的一个模块,就是一个脚本文件。require 命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。

{
  id: '...',
  exports: { ... },
  loaded: true,
  ...
}

以后需要用到这个模块的时候,就会到 exports 属性上面取值。即使再次执行 require 命令,也不会再次执行该模块,而是到缓存之中取值。

ES 模块加载原理

深入理解 ES Modules

commonjs 和 es6 对循环加载的处理方式

JavaScript 模块的循环加载
1 CommonJS 的做法是,一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
2 ES6 模块的运行机制与 CommonJS 不一样,它遇到模块加载命令 import 时,不会去执行模块,而是只生成一个引用。等到真的需要用到时,
再到模块里面去取值。

COMMONJS 服务端开发的规范

(1)是 javascript 的静态模块化规范,只适用于服务端 nodejs 开发 ,因为在它是同步加载模块的,这在浏览器端时会受到网速的限制。
(2)模块均是同步阻塞式加载,无法实现按需异步加载。

AMD(异步模块定义) 规范 浏览器端模块化开发的规范

define(id?, dependencies?, factory);

    1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
    1. CMD 推崇依赖就近,AMD 推崇依赖前置。

无论是 commonjs 还是 amd 和 cmd ,三者都是折中的产物,缺点:
(1)应用场景单一,无法跨环境使用
(2)构建工具不统一
(3)不同规范模块无法混合使用

ES6 MOdule

目前 stage3 阶段的 import()函数可以满足按需加载需求,babel 和 webpack 已经实现对此函数的支持。

webpack 和异步模块

AMD 本身具备异步加载的功能,但是并不支持定义模块的 chunk Name, webpack 对其打包出来的 js 使用 id 来命名。这对于线上错误跟踪比较麻烦
Commonjs 和 es6 module 没有异步加载的功能,webpack 通过特定的 api 进行支持
webpack1 requrie.ensure([],()=>{},'chunk name')
weback2 使用 import 函数来实现,通过特定的注释提供 chunk name

webpack hash 和 chunk hash

hash 针对的是整个工程中所有有参与构建的文件
chunk hash 是各个打包成模块的文件的 hash

contenthash
js 使用的是 chunkhash
css 可以使用 MiniCssExtractPlugin 这个插件里面的 content hash

webpack compiler 和 compilation

compiler 对象代表的是 webpack 执行环境的完整配置,只会在启动 webpack 时被创建,并且在 webpack 运行期间不会被修改。
compilation 对象代表某个版本的资源对应的编译进程。

编写自己的 webpack 插件

// 编写一个构造函数
const htmlwebpackprocess = function(options) {
  this.options = { ...options };
};
// 针对指定事件阶段 编写apply 方法
htmlwebpackprocess.prototype.apply = compiler => {
  compiler.plugin("compilation", compilation => {
    compilation.plugin(
      "html-webpack-plugin-before-html-procss",
      (htmlPluginData, callback) => {
        //插件逻辑 修改htmlPluginData
        callback(null, htmlWebpackData);
      }
    );
  });
};

livereload 和 HMR

lovereload 是 websocket 与浏览器通信,然后刷新页面
HMR 也是 websocket ,但是只会刷新待更新模块,不会直接刷新页面

模块化和组件化

模块化中的模块一般指的是 Javascript 模块,将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起。
块的内部数据相对而言是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。比如一个用来格式化时间的模块。
组件化将模板、样式和逻辑都抽象独立出来,是一个独立的可视和可交互的单元,组件则包含了 template、style 和 script,而它的 Script
可以由各种模块组成。

模块化

模块化方案看法

模块化是一种处理复杂系统的方式,它可以把系统代码划分为一系列职责单一,高度解耦合且可替换的模块,使系统的可维护性更加简单易得。

COMMONJs 的诞生开启了“ JavaScript 模块化的时代”,CommonJS 的模块提案为在服务器端的 JavaScript 模块化做出了很大的贡献,
但是在浏览器下的 JavaScript 模块应用很有限。随之而来又诞生了其它前端领域的模块化方案,像 requireJS、SeaJS 等,然而这些模块化方案
并不是十分适用 ,并没有从根本上解决模块化的问题。

ES6 模块的循环加载,如果存在着相互调用,且存在截止条件,并不会是程序崩溃。但是,如果造成了无限循环调用,会使得程序崩溃,内存溢出。
ES6 模块,使用 import 引入时,其实是建立了与模块之间的引用,当用到引入的模块中的变量时,再去模块里取值。

模块导出的方法

es5 exports module.exports
es6 export
export default 默认输出的一个变量

module.exports, exports 是对象,而 export, export defalut 是 ES6 的语法

前端响应式

1 三个视口 布局视口 视觉视口 理想视口
设备像素比 = 设备像素/ css 像素个数

XSS 攻击方式 (跨站脚本攻击)

1 反射形攻击: 指攻击代码通过 url 提交到服务器处理并返回到客服端,最终由浏览器执行造成攻击。比如 url 中参数是一段攻击代码<script>alert(1)</script>,被放到 dom 中执行。
2 存储型攻击:持久型 XSS,主要是将 XSS 代码发送到服务器(不管是数据库、内存还是文件系统等。),然后等浏览器执行该代码造成攻击
3 DOM XSS 而是通过浏览器端的 DOM 解析。这完全是客户端的事情。DOM XSS 代码的攻击发生的可能在于我们编写 JS 代码造成的

攻击方法

在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
在标签的 href、src 等属性中,包含 javascript: 等可执行代码。
在 onload、onerror、onclick 等事件中,注入不受控制代码。
在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。
前端安全系列(一):如何防止XSS攻击?

XSS 防御

1 没有过滤危险的 DOM 节点。如具有执行脚本能力的 script, 具有显示广告和色情图片的 img, 具有改变样式的 link, style, 具有内嵌页面的 iframe, frame 等元素节点。
2 没有过滤危险的属性节点。如事件, style, src, href 等
3 对 cookie 设置 httpOnly。
4 设置CSP(网页安全政策), 可配置请求头或者通过meta 标签配置,CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。
如果你需要内联脚本,你也可以通过 nonce-hash 等方式来实现。

WebSocket

1 传统轮询技术:Ajax 短轮询即客户端周期性的向服务器发起 HTTP 请求。由于 HTTP/1.1 的持久连接(建立一次 TCP 连接,发送多个请求)和管线化技术(异步发送请求),
使得 HTTP 请求可以在建立一次 TCP 连接之后发起多个异步请求。
这种模式下会造成带宽浪费以及时效性问题

Comet(服务端推送)
AJAX 长轮询:短轮询是服务器立即响应,不管数据是否有效;长轮询是等待数据更新后响应。
服务器推送技术(HTTP 流),在客户端只发起一次 HTTP 请求,服务器保持连接状态,在数据更新之后,服务器会传输数据,否则保持连接状态。此时一个 requset 对应多个 response。

WebSocket 协议是双向通信协议,在建立连接之后,客户端和服务器都可以主动向对方发送或接受数据。WebSocket 协议建立的前提需要借助 HTTP 协议,建立连接之后,持久连接的双向通信就与 HTTP 协议无关了。
在 JS 中创建 WebSocket 后,会有一个 HTTP 请求发向浏览器以发起请求。在取得服务器响应后,建立的连接会使用 HTTP 升级将 HTTP 协议转换为 WebSocket 协议。
也就是说,使用标准的 HTTP 协议无法实现 WebSocket,只有支持那些协议的专门浏览器才能正常工作。

Redis

Redis 是一个使用 C 语言写成的,开源的 key-value 数据库。数据都是缓存在内存中。

好处:
速度快,因为存储在内存中
支持丰富数据类型,支持 string,list,set,SortedSet,hash
list : 有顺序,可重复
set: 无顺序,不能重复
SortedSet: 有顺序,不能重复
丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除

关系型数据库和非关系型数据库

关系型数据库:指采用了关系模型来组织数据的数据库。关系模型指的就是二维表格模型,关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。
非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合,可以是文档或者键值对等。

组件化的理解

是什么

组件化将模板、样式和逻辑都抽象独立出来,是一个独立的可视和可交互的单元,组件则包含了 template、style 和 script,而它的 Script 可以由各种模块组成。
为什么
组件化可以使我们复用代码,更高效处理业务。
把复杂系统拆分成多个组件,分离组件边界和责任,便于独立开发,升级和维护。

怎么做
通常前端应用中,我们可以封装成两类组件,分别使原子组件,业务组件。
基础组件或者叫原子组件,应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件)
原子组件可以是一个按钮,一个输入框等等,不耦合任何的业务逻辑,只提供接口。
业务逻辑组件可以是一个列表等。

TCP 三次握手

参考文章
客户端–发送带有 SYN 标志的数据包–一次握手–服务端
服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端

为什么需要三次握手

三次握手的目的就是建立可靠的通信通道,而通信的过程其实就是数据的发送以及接受,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。

为什么需要回传 syn

接受端回传发送端所发送的 syn 是为了告诉发送端,我接收到的信息确实就是你发送的信号

传了 syn 为什么还要 ack

双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方到接收方的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。

TCP 四次挥手

客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号
服务器-关闭与客户端的连接,发送一个 FIN 给客户端
客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加 1

为什么建立连接是三次握手,关闭连接确是四次挥手呢?

建立连接的时候, 服务器在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,
也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACK 和 FIN 一般都会分开发送,从而导致多了一次。

TCP 协议如何保证可靠传输

1 应用数据被分割成 TCP 认为最合适发送的数据块
2 TCP 对每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层
3 校验和,TCP 将保持它首部和数据的校验和。发送方计算和发送校验和,接收方检验校验和,确保数据无误
4 流量控制: 使用滑动窗口协议(发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。)。
5 拥塞控制:当网络拥塞时,减少数据的发送。(拥塞窗口)
6 停止并等待协议。

状态码

1XX 信息状态码 接收的请求正在处理
2XX 成功状态码 接受的请求正常处理完毕
3XX 重定向状态码 需要进行附加操作以完成请求
4XX 客户端错误状态码 服务器无法处理请求
5XX 服务器错误状态码 服务器处理请求出错

Content-Security-Policy

http 请求头,告诉浏览器允许和不允许加载的资源。

常见的两种登陆方式

参考资料
1 服务器 session+客户端 sessionId
2 token
前面说到 sessionId 的方式本质是把用户状态信息维护在 server 端,token 的方式就是把用户的状态信息加密成一串 token 传给前端,然后每次发请求时把 token 带上,
传回给服务器端;服务器端收到请求之后,解析 token 并且验证相关信息;

CSRF 跨站域请求伪造

通过借助受害者的 cookie 来骗取服务器的信任,伪造请求。
本质上讲,XSS 是代码注入问题,CSRF 是 HTTP 问题。XSS 是内容没有过滤导致浏览器将攻击者的输入当代码执行。CSRF 则是因为浏览器在发送 HTTP 请求时候自动带上 cookie,
而一般网站的 session 都存在 cookie 里面。
防御策略
1 验证 HTTP referer 字段;
2 请求的地址中添加 token 并验证;token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,
3 http 头中自定义属性并验证。这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里
4 增加交互的验证

前端安全系列(二):如何防止CSRF攻击?

帧动画的实现方式

参考资料
css3 动画,transform,transition,animation (1)连续切换图片的 url,(2)连续切换雪碧图
js 动画
canvas 动画
svg 动画
three.js 3d 动画

CDN 的作用和原理

内容分发网络。
在现有的 internet 增加一层新的网络架构,将网站的内容发布到各个网络节点,然后用户访问的时候可以从最近的节点获取返回内容。
通过这样可以提高用户访问访问网站的响应速度。

理解什么是协议,了解 TCP/IP 网络协议族的构成,每层协议在应用程序中发挥的作用

协议就是两台电脑间通讯必须遵守的共同的规则。

通常 TCP/IP 协议族分为四层
分别是
应用层 通过应用进程间的交互来完成特定网络应用
传输层 负责向两台主机进程之间的通信提供通用的数据传输服务
网络层 处理在网络上流动的数据包。该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传送给对方
数据链路层 两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议

物理层 相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异

会话层:建立、管理和终止会话(会话协议数据单元SPDU)
表示层:对数据进行翻译、加密和压缩(表示协议数据单元PPDU)

DNS 的作用、DNS 解析的详细过程,DNS 优化原理

DNS(Domain Name System,域名系统),因特网上作为域名和 IP 地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。通过主机名,
最终得到该主机名对应的 IP 地址的过程叫做域名解析(或主机名解析)

DNS 解析的详细过程
1 查找本地 DNS 服务器缓存 (浏览器缓存、系统缓存、路由器缓存、ISP(运营商)DNS 缓存)
2 查询根 DNS 服务器
3 域 DNS 服务器
4 最后把 IP 地址返回给用户电脑,并缓存。

DNS prefetch 即 DNS 预解析,通过向 DNS 服务器提前获取域名的实际指向,可以有效减少浏览器在发送请求时的 DNS 解析时间

webpack 优化

1 使用 happyPack 能把任务分解成多个子进程去并发执行,处理完成之后再把结果发送给主进程
2 tree-shaking 剔除掉 js 中没用的代码。
(1)依赖静态的 es6 模块语法 (2)配置关闭 babel 里面转换模块为 commonjs 的功能
(3)webpack4 通过 pack.json 的 sideEffects

副作用代码
Tree Shaking 与副作用代码
如果一个模块里面出现一些副作用代码,不是 export 导出的代码,而是一些全局代码,这样子的代码会被保留下来的。而通过配置 sideEffects ,把这个模块标记为 false; 这样子就能完整移除

webpack 优化打包

Webpack 打包优化之速度篇
1 减小文件搜索范围
配置 resolve.modules, alias 别名
设置 test & include & exclude
2 使用增强代码压缩工具
webpack-parallel(parəˌlel)-uglify-plugin

3 用 Happypack 来加速代码构建
webpack loader 虽然异步调用,但是还是受限于 node 的单线程模式。
Happypack 的处理思路是:将原有的 webpack 对 loader 的执行过程,从单一进程的形式扩展多进程模式,从而加速代码构建。
在编译过程中,除了利用多进程的模式加速编译,还同时开启了 cache 计算,能充分利用缓存读取构建文件,对构建的速度提升也是非常明显的

4 设置 babel 的 cacheDirectory 为 true (Webpack 构建将尝试从缓存中读取,以避免在每次运行时运行潜在昂贵的 Babel 重新编译过程)
5 使用 DllPlugin 和 DllReferencePlugin 来提取公共依赖代码。
dll和commonsChunk概念上的区别,commonsChunk之所以慢和大,是因为每次run的时候,都会去做一次打包,而实际上我们不会一直去更新我们引用的依赖库,
所以dll的做法就等于是,事先先打包好依赖库,然后只对每次都修改的js做打包。

让你的Webpack起飞—考拉会员后台Webpack优化实战

webpack4 升级log

webpack 4: released today!!
webpack-4.0更新日志
Webpack 4 升级与使用

webpack HMR 原理

weboack-dev-server 可以和浏览器之间建立一个 web socket 进行通信,一旦新文件被打包出来,告诉浏览器这个消息,然后就可以自动刷新页面或者进行热替换操作。

OOP 面向对象编程

核心:封装 继承 多态

性能优化

1 重定向优化:301(永久重定向)302(临时重定向)304(Not Modified)尽量减少永久重定向
301 客户端请求过一次之后就会记住,第二次就直接请求另外一个网址,302 还是要继续请求一遍这个网址
2 DNS 优化
减少 DNS 请求次数
DNS 预解析 通过 dns-prefetch
3 TCP 优化
减少 TCP 请求方式有两种,一种是资源合并,另外一种是使用长链接,Http1.1,可以复用连接,大大减少握手次数和释放次数(http 头部会加上 connection:keep-alive)
4 渲染优化
可以使用服务端渲染的方式进行渲染
同时还可以使用 defer 和 async 等属性控制 javascript 的渲染顺序,保证页面的加载速度。

浏览器与 Node 的事件循环(Event Loop)有何区别?

js执行时单线程的,这是根据浏览器用途来决定的,因为js作为脚本语言,是用来与用户互动的,如果使用多线程,会出现同一时刻不同的操作行为,浏览器不知道执行谁的,当然我们可以引入锁的机制,但是这会
大大提高复杂度,所以从诞生的时候就使用单线程处理。

因为js的执行是单线程的,所以某一时候内只能执行某个任务。对于异步任务。是采用循环任务队列的形式的。

浏览器多线程
GUI 渲染线程
Javascript 引擎线程
定时触发器线程
事件触发线程
异步请求线程

浏览器中的 EVENT LOOP
macro 宏任务(可以有多个队列)

setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等。

micro 微任务(只有一个队列)

new Promise().then(回调)、MutationObserver(html5 新特性 Mutation Observer(变动观察器)是监视 DOM 变动的接口。当 DOM 对象树发生任何变动时,Mutation Observer 会得到通知)

Animation callbacks (in requestAnimationFrame 队列)

连续调用两句 requestAnimationFrame,它们会在同一次事件循环内执行,Animation callbacks 执行队列里的全部任务,但如果任务本身又新增 Animation callback 就不会当场执行了,因为那是下一个循环。它是在微任务队列之后执行。

当某个宏任务(一个 setTimeout 就是一个,执行完之后就立即看微任务队列了)执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。

如果微任务队列执行过程中继续有微任务增加到微任务,那这些新增的微任务还是会执行的,直到清空微任务队列

console.log(0);
setTimeout(() => {
  console.log(1);
}, 0);
Promise.resolve().then(() => {
  console.log(3);
  Promise.resolve().then(() => {
    console.log(4);
  });
});

渲染不是时刻进行的,浏览器会有滚动的节奏来渲染页面,称为 render steps。它内部分为三个步骤,
Structure - 构建 DOM 树的结构
Layout - 确认每个 DOM 的大致位置(排版)
Paint - 绘制每个 DOM 具体的内容(绘制)

requestAnimationFrame 这里还有这个 api,是一个特别的异步任务,用于在浏览器中运行任何类型的动画,它能保证执行时机在浏览器适合的时机,它是在渲染动作
执行之前执行的,这个函数的执行频率取决于您的浏览器和计算机的帧速率,但通常为60fps注册的方法不加入异步队列,而是加入渲染这一边的队列中,
它在渲染的三个步骤之前被执行。通常用来处理渲染相关的工作

我们来看一下 setTimeout 和 requestAnimationFrame 的差别。假设我们有一个元素 box,并且有一个 moveBoxForwardOnePixel 方法,作用是让这个元素
向右移动 1 像素。

// 方法 1
function callback() {
  moveBoxForwardOnePixel();
  requestAnimationFrame(callback);
}
callback();

// 方法 2
function callback() {
  moveBoxForwardOnePixel();
  setTimeout(callback, 0);
}
callback();

有这样两种方法来让 box 移动起来。但实际测试发现,使用 setTimeout 移动的 box 要比 requestAnimationFrame 速度快得多。这表明单位时间内 callback 被调用的次数是不一样的。

这是因为 setTimeout 在每次运行结束时都把自己添加到异步队列。等渲染过程的时候(不是每次执行异步队列都会进到渲染循环)异步队列已经运行过很多次了,所以渲染部分会一下会更新很多像素,而不是 1 像素。requestAnimationFrame 只在渲染过程之前运行,因此严格遵守“执行一次渲染一次”,所以一次只移动 1 像素,是我们预期的方式。

Promise.resolve().then(() => {
  console.log("Promise1");
  setTimeout(() => {
    console.log("setTimeout2");
  }, 0);
});
setTimeout(() => {
  console.log("setTimeout1");
  Promise.resolve().then(() => {
    console.log("Promise2");
  });
}, 0);

node 中的事件循环的顺序:
timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调
I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
idle, prepare 阶段:仅 node 内部使用
poll 阶段:获取新的 I/O 事件, 适当的条件下 node 将阻塞在这里
check 阶段:执行 setImmediate() 的回调
close callbacks 阶段:执行 socket 的 close 事件回调

常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作等。
常见的 micro-task 比如: process.nextTick、new Promise().then(回调)等。

Node.js 中,macrotask 会在事件循环的各个阶段之间执行(先执行完全部的 macro 任务),也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。

实际上 await 是一个让出线程的标志。await 后面的表达式会先执行一遍,将 await 后面的代码加入到 microtask 中
综上
浏览器环境下,microtask 的任务队列是每个 macrotask 执行完之后执行。而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个
阶段执行完毕,就会去执行 microtask 队列的任务。

vuex 的设计理念

思想就是把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。
通过一个全局的 store,单一状态树来保存应用的状态,并且通过 Store 选项注入到整个应用。

:介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景

// 订阅-发布模式
function Observer() {
  this.cache = {};
}
Observer.prototype.on = function(key, fn) {
  if (!this.cache[key]) {
    this.cache[key] = [];
  }
  this.cache[key].push(fn);
};
Observer.prototype.emit = function(key) {
  if (this.cache[key] && this.cache[key].length > 0) {
    let fns = this.cache[key];
  }
  fns.map((fn, index) => {
    fn.apply(this, argument);
  });
};
Observer.prototype.remove = (key, fn) => {
  let fns = this.cache[key];
  if (!fns || fns.length == 0) return;
  if (!fn) {
    this.cache[key] = [];
  } else {
    fns.map((item, index) => {
      if (item === fn) {
        fns.splice(index, 1);
      }
    });
  }
};
va obj = new Observer();
obj.on('hello', function(a,b){console.log(a,b)})

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象的状态得到通知并自动更新

在观察者模式中,依赖于 subject 对象的一系列 observer 对象在被通知之后只能执行同一个特定的更新方法,
发布订阅模式中则可以基于不同的主题去执行不同的自定义事件。
相对而言,发布订阅模式比观察者模式要更加灵活多变。

1.观察者模式维护的是一个单一事件对应多个依赖这个事件的对象之间关系,在软件设计中可以理解为是一个对象,维护一个依赖列表,
当任何状态发生改变自动通知它们。
观察者是: event->[obj1,obj2obj3,....]

2.订阅发布模式维护的是多个主题(事件) 以及依赖于各个主题(事件)的对象之间的关系
订阅发布是:{
event1->[obj1,obj2....],
event2->[obj1,obj2.....],....}

img
资料

http2 特点

1 采用二进制传输数据
2 多路复用
3 头信息压缩 HTTP2.0则使用了专门为首部压缩设计的HPACK算法
4 服务器推送

http2 多路复用

多路复用代替了 HTTP1.x 的序列和阻塞机制,所有的相同域名请求都通过同一个 TCP 连接并发完成。在 HTTP1.x 中,并发多个请求需要多个 TCP 连接,
浏览器为了控制资源会有 6-8 个 TCP 连接都限制。

在 http1.1 中默认开启 keep-alive,解决了上面说到的问题,但是 http 的传输形式是一问一答的形式,一个请求对应一个响应(http2 中已经不成立,
一个请求可以有多个响应,server push),在 keep-alive 中,必须等下上一个请求接受才能发起下一个请求,所以会收到前面请求的阻塞。
使用 pipe-line 可以连续发送一组没有相互依赖的请求而不比等到上一个请求先结束,看似 pipe-line 是个好东西,但是到目前为止我还没见过这种类型的连接,
也间接说明这东西比较鸡肋。pipe-line 依然没有解决阻塞的问题,因为请求响应的顺序必须和请求发送的顺序一致,如果中间有某个响应花了很长的时间,
后面的响应就算已经完成了也要排队等阻塞的请求返回,这就是线头阻塞

HTTP2 中同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。单个连接上可以并行交错的请求和响应,之间互不干扰
在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)
http2 的传输是基于二进制帧的。每一个 TCP 连接中承载了多个双向流通的流,每一个流都有一个独一无二的标识和优先级,而流就是由二进制帧组成的。
二进制帧的头部信息会标识自己属于哪一个流,所以这些帧是可以交错传输,然后在接收端通过帧头的信息组装成完整的数据。这样就解决了线头阻塞的问题
,同时也提高了网络速度的利用率。

如何实现一个 new

var _new = function(fn, ...arg) {
  if (typeof fn !== "function") {
    throw "newOperator function the first param must be a function";
  }
  const obj = Object.create(fn.prototype);
  const ret = fn.apply(obj, arg);
  return ret instanceof Object ? ret : obj;
};
function b() {
  this.a = 1;
}
_new(b);

手写算法

已知如下数组:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10],
  flatarr = arr.flat(Infinity).sort((a, b) => {
    return a - b;
  });
flatarr.filter((el, index) => {
  return flatarr.indexOf(el) == index;
});
// 数组扁平化
var flatArr = arr => {
  if (!Array.isArray(arr)) {
    return arr;
  } else {
    return arr.reduce((acc, cur) => {
      return acc.concat(flatArr(cur));
    }, []);
  }
};

flatArr(arr);
// 数组去重方案
// 1 通过一个新数组和一个indexOf 来去除重复
// 2 sort 之后相邻的数不一致
arr.sort().filter(function(item, pos, ary) {
  return item != ary[pos + 1];
});
// 3
return Array.from(new Set(a));

// 直接字符串化
arr
  .toString()
  .split(",")
  .map(Number);

ES5/ES6 的继承除了写法以外还有什么区别?

资料

1 class 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量。
2 class 声明内部会启用严格模式。不能引用未声明变量等
3 class 的所有方法(包括静态方法和实例方法)都是不可枚举的。
4 必须使用 new 调用 class。
5 class 内部无法重写类名。

0.1 + 0.2 != 0.3

计算机中所有的数据都是以二进制存储的,所以在计算时计算机要把数据先转换成二进制进行计算,然后在把计算结果转换成十进制。在计算 0.1+0.2 时,
二进制计算发生了精度丢失,导致再转换成十进制后和预计的结果不符

ECMAScript 中的 Number 类型遵循 IEEE 754 标准。使用 64 位固定长度来表示。
JavaScript 使用的是 64 位双精度浮点数编码,所以它的符号位占 1 位,指数位占 11 位,尾数位占 52 位。
但是由于限制,有效数字第 53 位及以后的数字是不能存储的,它遵循,如果是 1 就向前一位进 1,如果是 0 就舍弃的原则。
所以这样导致了精度丢失的问题

资料

proto

proto 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,
但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,
然后再来为原型添加方法/属性。

ES5 和 ES6 继承的区别

1

function Super() {}
function Sub() {}

Sub.prototype = new Super();
Sub.prototype.constructor = Sub;

var sub = new Sub();

Sub.__proto__ === Function.prototype;

//ES5 , Sub方法的__proto__ 指向构造函数的原型

ES6 class

class Super {}
class Sub extends Super {}

const sub = new Sub();

Sub.__proto__ === Super;

// 子类可以直接通过 __proto__ 寻址到父类。

2 ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this 上面(所以必须先调用 super 方法),
然后再用子类的构造函数修改 this。

Class 注意点

1 class 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量。
2 class 声明内部会启用严格模式。
3 class 的所有方法(包括静态方法和实例方法)都是不可枚举的。
4 class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用 new 来调用。
5 必须使用 new 调用 class。
6 class 内部无法重写类名。

proto和 prototype 的区别和关系?

为了实现方法和属性的共享,js为构造函数设置一个prototype属性所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,
就放在构造函数里面。实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。

1 对象具有属性proto,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型
中定义的属性和方法。
2 方法(Function)方法这个特殊的对象,除了和其他对象一样有上述proto属性之外,还有自己特有的属性——原型属性(prototype),这个
属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,
叫做 constructor,这个属性包含了一个指针,指回原构造函数。

        --> __Proto__  --> Function.prototype; (构造函数的原型)--->__proto --> object.prototype -- __proto -- null
Foo()-->
        --> prototype --> Foo.prototype(原型对象)


        Foo.prototype --> constructor -- >  Foo()

        var f = new Foo() --> constructor 指向对象的构造函数
                          --> __Proto__

参考资料

slice splice

slice start end 返回修改后的数组
splice start num item1-n 开始位置,删除数量,插入的数据 返回删除的元素

substring substr

substring start,end 用于提取字符串中介于两个指定下标之间的字符
substr 用于返回一个从指定位置开始的指定长度的子字符串。substr(start [, length ])

ES6 set map

set 对应的是集合
集合以[值,值]的形式存储元素。集合是由一组无序且唯一(即不能重复)的项组成的,可以把集合想象成一个既没有重复元素,也没有顺序概念的数组。

var set = new Set();
set.add(1);
set.values();
set.size();
set.has(1);
set.delete(1);

map 对应的是字典
字典则是以[键,值]的形式来存储元素。字典也称作映射。

var map = new Map();
map.set("Gandalf", "gandalf@email.com");
map.set("John", "johnsnow@email.com");
map.set("Tyrion", "tyrion@email.com");
console.log(map.has("Gandalf")); //输出true console.log(map.size); //输出3
console.log(map.keys()); //输出["Gandalf", "John", "Tyrion"]
console.log(map.values()); //输出["gandalf@email.com", "johnsnow@email.com", "tyrion@email.com"]
console.log(map.get("Tyrion")); //输出tyrion@email.com
map.delete("John");

除了 Set 和 Map 这两种新的数据结构,ES6 还增加了它们的弱化版本,WeakSet 和 WeakMap。
Map 和 Set 与其弱化版本之间仅有的区别是:

1 WeakSet 或 WeakMap 类没有 entries、keys 和 values 等方法;
2 只能用对象作为键。WeakSet 的成员只能是对象,而不能是其他类型的值。WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名。

Symbol

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值

let s = Symbol();

对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,
有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。

webpack 与 grunt、gulp 的不同

gulp 是为了规范前端开发流程,实现前后端分离,模块开发,资源合并压缩,版本控制等功能的前端自动化构建工具。形象点说就是通过流水线,在流水线上
对资源进行一步步的操作,最终输出结果文件。
webapack 是前端模块化管理和打包的工具。他可以将许多松散的模块和资源按照依赖和规则打包成符合生产部署的前端资源。还可以将按需加载的模块进行
代码分离。等到实际需要的时候再异步加载。通过loader 转换,任何形式的资源都可以视作模块。

gulp侧重对前端开发的整个过程进行控制管理,我们可以通过task 配置不同的任务(比如说启动server,对less,sass进行预编译,对文件进行压缩合并),
来让gulp实现不同的功能。从而构建整个前端开发流程。

webpack 更侧重于对模块的打包,我们可以把开发中的所有资源都看成模块,最初weppack就是设计来对js代码打包的,然后慢慢通过Loader扩展到其他资源的
打包处理。

首先三者都是前端的构建工具
grunt 和 gulp 是基于任务和流的,找到一个或者一类文件,然后对其进行一系列的链式操作,更新流上的数据,整条链式操作构成一个任务,
多个任务构成了整个 web 的构建流程。
webpack 是基于入口文件,然后递归解析所有需要加载的资源文件,然后用不同的 loader 处理不同的问天,用 Plugin 来扩展 webpack 功能
webpack 与前者最大的不同就是支持代码分割,模块化(AMD,CommonJ,ES2015),全局分析

webpack 常用 loader 和 plugin

css-loader 可以用来解析通过@import 和 url() 引入的 css 资源
style-loader 通过注入<style>标签来把 css 增加到 dom 里面
babel-loader 使用 babel 来转译 es6
file-loader: 把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
url-loader: url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 base64

define-plugin:定义环境变量
commons-chunk-plugin:提取公共代码

对于 loader,它就是一个转换器,将 A 文件进行编译形成 B 文件,这里操作的是文件,比如将 A.scss 或 A.less 转变为 B.css,单纯的文件
转换过程;
对于 plugin,它就是一个扩展器,
在编译的整个生命周期中,Webpack 会触发许多事件钩子,Plugin 可以监听这些事件,根据需求在相应的时间点对打包内容进行定向的修改

什么是 bundle,什么是 chunk,什么是 module

bundle 是由 webpack 打包出来的文件,chunk 是指 webpack 在进行模块的依赖分析的时候,代码分割出来的代码块。module 是开发中的单个模块

虚拟 dom

让虚拟DOM和DOM-diff不再成为你的绊脚石

虚拟 dom 就是把 dom 元素抽象成一个 js 的树形结构对象。
如何生成
首先我们构造出这么个dom对象。比如
{
type = type;
props = props;
children = children;
}

在vue里面,是把版本编译成Ast语法树,再进行AST优化,最终生成render函数,执行render函数可以返回虚拟dom

1数据 + 模板生成虚拟 DOM
2虚拟 DOM 生成真实 DOM
3数据发生改变
4新的数据 + 模板生成新的虚拟 DOM 而不是真实 DOM【性能 up↑】
5用新的虚拟 DOM 和原来的虚拟 DOM 作对比(diff 算法,后面会详细介绍)【性能 up↑】
6找出发生改变的元素
7直接修改原来的真实 DOM【性能 up↑】

Vue 之所以引入了 Virtual DOM,更重要的原因是为了解耦 HTML 依赖,这带来两个非常重要的好处是:

因为dom操作是昂贵的,通过虚拟dom 对比,可以找到只需要更新的那部分进行更新,这对于性能有一定的帮助,但是这并不是主要的原因。

Vue 2.0 引入 vdom 的主要原因是 vdom 把渲染过程抽象化了,从而使得组件的抽象能力也得到提升,并且可以适配 DOM 以外的渲染目标。
不再依赖 HTML 解析器进行模版解析,可以进行更多的 AOT 工作提高运行时效率:通过模版 AOT 编译,Vue 的运行时体积可以进一步压缩,运行时效率可以进一步提升;
可以渲染到 DOM 以外的平台,实现 SSR、同构渲染这些高级特性,Weex 等框架应用的就是这一特性。

隐式转换

if([] == false) console.log('1');
if({} == false) console.log('2');
if(![]) console.log('3');
if([1] == [1]) console.log('4'); 对象指向不同的引用地址。

输出 1
1 第一个发生隐式转换
[].toString() == "";
Number("") = Number(false)
2 所有对象都是真

现在的服务端渲染和以前的服务端渲染的区别

以前的服务端渲染,是以文档为核心理念,通过在html里面放占位符,然后由服务端逻辑替换成实际数据后返回。
现在的服务器渲染的目的只是为了加速和搜索引擎优化,本质上是一个独立的应用程序,一个工程。

vuex 中为什么把把异步操作封装在 action,把同步操作放在 mutations?

作者知乎回答

中文翻译可能有些偏差(不是我翻的)。区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化
。事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。
异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer
必须同步返回下一个状态一样)。
同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),
这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,
你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个点子一直没时间做,
那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用的异步状态变化很有帮助。

区分actions 和mutations 只是为了能在devtools里面能追踪状态变化,这其实是一个框架架构上的约束,vuex里面真正限制的一点就是Mutaions里面
必须是同步的操作,因为这样子可以在每一次mutations执行完之后生成一个快照,记录执行的信息和状态,并且能在devtools里面看到。

深拷贝

function deepClone(data) {
  var newTarget;
  if (type(data) == "array") {
    newTarget = [];
    data.map((el.index) => {
      newTarget.push(deepClone(data[index])
    })
  } else if (type(data) == "object") {
    newTarget = {};
    for(var key in data) {
      if(data.hasOwnProperty[key]) {
        if(typeof data[key] == "object") {
          newTarget[key] == deepClone(data[key])
        } else {
          newTarget[key] = data[key];
        }
      }
    }
  } else {
    return data;
  }

}
function type(data) {
  var toString = Object.prototype.toString,
    map = {
      "[object Boolean]": "boolean",
      "[object Number]": "number",
      "[object String]": "string",
      "[object Function]": "function",
      "[object Array]": "array",
      "[object Date]": "date",
      "[object RegExp]": "regExp",
      "[object Undefined]": "undefined",
      "[object Null]": "null",
      "[object Object]": "object"
    };
  return map[toString.call(data)];
}

HTML5 为什么只需要?

因为 HTML5 不基于 SGML,所以不需要引用 DTD,DTD 是一种文档类型定义的规则,它定义了文档元素之间的关系和如何组织;
并且不同 DTD 包含不同的元素。
DOCTYPE标签是一种标准通用标记语言的文档类型声明
DOCTYPE 是用来指示浏览器本页面使用哪个 HTML 版本进行编写的指令,所以必须写这个。

Vue 和 React 的不同和相同

相同:
1 使用 Virtual Dom
2 提供响应式和组件化的视图组件
3 将注意力集中在核心库的开发,将其他的功能如路由和全局状态管理交给相关的库

不同:

1 性能上优化
react 应用上,当某个组件状态发生改变,它会以该组件为根,重新渲染整个组件字树。如要避免不必要的子组件的重渲染,
需要自己实现优化,比如 PureComponent(PureComponent 通过 prop 和 state 的浅比较来实现 shouldComponentUpdate),
或者使用 shouldComponentUpdate 来手动比较 props 或者 state 的变化来判断是否需要更新子组件
参考资料
在 vue 中,组件的依赖是渲染过程中自动追踪的,系统自己精确知道哪个组件需要重渲染,所以 vue 不需要考虑此类优化。
2 react 中,一切都是 js,不仅把 html 用 jsx 来表达,css 也适用 js 来表达。vue 整体思想上还是拥抱经典的 web 技术。
使用 jsx 优点是能更强控制整个渲染的流程,包括变量的引用等,而且开发工具的支持也比较完善。
但 vue 也是支持 jsx 的,但推荐还是使用 template,这种的优势是对于习惯 html 的开发者来说更友好,
然后基于 html 的模版也更容易迁移到 vue。
3 向上扩展性
vue 提供了 vue-cli 脚手架,虽然 react 也提供了 create-react-app 脚手架,但相比之下,vue 的脚手架的优势是:
支持项目生成时候的任何配置,并且提供了各种用途的模版。
4 向下扩展性
react 学习之前需要学习 es6 和 jsx ,因为大多数示例都是这些语法,而 vue 不需要学习这些,并且通过应用一个 js 文件就可以编写
vue 代码,更快的学习曲线。
原生渲染上
React Native 能使你用相同的组件模型编写有本地渲染能力的 APP (iOS 和 Android)。能同时跨多平台开发。
相应地,Vue 和 Weex 会进行官方合作,Weex 允许你使用 Vue 语法开发不仅仅可以运行在浏览器端,还能被用于开发 iOS 和 Android 上的原生应用的组件。

vue 异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。
为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。
Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。

vue2.6

1 统一 v-slot 代替取代了 slot 和 slot-scope
2 动态参数:可以用方括号括起来的 JavaScript 表达式作为一个指令的参数

<a v-bind:[attributeName]="url"> ... </a>

3 使用 Vue.observable()创建一个响应对象
4 v-for 可以遍历 map set

vue $attrs 和$listener

都是用来多层级组件通信的,
比如有三层 parent,me,son,$attrs 可以把parent的属性传递到son 里面,通过$attrs 来获取
$listener 可以做一个中介,把 son 组件的事件抛到 parent 组件。通过 me 组件上面的 v-on="$listeners"

new Array(20) 和 Array.apply(null, {length: 20})

new Array 建的数组是没有初始化的,用 map 并不能循环出来
Array.apply(null, {length: 20}) 创建的数组每一个上面都被赋值 undefined;
还可以用以下方式创建不为空的数组
Array.from({length: 20})
// 方法 2
Array(20).fill(null)

你觉得自己印象最深刻的一件事或者一个 bug

我印象比较深刻的是在做一个第三方联登的时候,出现了一个严重的 bug,如果手机上装有第三方 app 但是没有登陆账号,这个时候调起联登会失败。当出现这个问题的时候,首先是到开发文档上面查看,然后去到对方的开发者论坛看看有没有对应的问题,最终通过论坛联系到对方的开发者,确定是有这么个问题,并且对方无法快速修复并且得排期,然后就把这个结果反馈给上司。

for of 和 for in 的区别

ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for...of 循环,作为遍历所有数据结构的统一的方法。

一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口。任何数据结构只要部署 Iterator 接口就可以用 for...of 循环遍历它的成员。
也就是说,for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。

具有Symbol.iterator属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有next方法

for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList
对象--->document.querySelectorAll("p");), Generator 对象,以及字符串。

并不是搜索的类数组对象都部署 iterator j 接口,我们可以使用 array.from 将其转换为数组

ES6 提供三个新的方法——entries(),keys()和 values()——用于遍历数组。它们都返回一个遍历器对象(,可以用
for...of 循环进行遍历,唯一的区别是 keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
如果不使用 for...of 循环,可以手动调用遍历器对象的 next 方法,进行遍历。

与 forEach()不同的是,for of 可以正确响应 break、continue 和 return 语句

遍历对象 通常用 for in 来遍历对象的键名

7 种实现左边固定,右边自适应的方法

1 使用 calc
2 使用 flex
3 使用 float left 就可以
4 使用 左边 position: absolute, 右边 margin
5 左边右边 position:absolute
6 父亲 display:table;width:100% 左边 display: table-cell; 固定宽度,右边 display:table-cell
7 display: grid;/设置为 grid 布局/ grid-template-columns:200px auto;

为什么使用 pwa

首先,PWA 技术在国外的接受程度比较高,而且兼容性也比较好。因为美国那边主要是chrome 和 safari,然后那边的业务团队希望我们也能把PWA 部署到我们的应用上面,做离线访问和桌面的图标。

PWA 做了什么

PWA 全称是 Progressive Web Apps,意味着是渐进式的,也就是在现有的基础上进行逐渐添加,从而改善用户体验,并不需要推倒重来,对整个站点进行改造。

PWA 不是一个单独的技术,他包含一个 Manifest.json 用来配置桌面图标,定制 PWA 的启动画面的图标和颜色,service-worker,push apid,Notification API 等api。

我们的应用第一期迭代了桌面图标和缓存了静态资源的功能。

pwa 整个过程

1 register 首先是注册 server worker
2 install 安装 在这个过程中会根据你的缓存资源列表完成静态资源等的缓存
3 开始激活 service worker 清理旧版本的缓存资源
4 激活后,我们可以监听 fetch 事件,sync,push 推送
3 我们在 fetch 时间里面拦截请求,并且返回缓存。

Google AMP

AMP 不仅是一个前端技术框架,它还提供了一套完整的生态,包含一系列 ”服务于“ 页面加载体验的规范和约束

AMP HTML - 提供了一系列 AMP 自定义的 HTML 标签及规则,有效地保证了 HTML 的加载体验。
•AMP JS - 提供了一系列 JS 组件,其资源直接来源于 Google CDN。
•AMP Cache - 利用 Google 的缓存及预加载方案可以达到页面的秒出。

AMP 是由几个部分组成的:

html (普通 HTML + AMP 组件)
js (内联的脚本或者绑定属性)
cache (Google AMP cache 自动抓取)

劣势
只能使用 AMP 组件
不能使用 cookies 和 localStorage
无法直接支持 touch 事件
优势
重点关注在业务逻辑上,花更少的精力在性能方面,开发效率更高
依靠 AMP 获得了更好的性能
SEO 效果更好

GDD 2018

Flutter

Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。

Flutter 是 Google 开源的新一代跨平台 UI 框架。不同于其他我们熟知的移动端跨平台方案,
Flutter 更像游戏引擎,因为 Flutter 有自己的渲染引擎:我们在 Flutter 上写了界面后,
Flutter 会在自己的 canvas 上渲染,移动端不负责绘制。

React Native 能做到这些的核心原理就是 JavaScriptCore,一个 JavaScript 虚拟机。
通过 JavaScriptCore,Javascript 能和其它语言互相转义,同时 JavaScriptCore 能运行在 iOS,Android 以及其它平台上,
这些可能性放在一起,就成为了 React Native 的基底。有了这个基底之后,Facebook 便在这个基础之上,
封装了各平台的应用层接口,定义了 Javascript 和封装后的接口之间的通信协议,最终,实现了使用 JavaScript
在不同平台开发具有原生体验的应用。

骨架屏

是一种优化用户体验的方案。在页面完全渲染完成之前,用户会看到一个样式简单,描绘了当前页面的大致框架,感知到页面正在逐步加载,
最终骨架屏中各个占位部分被完全替换,体验良好。

图片懒加载原理

是什么
图片懒加载就是当图片还未滚动到可视区域前,先使用一张默认图片显示,等图片到达可视区域或者距离可视区域一定距离的时候,才设置真正的路径去请求图片。
为什么
因为我们可视区域的面积是一定的,当用户没有任何操作,其实这些图片是可以不用请求的,如果去请求一个是对服务器造成压力,另外一方面是造成浪费。所以 web 懒加载技术十分必要
怎么做
其实我们只要判断图片元素是否到达滚动到可视区域的一定距离,然后通过 js 动态修改元素的 src 就可以实现懒加载了。
判断公式:视口高度 + 滚动高度 + 自定义距离 > 图片距离距离文档原点的距离

你们有没有 Code Review

为了达到 Code Review 的目的,我们将 Git Flow 的 两个主分支:develop 和 master 设置为受保护的(protected)分支
,这样非项目 Owner 若要将代码提交到 protected 的分支上,就必须要使用 GitLab 的 Merge Request 功能,
我们的 Code Review 就是基于这个功能实现的。新建一个分支,然后提交 Merge Request,并且等待同时 code review 之后再删除该分支

为什么 bootstrap 选择 12 列布局

12 列网格是一种惯例,它不是标准。它存在的原因是因为 12 可以除以 1,2,3,4,6 和 12.因此它非常灵活。

栅格系统

栅格系统用于通过一系列的行(row)与列(column)的组合来创建页面布局,你的内容就可以放入这些创建好的布局中。

实现 parseInt()

function _parseInt(str, radix = 10) {
  let type = typeof str,
    result = 0;
  if (type !== "string" && type !== "number") return NaN;
  str = String(str)
    .trim()
    .split(".")[0];
  let length = str.length;
  if (!length) return NaN;
  if (typeof radix !== "number" || radix < 2 || raidx > 36) {
    return NaN;
  }
  let arr = str.split("").reverse();
  for (var i = 0; i < length; i++) {
    result += Math.floor(arr[i] * Math.pow(radix, i));
  }
  return sult;
}

自我介绍

面试官你好,我叫许健彬,毕业于仲恺农业工程学院,我的专业是电子信息工程。在大三的时候开始接触前端的,当时是在我们学院院长的一个实验室里学习和从事前端开发工作,
这个实验室是隶属于广东省科技厅的,因此接触到的项目主要是科研项目和政企相关的项目,技术栈也是以 Jquery 和 Bootstrap 为主。在里面完成几个项目之后,
在大三暑假就自己出来找了份实习工作。我的第一份校外实习工作是在唯品会的出口事业部,然后在该部门完成实习考核并且转正。在这段工作经历中参加了两个项目的开发,
第一个是海外 B2C 电商项目的移动端,该项目是唯品会的海外出口业务主营业务。该项目采用的技术栈是 zepto 和 ejs , 并且后期也对该项目用 vue 进行重构。
然后参与的另外一个项目是海外社交分享电商项目,该项目采用 vue 技术栈开发。然后去年 12 月份的时候,由于唯品会的战略方向改变,我们部门关停,
所以我和几位同事来到了一家做出口电商的创业公司,公司有 app 和移动端两条研发线,我负责整个移动端的技术研发任务。该项目使用 vue 的技术栈进行开发,
同时我这边还负责一层 node 中间层的服务开发。最后公司由于领导层变动和政策原因选择把国内的办公点搬去杭州,给了我们几个核心成员调薪和补贴让我们
过去那边, 我考虑了一下,还是想留在广州发展,所以选择n+1退出,然后近期在寻找前端开发的工作。

函数式组件和高阶组件

函数式组件是就是一个没有任何状态,也没有生命周期方法,可以接受一些 props 的函数,因为函数式组件只是函数,所以渲染开销低很多。
用处:
函数式组件主要用来做组件的外壳,就是写模版之前,可以先对传进来的上下文做一些处理。或者构建那些内部不需要太多的逻辑的 UI 组件或者动画。
例如日志打点功能,很多页面都需要用到,或者根据权限来渲染不同组件等功能。

高阶组件: 可以简单理解为可以生成其他组件的组件。它接受一个组件作为参数,并返回一个功能增强的组件。
所以函数式组件可以用作高阶组件。

rollup

Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。
适合 library 的开发,webpack 适合应用程序的开发

Rollup 还不支持一些特定的高级功能如代码拆分和运行时态的动态导入 import ,如果你的项目中更需要这些功能,那使用 Webpack 可能更符合你的需求。

插槽和作用域插槽

作用域插槽 通过 v-slot 能把父级作用域下的数据传到子组件

函数节流 throttle 与函数防抖 debounce

throttle 方案的电梯。保证如果电梯第一个人进来后,10 秒后准时运送一次,不等待。如果没有人,则待机。
debounce 方案的电梯。如果电梯里有人进来,等待 10 秒。如果又有人进来,10 秒等待重新计时,直到 10 秒超时,开始运送。

一般对于 window 的 resize 和 scroll 事件和 mousemove 事件,实际需求一般是采用 throttle 减少其回调函数执行频率;而文本输入自动完成等事件一般采用 debounce 来解决,

 function debounce(fn, wait, immediate, isfirst) {
     var timeout, first = isfirst || false;
     return  function() {
         var ctx = this,
             args = arguments;
         if(first) {
             fn.apply(ctx, args);
             first = false;
         }
         var throttler = function() {
             timeout = null;
             fn.apply(ctx, args);
         };
         if(!immediate) clearTimeout(timeout);
         if(!immediate || !timeout) timeout = setTimeout(throttler, wait);
     }
 },
     function throttle(fn, wait) {

     return this.debounce(fn, wait, true, true);
 },
function throttle(fn, wait) {
      let timer= null; // 保存定时器ID
      let immediate = true;
      return function () {
         if (!fn || typeof fn !== 'function') return;

         // 保证第一次执行不用等待
         if (immediate) {
           fn.apply(this, arguments);
           immediate = false;
           return;
         }
        if (timer) return; // 定时器存在,则说明还没执行需要return
        timer = setTimeout(async () => { 
          await fn.apply(this, arguments); // await 避免异步方法导致的问题
          clearTimeout(timer);
          timer = null;
        }, wait);
    };
 }

Vue 双向绑定实现原理

参考资料

通过数据劫持和结合发布-订阅模式来实现。最核心的方法便是通过 Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的。
首先通过数据监听器来监听属性变动,并且通知订阅者,然后需要一个指令解析器来遍历元素节点,替换数据和绑定更新函数,最后需要一个观察对象,
来订阅属性变动并且执行相应的回调函数。

模型(Model)通过 Observer、Dep、Watcher、Directive 等一系列对象的关联,和视图(DOM)建立起关系。归纳起来,Vue.js
在这里主要做了三件事:

通过 Observer 对 data 做监听,并且提供了订阅某个数据项变化的能力。
把 template 编译成一段 document fragment,然后解析其中的 Directive,得到每一个 Directive 所依赖的数据项和 update 方法。
通过 Watcher 把上述两部分结合起来,即把 Directive 中的数据依赖通过 Watcher 订阅在对应数据的 Observer 的 Dep 上。当数据变化时,
就会触发 Observer 的 Dep 上的 notify 方法通知对应的 Watcher 的 update,进而触发 Directive 的 update 方法来更新 DOM 视图
,最后达到模型和视图关联起来。

首先通过Object.defineProperty对数据的属性进行监听拦截,当获取数据的时候,就往订阅器上面增加监听对象,然后解析模板指令,
得到指令的数据项和绑定更新方法,把 Directive 中的数据依赖通过 Watcher 订阅在对应数据的 Observer 的 Dep 上然后当数据发生改变的时候,
就通过订阅器Dep上的notify 方法来通知执行监听器上面的更新方法,进行触发指令的更新方法来更新试图。

Vue 的依赖追踪通过 ES5 的 Object.defineProperty 方法实现。比如,我们给它一个原生对象,Vue 会遍历这个数据对象的属性,然后进行属性
转换。每一个属性会被转换为一个 getter 和一个 setter。同时每个组件会有一个对应的 watcher 对象,这个对象的职责就是在当前组件被渲
染的时候,记录数据上面的哪些属性被用到了。

例如,在渲染函数里面用到 A.B 的时候,这个就会触发对应的 getter。整个渲染流程具体要点如下:

当某个数据属性被用到时,触发 getter,这个属性就会被作为依赖被 watcher 记录下来。

整个函数被渲染完的时候,每一个被用到的数据属性都会被记录。

相应的数据变动时,例如给它一个新的值,就会触发 setter,通知数据对象对应数据有变化。

此时会通知对应的组件,其数据依赖有所改动,需要重新渲染。

对应的组件再次调动渲染函数,生成 Virtual DOM,实现 DOM 更新。

vue 怎么监听数组变化

第一步:先把原生 Array 的原型方法继承下来。

第二步:对继承后的对象使用 Object.defineProperty 做一些拦截操作。

第三步:把加工后可以被拦截的原型,赋值到需要被拦截的 Array 类型的数据的原型上。

Web Components

Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 web 应用中使用它们。
它有自己的生命周期并且可以设置回调函数,我们可以理解为像 video 这些标签

render 函数

render 函数叫做渲染函数,渲染函数是用来生成 Virtual DOM(vdom)的。render 函数依赖于页面上的 data,如果数据有所改变,
就会重新调用 render 函数,然后生成一个新的 virtual dom,然后新旧 vnode 进行 diff,最后更新
Vue 推荐使用模板来构建我们的应用界面,在底层实现中 Vue 会将模板编译成 renderFn 函数,当然我们也可以不写模板,
直接写渲染函数,以获得更好的控制。

vue 的整个生成过程

1 先实例化 vue,收集依赖和初始化事件
2 把 template 编译成 render 函数,这个过程包括把 html 解析成 AST,然后进行优化节点,最后再根据 AST 生成 render 函数
3 根据 render 函数生成 VNode(虚拟 dom)。
4 最后生成真实 DOM 插入 DOM 树中

vue 各个生命周期做了什么

生成 vue 实例
1 beforeCreate
收集依赖和初始化事件
2 created
生成渲染函数
3 beforeMounted
生成 dom 节点
4 mounted
5 beforeUpdate
重新生成虚拟 dom 并且 diff
6 Updated
7 beforeDestory 调用销毁方法
8 Destoryed

jquery 如何实现$() 创建对象不用 new,并且你看过源码嘛

Jquery 使用工厂方法,利用构造器创造了一个新的 jquery 对象并返回,省去了用户的 new 操作。
jQuery 在这里的原型对象上实现 init 构造方法,并且构造方法的原型对象指向 Jq 的原型对象,这样就可以实现新建一个 Jquery 对象并且继承到 jquery 原型方法。

var $ = jQuery = function(node) {
  return new jQuery.fn.init(node);
});
jQuery.fn = jQuery.prototype = {
  init: function(x) {
    return this;
  }
};
jQuery.fn.init.prototype = JQuery.fn;

jquery 源码看过部分,jquery 对于我们的意义就是帮我们屏蔽了各个平台的差异,解决了多浏览器的兼容问题,优化了 js,
同时使用方便,把底层的 api 进行了封装,同时里面一些设计模式还是值得我们学习的。

middleware

可以看成是插件,用来扩展功能模块,并且执行之后需要把控制权交给下层中间件

pm2

pm2 是一个带有负载均衡功能的 Node 应用的进程管理器.
当你要把你的独立代码利用全部的服务器上的所有 CPU,并保证进程永远都活着,0 秒的重载, PM2 是完美的。

暂时性死区

let 和 const 不存在变量提升,它们所声明的变量一定要在声明后使用
const 声明后必须赋值
在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性

六种继承的方式

1 原型链继承
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着proto这条链上的原型对象向上找,这就是原型链。

function Super() {
  this.name = "a";
}
Super.prototype.greet = function() {};
function Sub() {}
Sub.prototype = new Super();

优点: 能通过 instanceOf 和 isPrototypeOf 的检测
缺点:
1 存在引用缺陷,共享继承的引用类型属性,
2 无法向父类构造函数传递参数

2 构造函数继承

function Super() {
  this.name = "a";
}
function Sub() {
  Super.apply(this, argument);
}
Sub.prototype = new Super();

优点:解决了 superType 中的私有属性变公有的问题,可以传递参数
缺点:没有原型,每次创建一个 Child 实例对象时候都需要执行一遍 Parent 函数,无法复用一些公用函数。
3 组合式继承

function Super() {
  this.name = "a";
}
Super.prototype.greet = function() {};
function Sub() {
  Super.apply(this, argument);
}
Sub.prototype = new Super();

优点:继承前两者的优点,能通过 instanceOf 和 isPrototypeOf 的检测
缺点:两次调用父构造器函数,浪费内存

4 寄生继承

function createAnother(original) {
  var clone = object(original); //通过调用函数创建一个新对象
  clone.sayHi = function() {
    //以某种方式来增强这个对象
    alert("hi");
  };
  return clone; //返回这个对象
}
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);

5 寄生组合式继承

function Animal(color) {
  this.color = color;
}
Animal.prototype.greet = function(sound) {
  cinsole.log(round);
};
function Dog() {
  Animai.apply(this, arguments);
  this.name = "dog";
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

6 原型式继承

function create(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

var person = {
  name: "a",
  friends: ["b", "c", "d"]
};
var anotherPerosn = Object.create(person, {
  name: {
    value: "d"
  }
});

7 clsss 继承

class Animal {
  construct(color) {
    this.color = color;
  }
  greet() {
    console.log(111);
  }
}
class Dog extends Animal {
  construct(color) {
    super(color);
  }
}

Class 于原型链继承的区别

实际上他们没有任何区别,使用 class 就是让 javascript 引擎去实现原来我们需要自己编写的原型链继承代码

如何理解敏捷开发

我理解的敏捷开发就是相对于传统的开发模式,把整个产品完全地实现并验证之后才上线,而敏捷开发把产品拆成不同
的阶段,然后开发并且很快把它上线了,然后可以去验证,如果不对就下线,如果还有改进余地,
下个版本再去改它。整个流程就是一个快速试错和拥抱变化的过程。

如何理解语义化

首先语义化就是根据内容的结构化,选择合适的标签和规则来开发 html。
为什么
1 有利于 seo,因为爬虫依赖于 HTML 标记来确定上下文和各个关键字的权重
2 有利于代码的维护,让代码更具维护性和可读性
3 方便其他设备解析,比如屏幕阅读器和盲人设备等

怎么做
1 首先是正确命名 id 和 class ,还有正确的结构化代码,用上适当的语义化标签,注意不同标签的权重

H5 标签

header footer section aside(可用作文章侧栏等),nav,article

H5 新增了什么特性

1 canvas
2 语义化的标签
3 存储 localstore seeeionstore,cache
4 媒体 audio video
5 webworker websocket

websocket 能够用来让客户端和远程服务端通过 web 建立全双工通信

Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,它允许一段 JavaScript 程序运行在主线程之外的另外
一个线程中。工作线程允许开发人员编写能够长时间运行而不被用户所中断的后台程序, 去执行事务或者逻辑,
并同时保证页面对用户的及时响应,可以将一些大量计算的代码交给 web worker 运行而不冻结用户界面

CSS3 新增了

1 css 选择器
2 border-radius
3 flex 弹性布局盒子,可以无需计算去布局,方便排列
4 transform
5 媒体查询

配合 seo 具体做了什么

1 对于页面头部的标签进行了优化,因为我们通过域名来区分站点的,然后对于各个国家可以通过标签来进行区分,
比如 lannguage,country 这些来告诉爬虫,还有一些对爬虫有影响的标签进行优化(title,desc)
2 修改页面设计和布局不合理的地方,比如一些标签的权重不是很对,H1-h6 这些,这对于我们页面的关键字有影响,然后对于添加一些连接方面的东西加强链接的结构
3 优化 js 冗余代码和使用多一些缓存来优化。比如 amp,pwa 等

什么时候你不能使用箭头函数?

参考资料
1 定义对象方法
2 定义事件回调函数

  1. 定义构造函数

让 node 进程一直再跑

forever 管理多个站点,每个站点访问量不大,不需要监控。
pm2 网站访问量比较大,需要完整的监控界面。

bootstrap

优点
1 比较成熟,经过大量项目的使用和测试
2 完善的文档
3 大量组件样式,接受定制
缺点:
1 如果自己的样式需求,需要重新定制样式或者重写
2 有一些兼容问题,可以引入其他文件,但是比较大,会导致加载速度变慢

css 继承的属性

可继承:
font 相关的 fonts-size ,color ,font-family
文本相关的 text-align line-height
不可继承的:
盒子模型:width height margin padding
背景属性
定位属性

position

static 没有定位,出现在正常流
relative: 相对于其正常位置进行定位

margin 重叠

只有垂直 margin 会重叠

BFC 应用场景

如何创建
1 float 不为 node
2 position 的值不是 static 或 raletive
3 display 属性 为 table table-cell,flex, inline-block block ...
4 overflow: auto hidden scroll (不为 visible)

场景:
1 去除 margin 重叠
2 清除浮动
3 避免某元素被浮动元素覆盖

盒子模型

标准盒子模型: width height 包含 content
IE 盒子模型:width 包含 border 和 padding

line-height

“行高”顾名思意指一行文字的高度。具体来说是指两行文字间基线之间的距离

顶线,中线,基线,底线

层叠上下文

层叠上下文是 html 中的一个三维的概念。如果一个元素含有层叠上下文,那么在 z 轴上就会在普通元素之上。
顺序:从低到高
1 border/background 指的是层叠上下文元素的边框和背景色
2 负 z-index
3 block 水平盒子
4 float 盒子
5 inline inline-block
6 z-index 0 auto
7 z-index 正

AOT JIT

AOT 运行前编译
JIT 运行时编译

###为什么 javascript 是单线程
其实这与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式
操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程
2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作。当然,我们可以为浏览器引入“锁”的机制来解决这
些冲突,但这会大大提高复杂性,所以 JavaScript 从诞生开始就选择了单线程执行。

this 指向

如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的 this 指向该对象。如果函数独立调用,
那么该函数内部的 this,则指向 window。

通过 new 操作符调用构造函数,会经历以下 4 个阶段。

创建一个新的对象;
将构造函数的 this 指向这个新对象;
指向构造函数的代码,为这个对象添加属性,方法等;
返回新对象。

webpack3 到 webpack4

webpack 为什么那么难用
1 文档不完善,导致使用者和开发者遇到问题都很难下手;
2 项目需要使用的插件数量太多,且面向配置,导致维护成本指数级上升。

webpack4 受号称 0 配置的 parcel [ˈpɑːsl] 启发,webpack4 增加了一些默认配置,摒弃掉了一些难懂的配置,
对用户更加友好

1 默认入口和出口的路径
2 提供两种构建模式选择 development 和 production,通过配置 mode 选项,这用就不用通过 DefinePlugin 来定义环境变量
3 去掉 commonchunk,新增了 optimization 选项,智能的根据所选模式 mode 为做运行优化,默认开启压缩
4 使用新的 mini-css-extract-plugin 来代替旧的 css 提取方案,他可以将 css 拆成独立的包,同时配合 splitChunks
可以自定义 css 文件拆分

为什么不做小程序

公司从战略层面上考虑,因为我们的目标是打通海外市场,海外华人市场只是我们的第一步,在海外,PWA 技术的欢迎程序更高,
所以我们倾向于使用 PWA 技术来升级我们的站点,而不把资源投入到小程序上面。

PWA 参考网站

阅文集团起点国际站
企鹅电竞

分享画图技术点

api
lineto
fill
drawImage
arcTo
moveTo
measureText
beginPath
closePath

如何实现 返回列表保存访问位置

我们使用的 vue-router 的 keep-alive 来实现的。keep-alive 是针对组件来进行缓存的,所以存在一种情况需要兼容,
比如我们在首页有几个样式相同的 tab,他们对应的路由是/list/a, /list/b; 他们都使用相同的组件,但是这样子我们在第一个 tab
滚动,然后切换到第二个 tab,这时候可能还保存这第一个 tab 的距离或者数据。所以我们结合 vue-router 的导航守卫来进行设计,
在 routerLeave 的时候,设计一个对象缓存该路由对应的数据,然后当切换 tab 的时候,我们会匹配当前的路由并通过 routerUpdate
钩子把数据填回去。

列表优化

1 DOM 回收
因为 DOM 节点本身并非性能的消耗大户,但是每一个节点都会增加一些额外的内存、布局、样式和绘制。一旦达到一定的数量,
在低端设备上会发现明显的变慢或者卡死。DOM 回收可以让我们的 DOM 节点保持一个较低的水平。
原理就是通过判断列表的滚动距离来计算出这个距离中所对应的元素。
2 使用墓碑元素进行占位,当数据获取后,替换墓碑元素

jpg png webp jpeg gif

gif 是一种无损的图片格式,采用无损压缩,因为他只存储 8 位索引,所以最多只能表达 256 种颜色,所以色彩丰富的图片不适合保存为
gif,适合色彩简单的 logo,icon
jpg 采用有损压缩的方式,适合色彩比较丰富的图片保存,不适合小图标
png-8 采用无损压缩,对透明的支持比 gif 好,并且体积也更小,但是不支持动画
png-24 采用无损压缩,与 jpg 一样能表达丰富的图片细节,但是文件大小比 jpg 大很多

webp 支持有损压缩、无损压缩、透明和动画,并且相同视觉体验下,比其他的体积小了 1/3;

JPG 和 JPEG 其实是一个东西,没有区别,全名、正式扩展名是 JPEG
jpeg 渐进式 jpg,jpg 是从上往下显示,jpeg 是从模糊到清晰显示

比如说 logo、 icon 等色彩简单对透明度无要求的场景,更适合选用 gif;色彩丰富,同时又对图片尺寸有要求的场景,更适合选用 jpg;
需要透明半透明的 logo、icon 等简单图片或者需要保存为源文件便于二次修改的场景,更适合采用 png。

模板和 JSX

模板和 JSX 是各有利弊的东西。模板更贴近我们的 HTML,可以让我们更直观地思考语义结构,更好地结合 CSS 的书写。
JSX 和直接渲染函数,因为是真正的 JavaScript,拥有这个语言本身的所有的能力,可以进行复杂的逻辑判断,进行选择性的
返回最终要返回的 DOM 结构,能够实现一些在模板的语法限制下,很难做到的一些事情

如何理解 vue 是一个渐进式的框架

vue 作者尤雨溪:Vue 2.0,渐进式前端解决方案

从 vue 整个生态来说,他其实包含了非常多的东西,比如声明式的渲染,组件系统,客户端的路由,还有状态管理工具,还有构建工具
vue-cli。但其实构建一个应用不必要说全部都用上去,我们可以根据应用的复杂度去选择工具,来构建一个应用,这就是 vue 渐进式的想法。

对于组件的设计规则

写过『通用前端组件』吗?
1 首先是细粒度的考量 select
2 通用型考量 通用组件是与业务解耦但是又服务于业务开发的,可以通过 slot 赋予使用者一定的控制权,组件本身负责基本的行为和
dom 结构

sentry 原理

sentry 实现前端错误监控,通过对 window.onerror、window.onunhandledrejection(promise),还有能进行回调处理的函数
进行包装重写,统一不同浏览器环境下错误对象的差异(chrome,firefox,ie),输出统一的 stacktrace 后,重新整理数据结构。
再将最后处理过后的信息提交给 sentry 服务端处理。

为什么要选择 vue 去升级

1 拥抱现代化的开发方式。

首先是从开发效率上来说,使用 mvvm 框架的开发效率比较高,可维护性强。
二是基于学习成本来看,vue 的学习曲线相对平缓,并且团队的技术栈是 vue。(JSX ES6)
三是 Vue.js 的单组件系统,基于 HTML 的模板使得将已有的应用逐步迁移到 Vue 更为容易。

rebase

rebase 会把你当前分支的 commit 放到公共分支的最后面,所以叫变基
merge 会把公共分支和你当前的 commit 合并在一起,形成一个新的 commit 提交

选择 merge 还是 rebase 取决于你对 commit 历史时间线的定义
有两种观点:第一种认为,commit 历史应该显示的是什么时候具体发生了什么事,比如分支的创建与合并过程,有哪些分支,
分别合并在了什么地方等等。另一种认为,commit 历史应该显示的是这个项目经历过的状态,而不考虑具体的分支构建过程

rebase 你本地的修改,push 到多人环境中时用 merge

express 和 koa

所谓中间件就是在收到请求和发送响应之前这个阶段执行的一些函数。

Express 是一个简洁而灵活的 node.js Web 应用框架,可以设置中间件来响应 HTTP 请求。定义了路由表用于执行不同的 HTTP 请求动作。
可以通过向模板传递参数来动态渲染 HTML 页面。

express 内置了 router、view 等功能,通过中间件形式把业务逻辑细分,一个请求进来经过一系列中间件处理后再响应给用户
缺点: Express 是基于 callback 来组合业务逻辑,Callback 有两大硬伤,一是不可组合,二是异常不可捕获

Express 中的中间件分为两类: 通过 router.use()方法挂载的路由中间件和通过 app.use()方法挂载的应用中间件
如果将任何项传递到 next() 函数(除了字符串 'route'),那么 Express 会将当前请求视为处于错误状态,并跳过所有剩余的非错误处理路由和中间件函数

那如何去处理抛出错误,最简单的方法在每个回调里面写 try-catch,但是这样太麻烦了
第二种方法是,回调函数使用 async awit

const asyncHandler = fn => (req, res, next) =>
  Promise.resolve()
    .then(() => fn(req, res, next))
    .catch(next);

router.get(
  "/",
  asyncHandler(async (req, res) => {
    const user = await db.userInfo();
    res.json(user);
  })
);

第三种方法我们可以修改路由层,因为每个路由对应会生成一个 layer 实例,并且由 router 匹配,执行回调

const Layer = require("express/lib/router/layer");

Object.defineProperty(Layer.prototype, "handle", {
  enumerable: true,
  get() {
    return this.__handle;
  },
  set(fn) {
    if (fn.length === 4) {
      this.__handle = fn;
    } else {
      this.__handle = (req, res, next) =>
        Promise.resolve()
          .then(() => fn(req, res, next))
          .catch(next);
    }
  }
});
app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send("Something broke!");
});

koa 借助 promise 和 generator 的能力,丢掉了 callback,完美解决异步组合问题和异步异常捕获问题。并有力地增强错误处理
koa 把 express 中内置的 router、view 等功能都移除了,使得框架本身更轻量化。

app.on("error", err => {
  log.error("server error", err);
});

资料
express 是基于 connect 开发的,connect 是一个 node 的中间件,用来处理 http 请求。比如说 cookie 解析,参数解析,url 解析等

304缓存,有了Last-Modified,为什么还要用ETag?有了Etag,为什么还要用Last-Modified?Etag一般怎么生成?

有了Last-Modified,为什么还要用ETag?
(1)因为如果在一秒钟之内对一个文件进行两次更改,Last-Modified就会不正确。
(2)某些服务器不能精确的得到文件的最后修改时间。
(3)一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,
而重新GET。

有了Etag,为什么还要用Last-Modified?
因为有些时候 ETag 可以弥补 Last-Modified 判断的缺陷,但是也有时候 Last-Modified 可以弥补 ETag 判断的缺陷,比如一些图片等静态文件的修改,如果每次扫描内容生成 ETag 来比较,显然要比直接比较修改时间慢很多。所有说这两种判断是相辅相成的。

ETag的值是服务端对文件的索引节,大小和最后修改时间进行Hash后得到的。

flexbox

是一个弹性布局盒子,用来提供一种方便的布局方式,不需要计算布局,而是通过自身的属性来自适应布局。

SEO 优化

有挺多方面的,比如常见的头部,TDK 优化,页面布局优化,使用合适的权重标签,url 优化,sitemap 和 robots.txt 等。

手写 ajax

function ajax(url) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
      console.log(xhr.responseText);
    }
  };
  xhr.send();
}

readyState是XMLHttpRequest对象的一个属性,用来标识当前XMLHttpRequest对象处于什么状态。
status是XMLHttpRequest对象的一个属性,表示响应的HTTP状态码
为什么onreadystatechange的函数实现要同时判断readyState和status呢?
第一种思考方式:只使用readyState
服务响应出错了,但还是返回了信息,这并不是我们想要的结果
如果返回不是200,而是404或者500,由于只使用readystate做判断,它不理会放回的结果是200、404还是500,只要响应成功返回了,
就执行接下来的javascript代码,结果将造成各种不可预料的错误。所以只使用readyState判断是行不通的。
第二种思考方式:只使用status判断
onreadystatechange函数的执行不是只在readyState变为4的时候触发的,而是readyState(2、3、4,)的每次变化都会触发,
3,4 阶段已经是请求返回了,这时http状态码已经确定,所以就出现了前面说的那种情况。可见,单独使用status判断也是行不通的。

Promise 实现方法

Promise((resolve, reject) => {
  setTimeout(() => {
    resolve;
  }, 0);
}).then(val => {}, err => {});

const pending = 0,
  finish = 1,
  fail = 2;
function Promise(fn) {
  var self = this;
  this.state = pending;
  this.value = null;
  this.resolveEvent = [];
  this.rejectEvent = [];
  self.then = function(onResolve, onFail) {
    self.resolveEvent.push(onResolve);
    self.rejectEvent.push(onFail);
    return self;
  };
  function resove(val) {
    if (self.state == pending) {
      self.state = finish;
      self.value = val;
      self.resolveEvent.map((el, index) => {
        el(val);
      });
    }
  }
  function reject(err) {
    if (self.state == pending) {
      self.value = err;
      self.state = fail;
      self.rejectEvent.map((el, index) => {
        el(err);
      });
    }
  }
  fn(resove, reject);
}
new Promise((resolve, reject) => {
  setTimeout(
    (resolve, reject) => {
      resolve(1);
    },
    5000,
    resolve,
    reject
  );
}).then(
  val => {
    console.log(222222222222, val);
  },
  err => {
    console.log(err);
  }
);

Node js 异常捕获

1 try catch 无法捕捉异步回调里的异常
2 Node.js 原生提供 uncaughtException 事件挂到 process 对象上,用于捕获所有未处理的异常
缺点: 失去错误的上下文(response 对象),没办法定位错误发生位置,需要做些处理,不然整个服务会 down 掉
3 使用 domain 模块捕捉异常

socket 和 http

参考
tcp 传输层,ip 网络层
http 建立的是短链接 Http 协议是基于 TCP 链接的。 应用层协议
socket socket 是对 TCP/IP 协议的封装,Socket 本身并不是协议,而是一个调用接口(API),通过 Socket,我们才能使用 TCP/IP 协议 建
立的是长链接
HTTP 是轿车,提供了封装或者显示数据的具体形式;Socket 是发动机,提供了网络通信的能力。

OpenGL

OpenGL(英语:Open Graphics Library,译名:开放图形库或者“开放式图形库”)是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)

jquery 源码值得学习的地方

1 jquery源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染
2 不同要new 来实例化对象
3 链式调用模式

常见请求头

[
100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息
200 OK 正常返回信息
201 Created 请求成功并且服务器创建了新的资源
202 Accepted 服务器已接受请求,但尚未处理
301 Moved Permanently 请求的网页已永久移动到新位置。
302 Found 临时性重定向。
303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
304 Not Modified 自从上次请求后,请求的网页未修改过。

    400 Bad Request  服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
    401 Unauthorized 请求未授权。
    403 Forbidden   禁止访问。
    404 Not Found   找不到如何与 URI 相匹配的资源。

    500 Internal Server Error  最常见的服务器端错误。
    503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。
]

他们都是重定向 并且有细微的区别,他们的区别主要来源于HTTP/1.0 和HTTP/1.1 应用程序对这些状态码处理方式不同而不同

http1.0:只有302码,没有303和307状态码;

http1.1:有302(理论上是可以放弃的,为了兼容1.0被保留,而且因为目前程序都没那么讲究所以302大量出现在一些项目上),303,307

302:HTTP/1.0和HTTP/1.1中的表现都是一样的

303:POST重定向为GET。

307:需要跟用户询问是否应该在新URI上发起POST方法,也就是说,307是不会把POST转为GET的

性能优化

代码优化
1 使用懒加载
2 使用委托代理
3 函数节流防抖
4 减少引起重排重绘
5 css 选择器不要太多复杂,因为是从右边往左去查询
4 编写动画的时候使用css3属性开启硬件加速

资源优化
1 静态资源的压缩合并
2 图片资源使用Base64,或者精灵图来减少请求
3 内联css资源

请求优化
1 避免永久重定向
2 使用DNS 预解析。避免太多的DNS请求
3 减少TCP请求,可以通过资源合并,或者http1.1使用keep-alive,或者使用http2.0 的多路复用 4 使用服务端渲染,还可以使用defer 或者async控制渲染顺序

性能优化其实就是让页面加载速度很快,使用起来很流畅,那如果要优化,我们就应该找到我们网站的性能瓶颈在哪,这个包含两个方面,一个是我们前端页面的加载速度,另外一个方面是接口的响应速度,接口的响应速度我们一般是通过一些日志打点来统计,对于前端我们是采用ga平台和一些第三方的分析网站来做这个性能分析的。因为我最近的这份工作是在创业公司的初始阶段,是以业务优先,对于性能优化这方面如果没有遇到严重的性能方面的问题,是没有特别去做,基本都是自己对于一些注意基础代码层面上和一些比较大的收益方面去做比如说加上cdn这些缓存。主要有这两个方面:首先是代码层面上,比如说懒加载,委托代理,节流防抖,减少引起重绘重排的代码(比如批量修改dom,使元素脱离文档流和提升到复合层),然后第二个层面是对于网络请求的优化:首先是对于DNS的优化,我们可以通过dns-prefectch 来预解析一些域名,然后可以通过利用http2.0 的优点,对于打包的策略进行一些优化,比如我们可以把一些资源进行更合理的拆分,还有对于一些异步组件拆分这些比较基础的东西。webpack4有一个新的css提取插件,我们可以利用它配合上split chunk 进行模块的拆分打包。然后后面我是想加上一些骨架屏等优化体验方面上的东西和离线访问的功能这些,这些都是属于有着比较直接的收益的方案。

grunt gulp webpack

Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。
 Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,
使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

grunt 和gulp
1 书写方式 grunt 运用配置的思想来写打包脚本,一切皆配置 gulp 是用代码方式来写打包脚本,并且代码采用流式的写法
2 grunt 任务对应的是树的形式,大任务里面可以嵌套子任务,gulp只能通过多个task的形式
3 grunt 是串行执行,gulp 基于并行执行任务的思想,通过一个pipe方法,以数据流的方式处理打包任务。通过回调函数形式来实现顺序执行
4 grunt 是基于文件的,gulp是基于流

手写bind 和call 和appluy

bind

Function.prototype.myBind = function(context){
	if (typeof this !== 'function') {
    	throw new TypeError('Error')
  	}
	let _this = this,
	 	args = [...arguments].slice(1);
	return function (){
		// 判断是否被当做构造函数使用
		if (this instanceof F) {
		return _this.apply(this, args.concat([...arguments]))
		}
		return _this.apply(context,args.concat([...arguments]));
	}
}

apply

Function.prototype.myApply = function (context){
 if(!context) context = window;
 context.fn = this;
 let args = arguments[1],
 result;
 if(args) {
   result = context.fn(...args)
 } else {
   result = context.fn();
 }
delete context.fn;
return result;
}

call

Function.prototype.myCall = function (context){
  if(typeof context == "undefined" || context = null) context = window;
  context.fn = this;
  let args = [...arguments].slice(1),
    result;
  result = context.fn(...args)
  delete context.fn;
  return result;
}

反向代理和正向代理

正向代理
一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容, 客户端向代理发送一个请求并指定目标(原始服务器),然后代理
向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
反向代理
以代理服务器来接受 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给
Internet 上请求连接的客户端,反向代理可以看成多台服务器和代理服务器之间的代理,与正向代理相反

为什么需要测试

从语言角度
JavaScript 是动态语言, 缺少类型检查,编译期间无法定位到错误
JavaScript 宿主的兼容性问题, 比如 DOM 操作在不同浏览器上的表现
从工程角度
测试可以快速反馈你的功能输出,验证你的想法
测试可以保证代码重构的安全性,没有一成不变的代码,测试用例能给你多变的代码结构一个定心丸。

测试框架通常提供TDD(测试驱动开发)或BDD(行为驱动开发)的测试语法来编写测试用例,

前端测试

[聊一聊前端测试]tmallfe/tmallfe.github.io#37

e2e

什么是E2E: E2E(End To End)即端对端测试,属于黑盒测试,通过编写测试用例,自动化模拟用户操作,确保组件间通信正常,程序
流数据传递如预期。

前端单元测试

mocha是一款功能丰富的javascript单元测试框架,支持TDD,BDD等多种接口,它既可以运行在nodejs环境中,也可以运行在浏览器环境中。
mocha可以良好的支持javascript异步的单元测试
Jesmine
内置断言库,集成度高,方便支持异步测试,但是灵活性差,断言风格单一。

断言库
1 assert assert模块是Node的内置模块,用于断言。
2 should.js should.js是个第三方断言库,常和Mocha联合使用。
3 Chai Chai是个断言库,常和Mocha结合使用。他有多种断言风格(assertion style):assert, expect, should

测试辅助工具:Sinon
为什么需要Sinon?在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,记录Log,
文件系统之类的),而我们没法控制这些外部依赖的对象。例如:前端项目通常是用Ajax去服务端请求数据,得到数据之后做进一步的处理。
但是做单元测试的时候通常不真的去服务端请求数据,不仅麻烦,可能服务端接口还没做好,这种不确定的依赖使得测试变得复杂。
所以我们需要模拟这个请求数据的过程,Sinon用来解决这个问题。

从零开始做Vue前端架构(6)单元测试 & 代码覆盖率

post和get的区别

1 传输方式上,get通过url,post通过request body
2 http协议并未规定get和post的长度限制 post数据量无限制,get 有限制,跟url 长度限制一样,不同浏览器和服务端对url 的限
制不一样,支持Chrome,则最大长度8182byte
3 数据类型,get 只能发送字符串,post能更多的类型,json,form-data
4 意义,get 是用来获取信息的,post是用来写入或者修改数据的
5 post比get慢,一个原因post在真正接收数据之前会先将请求头发送给服务器进行确认,post 包含更多的请求头,get会将数据缓存起来
,而post不会

###js浮点数运算不精确 如何解决?
通过toFixed(num)方法来保留小数。因为这个方法是根据四舍五入来保留小数的,所以最后的计算结果不精确。
把要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,计算完以后再降级

function add(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length;
  const num2Digits = (num2.toString().split('.')[1] || '').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}

工作中比较出色和头疼的点

1首先是技术上的,因为我们随着用户的增加,会有更多的问题不断地暴露,那我们要做的是就是把前端监控这一部分给加进到我们的业务中
,因为当时我们做的监控更多的是后端的监控和日志,所以我就推进前端监控在我们业务中的落地。还有一个方面我们的业务在美国,对于那边
的网络状态我们没办法控制,而且我们当时也没有加上cdn这些东西,整个应用的加载速度比较慢,所以我采用了PWA的技术方案,应为对于国外,
pwa的推广度更高,所以我当时用srever-worker进行一期的性能优化,用来缓存我们网站的静态资源。这些技术方案都是参考一些国内公司出海项目
的经验。
2 是业务上面的吧,因为我们是创业公司,整个迭代任务很紧也很重,当时我们的人手也不是很多,所以我们会让业务进行优先级排序和对一些业务
给出渐进式的实现方案,比如一个佣金结算和体现,因为们的提现周期是一个月,那其实对于佣金的提现其实我们不必和佣金结算这个版本同时上线
,我们可以在页面中先给文案提示,然后在后面迭代上去,拆分成两个版本。这就涉及到敏捷开发的概念,我们对于应用的开发,其实是一个不断地试错过程,我们没必要
等到功能完整开发完之后才上线。

脏检测

当一个作用域创建的时候,angular会去解析模板中当前作用域下的模板结构,并且自动将那些插值(如{{text}})或调用
(如ng-click="update")找出来,并利用$watch建立绑定。
Angular对常用的dom事件、xhr事件进行了封装,触发时会调用调用$scope.$apply(),$apply内部会触发$digest递归遍历
$digest cycle。
在$digest流程中,Angular将遍历每个数据变量的watcher,比较它的新旧值。当新旧值不同时,
调用listener函数进行相应操作,并将旧值更新为新值。它将不断重复这一过程,直到所有数据变量的新旧值相等;
最后更新界面

缺点;在 AngularJS 中,当 watcher 越来越多时会变得越来越慢,因为作用域内的每一次变化,所有 watcher 都要重新计算。
并且,如果一些 watcher 触发另一个更新,脏检查循环 (digest cycle) 可能要运行多次

docker

Docker,是一款现在最流行的 软件容器平台,提供了软件运行时所依赖的环境

虚拟机
是什么
在物理机上 模拟出一套硬件环境和操作系统,应用软件可以运行于其中,并且毫无感知,是一套隔离的完整环境。本质上,它只是物理机上的一份 运行文件。
为什么
解决环境配置与迁移的繁琐问题
提高资源利用率与隔离;

传统虚拟机的缺点
资源占用大
启动缓慢 由于完整系统,在启动过程中就需要运行各种系统应用和步骤
冗余步骤多:系统登陆和检查等

Linux 容器:
在 进程层面 模拟出一套隔离的环境配置,但并没有模拟硬件和完整的操作系统。
因此它完全规避了传统虚拟机的缺点,在启动速度,资源利用上远远优于虚拟机;

Docker 就是基于 Linux 容器的一种上层封装,提供了更为简单易用的 API 用于操作
三个核心概念
镜像:
从原理上说,镜像属于一种 root 文件系统,包含了一些系统文件和环境配置等,可以将其理解成一套 最小操作系统。
一个镜像就是一个 Image 二进制文件,可以任意迁移,删除,添加;
容器
容器可以理解成镜像的实例,一旦创建后,就可以简单理解成一个轻量级的操作系统,可以在内部进行各种操作,
例如运行 node 应用,拉取 git 等
仓库
为了便于镜像的使用,Docker 提供了类似于 git 的仓库机制,在仓库中包含着各种各样版本的镜像。官方服务是 Docker Hub;

内存泄露

意外的全局变量: 无法被回收
定时器: 未被正确关闭,导致所引用的外部变量无法被释放
事件监听: 没有正确销毁 (低版本浏览器可能出现)
闭包: 会导致父级中的变量无法被释放
dom 引用: dom 元素被删除时,内存中的引用未被正确清空

你有没有遇到什么挫折,是怎么解决的

没有什么太大的挫折,但是遇到过算是比较大的挑战吧,当时来到一家创业公司这里工作,在那里自己负责的是一整个移动端的项目,对于自己
来说,算是比较大的挑战,毕竟在当时算是一个应届毕业生。在那时候,自己需要负责整个应用的选型,架构,开发,还有需求对接,进度对接等一堆会议
,每天基本都处于超负荷的状态,一开始也比较忙乱,比较累,

你的未来规划是什么

未来两三年,我希望打磨一下自己的技术广度和深度,并且制定一些读书计划,培养自己的管理思维和能力,最终朝着团队leader的角色努力。

你觉得你是一个什么人

优点:
我觉得自己是一个比较有责任心的人,也算是一个工作狂,认定一件事,一个目标,就拼尽全力去做,当时在创业公司的时候,我们基本
是九九六的工作状态,我记得我离职的时候,我还剩下一个星期的假还没有调休,不过公司最后面还是补偿给我了,但同时也是一个享受
生活的人,周六日的时候,我喜欢自己做饭,然后和舍友或者叫上朋友一起过来吃饭,我很享受做饭的宁静和创造感。我认为我们还是应该
区分生活和工作,这样才能有持续的热情来工作和生活。
缺点:
自己可能会有点自控力不足,没有很好的控制自己,比如说有时候想做一件啥事情就去做了,或者看着手机看着看着时间就过了。这些
有的时候是十分正常的生活现象,但是很可能这些行为就是别人在收割我们的注意力来产生商业价值,所以我现在做事情前会更理智
思考一下,是不是值得做或者要花多少时间来做。

声明式和命令式

vue和jquery之间的区别就是一个是声明式一个是命令式,jQuery是命令式的操作DOM,命令式的局部更新视图,而现代主流框架Vue,React,Angular等都是声明式的,声明式的局部更新视图。

是两种编程编程范式
声明式就是你不用管具体怎么实现,我们只要告诉程序我们想要什么 比如vue

var numbers = [1,2,3,4,5]
var total = numbers.reduce (function (sum, n) {
  return sum + n
});
console.log (total) //=> 15

命令式就是按照你的命令一步步实现,比如jquery

var numbers = [1,2,3,4,5]
var total = 0 for(var i = 0; i < numbers.length; i++) {
  total += numbers[i]
}
console.log (total) //=> 15

nodejs子进程 spawn,exec,execFile和fork的用法和区别?

都是用来创建进程的
spawn函数用给定的命令发布一个子进程,只能运行指定的程序,参数需要在列表中给出。使用spawn函数会将标准的IO对象转换为流。

exec也是一个创建子进程的函数,与spawn函数不同它可以直接接受一个回调函数作为参数,回调函数有三个参数,分别是err, stdout ,
stderr。exec函数先将所要返回的数据缓存在内存中,然后返回。

execFile函数与exec函数类似,但execFile函数更显得精简,因为它可以直接执行所指定的文件。execFile函数比exec函数高效因为execFile并不会衍生新的shell

fork函数可直接运行Node.js模块,我们可以直接通过指定模块路径而直接进行操作。通过fork函数衍生的
子进程会建立通信管道,衍生的子进程可以通过send函数向主进程发送信息,主进程也可以通过send函数向子进程发送信息

为什么 Math.random() 实现乱序 时不彻底

var values = [1, 2, 3, 4, 5];

values.sort(function(){
    return Math.random() - 0.5;
});

console.log(values)

因为sort函数不同浏览器实现的方式还不一样,v8 在处理 sort 方法时,当目标数组长度小于 10 时,使用插入排序;反之,使用快速排序和插入排序
的混合排序。其实就在于在插入排序的算法中,当待排序元素跟有序元素进行比较时,一旦确定了位置,就不会再跟位置前面的有序元素进行比较,导致
所以就乱序的不彻底。

function shuffle(a) {
    for (let i = a.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
}

移动和 PC 以及 toB 和 toC 的区别

移动和PC
1 响应式和布局方面的不同
2 要兼容的是不同浏览器,移动端要兼容的更多的是不同的手机。移动端的浏览器基本都用的是wikit内核,更多考虑的应该是手机分辨率的适配
3 对于交互事件上也有区分,PC的是鼠标交互,而移动端的是触摸操作。

Tob 和ToC 的区别
1 首先 ToC类产品为信息架构相对简单的产品,主要着重于用户体验方面,而ToB是流程上和功能上比较复杂且对用户的亲切性低
2 架构层面上,C类产品的挑战难度应该是高并发,而B类产品的挑战在于业务复杂度
3 技术实现细节上面,ToB 需要一个统一、规范的交互组件库来降低设计与开发成本,保证用户体验不被打断,而ToC 业务基本上对于UI和交互
的体验上要求比较高,迭代速度也会比较快。

GC 新生代老生代

其中新生代包括一个 New Space,老生代包括 Old Space,Code Space,和 Map Space,
此外还有一个特殊的 Large Object Space 用于存储特别大的对象(也属于老生代)
新生代
对象的布局结构信息在 Map Space 分配
编译出来的代码在 Code Space 分配
老生代
从新生代晋升过来的对象(存活超过2次)

你不知道的Node.js性能优化

React 16 加载性能优化指南

webpack 原理

从入口文件出发,webpack 递归地构建一个依赖关系图,这个依赖图包含着应用程序所需的每个模块,然后将所有这些模块打包为少量的 bundle - 通常只有一个 - 可由浏览器加载。

文章

express 中间件做了什么

1 一些路由的跳转处理,比如我们对分享url 做了处理,因为我们是通过url 来生成二维码的,url 太长会导致二维码生成有问题,所以我们做了短链处理,然后我们在定义了一个中间件来处理这种短链
还有分享的url我们会带上语言的标识,我们需要对它进行处理,然后跳转。
2 拼接分享页面
3 还有处理连登登陆的逻辑,用来sdk连登之后跳转然后获取url的code信息进行我们自己的服务登陆

中间件

功能可以执行以下任务:
执行任何代码。
更改请求和响应对象。
结束请求 - 响应周期。
调用堆栈中的下一个中间件函数。

Express应用程序可以使用以下类型的中间件:

应用程序级中间件
路由器级中间件
错误处理中间件
内置中间件
第三方中间件

node 支持promise 和async

Node.js 8(V8 v6.8/Chrome 68) 开始支持 async 函数,异步迭代和生成器在 Node.js 10 (v8 6.8/Chrome 68) 开始支持!

技术选型

支付宝前端应用架构的发展和选择
阅文前端技术选型
美团点评金融平台Web前端技术体系

TypeScript

如果你的项目中需要更好的质量和可用性保证,那么可以引入 TypeScript,在JS 层面我们遇到的最多的运行时问题就是 something is undefined,也就是空指针问题,
通过引入强类型语言,可以帮助我们在开发或编译阶段进行强类型的检查,使用类型系统让代码可控性、扩展性更强,协作更方便。

优点
TypeScript 是 JavaScript 的超集,其作用只在开发阶段发挥,其生成的代码不包含任何类型代码,但由类型系统保障
IDE 支持极好,除了自家的 VSCode 集成度超高,用户增长飞速,TypeScript 还支持市面上几乎所有主流 IDE
社区庞大,周边工具丰富

如果让你架构一个前端项目,你会从什么方面入手

1 对于框架的选型
2 思考如何对前后端的协作进行解耦
3 引入自动化构建部署流程
4 思考对于测试和代码质量方面的保证

项目规模、项目生命周期、团队规模、团队成员情况等实际情况综合考虑

工作中如何做好技术积累

工作中如何做好技术积累

你觉得创业经验对于你来说有什么收获

在创业经历中,我觉得最大的收获有两方面,一个是对于技术的提升,另外一方面是对业务更加熟悉。
首先是业务方面,因为自己是负责整个移动端,所以需要对业务比较熟悉,你需要了解需求才能去实现需求。比如对于电商网站的一整个购物流程,各种营销规则,第三方的登陆支付等等,都需要去了解。
其次是对于自己的技术方面的提升:在创业团队,会受限于各种资源的问题,所以基本都是一个人需要control 很多的东西,所以需要思考很多方面的东西,你需要对整个开发流程都很熟悉,对项目的整体架构有一个把控,思考如何设计项目,对于以后的扩展和维护怎么做,同时如果后面有成员增加,如何能快速上手,这些都需要你的经验和快速学习和应用能力。举个例子,譬如说我们前期要快速上线,而且工期很赶,对于代码质量没有做到百分之百,后面经常会出现一些问题,一开始我们可以通过后端的日志等去调试,可以解决大部分问题,但是后面也有一些问题可能是发生在客户端那边的,经过日志也没定位到问题,所以在后面的时候,我们需要思考如何去预防问题和快速解决,这点我通过引入监控系统sentry 来解决。那对于更细的问题,比如在项目中遇到的各种奇奇怪怪的问题,也需要自己查找各种资料或者使用一些调试方法来帮助你解决问题。

rem 的两种方案

移动端适配的问题在于高倍屏(retina) 的设备像素比较高,比如设备像素比为2 ,即布局适口1px 相当于 2px的设备像素比。

一种是借助JavaScript来动态修改meta标签中viewport中的initial-scale的值,把将布局视口大小设为设备像素尺寸,然后通过动态修改html中的font-size值,所以不同设备下相同的rem值对应的像素值是不同的,这样就实现了在不同设备下等比缩放。

另外一种是不设置缩放,然后设置一个数字,把网页分成n份,比如在设计稿中750,分成10份,那这样子1rem 就是 75px,那对于iphone 5 就是1rem = 320 / 7.5 ==42.666666667px; 其实就是按比列去算出 1rem 对应的像素的大小。

其实移动端的适配其实就是按照比例去算出那个像素大小,比如设计稿一个div 是75px, 我们把设计稿750px 分成 10份,每份 75px, 那再375的屏幕下面,他的css 像素大小就是 375 / 10 * 1 = 37.5px; 那这样就是按照比例缩小,然后我们再通过rem 这个单位来进行统一的换算计算。

flexible 2.0 设置body 的.fontSize 是为了不影响字体
一篇真正教会你开发移动端页面的文章-二
1px 实现方案
使用Flexible实现手淘H5页面的终端适配 #17

vue 3.0

1 更小,编译出对tree-shaking 更友好的代码
2 更快

基于proxy ,性能优于getter,setter,
对于 virtual dom 的重构(因为传统的vnode 更新的时候是通过便利模板去更新dom,这样子如果模板上的动态节点很少,而静态节点很多,这种遍历都是性能的浪费,新版通过将模板切分为block,每个block 根节点去记住动态节点的信息,更新的时候直接遍历动态节点)
增加更多的编译时优化
3 自身的可维护性提高,通过ts重写
4 新加 函数api的形式来更好的分离组件逻辑
在2.0 的时候,我们通过多种方式来实现代码复用,比如说,Mixins, 高阶组件,或者是slot 这些方式,但是这些方式都存在一些缺点,比如混入的属性来源不清晰,存在命名冲突,而且对于slot 和高阶组件这些多余的组件实例可能存在一些性能上的浪费。对于函数式api 的形式来分离状态逻辑逻辑达到复用的目的,只共享数据处理逻辑,不会共享数据本身,在这类api中依旧使用 state 和 life-cycles。

vuejs/rfcs-function-api
一篇看懂 React Hooks
React v16.7 "Hooks" - What to Expect

性能优化

在我看来,性能优化涉及多个方面,但是主要的思路还是从关键渲染路径来考量的,即dns优化、使用cdn、资源压缩打包、开启gzip、再到重绘与回流,再到浏览器缓存策略的配置等常规的优化方案。在实际中,因为我最近的这份工作是在创业公司的初始阶段,是以业务优先,对于性能优化这方面如果没有遇到严重的性能方面的问题,是没有特别去做,基本都是自己对于一些注意基础代码层面上和一些比较大的收益方面去做比如说加上cdn缓存和配置缓存策略等方面。

这里可以从电商网站的特点过渡

因为我们是电商类型的网站,首先考虑的是首页的渲染时间,主要采取了模块和图片的懒加载,只加载首页相关的东西和渲染可视区域的图片。其次也有考虑使用服务端同构,但是由于时间和开发资源的关系,目前还未正式采用这一方案,仍然处于探索阶段。
主要有这两个方面:首先是代码层面上,比如说懒加载,委托代理,节流防抖,减少引起重绘重排的代码(比如批量修改dom,使元素脱离文档流和提升到复合层),然后第二个层面是对于网络请求的优化:首先是对于DNS的优化,我们可以通过dns-prefectch 来预解析一些域名,然后可以通过利用http2.0 的优点,对于打包的策略进行一些优化,比如我们可以把一些资源进行更合理的拆分,还有对于一些异步组件拆分这些比较基础的东西。webpack4有一个新的css提取插件,我们可以利用它配合上split chunk 进行模块的拆分打包。然后后面我是想加上一些骨架屏等优化体验方面上的东西和离线访问的功能这些,这些都是属于有着比较直接的收益的方案。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant