关于前端:腾讯前端必会面试题

43次阅读

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

CDN 的应用场景

  • 应用第三方的 CDN 服务:如果想要开源一些我的项目,能够应用第三方的 CDN 服务
  • 应用 CDN 进行动态资源的缓存:将本人网站的动态资源放在 CDN 上,比方 js、css、图片等。能够将整个我的项目放在 CDN 上,实现一键部署。
  • 直播传送:直播实质上是应用流媒体进行传送,CDN 也是反对流媒体传送的,所以直播齐全能够应用 CDN 来进步访问速度。CDN 在解决流媒体的时候与解决一般动态文件有所不同,一般文件如果在边缘节点没有找到的话,就会去上一层接着寻找,然而流媒体自身数据量就十分大,如果应用回源的形式,必然会带来性能问题,所以流媒体个别采纳的都是被动推送的形式来进行。

为什么 0.1 + 0.2 != 0.3,请详述理由

因为 JS 采纳 IEEE 754 双精度版本(64 位),并且只有采纳 IEEE 754 的语言都有该问题。

咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1 在二进制示意为

// (0011) 示意循环
0.1 = 2^-4 * 1.10011(0011)

那么如何失去这个二进制的呢,咱们能够来演算下

小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)

回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.10.2 都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。

所以 2^-4 * 1.10011...001 进位后就变成了 2^-4 * 1.10011(0011 * 12 次)010。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11 次)0100 , 这个值算成十进制就是 0.30000000000000004

上面说一下原生解决办法,如下代码所示

parseFloat((0.1 + 0.2).toFixed(10))

请实现 DOM2JSON 一个函数,能够把一个 DOM 节点输入 JSON 的格局

题目形容:

<div>
  <span>
    <a></a>
  </span>
  <span>
    <a></a>
    <a></a>
  </span>
</div>

把上诉 dom 构造转成上面的 JSON 格局

{
  tag: 'DIV',
  children: [
    {
      tag: 'SPAN',
      children: [{ tag: 'A', children: [] }
      ]
    },
    {
      tag: 'SPAN',
      children: [{ tag: 'A', children: [] },
        {tag: 'A', children: [] }
      ]
    }
  ]
}

实现代码如下:

function dom2Json(domtree) {let obj = {};
  obj.name = domtree.tagName;
  obj.children = [];
  domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));
  return obj;
}

扩大思考: 如果给定的不是一个 Dom 树结构 而是一段 html 字符串 该如何解析?

那么这个问题就相似 Vue 的模板编译原理 咱们能够利用正则 匹配 html 字符串 遇到开始标签 完结标签和文本 解析结束之后生成对应的 ast 并建设相应的父子关联 一直的 advance 截取残余的字符串 直到 html 全副解析结束

分片思维解决大数据量渲染问题

题目形容: 渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染

实现代码如下:

let ul = document.getElementById("container");
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
// 总页数
let page = total / once;
// 每条记录的索引
let index = 0;
// 循环加载数据
function loop(curTotal, curIndex) {if (curTotal <= 0) {return false;}
  // 每页多少条
  let pageCount = Math.min(curTotal, once);
  window.requestAnimationFrame(function () {for (let i = 0; i < pageCount; i++) {let li = document.createElement("li");
      li.innerText = curIndex + i + ":" + ~~(Math.random() * total);
      ul.appendChild(li);
    }
    loop(curTotal - pageCount, curIndex + pageCount);
  });
}
loop(total, index);

扩大思考:对于大数据量的简略 dom 构造渲染能够用分片思维解决 如果是简单的 dom 构造渲染如何解决?

这时候就须要应用虚构列表了 大家自行百度哈 虚构列表和虚构表格在日常我的项目应用还是很频繁的

