【JS面试大全下】

749 阅读14分钟

前言

总结超多常见的JS基础知识点,将网络上大多数文章都看了一遍。
再规划一遍,有什么问题都没有答案,自己大部分从掘金查找优质的文章。
因为都是零碎的问题,找的问题也太多或大致相同。
也就顾及不到什么版权问题,望见谅!嘻嘻
为自己大三下实习打好基础!自己要加油!!
也整理给有需要的小朋友们 ✨

JS基础

101. toPrecision 和 toFixed 和 Math.round 的区别?

toPrecision用于处理精度,从左至右第一个不为0的数开始数起。

toFixed用于处理小数点后精度的个数,从小数点处开始数起,末尾精度四舍五入计算。结果为字符型。

108. 什么是 MVVM?比之 MVC 有什么区别?什么又是 MVP ?

MVC是分离Model、View和Controller的交互模式,Controller主要用于控制用户与应用的响应操作,当页面节点发生变化时,会通过监听函数执行操作,并对Model进行更改,然后再去通知View层更新。

MVP和MVC的不同是MVP使用了presenter,MVC应用的是观察者模式,来实现当Model层发生变化时来更新View,因为View没有暴露给Controller接口,因此不能控制View的更新,这样View和Model层耦合在一起,而MVP实现了两者的解耦,presenter将View和Model绑定在一起实现同步更新,而MVVM将MVP的同步更新给自动化了。

MVVM中指的是Model、View和ViewMode,用来实现视图与数据分离的状态,View是指视图区域,用于页面渲染,展示数据。Model用于存储数据和数据的逻辑交互功能。ViewModel属于连接两者的纽带,当数据变化时,ViewModel监听到数据变化响应给View更新视图,当页面节点变化时,ViewModel响应给Model进行数据的更新。利用双向数据绑定同步更新View和Model。

109. vue 双向数据绑定原理?

实现MVVM的双向数据绑定,应用的是数据劫持结合订阅者发布者模式。首先监听者对数据属性进行监听通过Object.defineProperty()来劫持各个属性上的getter和setter,当数据变化时通知给订阅者,并且触发响应的监听回调来更新视图。MVVM作为一个数据接口还会有一个解析器,对每个元素节点的指令进行扫描和解析,并根据模板指令替换数据,进行初始化页面,同时绑定相应的更新函数,订阅数据变化,添加监听数据的订阅者,一旦数据有变化收到通知更新视图,这就实现了数据变化更新视图,视图变化更新数据的双向数据绑定。

110. Object.defineProperty 介绍?

Obect.defineProperty()方法有三个参数,第一个参数为需要定义属性的对象,第二个参数是需要定义的属性,第三个参数是描述符,属性的描述符有四个属性:value(属性的值)、writable(可读写)、enumerable(可枚举)、configurable(属性是否可配置修改)。

111. 使用 Object.defineProperty() 来进行数据劫持有什么缺点?

有些对数据的操作用这种方式无法进行数据劫持,比如对数组数据的修改和给对象新增属性。Vue3.0中可用proxy对对象进行代理实现,从而实现数据劫持,但兼容性不好,因为是ES6的语法。

112. 什么是 Virtual DOM?为什么 Virtual DOM 比原生 DOM 快?

当页面发生更新变化时,原生DOM更新的开销比较大,因此使用JS代码进行生成一个虚拟DOM,当数据发生更新时,会生成新的虚拟DOM,然后和之前的虚拟DOM进行对比,利用diff算法比较出两者的差异,然后将两者对应节点的不同结合到原生DOM树上完成更新。

使用虚拟DOM可以节约性能,提升操作效率,节省开销。避免使用原生DOM而带来的变化产生的回流与重绘。提升开发时的可维护性。

113. 如何比较两个 DOM 树的差异?

利用diff算法来比较两个DOM树的差异。两棵DOM树完全比较的时间度为O(n^3),在前端过程中我们一般不跨层级的移动元素,为了将时间度降为最低,比较两棵树的同一层级的节点。首先会对两棵树进行一个深度遍历,并且对每个节点标上序号。当深度遍历一个DOM树的时候,当遍历到一个节点会对比另一个DOM树的节点,如果有差异就保存到一个对象中。

114. 什么是 requestAnimationFrame ?

