前端面试题整理(转)

167 阅读25分钟

箭头函数与普通函数(function)的区别是什么?构造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么?

箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:

  1. 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象;

  2. 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替;

  3. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数;

  4. 不可以使用new命令,因为:

    • 没有自己的 this,无法调用 call、apply
    • 没有 prototype 属性,而 new 命令在执行时需要将钩子函数的 prototype 赋值给新的对象的 __proto__

Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

原因如下:

  1. Object.defineProperty 无法低耗费的监听到数组下标的变化,导致通过数组下标添加元素,不能实时响应;
  2. Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历。如果属性值是对象,还需要深度遍历。 Proxy 可以劫持整个对象, 并返回一个新的对象。
  3. Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

vuereact 都是采用 diff 算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点(这里对应的是一个 key => index 的 map 映射)。如果没有找到就认为是一个新增节点。而如果没有 key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个 map 映射,另一种是遍历查找。相比而言,map 映射的速度更快。

['1', '2', '3'].map(parseInt) what & why ?

['1', '2', '3'].map(parseInt) 的输出结果为 [1, NaN, NaN]。

因为 parseInt(string, radix) 将一个字符串 string 转换为 radix 进制的整数, radix 为介于 2-36 之间的数。

在数组的 map 方法的回调函数中会传入 item(遍历项)index(遍历下标) 作为前两个参数,所以这里的 parseInt 执行了对应的三次分别是

  • parseInt(1, 0)
  • parseInt(2, 1)
  • parseInt(3, 2)

对应的执行结果分别为 1、NaN、NaN

简述浏览器缓存读取规则

浏览器缓存可以优化性能,比如直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,则使用缓存从而减少响应数据。

缓存位置

Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPSService Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由缓存哪些文件、如何匹配缓存、如何读取缓存,而缓存是可持续性的。Service Worker 也是 PWA 的核心技术。

Memory Cache

Memory Cache 也就是内存中的缓存,主要包含的是当前页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据很高效,但是缓存持续性很短,会随着进程的释放而释放。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

Disk Cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本上是最大的。它会根据 HTTP Header 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache

Push Cache

Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被时候用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂(大约 5 分钟)。

缓存过程分析

浏览器与服务器通信的方式为应答模式,即是:浏览器发起 HTTP 请求 - 服务器响应该请求。浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存表示存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源返回的响应头来确定的。

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识;
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存表示存入浏览器缓存中;

介绍下 Set、Map、WeakSet 和 WeakMap 的区别?

Set

  1. 成员不能重复;
  2. 只有键值,没有键名,有点类似数组;
  3. 可以遍历,方法有 add、delete、has

WeakSet

  1. 成员都是对象(引用);
  2. 成员都是弱引用,随时可以消失(不计入垃圾回收机制)。可以用来保存 DOM 节点,不容易造成内存泄露;
  3. 不能遍历,方法有 add、delete、has

Map

  1. 本质上是键值对的集合,类似集合;
  2. 可以遍历,方法很多,可以跟各种数据格式转换;

WeakMap

  1. 只接收对象为键名(null 除外),不接受其他类型的值作为键名;
  2. 键名指向的对象,不计入垃圾回收机制;
  3. 不能遍历,方法同 get、set、has、delete

(挖财)什么是防抖和节流?有什么区别?如何实现?

防抖

触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。

function debounce(fn, timing) {
  let timer;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(fn(), timing);
  }
}

节流

高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行效率。

function throttle(fn, timing) {
  let trigger;
  return function() {
    if (trigger) return;
    trigger = true;
    fn();
    setTimeout(() => {
      trigger = false;
    }, timing);
  }
}

Tips:我记这个很容易把两者弄混,总结了个口诀,就是 DTTV(Debounce Timer Throttle Variable - 防抖靠定时器控制,节流靠变量控制)。

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

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

setTimeout、Promise、Async/Await 的区别

setTimeout: setTimeout 的回调函数放到宏任务队列里,等到执行栈清空以后执行;

Promise: Promise 本身是同步的立即执行函数,当在 executor 中执行 resolve 或者 reject 的时候,此时是异步操作,会先执行 then/catch 等,当主栈完成时,才会去调用 resolve/reject 方法中存放的方法。

async: async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

(头条、微医)Async/Await 如何通过同步的方式实现异步

Async/Await 是一个自执行的 generate 函数。利用 generate 函数的特性把异步的代码写成“同步”的形式。