说一下 vue3.0 你理解多少?

 <!-- 响应式原理的扭转 Vue3.x 应用 Proxy 取代 Vue2.x 版本的 Object.defineProperty -->
 <!-- 组件选项申明形式 Vue3.x 应用 Composition API setup 是 Vue3.x 新增的一个选项,他
    是组件内应用 Composition API 的入口 -->
 <!-- 模板语法变动 slot 具名插槽语法 自定义指令 v-model 降级 -->
 <!-- 其它方面的更改 Suspense 反对 Fragment(多个根节点) 和 Protal (在 dom 其余局部渲染组建内容)组件
     针对一些非凡的场景做了解决。基于 treeshaking 优化,提供了更多的内置性能。-->

代码输入后果

 var a=3;
 function c(){alert(a);
 }
 (function(){
  var a=4;
  c();})();

js 中变量的作用域链与定义时的环境无关,与执行时无关。执行环境只会扭转 this、传递的参数、全局变量等

为什么须要革除浮动?革除浮动的形式

浮动的定义: 非 IE 浏览器下,容器不设高度且子元素浮动时,容器高度不能被内容撑开。此时,内容会溢出到容器里面而影响布局。这种景象被称为浮动(溢出)。

浮动的工作原理:

  • 浮动元素脱离文档流,不占据空间(引起“高度塌陷”景象)
  • 浮动元素碰到蕴含它的边框或者其余浮动元素的边框停留

浮动元素能够左右挪动,直到遇到另一个浮动元素或者遇到它外边缘的蕴含框。浮动框不属于文档流中的一般流,当元素浮动之后,不会影响块级元素的布局,只会影响内联元素布局。此时文档流中的一般流就会体现得该浮动框不存在一样的布局模式。当蕴含框的高度小于浮动框的时候,此时就会呈现“高度塌陷”。

浮动元素引起的问题?

  • 父元素的高度无奈被撑开,影响与父元素同级的元素
  • 与浮动元素同级的非浮动元素会追随其后
  • 若浮动的元素不是第一个元素,则该元素之前的元素也要浮动,否则会影响页面的显示构造

革除浮动的形式如下:

  • 给父级 div 定义 height 属性
  • 最初一个浮动元素之后增加一个空的 div 标签,并增加 clear:both 款式
  • 蕴含浮动元素的父级标签增加 overflow:hidden 或者overflow:auto
  • 应用 :after 伪元素。因为 IE6- 7 不反对 :after,应用 zoom:1 触发 hasLayout**
.clearfix:after{
    content: "\200B";
    display: table; 
    height: 0;
    clear: both;
  }
  .clearfix{*zoom: 1;}

动静布局求解硬币找零问题

题目形容: 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算能够凑成总金额所需的起码的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

示例 1:输出: coins = [1, 2, 5], amount = 11
输入: 3
解释: 11 = 5 + 5 + 1

示例 2:输出: coins = [2], amount = 3
输入: -1

实现代码如下:

const coinChange = function (coins, amount) {
  // 用于保留每个指标总额对应的最小硬币个数
  const f = [];
  // 提前定义已知状况
  f[0] = 0;
  // 遍历 [1, amount] 这个区间的硬币总额
  for (let i = 1; i <= amount; i++) {
    // 求的是最小值,因而咱们预设为无穷大,确保它肯定会被更小的数更新
    f[i] = Infinity;
    // 循环遍历每个可用硬币的面额
    for (let j = 0; j < coins.length; j++) {
      // 若硬币面额小于指标总额,则问题成立
      if (i - coins[j] >= 0) {
        // 状态转移方程
        f[i] = Math.min(f[i], f[i - coins[j]] + 1);
      }
    }
  }
  // 若指标总额对应的解为无穷大,则意味着没有一个符合条件的硬币总数来更新它,本题无解,返回 -1
  if (f[amount] === Infinity) {return -1;}
  // 若有解,间接返回解的内容
  return f[amount];
};

浏览器渲染优化

(1)针对 JavaScript: JavaScript 既会阻塞 HTML 的解析,也会阻塞 CSS 的解析。因而咱们能够对 JavaScript 的加载形式进行扭转,来进行优化:

(1)尽量将 JavaScript 文件放在 body 的最初

(2)body 两头尽量不要写 <script> 标签

(3)<script>标签的引入资源形式有三种,有一种就是咱们罕用的间接引入,还有两种就是应用 async 属性和 defer 属性来异步引入,两者都是去异步加载内部的 JS 文件,不会阻塞 DOM 的解析(尽量应用异步加载)。三者的区别如下:

  • script 立刻进行页面渲染去加载资源文件,当资源加载结束后立刻执行 js 代码,js 代码执行结束后持续渲染页面;
  • async 是在下载实现之后,立刻异步加载,加载好后立刻执行,多个带 async 属性的标签,不能保障加载的程序;
  • defer 是在下载实现之后,立刻异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果 DOM 树曾经筹备好,则立刻执行。多个带 defer 属性的标签,依照程序执行。

(2)针对 CSS:应用 CSS 有三种形式:应用link、@import、内联款式,其中 link 和 @import 都是导入内部款式。它们之间的区别:

  • link:浏览器会派发一个新等线程 (HTTP 线程) 去加载资源文件,与此同时 GUI 渲染线程会持续向下渲染代码
  • @import:GUI 渲染线程会临时进行渲染,去服务器加载资源文件,资源文件没有返回之前不会持续渲染(妨碍浏览器渲染)
  • style:GUI 间接渲染

内部款式如果长时间没有加载结束,浏览器为了用户体验,会应用浏览器会默认款式,确保首次渲染的速度。所以 CSS 个别写在 headr 中,让浏览器尽快发送申请去获取 css 款式。

所以,在开发过程中,导入内部款式应用 link,而不必 @import。如果 css 少,尽可能采纳内嵌款式,间接写在 style 标签中。

(3)针对 DOM 树、CSSOM 树: 能够通过以下几种形式来缩小渲染的工夫:

  • HTML 文件的代码层级尽量不要太深
  • 应用语义化的标签,来防止不规范语义化的非凡解决
  • 缩小 CSSD 代码的层级,因为选择器是从左向右进行解析的

(4)缩小回流与重绘:

  • 操作 DOM 时,尽量在低层级的 DOM 节点进行操作
  • 不要应用 table 布局,一个小的改变可能会使整个 table 进行从新布局
  • 应用 CSS 的表达式
  • 不要频繁操作元素的款式,对于动态页面,能够批改类名,而不是款式。
  • 应用 absolute 或者 fixed,使元素脱离文档流,这样他们发生变化就不会影响其余元素
  • 防止频繁操作 DOM,能够创立一个文档片段documentFragment,在它下面利用所有 DOM 操作,最初再把它增加到文档中
  • 将元素先设置display: none,操作完结后再把它显示进去。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。
  • 将 DOM 的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于 浏览器的渲染队列机制

浏览器针对页面的回流与重绘,进行了本身的优化——渲染队列

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了肯定的数量或者到了肯定的工夫距离,浏览器就会对队列进行批处理。这样就会让屡次的回流、重绘变成一次回流重绘。

将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,本来应该是触发屡次回流,变成了只触发一次回流。

实现一个 add 办法

题目形容: 实现一个 add 办法 使计算结果可能满足如下预期:
add(1)(2)(3)()=6
add(1,2,3)(4)()=10

其实就是考函数柯里化

实现代码如下:

function add(...args) {let allArgs = [...args];
  function fn(...newArgs) {allArgs = [...allArgs, ...newArgs];
    return fn;
  }
  fn.toString = function () {if (!allArgs.length) {return;}
    return allArgs.reduce((sum, cur) => sum + cur);
  };
  return fn;
}

如何应用 for…of 遍历对象

for…of 是作为 ES6 新增的遍历形式,容许遍历一个含有 iterator 接口的数据结构(数组、对象等)并且返回各项的值,一般的对象用 for..of 遍历是会报错的。