requestAnimationFrame是专门为浏览器解决js执行动画的api,我们知道动画效果是通过一帧一帧连续变化而形成的效果,如果我们用定时器来执行动画的话,会因为定时器属于异步函数而可能在规定时间之后执行,因为js是单线程的,所以异步队列要等同步任务执行完毕后再执行回调函数,这样就不能保证动画的流畅性。这时可以利用requestAnimationFrame来解决,它接收一个参数为动画执行函数,例如

function animation(){
    var div = document.querySelector('.box');
    div.style.width = parseInt(div.style.width) + 1 + 'px';
    if(div.style.width < 200){
        requestAnimationFrame(animation)
    }
}
requestAnimationFrame(animation);

115. 谈谈你对 webpack 的看法

使用webpack的主要目的就是为了简化项目依赖的管理,它将所有资源看成是一个模块,将所有逻辑代码看成是一个整体,从打包入口着手,将项目所需的依赖通过loader和plugin进行处理,然后输出一个能通过浏览器解析的js代码。webpack主要有四个核心的概念,分别是entry、output、loader和plugin。

entry是指项目打包的入口,在这个入口中找寻所有依赖文件。

output是项目打包的出口,打包成一个兼容性的js代码,默认位置为'./dist'。

loader属于webpack的编译器,是用于处理非JavaScript文件的打包,在对loader进行配置的时候,test用于规定哪些后缀结尾的文件用于打包,内容为正则表达式,use属性用于表示哪个loader用于对test文件进行预处理,常见的有css-loader、style-loader等。

plugins插件可以用于更广范围的功能,比如文件的压缩、优化、搭建服务器等功能。要使用一个插件先用npm安装,然后再添加到配置文件中使用。

116. offsetWidth/offsetHeight, clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别?

offsetWidth/offsetHeight返回一个只读属性,包含元素的border、padding、content以及scrollbar。

offsetLeft/offsetTop返回元素距离其最近的含有定位的祖先元素offsetParent的左侧距离和顶部距离。

clientWidth/clientHeight返回一个只读属性,为元素的内部宽度,content+padding。

clientLeft/clientTop返回元素顶部边框/左侧边框的宽度。

scrollWidth/scrollHeight只读,返回元素实际的宽度和高度,包括被卷起的高度/宽度。

scrollLeft/scrollTop返回元素被卷去的上侧距离/左侧距离。

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

函数式编程是一种编程规范,主要是通过一系列的函数调用来进行一些页面上内容的实现与操作。

118. 异步编程的实现方式?

异步编程的实现可以分为以下几种:

  1. 回调函数形式:通过回调函数来实现异步编程,让不同的任务按照顺序执行,前一个执行完毕才会执行下一个,但缺点是当任务量比较大时会产生层层嵌套的回调地狱问题,造成代码冗余复杂,难以维护。
  2. Promise对象:Promise可以解决回调地狱问题,通过new创建一个promise实例对象,接受一个回调函数作为参数,函数中接收两个状态函数分别为resolve和reject,代表状态由等待状态进入执行成功和执行失败状态。返回的promise对象可通过.then进行链式调用来实现后序的函数操作。
  3. generator,它可以在函数的执行过程中将执行权转移出去,在函数外部还可以将执行权转移回来。当我们遇到异步函数的时候将执行权转移出去,当异步函数执行完毕之后将执行权转移回来。这样我们在函数内部可以将异步语句以同步的方式来书写。
  4. async和await:普通函数前加上async可以将函数转化为异步函数,返回promise对象,函数内部执行到await语句时,如果语句返回promise对象则等当前语句执行完毕之后再向下执行。

119. Js 动画与 CSS 动画区别及相应实现

CSS动画较为简单,浏览器对CSS动画有很好的的处理,但不易于控制,不够灵活,且兼容性不够好。

JS动画可以很好的控制动画的灵活性,并且易于控制,并且可以单帧的进行控制操作,功能强大,但代码量大,可用于大型动画项目,若小动画还是css比较适宜。

120. get 请求传参长度的误区

其实get请求长度并没有限制参数内容长度的说法,同样post传参也没有长度限制,而是get请求一般是通过将参数加在url地址上,而通过浏览器和服务器对url长度有限制,因此get请求传参长度就会有限制,通常不同的浏览器对url长度限制不同。

121. URL 和 URI 的区别?