var fetch = require("node-fetch");

function *gen() { // 这里的 * 可以看成 async
  var url = "https://api.github.com/users/github";
  var result = yield fetch(url); // 这里的 yield 可以看成 await
  console.log(result.bio);
}

var g = gen();
var result = g.next();
result.value.then(data => data.json()).then(data  => g.next(data));

简述一下 Generator 函数

传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做“协程”(coroutine),意思是多个线程互相协作,完成异步任务。

协程有点像函数,又有点像线程,它的运行流程大致如下:

  • 第一步,协程 A 开始执行;
  • 第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B
  • 第三步,(一段时间后)协程 B 交还执行权;
  • 第四步,协程 A 恢复执行;

上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。

举例来说,读取文件的协程写法如下:

function* asyncJob() {
 // ...
 var f = yield readFile(fileA);
 // ...
}

上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield 命令是异步两个阶段的分界线。协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

function* gen(x) {
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: false }

next 是返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以接受参数,向 Generator 函数体内输入数据。

上面代码中,第一个 next 方法的 value 属性,返回表达式 x + 2 的值 3。第二个 next 方法带有参数 2,这个参数可以传入 Generator 函数,作为 上个阶段 异步任务的返回结果,被函数体内的变量 y 接收。因此,这一步的 value 属性,返回的就是 2(变量 y 的值)。

(滴滴、挖财、微医、海康)JS 异步解决方案的发展历程以及优缺点。

回调函数

优点:解决了同步的问题(整体任务执行时长);

缺点:回调地狱,不能用 try catch 捕获错误,不能 return;

Promise

优点:解决了回调地狱的问题;

缺点:无法取消 Promise,错误需要通过回调函数来捕获;

Generator

特点:可以控制函数的执行。

Async/Await

优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题;

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低;

为什么 Vuex 的 mutation 和 redux 的 reducer 中不能做异步操作?

纯函数,给定同样的输入返回同样的输出,可预测性。

(京东)下面代码中 a 在什么情况下会打印 1?

var a = ?;
if(a == 1 && a == 2 && a == 3){
 	console.log(1);
}

解答:

var a = {
  value: 0,

  valueOf() {
    return ++this.value;
  }
};

在 Vue 中,子组件为何不可以修改父组件传递的 Prop,如果修改了,Vue 是如何监控到属性的修改并给出警告的。

  1. 因为 Vue单项数据流,易于检测数据的流动,出现了错误可以更加迅速的定位到错误发生的位置;
  2. 通过 setter 属性进行检测,修改值将会触发 setter,从而触发警告;

实现一个 sleep 函数

比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现

// Promise
function sleep1(time) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, time);
  })
}

sleep1(1000).then(() => console.log("sleep1"));

// Generator
function* sleep2(time) {
  return yield sleep1(time);
}

const s = sleep2(1500);
s.next().value.then(() => console.log("sleep2"));

// Async/Await
async function sleep3(time) {
  await sleep1(time);
}

(async () => {
  await sleep3(2000);
  console.log("sleep3")
})()

双向绑定和 vuex 是否冲突

当在严格模式中使用 Vuex 时,在属于 Vuexstate 上使用 v-model 会导致出错。 解决方案:

  1. <Input> 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用一个方法;
  2. 使用带有 setter 的双向绑定计算属性;

call 和 apply 的区别是什么,哪个性能更好一些

  1. Function.prototype.applyFunction.prototype.call 的作用是一样的,区别在于传入参数的不同;

  2. 第一个参数都是指定函数体内 this 的指向;

  3. 第二个参数开始不同,apply 是传入带下标的集合,数组或者类数组,apply 把它传给函数作为参数,call 从第二个开始传入的参数是不固定的,都会传给函数作为参数;

  4. callapply 的性能要好,call 传入参数的格式正式内部所需要的格式;

其中 this 是你想指定的上下文,他可以是任何一个 JavaScript 对象(JavaScript 中一切皆对象),call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里。  

为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?

  1. 能够完成整个 HTTP 请求+响应(尽管不需要响应内容);
  2. 触发 GET 请求之后不需要获取和处理数据,服务器也不需要发送数据;
  3. 跨域友好;
  4. 执行过程无阻塞;
  5. 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好;
  6. GIF 的最低合法体积最小(合法的 GIF 只需要 43 个字节)

