写点什么

阿里前端高频面试题

作者:beifeng1996
  • 2022 年 9 月 12 日
    浙江
  • 本文字数:8004 字

    阅读完需:约 26 分钟

说一下 HTTP 3.0

HTTP/3 基于 UDP 协议实现了类似于 TCP 的多路复用数据流、传输可靠性等功能,这套功能被称为 QUIC 协议。


  1. 流量控制、传输可靠性功能:QUIC 在 UDP 的基础上增加了一层来保证数据传输可靠性,它提供了数据包重传、拥塞控制、以及其他一些 TCP 中的特性。

  2. 集成 TLS 加密功能:目前 QUIC 使用 TLS1.3,减少了握手所花费的 RTT 数。

  3. 多路复用:同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,解决了 TCP 的队头阻塞问题。

  4. 快速握手:由于基于 UDP,可以实现使用 0 ~ 1 个 RTT 来建立连接。

为什么需要清除浮动?清除浮动的方式

浮动的定义: 非 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;  }
复制代码

扩展运算符的作用及使用场景

(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 3console.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); // 1Math.max(...numbers); // 9
复制代码

闭包产生的本质

当前环境中存在指向父级作用域的引用

实现一个扇形

用 CSS 实现扇形的思路和三角形基本一致,就是多了一个圆角的样式,实现一个 90°的扇形:


div{    border: 100px solid transparent;    width: 0;    heigt: 0;    border-radius: 100px;    border-top-color: red;}
复制代码

说一下 data 为什么是一个函数而不是一个对象?

JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。而在Vue中,我们更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当我们每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。

页面有多张图片,HTTP 是怎样的加载表现?

  • HTTP 1下,浏览器对一个域名下最大 TCP 连接数为 6,所以会请求多次。可以用多域名部署解决。这样可以提高同时请求的数目,加快页面图片的获取速度。

  • HTTP 2下,可以一瞬间加载出来很多资源,因为,HTTP2 支持多路复用,可以在一个 TCP 连接中发送多个 HTTP 请求。

documentFragment 是什么?用它跟直接操作 DOM 的区别是什么?

MDN 中对documentFragment的解释:


DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document 使用,就像标准的 document 一样,存储由节点(nodes)组成的文档结构。与 document 相比,最大的区别是 DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。


当我们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。在频繁的 DOM 操作时,我们就可以将 DOM 元素插入 DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和直接操作 DOM 相比,将 DocumentFragment 节点插入 DOM 树时,不会触发页面的重绘,这样就大大提高了页面的性能。

TCP 和 UDP 的使用场景

  • TCP 应用场景: 效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有 UDP 高。例如:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。

  • UDP 应用场景: 效率要求相对高,对准确性要求相对低的场景。例如:QQ 聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。

常见的 HTTP 请求头和响应头

HTTP Request Header 常见的请求头:


  • Accept:浏览器能够处理的内容类型

  • Accept-Charset:浏览器能够显示的字符集

  • Accept-Encoding:浏览器能够处理的压缩编码

  • Accept-Language:浏览器当前设置的语言

  • Connection:浏览器与服务器之间连接的类型

  • Cookie:当前页面设置的任何 Cookie

  • Host:发出请求的页面所在的域

  • Referer:发出请求的页面的 URL

  • User-Agent:浏览器的用户代理字符串


HTTP Responses Header 常见的响应头:


  • Date:表示消息发送的时间,时间的描述格式由 rfc822 定义

  • server:服务器名称

  • Connection:浏览器与服务器之间连接的类型

  • Cache-Control:控制 HTTP 缓存

  • content-type:表示后面的文档属于什么 MIME 类型


常见的 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 格式的数据。

setTimeout 模拟 setInterval

描述:使用setTimeout模拟实现setInterval的功能。


实现