URL是统一资源定位符,URI是统一资源标识符,它是一个抽象的概念,是对资源的一个唯一的标识,不管通过什么方式,只要能对资源进行唯一标识,都可称为URI。URL是URI的一种,它是通过地址来对资源进行唯一标识。URN是统一资源名称,同理它也是URI的一种,它是通过名称来对资源进行统一标识。

122. get 和 post 请求在缓存方面的区别

get类似于查找的过程,用于获取数据,因此它不用每次都向服务器提交请求,可以使用缓存。

post不同,它一般是用于增加和修改功能,因此它必须每次提交都要向服务器请求,必须与数据库交互,不能使用缓存。

缓存只适用于那些不会向修改和添加服务器端数据的请求,一般get都是查找请求,不会更新数据库,因此get适宜用缓存数据,而post会更新数据库,因此不能利用缓存。

123. 图片的懒加载和预加载

图片的懒加载指的是一个延迟加载的功能,在页面加载的过程中,页面中的所有图片不会一次性全部加载出来,而是当页面滚动到当前位置时图片再加载,这样可以减轻页面加载的压力,使页面更为流畅。

图片的预加载指的是在页面加载之前图片就已经加载完毕并保存到本地中,当需要渲染的时候直接从本地获取,节省了图片加载的时间。预加载可利用Image创建一个实例对象,通过为image对象设置src属性来实现图片的预加载。懒加载和预加载都提高了网页的性能,一个是延迟甚至是不加载,一个是提前加载,懒加载对服务器端有一定的缓解压力作用,而预加载增加了服务器端的压力。

124. mouseover 和 mouseenter 的区别?

两者的区别是是否支持冒泡,两者功能类似,都是鼠标移动到某个元素上时会触发。mouseenter不支持冒泡,因此当鼠标移动到元素的子元素上时,父元素上可触发mouseover和mouseout事件,但不会触发mouseenter和mouseleave事件。

125. js 拖拽功能的实现

涉及到三个事件:mousedown、mousemove、mouseup

在mousedown事件函数中首先获取鼠标点击时的位置,可通过事件对象来获取,event.clientX和event.clientY,以及被拖拽元素的初始位置,可通过offsetLeft和offsetTop获取,并且创建一个鼠标移动事件mousemove,在这个事件函数中,将鼠标的位置-鼠标的初始位置+元素的初始位置 赋值给元素的位置样式,不断的鼠标移动就会不断的去触发这个事件。然后在创建鼠标抬起事件mouseup,在这个事件函数中取消鼠标摁下事件和鼠标移动事件,清除状态。

126. 为什么使用 setTimeout 实现 setInterval?怎么模拟?

setInterval计时器是每通过一定时间去执行一个函数,但实际上是每经过一定时间将这个事件添加到任务队列中,等待执行栈中的事件执行完毕之后才从任务队列中获取事件压入执行栈,这样就不能保证是每隔一段时间去执行一个事件了。那么可以通过setTimeout方法来执行,原理是利用递归的方式不断的调用函数来执行setTimeout。

function myInterval(fn, wait){
    var timer = {
        flag: true
    };
    function interval(){
        if(timer.flag){
            fn();
            setTimeout(interval, wait);
        }
    }
    setTimeout(interval, wait);
    return timer;
}

127. let 和 const 的注意点?

  1. let和const声明变量时不能变量提升。
  2. let和const有自己单独的作用域。
  3. 不允许重复声明,重复声明会报错。
  4. const声明的变量不允许修改其值,const声明的为常量。

128. 什么是 rest 参数?

rest 参数(形式为...变量名),用于获取函数的多余参数。

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

尾调用指的是函数的最后一步调用另一个函数,我们的代码执行是基于执行栈的,所以当我们在函数内调用另一个函数时是保留了当前的执行上下文,当在最后一步调用函数时会将新的执行上下文压入到执行栈中,因为是在最后一步调用因此我们可以不必再保留当前的执行上下文,从而优化了内存,这就是尾调用的好处。

130. Symbol 类型的注意点?

Symbol函数不能使用new命令,否则会报错。

Symbol函数可以接受字符串作为参数,表示对实例的描述。

Symbol作为属性名不会出现在for..in以及for..of中。

131. Set 和 WeakSet 结构?

Set作为一种数据结构,类似于数组,它保证了数组元素的唯一性,没有重复的值。

WeakSet与Set类似,也是不重复的值的集合,但成员只能是对象,不能是其他类型的值,它代表一种弱引用不能被垃圾回收机制回收

132. Map 和 WeakMap 结构?