(百度)实现 (5).add(3).minus(2) 功能

Number.prototype.add = function(n) {
  return this + n;
}

Number.prototype.minus = function(n) {
  return this - n;
}

操作题

某公司 1 到 12 月份的销售额存在一个对象里面 如下:{1:222, 2:123, 5:888},请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]

解答:

function convert(obj) {
  return Array.from({ length: 12 }).map((item, index) => obj[index] || null).slice(1);
}s

考察了 类数组 length 属性

redux 为什么要把 reducer 设计成纯函数

redux 的设计思想就是不产生副作用,数据更改的状态可回溯,所以 redux 中处处都是纯函数。

Vue 的父组件和子组件生命周期钩子执行顺序是什么

  1. 加载渲染过程:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
  2. 子组件更新过程:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
  3. 父组件更新过程:父 beforeUpdate -> 父 updated
  4. 销毁过程:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?

v-for 中使用事件代理可以使监听器数量和内存占用率都减少,vue 内部并不会自动做事件代理,所以在 v-for 上使用事件代理在性能上会更优。

简单说说 js 中有哪几种内存泄露的情况

  1. 意外的全局变量;
  2. 闭包;
  3. 未被清空的定时器;
  4. 未被销毁的事件监听;
  5. DOM 引用;

跨域问题如何解决

  1. JSONP
  2. CORS(Cross-Origin-Resource-Share,跨域资源共享),由服务端设置响应头通过浏览器的同源策略限制
    • Access-Control-Allow-Origin: *;
    • Access-Control-Allow-Methods: *;
    • Access-Control-Allow-Headers: *;
    • Access-Control-Allow-Credentials: true;

instanceof 的实现原理

while (x.__proto__) {
  if (x.__proto__ === y.prototype) {
    return true;
  }
  x.__proto__ = x.__proto__.__proto__;
}
if (x.__proto__ === null) {
  return false;
}

react 组件的生命周期

初始化阶段

  1. constructor(): 用于绑定事件,初始化 state
  2. componentWillMount():组件将要挂载,在 render 之前调用,可以在服务端调用。
  3. render():用作渲染 dom;
  4. componentDidMount():在 render 之后,而且是所有子组件都 render 之后才调用。

更新阶段

  1. getDerivedStateFromProps:getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容;
  2. componentWillReceiveProps(nextProps): 在这里可以拿到即将改变的状态,可以在这里通过 setState 方法设置 state
  3. shouldComponentUpdate(nextProps, nextState): 他的返回值决定了接下来的声明周期是否会被调用,默认返回 true
  4. componentWillUpdate(): 不能在这里改变 state,否则会陷入死循环
  5. componentDidUpdate(): 和componentDidMount()类似,在这里执行 Dom 操作以及发起网络请求

析构阶段

  1. componentWillUnmount():主要执行清除工作,比如取消网络请求,清除事件监听。

简述 Flux 思想

Flux 最大的特点就是,数据单向流动

  1. 用户访问 View
  2. View 发出用户的 Action
  3. Dispatcher 收到 Action,要求 Store 进行对应的更新;
  4. Store 更新后,发出一个 “change” 事件;
  5. View 收到 “change” 后,更新页面;

简述执行上下文和执行栈

执行上下文

  • 全局执行上下文:默认的上下文,任何不在函数内部的代码都在全局上下文里面。它会执行两件事情:创建一个全局的的 window 对象,并且设置 this 为这个全局对象。一个程序只有一个全局对象。
  • 函数执行上下文:每当一个函数被调用时,就会为该函数创建一个新的上下文,每个函数都有自己的上下文,不过是在被函数调用的时候创建的。函数上下文可以有任意多个,每当一个新的执行上下文被创建,他会按照定义的顺序执行一系列的步骤。
  • Eval 函数执行上下文:执行在 eval 函数内部的代码有他自己的执行上下文。

执行栈

执行栈就是一个调用栈,是一个后进先出数据结构的栈,用来存储代码运行时创建的执行上下文。

this 绑定

全局执行上下文中,this 指向全局对象。

函数执行上下文中,this 取决于函数是如何被调用的。如果他被一个引用对象调用,那么 this 会设置成那个对象,否则是全局对象。

简述浏览器与 Node 的事件循环

浏览器

  • 宏任务:script 中的代码、setTimeout、setInterval、I/O、UI render;
  • 微任务:promise(async/await)、Object.observe、MutationObserver;