const mySetInterval(fn, time) {    let timer = null;    const interval = () => {        timer = setTimeout(() => {            fn();  // time 时间之后会执行真正的函数fn            interval();  // 同时再次调用interval本身        }, time)    }    interval();  // 开始执行    // 返回用于关闭定时器的函数    return () => clearTimeout(timer);}
// 测试const cancel = mySetInterval(() => console.log(1), 400);setTimeout(() => { cancel();}, 1000); // 打印两次1
复制代码

代码输出结果

 var a=3; function c(){    alert(a); } (function(){  var a=4;  c(); })();
复制代码


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

继承

原型继承

核心思想:子类的原型成为父类的实例


实现


function SuperType() {    this.colors = ['red', 'green'];}function SubType() {}// 原型继承关键: 子类的原型成为父类的实例SubType.prototype = new SuperType();
// 测试let instance1 = new SubType();instance1.colors.push('blue');
let instance2 = new SubType();console.log(instance2.colors); // ['red', 'green', 'blue']
复制代码


原型继承存在的问题


  1. 原型中包含的引用类型属性将被所有实例对象共享

  2. 子类在实例化时不能给父类构造函数传参

构造函数继承

核心思想:在子类构造函数中调用父类构造函数


实现


function SuperType(name) {    this.name = name;    this.colors = ['red', 'green'];    this.getName = function() {        return this.name;    }}function SubType(name) {    // 继承 SuperType 并传参    SuperType.call(this, name);}
// 测试let instance1 = new SubType('instance1');instance1.colors.push('blue');console.log(instance1.colors); // ['red','green','blue']
let instance2 = new SubType('instance2');console.log(instance2.colors); // ['red', 'green']
复制代码


构造函数继承的出现是为了解决了原型继承的引用值共享问题。优点是可以在子类构造函数中向父类构造函数传参。它存在的问题是:1)由于方法必须在构造函数中定义,因此方法不能重用。2)子类也不能访问父类原型上定义的方法。

组合继承

核心思想:综合了原型链和构造函数,即,使用原型链继承原型上的方法,而通过构造函数继承实例属性。


实现


function SuperType(name) {    this.name = name;    this.colors = ['red', 'green'];}Super.prototype.sayName = function() {    console.log(this.name);}function SubType(name, age) {    // 继承属性    SuperType.call(this, name);    // 实例属性    this.age = age;}// 继承方法SubType.prototype = new SuperType();
// 测试let instance1 = new SubType('instance1', 1);instance1.sayName(); // "instance1"instance1.colors.push("blue");console.log(instance1.colors); // ['red','green','blue']
let instance2 = new SubType('instance2', 2);instance2.sayName(); // "instance2"console.log(instance2.colors); // ['red','green']
复制代码


组合继承存在的问题是:父类构造函数始终会被调用两次:一次是在创建子类原型时new SuperType()调用,另一次是在子类构造函数中SuperType.call()调用。

寄生式组合继承(最佳)

核心思想:通过构造函数继承属性,但使用混合式原型继承方法,即,不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。


实现


function SuperType(name) {    this.name = name;    this.colors = ['red', 'green'];}Super.prototype.sayName = function() {    console.log(this.name);}function SubType(name, age) {    // 继承属性    SuperType.call(this, name);    this.age = age;}// 继承方法SubType.prototype = Object.create(SuperType.prototype);// 重写原型导致默认 constructor 丢失,手动将 constructor 指回 SubTypeSubType.prototype.constructor = SubType;
复制代码

class 实现继承(ES6)

核心思想:通过 extends 来实现类的继承(相当于 ES5 的原型继承)。通过 super 调用父类的构造方法 (相当于 ES5 的构造函数继承)。


实现


class SuperType {    constructor(name) {        this.name = name;    }    sayName() {        console.log(this.name);    }}class SubType extends SuperType {    constructor(name, age) {        super(name);  // 继承属性        this.age = age;    }}
// 测试let instance = new SubType('instance', 0);instance.sayName(); // "instance"
复制代码


虽然类继承使用的是新语法,但背后依旧使用的是原型链。

object.assign 和扩展运算法是深拷贝还是浅拷贝,两者区别

扩展运算符:


let outObj = {  inObj: {a: 1, b: 2}}let newObj = {...outObj}newObj.inObj.a = 2console.log(outObj) // {inObj: {a: 2, b: 2}}
复制代码


Object.assign():


let outObj = {  inObj: {a: 1, b: 2}}let newObj = Object.assign({}, outObj)newObj.inObj.a = 2console.log(outObj) // {inObj: {a: 2, b: 2}}
复制代码


可以看到,两者都是浅拷贝。


  • Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。

  • 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制 ES6 的 symbols 属性。

typeof null 的结果是什么,为什么?

typeof null 的结果是 Object。


在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:


000: object   - 当前存储的数据指向一个对象。  1: int      - 当前存储的数据是一个 31 位的有符号整数。010: double   - 当前存储的数据指向一个双精度的浮点数。100: string   - 当前存储的数据指向一个字符串。110: boolean  - 当前存储的数据是布尔值。
复制代码


如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位,为存储其他四种数据类型提供了额外两个 bit 的长度。


有两种特殊数据类型:


  • undefined 的值是 (-2)30(一个超出整数范围的数字);

  • null 的值是机器码 NULL 指针(null 指针的值全是 0)


那也就是说 null 的类型标签也是 000,和 Object 的类型标签一样,所以会被判定为 Object。

GET 方法 URL 长度限制的原因

实际上 HTTP 协议规范并没有对 get 方法请求的 url 长度进行限制,这个限制是特定的浏览器及服务器对它的限制。IE 对 URL 长度的限制是 2083 字节(2K+35)。由于 IE 浏览器对 URL 长度的允许值是最小的,所以开发过程中,只要 URL 不超过 2083 字节,那么在所有浏览器中工作都不会有问题。


GET的长度值 = URL(2083)- (你的Domain+Path)-2(2是get请求中?=两个字符的长度)
复制代码


下面看一下主流浏览器对 get 方法中 url 的长度限制范围:


  • Microsoft Internet Explorer (Browser):IE 浏览器对 URL 的最大限制为 2083 个字符,如果超过这个数字,提交按钮没有任何反应。

  • Firefox (Browser):对于 Firefox 浏览器 URL 的长度限制为 65,536 个字符。

  • Safari (Browser):URL 最大长度限制为 80,000 个字符。

  • Opera (Browser):URL 最大长度限制为 190,000 个字符。

  • Google (chrome):URL 最大长度限制为 8182 个字符。


主流的服务器对 get 方法中 url 的长度限制范围:


  • Apache (Server):能接受最大 url 长度为 8192 个字符。

  • Microsoft Internet Information Server(IIS):能接受最大 url 的长度为 16384 个字符。


根据上面的数据,可以知道,get 方法中的 URL 长度最长不超过 2083 个字符,这样所有的浏览器和服务器都可能正常工作。

树形结构转成列表

题目描述:


[    {        id: 1,        text: '节点1',        parentId: 0,        children: [            {                id:2,                text: '节点1_1',                parentId:1            }        ]    }]转成[    {        id: 1,        text: '节点1',        parentId: 0 //这里用0表示为顶级节点    },    {        id: 2,        text: '节点1_1',        parentId: 1 //通过这个字段来确定子父级    }    ...]
复制代码


实现代码如下:


function treeToList(data) {  let res = [];  const dfs = (tree) => {    tree.forEach((item) => {      if (item.children) {        dfs(item.children);        delete item.children;      }      res.push(item);    });  };  dfs(data);  return res;}
复制代码

替换元素的概念及计算规则

通过修改某个属性值呈现的内容就可以被替换的元素就称为“替换元素”。


替换元素除了内容可替换这一特性以外,还有以下特性:


  • 内容的外观不受页面上的 CSS 的影响:用专业的话讲就是在样式表现在 CSS 作用域之外。如何更改替换元素本身的外观需要类似 appearance 属性,或者浏览器自身暴露的一些样式接口。

  • 有自己的尺寸:在 Web 中,很多替换元素在没有明确尺寸设定的情况下,其默认的尺寸(不包括边框)是 300 像素×150 像素,如

  • 在很多 CSS 属性上有自己的一套表现规则:比较具有代表性的就是 vertical-align 属性,对于替换元素和非替换元素,vertical-align 属性值的解释是不一样的。比方说 vertical-align 的默认值的 baseline,很简单的属性值,基线之意,被定义为字符 x 的下边缘,而替换元素的基线却被硬生生定义成了元素的下边缘。

  • 所有的替换元素都是内联水平元素:也就是替换元素和替换元素、替换元素和文字都是可以在一行显示的。但是,替换元素默认的 display 值却是不一样的,有的是 inline,有的是 inline-block。


替换元素的尺寸从内而外分为三类:


  • 固有尺寸: 指的是替换内容原本的尺寸。例如,图片、视频作为一个独立文件存在的时候,都是有着自己的宽度和高度的。

  • HTML 尺寸: 只能通过 HTML 原生属性改变,这些 HTML 原生属性包括的 width 和 height 属性、的 size 属性。

  • CSS 尺寸: 特指可以通过 CSS 的 width 和 height 或者 max-width/min-width 和 max-height/min-height 设置的尺寸,对应盒尺寸中的 content box。


这三层结构的计算规则具体如下:(1)如果没有 CSS 尺寸和 HTML 尺寸,则使用固有尺寸作为最终的宽高。(2)如果没有 CSS 尺寸,则使用 HTML 尺寸作为最终的宽高。(3)如果有 CSS 尺寸,则最终尺寸由 CSS 属性决定。(4)如果“固有尺寸”含有固有的宽高比例,同时仅设置了宽度或仅设置了高度,则元素依然按照固有的宽高比例显示。(5)如果上面的条件都不符合,则最终宽度表现为 300 像素,高度为 150 像素。(6)内联替换元素和块级替换元素使用上面同一套尺寸计算规则。

用户头像

beifeng1996

关注

还未添加个人签名 2022.09.01 加入

还未添加个人简介

评论

发布
暂无评论
阿里前端高频面试题_JavaScript_beifeng1996_InfoQ写作社区