关于前端:前端高频面试题六附答案

2次阅读

共计 10545 个字符,预计需要花费 27 分钟才能阅读完成。

如何优化动画?

对于如何优化动画,咱们晓得,个别状况下,动画须要频繁的操作 DOM,就就会导致页面的性能问题,咱们能够将动画的 position 属性设置为 absolute 或者fixed,将动画脱离文档流,这样他的回流就不会影响到页面了。

Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题?你能说说如下代码的实现原理么?

1)Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题

  1. Vue 应用了 Object.defineProperty 实现双向数据绑定
  2. 在初始化实例时对属性执行 getter/setter 转化
  3. 属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的(这也就造成了 Vue 无奈检测到对象属性的增加或删除)

所以 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

2)接下来咱们看看框架自身是如何实现的呢?

Vue 源码地位:vue/src/core/instance/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
  // target 为数组  
  if (Array.isArray(target) && isValidArrayIndex(key)) {// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的 splice 变异办法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // key 曾经存在,间接批改属性值  
  if (key in target && !(key in Object.prototype)) {target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // target 自身就不是响应式数据, 间接赋值
  if (!ob) {target[key] = val
    return val
  }
  // 对属性进行响应式解决
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  1. 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  2. 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
  3. 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决

defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法

哪些状况会导致内存透露

1、意外的全局变量:因为应用未声明的变量, 而意外的创立了一个全局变量, 而使这个变量始终留在内存中无奈被回收
2、被忘记的计时器或回调函数:设置了 setInterval 定时器,而遗记勾销它,如果循环函数有对外部变量的援用的话,那么这个变量会被始终留在内存中,而无奈被回收。3、脱离 DOM 的援用:获取一个 DOM 元素的援用,而前面这个元素被删除,因为始终保留了对这个元素的援用,所以它也无奈被回收。4、闭包:不合理的应用闭包,从而导致某些变量始终被留在内存当中。

对象创立的形式有哪些?

个别应用字面量的模式间接创建对象,然而这种创立形式对于创立大量类似对象的时候,会产生大量的反复代码。但 js 和个别的面向对象的语言不同,在 ES6 之前它没有类的概念。然而能够应用函数来进行模仿,从而产生出可复用的对象创立形式,常见的有以下几种:

(1)第一种是工厂模式,工厂模式的次要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目标。然而它有一个很大的问题就是创立进去的对象无奈和某个类型分割起来,它只是简略的封装了复用代码,而没有建设起对象和类型间的关系。

(2)第二种是构造函数模式。js 中每一个函数都能够作为构造函数,只有一个函数是通过 new 来调用的,那么就能够把它称为构造函数。执行构造函数首先会创立一个对象,而后将对象的原型指向构造函数的 prototype 属性,而后将执行上下文中的 this 指向这个对象,最初再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因而能够应用 this 给对象赋值。构造函数模式绝对于工厂模式的长处是,所创立的对象和构造函数建设起了分割,因而能够通过原型来辨认对象的类型。然而构造函数存在一个毛病就是,造成了不必要的函数对象的创立,因为在 js 中函数也是一个对象,因而如果对象属性中如果蕴含函数的话,那么每次都会新建一个函数对象,节约了不必要的内存空间,因为函数是所有的实例都能够通用的。

(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它蕴含了通过构造函数创立的所有实例都能共享的属性和办法。因而能够应用原型对象来增加专用属性和办法,从而实现代码的复用。这种形式绝对于构造函数模式来说,解决了函数对象的复用问题。然而这种模式也存在一些问题,一个是没有方法通过传入参数来初始化值,另一个是如果存在一个援用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对援用类型值的扭转会影响所有的实例。

(4)第四种模式是组合应用构造函数模式和原型模式,这是创立自定义类型的最常见形式。因为构造函数模式和原型模式离开应用都存在一些问题,因而能够组合应用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数办法的复用。这种办法很好的解决了两种模式独自应用时的毛病,然而有一点有余的就是,因为应用了两种不同的模式,所以对于代码的封装性不够好。

(5)第五种模式是动静原型模式,这一种模式将原型办法赋值的创立过程挪动到了构造函数的外部,通过对属性是否存在的判断,能够实现仅在第一次调用函数时对原型对象赋值一次的成果。这一种形式很好地对下面的混合模式进行了封装。

(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的了解是,它次要是基于一个已有的类型,在实例化时对实例化的对象进行扩大。这样既不必批改原来的构造函数,也达到了扩大对象的目标。它的一个毛病和工厂模式一样,无奈实现对象的辨认。

对象继承的形式有哪些?

(1)第一种是以原型链的形式来实现继承,然而这种实现形式存在的毛病是,在蕴含有援用类型的数据时,会被所有的实例对象所共享,容易造成批改的凌乱。还有就是在创立子类型的时候不能向超类型传递参数。

(2)第二种形式是应用借用构造函数的形式,这种形式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种办法解决了不能向超类型传递参数的毛病,然而它存在的一个问题就是无奈实现函数办法的复用,并且超类型原型定义的办法子类型也没有方法拜访到。

(3)第三种形式是组合继承,组合继承是将原型链和借用构造函数组合起来应用的一种形式。通过借用构造函数的形式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现办法的继承。这种形式解决了下面的两种模式独自应用时的问题,然而因为咱们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

(4)第四种形式是原型式继承,原型式继承的次要思路就是基于已有的对象来创立新的对象,实现的原理是,向函数中传入一个对象,而后返回一个以这个对象为原型的对象。这种继承的思路次要不是为了实现发明一种新的类型,只是对某个对象实现一种简略继承,ES5 中定义的 Object.create() 办法就是原型式继承的实现。毛病与原型链形式雷同。

(5)第五种形式是寄生式继承,寄生式继承的思路是创立一个用于封装继承过程的函数,通过传入一个对象,而后复制一个对象的正本,而后对象进行扩大,最初返回这个对象。这个扩大的过程就能够了解是一种继承。这种继承的长处就是对一个简略对象实现继承,如果这个对象不是自定义类型时。毛病是没有方法实现函数的复用。

(6)第六种形式是寄生式组合继承,组合继承的毛病就是应用超类型的实例做为子类型的原型,导致增加了不必要的原型属性。寄生式组合继承的形式是应用超类型的原型的副原本作为子类型的原型,这样就防止了创立不必要的属性。

DNS 记录和报文

DNS 服务器中以资源记录的模式存储信息,每一个 DNS 响应报文个别蕴含多条资源记录。一条资源记录的具体的格局为

(Name,Value,Type,TTL)

其中 TTL 是资源记录的生存工夫,它定义了资源记录可能被其余的 DNS 服务器缓存多长时间。

罕用的一共有四种 Type 的值,别离是 A、NS、CNAME 和 MX,不同 Type 的值,对应资源记录代表的意义不同:

  • 如果 Type = A,则 Name 是主机名,Value 是主机名对应的 IP 地址。因而一条记录为 A 的资源记录,提供了标 准的主机名到 IP 地址的映射。
  • 如果 Type = NS,则 Name 是个域名,Value 是负责该域名的 DNS 服务器的主机名。这个记录次要用于 DNS 链式 查问时,返回下一级须要查问的 DNS 服务器的信息。
  • 如果 Type = CNAME,则 Name 为别名,Value 为该主机的标准主机名。该条记录用于向查问的主机返回一个主机名 对应的标准主机名,从而通知查问主机去查问这个主机名的 IP 地址。主机别名次要是为了通过给一些简单的主机名提供 一个便于记忆的简略的别名。
  • 如果 Type = MX,则 Name 为一个邮件服务器的别名,Value 为邮件服务器的标准主机名。它的作用和 CNAME 是一 样的,都是为了解决标准主机名不利于记忆的毛病。

什么是作用域?

ES5 中只存在两种作用域:全局作用域和函数作用域。在 JavaScript 中,咱们将作用域定义为一套规定,这套规定用来治理引擎如何在以后作用域以及嵌套子作用域中依据标识符名称进行变量(变量名或者函数名)查找

如果 new 一个箭头函数的会怎么样

箭头函数是 ES6 中的提出来的,它没有 prototype,也没有本人的 this 指向,更不能够应用 arguments 参数,所以不能 New 一个箭头函数。

new 操作符的实现步骤如下:

  1. 创立一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的 prototype 属性)
  3. 指向构造函数中的代码,构造函数中的 this 指向该对象(也就是为这个对象增加属性和办法)
  4. 返回新的对象

所以,下面的第二、三步,箭头函数都是没有方法执行的。

如何优化要害渲染门路?

为尽快实现首次渲染,咱们须要最大限度减小以下三种可变因素:

(1)要害资源的数量。

(2)要害门路长度。

(3)关键字节的数量。

要害资源是可能阻止网页首次渲染的资源。这些资源越少,浏览器的工作量就越小,对 CPU 以及其余资源的占用也就越少。同样,要害门路长度受所有要害资源与其字节大小之间依赖关系图的影响:某些资源只能在上一资源处理完毕之后能力开始下载,并且资源越大,下载所需的往返次数就越多。最初,浏览器须要下载的关键字节越少,解决内容并让其呈现在屏幕上的速度就越快。要缩小字节数,咱们能够缩小资源数(将它们删除或设为非关键资源),此外还要压缩和优化各项资源,确保最大限度减小传送大小。

优化要害渲染门路的惯例步骤如下:

(1)对要害门路进行剖析和个性形容:资源数、字节数、长度。

(2)最大限度缩小要害资源的数量:删除它们,提早它们的下载,将它们标记为异步等。

(3)优化要害字节数以缩短下载工夫(往返次数)。

(4)优化其余要害资源的加载程序:您须要尽早下载所有要害资产,以缩短要害门路长度

手写公布订阅

class EventListener {listeners = {};
    on(name, fn) {(this.listeners[name] || (this.listeners[name] = [])).push(fn)
    }
    once(name, fn) {let tem = (...args) => {this.removeListener(name, fn)
            fn(...args)
        }
        fn.fn = tem
        this.on(name, tem)
    }
    removeListener(name, fn) {if (this.listeners[name]) {this.listeners[name] = this.listeners[name].filter(listener => (listener != fn && listener != fn.fn))
        }
    }
    removeAllListeners(name) {if (name && this.listeners[name]) delete this.listeners[name]
        this.listeners = {}}
    emit(name, ...args) {if (this.listeners[name]) {this.listeners[name].forEach(fn => fn.call(this, ...args))
        }
    }
}

与缓存相干的 HTTP 申请头有哪些

强缓存:

  • Expires
  • Cache-Control

协商缓存:

  • Etag、If-None-Match
  • Last-Modified、If-Modified-Since

PWA 应用过吗?serviceWorker 的应用原理是啥?

渐进式网络应用(PWA)是谷歌在 2015 年底提出的概念。基本上算是 web 应用程序,但在外观和感觉上与 原生 app相似。反对 PWA 的网站能够提供脱机工作、推送告诉和设施硬件拜访等性能。

Service Worker是浏览器在后盾独立于网页运行的脚本,它关上了通向不须要网页或用户交互的性能的大门。当初,它们已包含如推送告诉和后盾同步等性能。未来,Service Worker将会反对如定期同步或天文围栏等其余性能。本教程探讨的外围性能是拦挡和解决网络申请,包含通过程序来治理缓存中的响应。

常见的程度垂直形式有几种?

// 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 translate 来调整元素的中心点到页面的核心。该办法须要思考浏览器兼容问题。.parent {position: relative;}

.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
// 利用相对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于盒子有宽高的状况:.parent {position: relative;}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
// 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 margin 负值来调整元素的中心点到页面的核心。该办法实用于盒子宽高已知的状况
.parent {position: relative;}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 本身 height 的一半 */
    margin-left: -50px;    /* 本身 width 的一半 */
}
// 应用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要 ** 思考兼容的问题 **,该办法在挪动端用的较多:.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}
// 另外,如果父元素设置了 flex 布局,只须要给子元素加上 `margin:auto;` 就能够实现垂直居中布局
.parent{display:flex;}
.child{margin: auto;}

对 CSS 工程化的了解

CSS 工程化是为了解决以下问题:

  1. 宏观设计:CSS 代码如何组织、如何拆分、模块构造怎么设计?
  2. 编码优化:怎么写出更好的 CSS?
  3. 构建:如何解决我的 CSS,能力让它的打包后果最优?
  4. 可维护性:代码写完了,如何最小化它后续的变更老本?如何确保任何一个共事都能轻松接手?

以下三个方向都是时下比拟风行的、普适性十分好的 CSS 工程化实际:

  • 预处理器:Less、Sass 等;
  • 重要的工程化插件:PostCss;
  • Webpack loader 等。

基于这三个方向,能够衍生出一些具备典型意义的子问题,这里咱们一一来看:

(1)预处理器:为什么要用预处理器?它的呈现是为了解决什么问题?

预处理器,其实就是 CSS 世界的“轮子”。预处理器反对咱们写一种相似 CSS、但理论并不是 CSS 的语言,而后把它编译成 CSS 代码:那为什么写 CSS 代码写得好好的,偏偏要转去写“类 CSS”呢?这就和原本用 JS 也能够实现所有性能,但最初却写 React 的 jsx 或者 Vue 的模板语法一样——为了爽!要想晓得有了预处理器有多爽,首先要晓得的是传统 CSS 有多不爽。随着前端业务复杂度的进步,前端工程中对 CSS 提出了以下的诉求:

  1. 宏观设计上:咱们心愿能优化 CSS 文件的目录构造,对现有的 CSS 文件实现复用;
  2. 编码优化上:咱们心愿能写出构造清晰、扼要易懂的 CSS,须要它具备高深莫测的嵌套层级关系,而不是无差别的一铺到底写法;咱们心愿它具备变量特色、计算能力、循环能力等等更强的可编程性,这样咱们能够少写一些无用的代码;
  3. 可维护性上:更强的可编程性意味着更优质的代码构造,实现复用意味着更简略的目录构造和更强的拓展能力,这两点如果能做到,天然会带来更强的可维护性。

这三点是传统 CSS 所做不到的,也正是预处理器所解决掉的问题。预处理器广泛会具备这样的个性:

  • 嵌套代码的能力,通过嵌套来反映不同 css 属性之间的层级关系;
  • 反对定义 css 变量;
  • 提供计算函数;
  • 容许对代码片段进行 extend 和 mixin;
  • 反对循环语句的应用;
  • 反对将 CSS 文件模块化,实现复用。

(2)PostCss:PostCss 是如何工作的?咱们在什么场景下会应用 PostCss?

它和预处理器的不同就在于,预处理器解决的是 类 CSS,而 PostCss 解决的就是 CSS 自身。Babel 能够将高版本的 JS 代码转换为低版本的 JS 代码。PostCss 做的是相似的事件:它能够编译尚未被浏览器广泛支持的先进的 CSS 语法,还能够主动为一些须要额定兼容的语法减少前缀。更强的是,因为 PostCss 有着弱小的插件机制,反对各种各样的扩大,极大地强化了 CSS 的能力。

PostCss 在业务中的应用场景十分多:

  • 进步 CSS 代码的可读性:PostCss 其实能够做相似预处理器能做的工作;
  • 当咱们的 CSS 代码须要适配低版本浏览器时,PostCss 的 Autoprefixer 插件能够帮忙咱们主动减少浏览器前缀;
  • 容许咱们编写面向未来的 CSS:PostCss 可能帮忙咱们编译 CSS next 代码;

(3)Webpack 能解决 CSS 吗?如何实现? Webpack 能解决 CSS 吗:

  • Webpack 在裸奔的状态下,是不能解决 CSS 的,Webpack 自身是一个面向 JavaScript 且只能解决 JavaScript 代码的模块化打包工具;
  • Webpack 在 loader 的辅助下,是能够解决 CSS 的。

如何用 Webpack 实现对 CSS 的解决:

  • Webpack 中操作 CSS 须要应用的两个要害的 loader:css-loader 和 style-loader
  • 留神,答出“用什么”有时候可能还不够,面试官会狐疑你是不是在背答案,所以你还须要理解每个 loader 都做了什么事件:

    • css-loader:导入 CSS 模块,对 CSS 代码进行编译解决;
    • style-loader:创立 style 标签,把 CSS 内容写入标签。

在理论应用中,css-loader 的执行程序肯定要安顿在 style-loader 的后面。因为只有实现了编译过程,才能够对 css 代码进行插入;若提前插入了未编译的代码,那么 webpack 是无奈了解这坨货色的,它会无情报错。

代码输入后果

var friendName = 'World';
(function() {if (typeof friendName === 'undefined') {
    var friendName = 'Jack';
    console.log('Goodbye' + friendName);
  } else {console.log('Hello' + friendName);
  }
})();

输入后果:Goodbye Jack

咱们晓得,在 JavaScript 中,Function 和 var 都会被晋升(变量晋升),所以下面的代码就相当于:

var name = 'World!';
(function () {
    var name;
    if (typeof name === 'undefined') {
        name = 'Jack';
        console.log('Goodbye' + name);
    } else {console.log('Hello' + name);
    }
})();

这样,答案就高深莫测了。

冒泡排序 – 工夫复杂度 n^2

题目形容: 实现一个冒泡排序

实现代码如下:

function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 外层循环用于管制从头到尾的比拟 + 替换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于实现每一轮遍历过程中的反复比拟 + 替换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素后面的数比前面的大
      if (arr[j] > arr[j + 1]) {
        // 替换两者
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  // 返回数组
  return arr;
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));

CSS 优化和进步性能的办法有哪些?

加载性能:

(1)css 压缩:将写好的 css 进行打包压缩,能够减小文件体积。

(2)css 繁多款式:当须要下边距和右边距的时候,很多时候会抉择应用 margin:top 0 bottom 0;但 margin-bottom:bottom;margin-left:left; 执行效率会更高。

(3)缩小应用 @import,倡议应用 link,因为后者在页面加载时一起加载,前者是期待页面加载实现之后再进行加载。

选择器性能:

(1)要害选择器(key selector)。选择器的最初面的局部为要害选择器(即用来匹配指标元素的局部)。CSS 选择符是从右到左进行匹配的。当应用后辈选择器的时候,浏览器会遍历所有子元素来确定是否是指定的元素等等;

(2)如果规定领有 ID 选择器作为其要害选择器,则不要为规定减少标签。过滤掉无关的规定(这样款式零碎就不会浪费时间去匹配它们了)。

(3)防止应用通配规定,如 *{}计算次数惊人,只对须要用到的元素进行抉择。

(4)尽量少的去对标签进行抉择,而是用 class。

(5)尽量少的去应用后辈选择器,升高选择器的权重值。后辈选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过三层,更多的应用类来关联每一个标签元素。

(6)理解哪些属性是能够通过继承而来的,而后防止对这些属性反复指定规定。

渲染性能:

(1)谨慎应用高性能属性:浮动、定位。

(2)尽量减少页面重排、重绘。

(3)去除空规定:{}。空规定的产生起因一般来说是为了预留款式。去除这些空规定无疑能缩小 css 文档体积。

(4)属性值为 0 时,不加单位。

(5)属性值为浮动小数 0.**,能够省略小数点之前的 0。

(6)标准化各种浏览器前缀:带浏览器前缀的在前。规范属性在后。

(7)不应用 @import 前缀,它会影响 css 的加载速度。

(8)选择器优化嵌套,尽量避免层级过深。

(9)css 雪碧图,同一页面相近局部的小图标,方便使用,缩小页面的申请次数,然而同时图片自身会变大,应用时,优劣思考分明,再应用。

(10)正确应用 display 的属性,因为 display 的作用,某些款式组合会有效,徒增款式体积的同时也影响解析性能。

(11)不滥用 web 字体。对于中文网站来说 WebFonts 可能很生疏,国外却很风行。web fonts 通常体积宏大,而且一些浏览器在下载 web fonts 时会阻塞页面渲染伤害性能。

可维护性、健壮性:

(1)将具备雷同属性的款式抽离进去,整合并通过 class 在页面中进行应用,进步 css 的可维护性。

(2)款式与内容拆散:将 css 代码定义到内部 css 中。

列表转成树形构造

题目形容:

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]

实现代码如下:

function listToTree(data) {let temp = {};
  let treeData = [];
  for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];
  }
  for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}
      temp[temp[i].parentId].children.push(temp[i]);
    } else {treeData.push(temp[i]);
    }
  }
  return treeData;
}
正文完
 0