如果须要遍历的对象是类数组对象,用 Array.from 转成数组即可。

var obj = {
    0:'one',
    1:'two',
    length: 2
};
obj = Array.from(obj);
for(var k of obj){console.log(k)
}

如果不是类数组对象,就给对象增加一个 [Symbol.iterator] 属性,并指向一个迭代器即可。

// 办法一:var obj = {
    a:1,
    b:2,
    c:3
};

obj[Symbol.iterator] = function(){var keys = Object.keys(this);
    var count = 0;
    return {next(){if(count<keys.length){return {value: obj[keys[count++]],done:false};
            }else{return {value:undefined,done:true};
            }
        }
    }
};

for(var k of obj){console.log(k);
}


// 办法二
var obj = {
    a:1,
    b:2,
    c:3
};
obj[Symbol.iterator] = function*(){var keys = Object.keys(obj);
    for(var k of keys){yield [k,obj[k]]
    }
};

for(var [k,v] of obj){console.log(k,v);
}

New 操作符做了什么事件?

1、首先创立了一个新对象
2、设置原型,将对象的原型设置为函数的 prototype 对象
3、让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)4、判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象

哪些状况会导致内存透露

以下四种状况会造成内存的透露:

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

扩大运算符的作用及应用场景

(1)对象扩大运算符

对象的扩大运算符 (…) 用于取出参数对象中的所有可遍历属性,拷贝到以后对象之中。

let bar = {a: 1, b: 2};
let baz = {...bar}; // {a: 1, b: 2}

上述办法实际上等价于:

let bar = {a: 1, b: 2};
let baz = Object.assign({}, bar); // {a: 1, b: 2}

Object.assign办法用于对象的合并,将源对象 (source) 的所有可枚举属性,复制到指标对象 (target)Object.assign 办法的第一个参数是指标对象,前面的参数都是源对象。(如果指标对象与源对象有同名属性,或多个源对象有同名属性,则前面的属性会笼罩后面的属性)。

同样,如果用户自定义的属性,放在扩大运算符前面,则扩大运算符外部的同名属性会被笼罩掉。

let bar = {a: 1, b: 2};
let baz = {...bar, ...{a:2, b: 4}};  // {a: 2, b: 4}

利用上述个性就能够很不便的批改对象的局部属性。在 redux 中的 reducer 函数规定必须是 一个纯函数 reducer 中的 state 对象要求不能间接批改,能够通过扩大运算符把批改门路的对象都复制一遍,而后产生一个新的对象返回。

须要留神:扩大运算符对对象实例的拷贝属于浅拷贝

(2)数组扩大运算符

数组的扩大运算符能够将一个数组转为用逗号分隔的参数序列,且每次只能开展一层数组。

console.log(...[1, 2, 3])
// 1 2 3
console.log(...[1, [2, 3, 4], 5])
// 1 [2, 3, 4] 5

上面是数组的扩大运算符的利用:

  • 将数组转换为参数序列
function add(x, y) {return x + y;}
const numbers = [1, 2];
add(...numbers) // 3
  • 复制数组
const arr1 = [1, 2];
const arr2 = [...arr1];

要记住:扩大运算符 (…) 用于取出参数对象中的所有可遍历属性,拷贝到以后对象之中,这里参数对象是个数组,数组外面的所有对象都是根底数据类型,将所有根底数据类型从新拷贝到新的数组中。

  • 合并数组

如果想在数组内合并数组,能够这样:

const arr1 = ['two', 'three'];const arr2 = ['one', ...arr1, 'four', 'five'];// ["one", "two", "three", "four", "five"]
  • 扩大运算符与解构赋值联合起来,用于生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];first // 1rest  // [2, 3, 4, 5]

须要留神:如果将扩大运算符用于数组赋值,只能放在参数的最初一位,否则会报错。

