龙空技术网

2021最新高频前端面试题

相逢便可一笑 498

前言:

目前兄弟们对“前端面试题2021”大约比较重视,姐妹们都需要剖析一些“前端面试题2021”的相关资讯。那么小编也在网摘上汇集了一些对于“前端面试题2021””的相关内容,希望姐妹们能喜欢,我们快快来学习一下吧!

1、URL 和 URI 的区别?

URI: Uniform Resource Identifier      指的是统一资源标识符URL: Uniform Resource Location        指的是统一资源定位符URN: Universal Resource Name          指的是统一资源名称URI 指的是统一资源标识符,用唯一的标识来确定一个资源,它是一种抽象的定义,也就是说,不管使用什么方法来定义,只要能唯一的标识一个资源,就可以称为 URI。URL 指的是统一资源定位符,URN 指的是统一资源名称。URL 和 URN 是 URI 的子集,URL 可以理解为使用地址来标识资源,URN 可以理解为使用名称来标识资源。
2、get 和 post 请求在缓存方面的区别?
缓存一般只适用于那些不会更新服务端数据的请求。一般 get 请求都是查找请求,不会对服务器资源数据造成修改,而 post 请求一般都会对服务器数据造成修改,所以,一般会对 get 请求进行缓存,很少会对 post 请求进行缓存。
3、图片的懒加载和预加载
懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的 src 属性,以此来实现图片的延迟加载。预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
4、mouseover 和 mouseenter 的区别?
当鼠标移动到元素上时就会触发 mouseenter 事件,类似 mouseover,它们两者之间的差别是 mouseenter 不会冒泡。由于 mouseenter 不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其 mouseover 和 mouseout 事件,但是却不会触发 mouseenter 和 mouseleave 事件。
5、js 拖拽功能的实现
一个元素的拖拽过程,我们可以分为三个步骤,第一步是鼠标按下目标元素,第二步是鼠标保持按下的状态移动鼠标,第三步是鼠标抬起,拖拽过程结束。这三步分别对应了三个事件,mousedown 事件,mousemove 事件和 mouseup 事件。只有在鼠标按下的状态移动鼠标我们才会执行拖拽事件,因此我们需要在 mousedown 事件中设置一个状态来标识鼠标已经按下,然后在 mouseup 事件中再取消这个状态。在 mousedown 事件中我们首先应该判断,目标元素是否为拖拽元素,如果是拖拽元素,我们就设置状态并且保存这个时候鼠标的位置。然后在 mousemove 事件中,我们通过判断鼠标现在的位置和以前位置的相对移动,来确定拖拽元素在移动中的坐标。最后 mouseup 事件触发后,清除状态,结束拖拽事件。
6、为什么使用 setTimeout 实现 setInterval?怎么模拟?
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
7、let 和 const 的注意点?
声明的变量只在声明时的代码块内有效不存在声明提升存在暂时性死区,如果在变量声明前使用,会报错不允许重复声明,重复声明会报错
8、什么是 rest 参数?
rest 参数(形式为...变量名),用于获取函数的多余参数。
9、什么是尾调用,使用尾调用有什么好处?
尾调用指的是函数的最后一步调用另一个函数。我们代码执行是基于执行栈的,所以当我们在一个函数里调用另一个函数时,我们会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这个时候我们可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
10、Symbol 类型的注意点?
.Symbol 函数前不能使用 new 命令,否则会报错。Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。Object.getOwnPropertySymbols 方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。Symbol.for 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。
11、什么是 Proxy ?
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
12、Reflect 对象创建目的?
将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty,放到 Reflect 对象上。修改某些 Object 方法的返回结果,让其变得更合理。让 Object 操作都变成函数行为。Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为。
13、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"
14、什么是 Promise 对象,什么是 Promises/A+ 规范?
Promise 对象是异步编程的一种解决方案,最早由社区提出。Promises/A+ 规范是 JavaScript Promise 的标准,规定了一个 Promise 所必须具有的特性。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是 pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者 rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。状态的改变是通过 resolve() 和 reject() 函数来实现的,我们可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。
15、请手写一个 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);  }};
16、观察者模式和发布订阅模式有什么不同?
发布订阅模式其实属于广义上的观察者模式在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。而在发布订阅模式中,发布者和订阅者之间多了一个调度中心。调度中心一方面从发布者接收事件,另一方面向订阅者发布事件,订阅者需要在调度中心中订阅事件。通过调度中心实现了发布者和订阅者关系的解耦。使用发布订阅者模式更利于我们代码的可维护性。
17、Vue 的生命周期是什么?
Vue 的生命周期指的是组件从创建到销毁的一系列的过程,被称为 Vue 的生命周期。通过提供的 Vue 在生命周期各个阶段的钩子函数,我们可以很好的在 Vue 的各个生命阶段实现一些操作。
18、computed 和 watch 的差异?
(1)computed 是计算一个新的属性,并将该属性挂载到 Vue 实例上,而 watch 是监听已经存在且已挂载到 Vue 实例上的数据,所以用 watch 同样可以监听 computed 计算属性的变化。(2)computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值。而 watch 则是当数据发生变化便会调用执行函数。(3)从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据。
19、vue-router 中的导航钩子函数
(1)全局的钩子函数 beforeEach 和 afterEachbeforeEach 有三个参数,to 代表要进入的路由对象,from 代表离开的路由对象。next 是一个必须要执行的函数,如果不传参数,那就执行下一个钩子函数,如果传入 false,则终止跳转,如果传入一个路径,则导航到对应的路由,如果传入 error ,则导航终止,error 传入错误的监听函数。(2)单个路由独享的钩子函数 beforeEnter,它是在路由配置上直接进行定义的。(3)组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。它们是直接在路由组件内部直接进行定义的。
20、vue有哪些常用的修饰符?
.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;
21、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。
22、keep-alive 组件有什么作用?
如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。
23、vue 中 mixin 和 mixins 区别?
mixin 用于全局混入,会影响到每个组件实例。mixins 应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并
24、如何封装一个 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;  }}
25、如何判断一个对象是否为空对象?
function checkNullObj(obj) {  return Object.keys(obj).length === 0;}
26、如何使用闭包实现每隔一秒打印 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);}
27、请手写一个 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);}
28、请手写一个观察者模式?
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] = [];      }    }  };})();
29、这是一个经常被人忽略的前端 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
30、js 中的命名规则
(1)第一个字符必须是字母、下划线(_)或美元符号($)(2)余下的字符可以是下划线、美元符号或任何字母或数字字符一般我们推荐使用驼峰法来对变量名进行命名,因为这样可以与 ECMAScript 内置的函数和对象命名格式保持一致。
31、Object.assign()、Math.ceil 和 Math.floor作用
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。Math.ceil() === 向上取整,函数返回一个大于或等于给定数字的最小整数。Math.floor() === 向下取整,函数返回一个小于或等于给定数字的最大整数。

标签: #前端面试题2021