Node

  • 宏任务:setTimeout、setInterval、setImmediate、script(整体代码)、I/O 操作等;
  • 微任务:process.nextTick(与普通微任务有区别,在微任务队列执行之前执行)、new Promise().then(回调) 等

区别

  • node 环境下的 setTimeout 定时器会依次一起执行,浏览器是一个一个分开的;
  • 浏览器环境下微任务的执行是每个宏任务执行之后,而 node 中微任务会在各个阶段执行,一个阶段结束立刻执行 microTask;

浏览器环境下:

while(true){
    宏任务队列.shift()
    微任务队列全部任务()
}

Node 环境下:

while(true){
    loop.forEach((阶段)=>{
        阶段全部任务()
        nextTick全部任务()
        microTask全部任务()
    })
}

什么是 CSP?

CSP(Content-Security-Policy)指的是内容安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由浏览器自己来实现。

通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式

<meta http-equiv="Content-Security-Policy">

CSP 也是解决 XSS 攻击的一个强力手段。

什么是 CSRF 攻击?如何防范 CSRF 攻击?

CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态(cookie),绕过后台的用户验证,冒充用户向服务器执行一些操作。

CSRF 攻击的本质是利用了 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。

防护方法:

  1. 同源检测,服务器检测请求来源;
  2. 使用 token 来进行验证;
  3. 设置 cookie 时设置 Samesite,限制 cookie 不能作为被第三方使用;

什么是尾调用,使用尾调用有什么好处?

尾调用指的是函数的最后一步调用另一个函数。我们代码执行是基于执行栈的,所以当我们在一个函数里调用另一个函数时,我们会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这个时候我们可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

谈一谈你理解的函数式编程?

“函数式变成”是一种“编程范式”,也就是如何编写程序的方法论。

它具有以下特性:闭包和高阶函数、惰性运算、递归、函数是“第一等公民”、只用“表达式”。

Vue 组件间如何通信?

父子组件通信

  1. props + emit
  2. refs+refs + parent
  3. provider/inject

兄弟组件通信

  1. eventBus
  2. parent.parent.refs

Vue 中 computed 和 watch 的差异?

  1. computed 是计算一个新的属性,并将该属性挂载到 Vue 实例上,而 watch 是监听已经存在且已挂载到 Vue 实例上的数据,所以用 watch 同样可以监听 computed 计算属性的变化;
  2. computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 值,才会计算新的值。而 watch 则是当数据发送变化便会调用执行函数;
  3. 从使用场景上来说,computed 适用多个数据影响一个数据,而 watch 使用一个数据影响多个数据。

简述一下 PWA

PWA(Progressive Web App)渐进式网页应用,目的是提升 Web App 的性能,改善 Web App 的用户体验。

特点

  1. 可安装:可以像原生 APP 在主屏幕上留有图标。
  2. 离线应用:可以离线使用,背后用的是技术是 Service Worker
  3. Service Worker 实际上是一段脚本,在后台运行。作为一个独立的线程,运行环境和普通脚本不同,所以不能直接参与 Web 交互行为,属于一种客户端代理。
  4. Service Worker 可以创建有效的离线体验,拦截网络请求,并根据网络是否可用判断是否使用缓存数据或者更新缓存数据。
  5. 消息推送

(阿里巴巴)介绍下 CacheStorage

CacheStorage 接口表示 Cache 对象的存储。它提供了一个 ServiceWorker、其他类型 woker 或者 window 范围内可以访问到的所有命名 cache 的主目录(它并不是一定要和 service workers 一起使用,即使它是在 service workers 规范中定义的),并维护一份字符串名称到相应 Cache 对象的映射。

CacheStorage 和 Cache,是两个与缓存相关的接口,用于管理当前网页/Web App 的缓存;在使用 Service Worker 时基本都会用到。它们与数据库有点类似,我们可以用 mongodb 来打个比喻:

  • CacheStorage 管理者所有的 Cache,是整个缓存 api 的入口,类似于 mongo;
  • Cache 是单个缓存库,通常一个 app 会有一个,类似 mongo 里的每个 db;

无论在 ServiceWorker 域或 window 域下,你都可以用 caches 来访问全局的 CacheStorage。

(阿里巴巴)Vue 双向数据绑定原理