const [...rest, last] = [1, 2, 3, 4, 5];         // 报错 const [first, ...rest, last] = [1, 2, 3, 4, 5];  // 报错
  • 将字符串转为真正的数组
[...'hello']    // ["h", "e", "l", "l", "o"]
  • 任何 Iterator 接口的对象,都能够用扩大运算符转为真正的数组

比拟常见的利用是能够将某些数据结构转为数组:

// arguments 对象
function foo() {const args = [...arguments];
}

用于替换 es5 中的 Array.prototype.slice.call(arguments) 写法。

  • 应用 Math 函数获取数组中特定的值
const numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
Math.max(...numbers); // 9

HTTPS 通信(握手)过程

HTTPS 的通信过程如下:

  1. 客户端向服务器发动申请,申请中蕴含应用的协定版本号、生成的一个随机数、以及客户端反对的加密办法。
  2. 服务器端接管到申请后,确认单方应用的加密办法、并给出服务器的证书、以及一个服务器生成的随机数。
  3. 客户端确认服务器证书无效后,生成一个新的随机数,并应用数字证书中的公钥,加密这个随机数,而后发给服 务器。并且还会提供一个后面所有内容的 hash 的值,用来供服务器测验。
  4. 服务器应用本人的私钥,来解密客户端发送过去的随机数。并提供后面所有内容的 hash 值来供客户端测验。
  5. 客户端和服务器端依据约定的加密办法应用后面的三个随机数,生成对话秘钥,当前的对话过程都应用这个秘钥来加密信息。

晓得 ES6 的 Class 嘛?Static 关键字有理解嘛

为这个类的函数对象间接增加办法,而不是加在这个函数对象的原型对象上

数组的遍历办法有哪些

办法 是否扭转原数组 特点
forEach() 数组办法,不扭转原数组,没有返回值
map() 数组办法,不扭转原数组,有返回值,可链式调用
filter() 数组办法,过滤数组,返回蕴含符合条件的元素的数组,可链式调用
for…of for…of 遍历具备 Iterator 迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历一般的 obj 对象,将异步循环变成同步循环
every() 和 some() 数组办法,some()只有有一个是 true,便返回 true;而 every()只有有一个是 false,便返回 false.
find() 和 findIndex() 数组办法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值
reduce() 和 reduceRight() 数组办法,reduce()对数组正序操作;reduceRight()对数组逆序操作

解释性语言和编译型语言的区别

(1)解释型语言
应用专门的解释器对源程序逐行解释成特定平台的机器码并立刻执行。是代码在执行时才被解释器一行行动静翻译和执行,而不是在执行之前就实现翻译。解释型语言不须要当时编译,其间接将源代码解释成机器码并立刻执行,所以只有某一平台提供了相应的解释器即可运行该程序。其特点总结如下

  • 解释型语言每次运行都须要将源代码解释称机器码并执行,效率较低;
  • 只有平台提供相应的解释器,就能够运行源代码,所以能够不便源程序移植;
  • JavaScript、Python 等属于解释型语言。

(2)编译型语言
应用专门的编译器,针对特定的平台,将高级语言源代码一次性的编译成可被该平台硬件执行的机器码,并包装成该平台所能辨认的可执行性程序的格局。在编译型语言写的程序执行之前,须要一个专门的编译过程,把源代码编译成机器语言的文件,如 exe 格局的文件,当前要再运行时,间接应用编译后果即可,如间接运行 exe 文件。因为只需编译一次,当前运行时不须要编译,所以编译型语言执行效率高。其特点总结如下:

  • 一次性的编译成平台相干的机器语言文件,运行时脱离开发环境,运行效率高;
  • 与特定平台相干,个别无奈移植到其余平台;
  • C、C++ 等属于编译型语言。

两者次要区别在于: 前者源程序编译后即可在该平台运行,后者是在运行期间才编译。所以前者运行速度快,后者跨平台性好。

正文完
 0