Map作为一种数据结构,类似于对象,是键值对形式存在,但是键的范围不仅仅可以是字符串,还可以是任何类型的值都可以当做键。

WeakMap是Map类似,但键只能是对象,不是是其他类型结构,同时键名所指向的对象不能计入垃圾回收机制。

133. 什么是 Proxy ?

Proxy意思是代理,可以修改默认操作的行为,它相当于在目标对象之前设置一层“拦截”,任何对目标对象的操作都要经过过滤和改写,等同于在语言层面上做出修改,即元编程。

134. Reflect 对象创建目的?

1)Object对象的一些内部方法放在了Reflect上面,比如:Object.defineProperty。主要是优化了语言内部的方法。

2)修改Object方法的返回,例如:Object.definePropery(obj,name,desc)无法定义属性时报错,而Reflect.definedProperty(obj,name,desc)则会返回false。

3)让Object变成函数的行为,以前的:name in obj和delete obj[name],可以让Reflect.has(name)和Reflect.deleteProperty(obj,name)替代。

4)Reflect方法和Proxy方法一一对应。主要就是为了实现本体和代理的接口一致性,方便用户通过代理操作本体。

135. require 模块引入的查找方式?

当require引入路径没有引入后缀时,首先查找该路径下是否有同名JS文件,若没有同名JS文件,就去找同名文件夹下的index.js,如果文件夹中没有index.js就会去当前文件夹中的package.json中查找main选项中的入口文件,如果指定入口文件不存在就会报错。

当require没有引入路径也没有引入后缀时,首先node.js会默认它引入的是系统模块,就会去node_modules中去查找同名js文件,若没有同名js文件就查找是否有同名的文件夹,找同名文件夹的index.js文件,若没有index.js文件就查看该文件夹中的package.json中的main选项中的入口文件,若都没有的话则会报错。

136. 什么是 Promise 对象,什么是 Promises/A+ 规范?

Promise对象是异步编程的一种解决方法,Promises/A+规范是JavaScript Promise的一种准则规范,规定了Promise的一些编程标准。Promise是一个构造函数,创建promise实例对象,接收一个回调函数作为参数,返回一个promise实例,该实例有三种状态,分别是pending、resolved和rejected,状态只能由pending转变为resolved或者pending转变为rejected,状态改变之后就凝固了不会转变为其他状态。在异步任务之后通过调用resolved或rejected方法来转变状态,返回一个promise对象,通过.then链式调用的方式来定义resolved和rejected的回调函数。

137. 手写一个 Promise

