最近10余篇文章的一篇汇总

前言

这个月基本都是记录一下面试时的一些知识点,这里找自己记得不是很熟练的汇总一下。

手写

手写部分,掌握的不熟的主要有深拷贝,JSONP和event bus。

function dcopy(obj){
  let map = new WeakMap()
  let help = function(obj){
    if(typeof obj!=='object' && typeof obj!=='function') return obj
    if(map.get(obj)) return obj
    map.set(obj, true)
    let proto = Object.getPrototypeOf(obj)
    let tar = new proto.constructor()
    if(Object.prototype.toString.call(tar)=='[object Set]') {
      obj.forEach(v=>{
        tar.add(dcopy(v))
      })
    }
    for(let i in obj){
      if(obj.hasOwnProperty(i)){
        tar[i] = help(obj[i])
      }
    }
    return tar 
  }
  return help(obj)
}
function jsonp({url,params,callback}){
  return new Promise((resolve, reject) => {
    let script = document.body.createElement('script')
    params = JSON.parse(JSON.stringify(params))
    let arr = []
    for(let key in params){
      arr.push(`${key}=${params[key]}`)
    }
    arr.push(`callback=${callback}`)
    script.src = `${url}?${arr.join("&")}`;
    document.appendChild(script)
    window[callback]=function(data){
      if(!data) reject('failed')
      resolve(data) 
      document.body.removeChild(script) 
    }
  })
}
class Event {
  constructor() {
    this.handlers = {};
  }
  on(event, cb) {
    if (!this.handlers[event]) {
      this.handlers[event] = [];
    }
    this.handlers[event].push(cb);
  }
  emit(event, ...args) {
    if (this.handlers[event]) {
      this.handlers[event].forEach(callback => {
        callback(...args);
      });
    }
  }
  off(event, cb) {
    const callbacks = this.handlers[event];
    const index = callbacks.indexOf(cb);
    if (index !== -1) {
      callbacks.splice(index, 1);
    }
  }
  once(event, cb) {
    const wrapper = (...args) => {
      cb.apply(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

Vue响应式

首先利用Proxy或Object.defineProperty生成的Observer既充当了监听器的作用,又充当发布者的角色,在数据属性变化后来通知订阅者;同时Compile解析模板中的指令,收集指令依赖的方法和数据,等待数据变化后的渲染;最后watcher连接两者,使得订阅依赖的数据变化转化为视图的变化。

同时Proxy优化了Object.defineProperty对于新增属性不被拦截、不需要遍历对象属性进行修改等问题

IFC

IFC的行框高度由它包含元素的最高高度来决定。在一个行内格式化上下文中,盒是一个接一个水平放置的,从包含块的顶部开始,同时这些盒之间的水平margin,border和padding都有效。

  1. 创建一个IFC,用其中一个元素撑开父元素的高度,然后设置其vertical-align:middle,其他行内元素则可以在此父元素下垂直居中。
  2. 当inline-level box宽度大于父容器宽度时会被拆分成多个inline-level box,左边距对第一个inline-level box生效,右边距对最后一个inline-level box生效。

V8 Hidden Class

首先,JS作为动态类型的语言,对象属性是可以在运行过程中动态的增删改的,若使用类似字典的数据结构来存储对象的属性,相对于静态类型的语言,动态查找属性在内存中的位置要慢的很多,为了提高访问速度,V8使用动态的创建hidden_class的形式去优化。

一个直白的例子:

function fn(){this.a = a;this.b = b}

当我们new fn() 时,首先会创建一个空的hidden_class,称为c0,接着执行到第一条语句时,会在c0的基础上创建c1,c1代替c0成为这个对象新的hidden_class,以此类推,最后c2是这个对象的hidden_class。

动态创建hidden_class的行为看起来很低效,但是当hidden_class可以被反复利用的时候,这种方式看起来就高效多了。

通知这种方式也是有局限性的,如如果我们使用delete方法,对象又会倒退回字典查找模式,删除属性,并进一步优化,代价很大。

node多进程

进程与线程的区别,前者是系统调度运行的基本单位,后者是CPU调度运行的基本单位,前者是后者的容器。Node开启多进程不是为了解决高并发,主要是解决了单进程模式下 Node.js CPU 利用率不足的情况,充分利用多核 CPU 的性能。

Nodejs创建多进程的方式主要有cluster和child_process两种方式。两者都是以fork一系列的API为主开辟新的进程,后者更加灵活,前者通过需要通过一个主进程管理多个工作进程,通过循环算法实现负载均衡,能够更简单的提高多核的利用率。

创建多进程并不是终点,能够实现进程间的通信,即IPC才是真正能够提升效率的表现,Node中实现IPC通道是依赖于libuv的,父进程在实际创建子进程之前,会创建IPC通道并监听它,然后才真正的创建出子进程。

几个React常问的问题

  1. Fiber:React利用了requestIdleCallback,同时对不支持这个API的浏览器添加了polyfill。在这个空闲时间内,从根节点开始遍历 Fiber Node,并在一段时间后交还给浏览器。React也为了Fiber架构做出了大量的调整,由于之前同步的、递归的Stack Reconcilation无法随意中断,也很难被恢复,不利于异步处理等特点,React转而使用了链表来模拟栈结构。最终变现为两个阶段,一个是协调,一个是提交,前者可以中断,后者仍然需要一口气执行到底。
  2. React为什么需要合成事件:更好的兼容性,摆脱DOM事件,挂载到document上,统一管理事务。
  3. React为什么需要默认支持SCU,它原本可以实现一个深比较,但是开发者如果不去遵循不可变数据的规则,SCU默认是不会执行的,数据变化了也不会渲染,因为a.push后,a==a。所以通过手动优化虽然繁琐,但是确实减少了React的BUG代码。同时深度比较也是消耗性能。
  4. React.nativeEvent.currentTarget可以拿到原生事件绑定的位置,指向的是document,即事件都挂载到document上。
  5. 从子->父的角度看Redux:创建一个最顶层的state,把数据当作props向下传。这个最顶层的存放state的地方即为store;传递了函数给子组件, 希望子组件通过传递参数’给父组件来改变父组件的状态。Redux也类似,我们可以把action看成传递的参数,因为action也不过是个js对象而已;而reducer则可以看出回调函数,他会根据action的信息来做出处理,正如Redux种reducer接受了dispatch的action后返回新的state一样。

简单认识websocket

WebSocket建立在 TCP 协议之上,与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP协议,同时它的数据格式比较轻量,性能开销小,通信高效。可以发送文本,也可以发送二进制数据。它不受没有同源限制,客户端可以与任意服务器通信。协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

HTTP握手后,WebSocket基于HTTP协议进行升级,服务端返回101状态码标识协议切换,后序的数据交互都按照新的协议进行。建立连接后,后续的操作都是基于数据帧的传递。

WebAssembly

JIT的出现确实带来了可观的性能提升,但是受限于JS动态类型和运行时编译的特点,判断类型成为了限制JIT的一个重要因素。对此Mozilla提出了asm.js,通过注解和检测等手段确保强类型,减少JS依据上下文判断类型的损耗,优化机器码的解释过程。

后来各家各户觉得Mozilla这思路挺好,于是联合起来,设定了WebAssembly,即一份字节码标准,以字节码的形式依赖虚拟机在浏览器中运行。

WASM并不能达到Assembly的性能,它只是一种约定的字节码,既然是字节码,肯定还是需要解释器翻译成机器码执行。它的优点是相对于JS字节码,它的翻译速度要快上许多。

其次对于CPU密集型任务,WASM也只是多提供了一种选择,有些时候使用WEBGL来做GPU加速往往是更好的选择。

最后,并不是指使用WASM就一定比JS快,V8使用的JIT有个很重要的优点,就是它会对热点代码进行抽取,直接编译生成二进制机器码方便接下来的调用。

路由

URL改变的情况下不刷新界面,如何在不刷新界面的状态下去检测到URL的改变。

从Hash路由来说,因为锚点的存在,当我们去修改锚点后面的数据,页面本身并不会发生改变,因此也就不存在上面的第一个问题,接着,我们通过hashChange这个事件来监听路由的变化,当我们使用浏览器的前进后退;当我们点击a标签;当我们使用window.location去改变URL时,这个事件都会被触发。因此第二个问题也比较方便的解决。

从History路由来说,H5之前,我们熟悉的是go,back等方法,H5之后,为了实现前端路由,History又引入了新的API,如pushState,replaceState两个方法,这两个方法对路由的改变并不会引起页面的刷新,因此解决了第一个问题。同时类似hashChange事件,我们还实现了popState来监听浏览器前进后退;同时我们又通过拦截pushState和a标签的点击来解决第二个问题。

二维码大概原理

用户进入一个网页后,服务器生成一个uid来标识用户,而显现出的二维码则标识了对应uid的链接。当使用正确的客户端打开这个链接时,服务器会收到用户的相关信息,并在客户端点击确认登陆后生成一个token给对应网页。之后的交互都依赖这个token。

Webpack生命周期

  1. 初始化参数,从配置文件或shell中
  2. 初始化Compile,加载插件,执行run方法开始编译
  3. 根据entry找到入口文件
  4. 从入口文件出发,执行所有的loader对模块编译,找到该模块依赖的模块,再递归的执行,知道所有编译结束。
  5. 拿到模块翻译后的最终内容和依赖关系
  6. 根据入口和模块的依赖关系,打包成一个个chunk,把每个chunk转换成输出文件加入输出列表
  7. 根据输出配置输出路径和文件名

Webpack的XHR

  1. 在watch模式下,webpack监听到文件的变化,重新打包并保存到内存中
  2. 通过sockjs在浏览器和服务端建立一个websocket长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,最主要的是新模块的hash值。
  3. 客户端某模块接收到新模块的hasn值,发送AJAX请求,服务端返回一个json,上面有所有需要更新的模块hash,根据这个更新列表发送JSONP。
  4. 检查新旧模块更新与否,更新模块依赖等。

移动端基本知识

device pixels,我们可以理解为一个点,有红蓝绿三个彩灯通过不同的亮度显示最后的色彩。

接着是设备独立像素,区别于上面的知识,设备独立像素可能包含多个设备像素。而1个CSS像素 等于 1个设备独立像素,设原来分辨率为100100,元素为5050,当我们分辨率改成200*200,元素自然显得更小了。

2K和4K就不说了,说说PPI,5.8英寸、分辨率为1125x2436的iphonex,ppi = Math.sqrt(11251125 + 24362436) / 5.8=463ppi。DPR则是物理像素除以设备独立像素。

掌握了上面的基本知识,再看viewport就很好理解了,假如你使用!+Tab,html文件会自动生成:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

这意味着当我们使用手机端测试时,宽度会默认为设备的宽度,如iphone设为375等。这样字体就不至于缩放的很小,否则,大部分浏览器会以980的宽度进行渲染。

initial-scale定义了初始缩放比例,maximum-scale定义了用户最多能放大多少倍,minimum-scale定义了最大能缩小多少倍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值