174道JavaScript 面试知识点总结(下)
[Vue中文社区](javascript:void(0);) 昨天、文章有许多金典阅读推荐建议点赞加收藏
来源 | https://github.com/CavsZhouyou/
全篇篇幅较长,保障阅读体验,故拆分为上中下3部分发布
174道JavaScript 面试知识点总结(上)
174道JavaScript 面试知识点总结(中)
以下为第三部分
121. URL 和 URI 的区别?
URI: Uniform Resource Identifier 指的是统一资源标识符URL: Uniform Resource Location 指的是统一资源定位符URN: Universal Resource Name 指的是统一资源名称URI 指的是统一资源标识符,用唯一的标识来确定一个资源,它是一种抽象的定义,也就是说,不管使用什么方法来定义,只要能唯一的标识一个资源,就可以称为 URI。URL 指的是统一资源定位符,URN 指的是统一资源名称。URL 和 URN 是 URI 的子集,URL 可以理解为使用地址来标识资源,URN 可以理解为使用名称来标识资源。
详细资料可以参考:《HTTP 协议中 URI 和 URL 有什么区别?》《你知道 URL、URI 和 URN 三者之间的区别吗?》《URI、URL 和 URN 的区别》
122. get 和 post 请求在缓存方面的区别
相关知识点:
get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。post 不同,post 做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此 get 请求适合于请求缓存。
回答:
缓存一般只适用于那些不会更新服务端数据的请求。一般 get 请求都是查找请求,不会对服务器资源数据造成修改,而 post 请求一般都会对服务器数据造成修改,所以,一般会对 get 请求进行缓存,很少会对 post 请求进行缓存。
详细资料可以参考:《HTML 关于 post 和 get 的区别以及缓存问题的理解》
123. 图片的懒加载和预加载
相关知识点:
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。 懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
回答:
懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的 src 属性,以此来实现图片的延迟加载。预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
详细资料可以参考:《懒加载和预加载》《网页图片加载优化方案》《基于用户行为的图片等资源预加载》
124. mouseover 和 mouseenter 的区别?
当鼠标移动到元素上时就会触发 mouseenter 事件,类似 mouseover,它们两者之间的差别是 mouseenter 不会冒泡。由于 mouseenter 不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其 mouseover 和 mouseout 事件,但是却不会触发 mouseenter 和 mouseleave 事件。
详细资料可以参考:《mouseenter 与 mouseover 为何这般纠缠不清?》
125. js 拖拽功能的实现
相关知识点:
首先是三个事件,分别是 mousedown,mousemove,mouseup当鼠标点击按下的时候,需要一个 tag 标识此时已经按下,可以执行 mousemove 里面的具体方法。clientX,clientY 标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用 offsetX 和 offsetY 来表示元素的元素的初始坐标,移动的举例应该是:鼠标移动时候的坐标-鼠标按下去时候的坐标。也就是说定位信息为:鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的 offetLeft.
回答:
一个元素的拖拽过程,我们可以分为三个步骤,第一步是鼠标按下目标元素,第二步是鼠标保持按下的状态移动鼠标,第三步是鼠标抬起,拖拽过程结束。这三步分别对应了三个事件,mousedown 事件,mousemove 事件和 mouseup 事件。只有在鼠标按下的状态移动鼠标我们才会执行拖拽事件,因此我们需要在 mousedown 事件中设置一个状态来标识鼠标已经按下,然后在 mouseup 事件中再取消这个状态。在 mousedown 事件中我们首先应该判断,目标元素是否为拖拽元素,如果是拖拽元素,我们就设置状态并且保存这个时候鼠标的位置。然后在 mousemove 事件中,我们通过判断鼠标现在的位置和以前位置的相对移动,来确定拖拽元素在移动中的坐标。最后 mouseup 事件触发后,清除状态,结束拖拽事件。
详细资料可以参考:《原生 js 实现拖拽功能基本思路》
126. 为什么使用 setTimeout 实现 setInterval?怎么模拟?
相关知识点:
// 思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果function mySetInterval(fn, timeout) { // 控制器,控制定时器是否继续执行 var timer = { flag: true }; // 设置递归函数,模拟定时器执行。 function interval() { if (timer.flag) { fn(); setTimeout(interval, timeout); } } // 启动定时器 setTimeout(interval, timeout); // 返回控制器 return timer;}
回答:
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
详细资料可以参考:《用 setTimeout 实现 setInterval》《setInterval 有什么缺点?》
127. let 和 const 的注意点?
- 1.声明的变量只在声明时的代码块内有效
- 2.不存在声明提升
- 3.存在暂时性死区,如果在变量声明前使用,会报错
- 4.不允许重复声明,重复声明会报错
128. 什么是 rest 参数?
rest 参数(形式为...变量名),用于获取函数的多余参数。
129. 什么是尾调用,使用尾调用有什么好处?
尾调用指的是函数的最后一步调用另一个函数。我们代码执行是基于执行栈的,所以当我们在一个函数里调用另一个函数时,我们会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这个时候我们可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
130. Symbol 类型的注意点?
- 1.Symbol 函数前不能使用 new 命令,否则会报错。
- 2.Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
- 3.Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。
- 4.Object.getOwnPropertySymbols 方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
- 5.Symbol.for 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
- 6.Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。
131. Set 和 WeakSet 结构?
- 1.ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
- 2.WeakSet 结构与 Set 类似,也是不重复的值的集合。但是 WeakSet 的成员只能是对象,而不能是其他类型的值。WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,
132. Map 和 WeakMap 结构?
- 1.Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
- 2.WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。
133. 什么是 Proxy ?
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
134. Reflect 对象创建目的?
- 1.将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty,放到 Reflect 对象上。
- 2.修改某些 Object 方法的返回结果,让其变得更合理。
- 3.让 Object 操作都变成函数行为。
- 4.Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为。
135. require 模块引入的查找方式?
当 Node 遇到 require(X) 时,按下面的顺序处理。(1)如果 X 是内置模块(比如 require('http')) a. 返回该模块。 b. 不再继续执行。(2)如果 X 以 "./" 或者 "/" 或者 "../" 开头 a. 根据 X 所在的父模块,确定 X 的绝对路径。 b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。 X X.js X.json X.node c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。 X/package.json(main字段) X/index.js X/index.json X/index.node(3)如果 X 不带路径 a. 根据 X 所在的父模块,确定 X 可能的安装目录。 b. 依次在每个目录中,将 X 当成文件名或目录名加载。(4)抛出 "not found"
详细资料可以参考:《require() 源码解读》
136. 什么是 Promise 对象,什么是 Promises/A+ 规范?
Promise 对象是异步编程的一种解决方案,最早由社区提出。Promises/A+ 规范是 JavaScript Promise 的标准,规定了一个 Promise 所必须具有的特性。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是 pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者 rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。状态的改变是通过 resolve() 和 reject() 函数来实现的,我们可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。
详细资料可以参考:《Promises/A+ 规范》《Promise》
137. 手写一个 Promise
const PENDING = "pending";const RESOLVED = "resolved";const REJECTED = "rejected";function MyPromise(fn) { // 保存初始化状态 var self = this; // 初始化状态 this.state = PENDING; // 用于保存 resolve 或者 rejected 传入的值 this.value = null; // 用于保存 resolve 的回调函数 this.resolvedCallbacks = []; // 用于保存 reject 的回调函数 this.rejectedCallbacks = []; // 状态转变为 resolved 方法 function resolve(value) { // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变 if (value instanceof MyPromise) { return value.then(resolve, reject); } // 保证代码的执行顺序为本轮事件循环的末尾 setTimeout(() => { // 只有状态为 pending 时才能转变, if (self.state === PENDING) { // 修改状态 self.state = RESOLVED; // 设置传入的值 self.value = value; // 执行回调函数 self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 状态转变为 rejected 方法 function reject(value) { // 保证代码的执行顺序为本轮事件循环的末尾 setTimeout(() => { // 只有状态为 pending 时才能转变 if (self.state === PENDING) { // 修改状态 self.state = REJECTED; // 设置传入的值 self.value = value; // 执行回调函数 self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 将两个方法传入函数执行 try { fn(resolve, reject); } catch (e) { // 遇到错误时,捕获错误,执行 reject 函数 reject(e); }}MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; }; // 如果是等待状态,则将函数加入对应列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); } // 如果状态已经凝固,则直接执行对应状态的函数 if (this.state === RESOLVED) { onResolved(this.value); } if (this.state === REJECTED) { onRejected(this.value); }};
138. 如何检测浏览器所支持的最小字体大小?
用 JS 设置 DOM 的字体为某一个值,然后再取出来,如果值设置成功,就说明支持。
139. 怎么做 JS 代码 Error 统计?
error 统计使用浏览器的 window.error 事件。
140. 单例模式模式是什么?
单例模式保证了全局只有一个实例来被访问。比如说常用的如弹框组件的实现和全局状态的实现。
141. 策略模式是什么?
策略模式主要是用来将方法的实现和方法的调用分离开,外部通过不同的参数可以调用不同的策略。我主要在 MVP 模式解耦的时候用来将视图层的方法定义和方法调用分离。
142. 代理模式是什么?
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。比如说常见的事件代理。
143. 中介者模式是什么?
中介者模式指的是,多个对象通过一个中介者进行交流,而不是直接进行交流,这样能够将通信的各个对象解耦。
144. 适配器模式是什么?
适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。假如我们需要一种新的接口返回方式,但是老的接口由于在太多地方已经使用了,不能随意更改,这个时候就可以使用适配器模式。比如我们需要一种自定义的时间返回格式,但是我们又不能对 js 时间格式化的接口进行修改,这个时候就可以使用适配器模式。
更多关于设计模式的资料可以参考:《前端面试之道》《JavaScript 设计模式》《JavaScript 中常见设计模式整理》
145. 观察者模式和发布订阅模式有什么不同?
发布订阅模式其实属于广义上的观察者模式在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。而在发布订阅模式中,发布者和订阅者之间多了一个调度中心。调度中心一方面从发布者接收事件,另一方面向订阅者发布事件,订阅者需要在调度中心中订阅事件。通过调度中心实现了发布者和订阅者关系的解耦。使用发布订阅者模式更利于我们代码的可维护性。
详细资料可以参考:《观察者模式和发布订阅模式有什么不同?》
146. Vue 的生命周期是什么?
Vue 的生命周期指的是组件从创建到销毁的一系列的过程,被称为 Vue 的生命周期。通过提供的 Vue 在生命周期各个阶段的钩子函数,我们可以很好的在 Vue 的各个生命阶段实现一些操作。
147. Vue 的各个生命阶段是什么?
Vue 一共有8个生命阶段,分别是创建前、创建后、加载前、加载后、更新前、更新后、销毁前和销毁后,每个阶段对应了一个生命周期的钩子函数。(1)beforeCreate 钩子函数,在实例初始化之后,在数据监听和事件配置之前触发。因此在这个事件中我们是获取不到 data 数据的。(2)created 钩子函数,在实例创建完成后触发,此时可以访问 data、methods 等属性。但这个时候组件还没有被挂载到页面中去,所以这个时候访问不到 $el 属性。一般我们可以在这个函数中进行一些页面初始化的工作,比如通过 ajax 请求数据来对页面进行初始化。(3)beforeMount 钩子函数,在组件被挂载到页面之前触发。在 beforeMount 之前,会找到对应的 template,并编译成 render 函数。(4)mounted 钩子函数,在组件挂载到页面之后触发。此时可以通过 DOM API 获取到页面中的 DOM 元素。(5)beforeUpdate 钩子函数,在响应式数据更新时触发,发生在虚拟 DOM 重新渲染和打补丁之前,这个时候我们可以对可能会被移除的元素做一些操作,比如移除事件监听器。(6)updated 钩子函数,虚拟 DOM 重新渲染和打补丁之后调用。(7)beforeDestroy 钩子函数,在实例销毁之前调用。一般在这一步我们可以销毁定时器、解绑全局事件等。(8)destroyed 钩子函数,在实例销毁之后调用,调用后,Vue 实例中的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。当我们使用 keep-alive 的时候,还有两个钩子函数,分别是 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
详细资料可以参考:《vue 生命周期深入》《Vue 实例》
148. Vue 组件间的参数传递方式?
(1)父子组件间通信第一种方法是子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。第二种是通过 ref 属性给子组件设置一个名字。父组件通过 $refs 组件名来获得子组件,子组件通过 $parent 获得父组件,这样也可以实现通信。第三种是使用 provider/inject,在父组件中通过 provider 提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provider 中的数据。(2)兄弟组件间通信第一种是使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。第二种是通过 $parent.$refs 来获取到兄弟组件,也可以进行通信。(3)任意组件之间使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。
详细资料可以参考:《VUE 组件之间数据传递全集》
149. computed 和 watch 的差异?
(1)computed 是计算一个新的属性,并将该属性挂载到 Vue 实例上,而 watch 是监听已经存在且已挂载到 Vue 实例上的数据,所以用 watch 同样可以监听 computed 计算属性的变化。(2)computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值。而 watch 则是当数据发生变化便会调用执行函数。(3)从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据。
详细资料可以参考:《做面试的不倒翁:浅谈 Vue 中 computed 实现原理》《深入理解 Vue 的 watch 实现原理及其实现方式》
150. vue-router 中的导航钩子函数
(1)全局的钩子函数 beforeEach 和 afterEachbeforeEach 有三个参数,to 代表要进入的路由对象,from 代表离开的路由对象。next 是一个必须要执行的函数,如果不传参数,那就执行下一个钩子函数,如果传入 false,则终止跳转,如果传入一个路径,则导航到对应的路由,如果传入 error ,则导航终止,error 传入错误的监听函数。(2)单个路由独享的钩子函数 beforeEnter,它是在路由配置上直接进行定义的。(3)组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。它们是直接在路由组件内部直接进行定义的。
详细资料可以参考:《导航守卫》
151. router 的区别?
$route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。而 $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。
152. vue 常用的修饰符?
.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;
153. vue 中 key 值的作用?
vue 中 key 值的作用可以分为两种情况来考虑。第一种情况是 v-if 中使用 key。由于 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因此当我们使用 v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input 元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此我们可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。第二种情况是 v-for 中使用 key。用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM。
详细资料可以参考:《Vue 面试中,经常会被问到的面试题 Vue 知识点整理》《Vue2.0 v-for 中 :key 到底有什么用?》《vue 中 key 的作用》
154. computed 和 watch 区别?
computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
155. keep-alive 组件有什么作用?
如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。
156. vue 中 mixin 和 mixins 区别?
mixin 用于全局混入,会影响到每个组件实例。mixins 应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并
详细资料可以参考:《前端面试之道》《混入》
157. 开发中常用的几种 Content-Type ?
(1)application/x-www-form-urlencoded浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL转码。(2)multipart/form-data该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。(3)application/json告诉服务器消息主体是序列化后的 JSON 字符串。(4)text/xml该种方式主要用来提交 XML 格式的数据。
详细资料可以参考:《常用的几种 Content-Type》
158. 如何封装一个 javascript 的类型判断函数?
function getType(value) { // 判断数据是 null 的情况 if (value === null) { return value + ""; } // 判断数据是引用类型的情况 if (typeof value === "object") { let valueClass = Object.prototype.toString.call(value), type = valueClass.split(" ")[1].split(""); type.pop(); return type.join("").toLowerCase(); } else { // 判断数据是基本数据类型的情况和函数的情况 return typeof value; }}
详细资料可以参考:《JavaScript 专题之类型判断(上)》
159. 如何判断一个对象是否为空对象?
function checkNullObj(obj) { return Object.keys(obj).length === 0;}
详细资料可以参考:《js 判断一个 object 对象是否为空》
160. 使用闭包实现每隔一秒打印 1,2,3,4
// 使用闭包实现for (var i = 0; i < 5; i++) { (function(i) { setTimeout(function() { console.log(i); }, i * 1000); })(i);}// 使用 let 块级作用域for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, i * 1000);}
161. 手写一个 jsonp
function jsonp(url, params, callback) { // 判断是否含有参数 let queryString = url.indexOf("?") === "-1" ? "?" : "&"; // 添加参数 for (var k in params) { if (params.hasOwnProperty(k)) { queryString += k + "=" + params[k] + "&"; } } // 处理回调函数名 let random = Math.random() .toString() .replace(".", ""), callbackName = "myJsonp" + random; // 添加回调函数 queryString += "callback=" + callbackName; // 构建请求 let scriptNode = document.createElement("script"); scriptNode.src = url + queryString; window[callbackName] = function() { // 调用回调函数 callback(...arguments); // 删除这个引入的脚本 document.getElementsByTagName("head")[0].removeChild(scriptNode); }; // 发起请求 document.getElementsByTagName("head")[0].appendChild(scriptNode);}
详细资料可以参考:《原生 jsonp 具体实现》《jsonp 的原理与实现》
162. 手写一个观察者模式?
var events = (function() { var topics = {}; return { // 注册监听函数 subscribe: function(topic, handler) { if (!topics.hasOwnProperty(topic)) { topics[topic] = []; } topics[topic].push(handler); }, // 发布事件,触发观察者回调事件 publish: function(topic, info) { if (topics.hasOwnProperty(topic)) { topics[topic].forEach(function(handler) { handler(info); }); } }, // 移除主题的一个观察者的回调事件 remove: function(topic, handler) { if (!topics.hasOwnProperty(topic)) return; var handlerIndex = -1; topics[topic].forEach(function(item, index) { if (item === handler) { handlerIndex = index; } }); if (handlerIndex >= 0) { topics[topic].splice(handlerIndex, 1); } }, // 移除主题的所有观察者的回调事件 removeAll: function(topic) { if (topics.hasOwnProperty(topic)) { topics[topic] = []; } } };})();
详细资料可以参考:《JS 事件模型》
163. EventEmitter 实现
class EventEmitter { constructor() { this.events = {}; } on(event, callback) { let callbacks = this.events[event] || []; callbacks.push(callback); this.events[event] = callbacks; return this; } off(event, callback) { let callbacks = this.events[event]; this.events[event] = callbacks && callbacks.filter(fn => fn !== callback); return this; } emit(event, ...args) { let callbacks = this.events[event]; callbacks.forEach(fn => { fn(...args); }); return this; } once(event, callback) { let wrapFun = function(...args) { callback(...args); this.off(event, wrapFun); }; this.on(event, wrapFun); return this; }}
164. 一道常被人轻视的前端 JS 面试题
function Foo() { getName = function() { alert(1); }; return this;}Foo.getName = function() { alert(2);};Foo.prototype.getName = function() { alert(3);};var getName = function() { alert(4);};function getName() { alert(5);}//请写出以下输出结果:Foo.getName(); // 2getName(); // 4Foo().getName(); // 1getName(); // 1new Foo.getName(); // 2new Foo().getName(); // 3new new Foo().getName(); // 3
详细资料可以参考:《前端程序员经常忽视的一个 JavaScript 面试题》《一道考察运算符优先级的 JavaScript 面试题》《一道常被人轻视的前端 JS 面试题》
165. 如何确定页面的可用性时间,什么是 Performance API?
Performance API 用于精确度量、控制、增强浏览器的性能表现。这个 API 为测量网站性能,提供以前没有办法做到的精度。使用 getTime 来计算脚本耗时的缺点,首先,getTime方法(以及 Date 对象的其他方法)都只能精确到毫秒级别(一秒的千分之一),想要得到更小的时间差别就无能为力了。其次,这种写法只能获取代码运行过程中的时间进度,无法知道一些后台事件的时间进度,比如浏览器用了多少时间从服务器加载网页。为了解决这两个不足之处,ECMAScript 5引入“高精度时间戳”这个 API,部署在 performance 对象上。它的精度可以达到1毫秒的千分之一(1秒的百万分之一)。navigationStart:当前浏览器窗口的前一个网页关闭,发生 unload 事件时的 Unix 毫秒时间戳。如果没有前一个网页,则等于 fetchStart 属性。loadEventEnd:返回当前网页 load 事件的回调函数运行结束时的 Unix 毫秒时间戳。如果该事件还没有发生,返回 0。
根据上面这些属性,可以计算出网页加载各个阶段的耗时。比如,网页加载整个过程的耗时的计算方法如下:
var t = performance.timing;var pageLoadTime = t.loadEventEnd - t.navigationStart;
详细资料可以参考:《Performance API》
166. js 中的命名规则
(1)第一个字符必须是字母、下划线(_)或美元符号($)(2)余下的字符可以是下划线、美元符号或任何字母或数字字符一般我们推荐使用驼峰法来对变量名进行命名,因为这样可以与 ECMAScript 内置的函数和对象命名格式保持一致。
详细资料可以参考:《ECMAScript 变量》
167. js 语句末尾分号是否可以省略?
在 ECMAScript 规范中,语句结尾的分号并不是必需的。但是我们一般最好不要省略分号,因为加上分号一方面有利于我们代码的可维护性,另一方面也可以避免我们在对代码进行压缩时出现错误。
168. Object.assign()
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
169. Math.ceil 和 Math.floor
Math.ceil() === 向上取整,函数返回一个大于或等于给定数字的最小整数。Math.floor() === 向下取整,函数返回一个小于或等于给定数字的最大整数。
170. js for 循环注意点
for (var i = 0, j = 0; i < 5, j < 9; i++, j++) { console.log(i, j);}// 当判断语句含有多个语句时,以最后一个判断语句的值为准,因此上面的代码会执行 10 次。// 当判断语句为空时,循环会一直进行。
171. 一个列表,假设有 100000 个数据,这个该怎么办?
我们需要思考的问题:该处理是否必须同步完成?数据是否必须按顺序完成?解决办法:(1)将数据分页,利用分页的原理,每次服务器端只返回一定数目的数据,浏览器每次只对一部分进行加载。(2)使用懒加载的方法,每次加载一部分数据,其余数据当需要使用时再去加载。(3)使用数组分块技术,基本思路是为要处理的项目创建一个队列,然后设置定时器每过一段时间取出一部分数据,然后再使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。
172. js 中倒计时的纠偏实现?
在前端实现中我们一般通过 setTimeout 和 setInterval 方法来实现一个倒计时效果。但是使用这些方法会存在时间偏差的问题,这是由于 js 的程序执行机制造成的,setTimeout 和 setInterval 的作用是隔一段时间将回调事件加入到事件队列中,因此事件并不是立即执行的,它会等到当前执行栈为空的时候再取出事件执行,因此事件等待执行的时间就是造成误差的原因。一般解决倒计时中的误差的有这样两种办法:(1)第一种是通过前端定时向服务器发送请求获取最新的时间差,以此来校准倒计时时间。(2)第二种方法是前端根据偏差时间来自动调整间隔时间的方式来实现的。这一种方式首先是以 setTimeout 递归的方式来实现倒计时,然后通过一个变量来记录已经倒计时的秒数。每一次函数调用的时候,首先将变量加一,然后根据这个变量和每次的间隔时间,我们就可以计算出此时无偏差时应该显示的时间。然后将当前的真实时间与这个时间相减,这样我们就可以得到时间的偏差大小,因此我们在设置下一个定时器的间隔大小的时候,我们就从间隔时间中减去这个偏差大小,以此来实现由于程序执行所造成的时间误差的纠正。
详细资料可以参考:《JavaScript 前端倒计时纠偏实现》
173. 进程间通信的方式?
- 1.管道通信
- 2.消息队列通信
- 3.信号量通信
- 4.信号通信
- 5.共享内存通信
- 6.套接字通信
详细资料可以参考:《进程间 8 种通信方式详解》《进程与线程的一个简单解释》
174. 如何查找一篇英文文章中出现频率最高的单词?
function findMostWord(article) { // 合法性判断 if (!article) return; // 参数处理 article = article.trim().toLowerCase(); let wordList = article.match(/[a-z]+/g), visited = [], maxNum = 0, maxWord = ""; article = " " + wordList.join(" ") + " "; // 遍历判断单词出现次数 wordList.forEach(function(item) { if (visited.indexOf(item) < 0) { // 加入 visited visited.push(item); let word = new RegExp(" " + item + " ", "g"), num = article.match(word).length; if (num > maxNum) { maxNum = num; maxWord = item; } } }); return maxWord + " " + maxNum;}
面试题仓库推荐:
- https://github.com/febobo/web-interview
推荐阅读:
174道JavaScript 面试知识点总结(上)
174道JavaScript 面试知识点总结(中)
JS语法 ES6、ES7、ES8、ES9、ES10、ES11、ES12新特性
19个JavaScript数组常用方法总结
JavaScript 代码整洁之道
面试官问 Vue 性能优化,我该怎么回答
深入浅出前端本地储存
VUE中文社区
编程技巧 · 行业秘闻 · 技术动向
文章出处说明:拷贝源于VUE中文社区公众号