function myPromise(fn){
    var self = this;
    this.state = 'penging';
    this.value = null;
    this.resolvedCallback = [];
    this.rejectedCallback = [];
    function resolve(value){
        if(value instanceof myPromise){
            return value.then(resolved, rejected);
        }
        setTimeout(() => {
            if(self.state == 'pending'){
                self.value = value;
                self.state = 'resolved';
                self.resolvedCallback.forEach(callback => {
                    callback(value);
                })
            }
        }, 0)
    }
    function reject(value){
        setTimeout(() => {
            if(self.state == 'pending'){
                self.value = value;
                self.state = 'rejected';
                self.rejectedCallback.forEach(callback => {
                    callback(value);
                })
            }
        }, 0)
    }
    //将两个方法传入函数执行
    try{
        fn(resolve, reject);
    } catch(e){
        rejecte(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.resolvedCallback.push(onResolved);
        this.rejectedCallback.push(onRejected);
    }
    //如果状态已经凝固 则直接进行对应的状态
    if(this.state == 'resolved'){
        onResolved(this.value);
    }
    if(this.state == 'rejected'){
        onRejected(this.value);
    }
}

138. 如何检测浏览器所支持的最小字体大小?

可以为DOM字体设置为某一个字体大小,然后再将这个字体取出来,如果能够成功,就说明支持。

139. 怎么做 JS 代码 Error 统计?

利用window.error事件

140. 单例模式模式是什么?

juejin.cn/post/684490…

141. 策略模式是什么?

juejin.cn/post/684490…

142. 代理模式是什么?

juejin.cn/post/684490…

143. 中介者模式是什么?

juejin.cn/post/684490…

144. 适配器模式是什么?

适配器模式用来解决当两个接口不兼容情况下,对接口进行包装适配而不需要改变原有接口,一般适用于当接口被应用到太多程序之中修改原有接口很不方便,这样就可以使用适配器来对接口进行包装输出,例如我们需要获取一个格式化后的时间,但不能对原有时间接口进行修改,就可以利用适配器对时间接口进行封装。

145. 观察者模式和发布订阅模式有什么不同?

发布订阅模式属于广义上的观察者模式,观察者模式中观察者需要直接订阅事件,当目标发出内容的改变之后,就会直接通知观察者进行响应。

发布订阅模式中有一个调度中心,能够实现发布者和订阅者之间的解耦,即当发布者发布事件之后,调度中心会一方面向发布者处接收事件,然后向订阅者发布事件,订阅者需要向调度中心订阅事件,这样的解耦有利于后期代码的可维护性。

146. Vue 的生命周期是什么?

Vue的生命周期指的是组件从创建到销毁的一系列过程。通过Vue在生命周期的各个阶段提供的钩子函数,我们可以在各个阶段进行一些操作。

147. Vue 的各个生命阶段是什么?

  1. beforeCreate钩子函数:在Vue组件初始化时产生,此时数据还没有得到监听,事件尚未配置,此时是无法获取数据的。
  2. created钩子函数:实例创建完成后触发,此时组件尚未挂载到页面中,但可以访问data、methods属性。一般此时我们可以用于获取页面的初始数据工作。
  3. beforeMount钩子函数:组件挂载到页面之前触发,此时会找到对应的template,编译成render函数。
  4. mounted钩子函数:组件挂载到页面之后触发,此时可通过DOM相关api获取dom元素。
  5. beforeUpdate钩子函数:当响应式数据或节点发生更新时触发,此时虚拟DOM尚未渲染完毕。
  6. updated钩子函数:虚拟DOM重新渲染完毕之后触发。
  7. beforeDestory钩子函数:在实例销毁之前调用,此时可用来销毁定时器,解绑全局事件等。
  8. destoryed钩子函数:在实例销毁之后调用,调用之后实例的所有监听事件都会解除绑定,所有子实例也会被消除。

148. Vue 组件间的参数传递方式?

juejin.cn/post/686154…

149. computed 和 watch 的差异?

computed是计算属性,当一个值需要通过一系列的变量计算得到或是通过监听某个事件得到,可以通过计算属性获得,得到的值可以用于函数中。

watch是监听某一个数据的值,当这个数据发生变化时,会产生对其他数据的影响,调用执行函数。总结就是computed是多个数据影响一个数据,而watch是一个数据影响多个数据。

150. vue-router 中的导航钩子函数

vue-router中的导航钩子函数可以成为路由守卫。

  1. 全局的导航钩子:beforeEach和afterEach,两者分别是在每个路由前使用和路由后使用,拿beforeEach例子来说,接收三个参数,分别为to,from,next,to表示要进入的路由,from表示离开的路由,next若参数为空则表示直接执行下一个钩子函数,若参数为路径,则导航到对应的路由,若参数为false,就禁止跳转,若为error则导航终于,传入错误的监听函数。
  2. 路由内导航钩子,在路由配置内定义,单独路由拥有的导航钩子。
  3. 组件内导航钩子,在组件内定义,主要有beforeRouteUpdate,beforeRouteEnter, beforeRouteLeave。

151. route和router 的区别?

route是路由信息对象,包括路由的path, params, name, hash, query等信息

router是路由实例对象,包括路径的跳转方法,钩子函数等。

152. vue 常用的修饰符?

vue常用的修饰符有.pevent, .stop, .self等,.pevent表示取消该事件的其默认行为,例如a标签取消点击跳转默认行为,.stop表示取消冒泡事件, .self表示该事件发生在这个元素本身而不是其子元素的事件。

153. vue 中 key 值的作用?

vue中的key值可以分为两种情况来调用:

  1. v-if中用到key值,因为vue为了更好更快速的渲染页面默认使用元素复用的原则,尽可能复用已有的元素而不是从头开始渲染,例如说一个input元素我们在更新切换时会复用同一个元素,那么用户在之前输入的内容可能会被保留下来这是不符合规范的,因此为每一个v-if添加一个key属性用来作为唯一标识,这样使用key的元素不会被复用。
  2. v-for中用到key值,是因为我们在使用v-for更新迭代渲染过的元素时,为了避免vue默认就地复用的原则,用唯一标识添加key值,当更新渲染时,如果数据列表发生改变,vue是不会采用移动DOM元素来更改位置,而是复用原先的元素,因此为每个列表项添加一个key值来追踪每个元素的身份。
  3. 为虚拟DOM的diff算法提供唯一标识

154. computed 和 watch 区别?

看149点

155. keep-alive 组件有什么作用?

如果我们在进行组件切换的时候需要保存一些组件的状态,就可以使用keep-alive组件将需要保存状态的组件包裹起来,防止多次渲染。

156. vue 中 mixin 和 mixins 区别?

mixin为全局混入,也就是说若创建了一个全局混入组件,会影响到所有vue组件创建的实例。

mixins提供了一个灵活的方式,可分发vue组件中的可复用功能,一个混入对象可混入任意组件内容。当组件使用混入对象时,所有混合对象的选项将被“混合”进组件内的所有选项。当组件和混入对象有相同选项时,会进行恰当的合并,例如数据对象在内部会进行递归合并,冲突时刻以组件数据优先。混入对象的钩子函数将在组件内钩子函数之前调用。值为对象的一些选项,例如methods,components等会合并为一个对象,两个对象键名冲突时会取组件对象的键值对。

157. 开发中常用的几种 Content-Type ?

  1. application/x-www-form-urlencoded:该数据格式主要存放在body中,主要是key1=val1&key2=val2的格式(键值对)进行编码。
  2. application/json:该数据格式主要是以json字符串格式进行编码。
  3. text/xml:该种方式主要是用来提交xml格式的数据。
  4. multipart/form-data:该种数据格式主要用来提交表单形式的数据。

158. 如何封装一个 javascript 的类型判断函数?

function typeof(value){
    if(value === null){
        return null + '';
    }
    if(typeof value === 'object'){
        //如果为引用数据类型
        let valueClass = Object.prototype.toString.call(value).split(' ')[1],
            type = valueClass.split('');
        type.pop();
        return type.join('').toLowerCase();
    } else {
        return value;
    }
}

159. 如何判断一个对象是否为空对象?

  1. 使用for..in:

    for(var k in obj){
        //如果对象非空,则可以执行到此处
        return true;
    }
    return false;
    
  2. 使用JSON自带的stringify方法,转化为json字符串

    if(JSON.stringify(obj) === '{}'){
        return false;
    }
    return true;
    
  3. ES6新增的Object.keys()方法,可以返回对象中所有可枚举属性组成的数据

    var a = {};
    Object.keys(a);   //[]
    //我们可以利用对象中可枚举数组的长度是否为0来判断是否为空对象
    if(Object.keys(obj).length === 0){
        return false;
    }
    return true;
    

160. 使用闭包实现每隔一秒打印 1,2,3,4

for(let i = 1; i <= 4; i++){
    setTimeout(function(){
        console.log(i)
    }, i*1000)
}

for(var i = 1; i <=4; i++){
    (function(j){
        setTimeout(function(){
            console.log(j)
        }, j*1000)
    })(i)
}

161. 手写一个 jsonp

function jsonp(url, params, callback){
    //首先判断url本身是否带有参数 即是否带有?
    queryString = url.indexOf('?') === '-1' ? '?' : '&';
    for(var k in params){
        if(params.hanOwnProperty(k)){
            queryString += k + '=' + params[k];
        }
    }
    //定义一个随机的函数名 添加到参数中
    var randomName = Math.random().toString().replace('.', '');
    var myFunction = 'myFunction' + randomName;
    queryString += 'callback' + '=' + myFunction;
    url += queryString;
    //动态创建script标签
    var myScript = document.createElement('script');
    myScript.src = url;
    window[myFunction] = function(){
        callback(...arguments);
        //删除这个动态脚本
        doucment.getElementsByTagName('head')[0].removeChild(myScript);
    };
    //将脚本插入到head中
    document.getElementsByTagName('head')[0].appendChild(myScript);
  

162. 手写一个观察者模式?

var event = (function(){
    var topics = {};
    return {
        //发布模式
        publish: function(topic, info){
            console.log('publish a topic:' + topic);
            if(topics.hasOwnProperty(topic)){
                topics[topic].forEach(function(handler){
                    handler(info ? info : {});
                })
            }
        },
        //订阅模式
        subscribe: function(topic, handler){
            console.log('subscribe a topic:' + topic);
            if(!topics.hasOwnProperty(topic)){
                topics[topic] = [];
            }
            topics[topic].push(handler);
        },
        remove: function(topic, handler){
            if(!topics.hasOwnProperty(topic)){
                return;
            }
            var topicIndex = -1;
            topics[topic].forEach(function(element, index){
                if(element === handler){
                    topicIndex = index;
                }
            })
            topics[topic].splice(topicIndex, 1);
        },
        removeAll: function(topic){
            console.log('remove all the handler on the topic');
            if(topics.hasOwnProperty(topic)){
                topics[topic].length = 0;
            }
        }
    }
})()
var handler = function(info){
    console.log(info);
}
//订阅hello主题
event.subscribe('hello', handler);
//发布hello主题
event.publish('hello', 'hello world');

163. EventEmitter 实现

class eventEmitter {
    constructor(){
        this.events = {}
    }
    on(event, callback){
        let callbacks = this.events[event] || [];
        callbacks.push(callback);
        this.events[event] = callbacks;
        return this;
    }
    emit(event, ...args){
        let callbacks = this.events[event];
        callbacks.forEach(fn => {
            fn(...args);
        })
        return this;
    }
    off(event, callback){
        let callbacks = this.events[event];
        callbacks = callbacks.filter(fn => return fn !== callback);
        this.events[event] = callbacks;
        return this;
    }
    once(event, callback){
        let wrapFun = function(...args){
            callback(...args);
            this.off(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(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3

165. 如何确定页面的可用性时间,什么是 Performance API?

JavaScript为了解决浏览器时间精度不够小只能精确到毫秒级别的误差以及无法得知向服务器端请求资源的事件,添加了一个performance的api,就是一个精密时间戳,它有两个方法,一个是navigationStart,它代表前一个网页关闭时的时间戳,另一个是loadEventEnd,它是当前网页的load事件的回到函数执行结束时的高精度时间戳。用这两个属性可以计算出网页加载整个的耗时:

var t = performance.timing;
var pageLoadTime = t.loadEventEnd - t.navigationStart;

166. js 中的命名规则

通常变量的命名规则为第一个字符要求是字母、下划线或者是美元符合$。其他字符可以是字母、数字、下划线和美元符号。命名规则通常要求为驼峰命名法,可以与ECMAScript的内置函数和对象配置一致。

167. js 语句末尾分号是否可以省略?

最好不要省略,因为语句末尾添加分号,在代码压缩优化之后不会产生错误,并且方便日后维护

168. 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);
}

当判断语句为多个语句时,以最后的语句为准,如上代码以j < 9为准。若判断语句为空,则会一直循环下去。

171. 一个列表,假设有 100000 个数据,这个该怎么办?

当我们有大量数据时需要考虑几个问题,首先这些数据是否需要同步显示,其次这些数据是否需要按照顺序显示

解决方法:

  1. 我们可以使用分页技术,让这些数据分页显示在浏览器上,每次只显示并加载一页数据,其余数据等浏览器操作不同页数时在向服务器端请求渲染。
  2. 可以使用懒加载方式,让一部分数据先显示出来,然后当浏览器需要显示某部分数据的时候再去加载那一部分数据,可以减轻服务器端压力,使性能优化。
  3. 可以给数据分组显示,比如显示一个定时器,一定时间内显示一部分数据。
  4. 采用虚拟列表,减轻页面的DOM元素构建压力,大大提升浏览器渲染效率

172. js 中倒计时的纠偏实现?

juejin.cn/post/684490…

173. 进程间通信的方式?

管道通信、任务队列通信、信号量通信、信号通信、套接字通信、共享内存通信。

174. 如何查找一篇英文文章中出现频率最高的单词?

function findWord(article){   
  if(article == null){   
    return article  
  }   
  article = article.trim().toLowerCase();   
  var wordList = article.match(/[a-z]+/g);   
  article = ' ' + wordList.join(' ') + ' ';   
  var max = 0;   
  var maxWord = '';   
  var list = [];   
  wordList.forEach(word => {   
    if(list.indexof(word) === '-1'){   
      list.push(word);   
      var newWord = new RegExp(' ' + word + ' ');    
      var wordLength = article.match(newWord).length;   
      if(wordLength > max){   
        max = wordLength;   
        maxWord = word;   
      }   
    }   
  })   
  return maxWord + ' ' + max;  
}

后言

因为字数限制 只能分为上下!
JS基础上篇

在这必须提到的借鉴文章-
牛客网大神