vue 通过双向数据绑定,来实现了 View 和 Model 的同步更新。vue 的双向数据绑定主要是通过数据劫持和发布订阅者模式来实现的。

首先我们通过 Object.defineProperty() 方法来对 Model 数据各个属性添加访问器属性,以此来实现数据的劫持,因此当 Model 中的数据发生变化的时候,我们可以通过配置的 setter 和 getter 方法来实现对 View 层数据更新的通知。

对于文本节点的更新,我们使用了发布订阅者模式,属性作为一个主题,我们为这个节点设置一个订阅者对象,将这个订阅者对象加入这个属性主题的订阅者列表中。当 Model 层数据发生改变的时候,Model 作为发布者向主题发出通知,主题收到通知再向它的所有订阅者推送,订阅者收到通知后更改自己的数据。

页面的可用性时间的计算

Performance 接口可以获取到当前页面中与性能相关的信息。

  • Performance.timing:Performance.timing 对象包含延迟相关的性能信息;

简述一下 WebAssembly

WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C/C++ 等语言提供一个编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 Javascript 共存,允许两者一起工作。

WebAssembly 提供了一条途径,以使得以各种语言编写的代码都可以以接近原生的速度在 Web 中运行。

(阿里巴巴)谈谈移动端点击

移动端 300 ms 点击(click 事件)延迟

由于移动端会有双击缩放的这个操作,因此浏览器在 click 之后要等待 300ms,判断这次操作是不是双击。

解决方案:

  1. 禁用缩放:user-scalable=no
  2. 更改默认的视口宽度
  3. CSS touch-action

点击穿透问题

因为 click 事件的 300ms 延迟问题,所以有可能会在某些情况触发多次事件。

解决方案:

  1. 只用 touch
  2. 只用 click

(阿里巴巴)谈谈 Git-Rebase

  1. 可以合并多次提交记录,减少无用的提交信息;
  2. 合并分支并且减少 commit 记录;

(阿里巴巴)简述懒加载

懒加载也叫延迟加载,指的是在长网页中延迟加载图像,是一种很好优化网页性能的方式。

懒加载的优点:

  1. 提升用户体验,加快首屏渲染速度;
  2. 减少无效资源的加载;
  3. 防止并发加载的资源过多会阻塞 js 的加载;

懒加载的原理:

首先将页面上的图片的 src 属性设为空字符串,而图片的真实路径则设置在 data-original 属性中,当页面滚动的时候需要去监听 scroll 事件,在 scroll 事件的回调中,判断我们的懒加载的图片是否进入可视区域,如果图片在可视区内则将图片的 src 属性设置为 data-original 的值,这样就可以实现延迟加载。

(腾讯)webpack 中 loader 和 plugin 的区别是什么?

loader:loader 是一个转换器,将 A 文件进行编译成 B 文件,属于单纯的文件转换过程;

plugin:plugin 是一个扩展器,它丰富了 webpack 本身,针对是 loader 结束后,webpack 打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行广泛的任务。

你有对 Vue 项目进行哪些优化?

(1)代码层面的优化

  • v-if 和 v-show 区分使用场景
  • computed 和 watch 区分使用场景
  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
  • 长列表性能优化
  • 事件的销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化无限列表性能
  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩
  • 减少 ES6 转为 ES5 的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建结果输出分析
  • Vue 项目的编译优化

(3)基础的 Web 技术的优化

  • 开启 gzip 压缩
  • 浏览器缓存
  • CDN 的使用
  • 使用 Chrome Performance 查找性能瓶颈

事件冒泡和事件捕获过程图:

事件冒泡和事件捕获

1-5是捕获过程,5-6是目标阶段,6-10是冒泡阶段;

说说Vue2.0和Vue3.0有什么区别

  1. 重构响应式系统,使用Proxy替换Object.defineProperty,使用Proxy优势:

    1. 可直接监听数组类型的数据变化
    2. 监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
    3. 可拦截apply、ownKeys、has等13种方法,而Object.defineProperty不行
    4. 直接实现对象属性的新增/删除
  2. 新增Composition API,更好的逻辑复用和代码组织

  3. 重构 Virtual DOM

    1. 模板编译时的优化,将一些静态节点编译成常量
    2. slot优化,将slot编译为lazy函数,将slot的渲染的决定权交给子组件
    3. 模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)
  4. 代码结构调整,更便于Tree shaking,使得体积更小

  5. 使用Typescript替换Flow