前端面试练习24.3.4

普通问题

说一说 严格模式和 非严格模式

关键词:

严格模式的使用,this指向,一些报错(delete时),eval函数的影响,变量声明

具体:

严格模式的使用,要在代码文件或函数的开头添加 'use strict';使用后JS引擎会额外的执行一些语法和规则

严格模式的 this全局环境中调用指向的是 undefined,非严格就是window

严格模式下,一些写法会报错

javascript 复制代码
// 非严格模式下
var x = 10;
//delete 操作符用于删除对象的属性或数组的元素
delete x; // 在非严格模式下,delete 操作符可能会默默失败,不会报错

console.log(x); // 输出:10


// 严格模式下
'use strict';

var x = 10;
delete x; // 在严格模式下,delete 操作符会抛出异常

console.log(x); // TypeError: Cannot delete property 'x' of #<Object>

严格模式下必须使用var let const关键字对变量声明,非严格可以不使用 var 关键字声明变量,隐式创建全局变量。

eval()函数 可以传入包含JS代码的字符串进行解析运行

非严格模式:eval 函数执行的代码在其执行位置的上下文中执行。

javascript 复制代码
// 非严格模式下
var x = 10;

function evalExample() {
  var y = 20;
  eval('var z = x + y;');
  console.log(z); // 输出:30,z 变量在 eval 函数内部创建,但是在 eval 函数外部可见
}

evalExample();

严格模式:eval 函数执行的代码无法创建新的变量或者函数,并且执行环境被限制在 eval 函数内部。

javascript 复制代码
// 严格模式下
'use strict';

var x = 10;

function evalExample() {
  var y = 20;
  eval('var z = x + y;');
  console.log(z); // 报错:ReferenceError: z is not defined,z 变量只存在于 eval 函数内部
}

evalExample();

SSL/TLS加密

  1. 客户端发送支持的TLS版本和加密算法列表: 客户端向服务器发送一个Hello消息,包含支持的TLS版本和加密算法列表。

  2. 服务器选择TLS版本和加密算法: 服务器收到客户端的Hello消息后,选择与客户端相匹配的TLS版本和加密算法,并生成一个证书,包含服务器的公钥。

  3. 服务器发送证书给客户端: 服务器将包含公钥的证书发送给客户端。

  4. 客户端验证服务器证书: 客户端收到服务器的证书后,验证证书的有效性。这包括检查证书的签发机构是否受信任、证书是否过期、证书中包含的域名是否与实际域名匹配等。

  5. 客户端生成预主密钥并使用服务器公钥加密: 客户端生成一个随机的预主密钥,并使用服务器的公钥进行加密,然后将加密后的预主密钥发送给服务器。

  6. 服务器使用私钥解密预主密钥: 服务器收到客户端发送的预主密钥后,使用自己的私钥进行解密。

  7. 客户端和服务器生成主密钥: 客户端和服务器使用约定好的加密算法,以客户端发送的预主密钥和服务器解密后的预主密钥为输入,生成共享的主密钥。

  8. 客户端和服务器确认握手: 客户端和服务器互相发送消息,确认握手过程已经完成。

  9. 客户端和服务器开始安全通信: 客户端和服务器使用生成的主密钥进行安全通信,包括加密和解密数据。

很基础的重点

什么是原型链,说说相关的理解

首先要明白一点:原型存在于对象中的。

接下来阐述什么原型:

在js中,每个构造函数内部都有一个prototype属性,该属性的值是个对象,该对象包含了该构造函数所有实例共享的属性和方法。当我们通过构造函数创建对象的时候,在这个对象中有一个指针,这个指针指向构造函数的prototype的值,我们将这个指向prototype的指针称为原型。或者用另一种简单却难理解的说法是:js中的对象都有一个特殊的[[Prototype]]内置属性,其实这就是原型。

关系:

所有的引用类型(包括数组,对象,函数)都有隐性原型属性(proto), 值也是一个普通的对象。

所有的函数,都有一个 prototype 属性,值也是一个普通的对象。

所有的引用类型的proto属性值都指向构造函数的 prototype 属性值

所以得出以下 关系:

构造函数.prototype===原型

原型.constructor===构造函数

实例对象.proto===原型

说一说你对闭包的理解

闭包的核心概念有三个要素:
  1. 函数(Function):闭包是一个函数,它可以执行特定的代码逻辑。
  2. 函数定义时的环境(Environment):闭包还包含了在函数定义时所处的作用域中的变量。
  3. 函数返回值(Return Value):闭包可以返回一个函数或者值,这取决于函数的具体实现。
闭包的主要特点包括:
  1. 记忆状态:闭包可以记住定义时的上下文信息,即使在定义之后,这些上下文已经不再存在。
  2. 封装性:闭包将变量和函数绑定在一起,形成一个封闭的单元,避免了全局变量的污染。
  3. 延迟执行:闭包中的函数可以延迟执行,等待外部条件满足后再执行。
javascript 复制代码
function outerFunction(x) {
    // 内部函数可以访问外部函数的参数和局部变量
    function innerFunction(y) {
        return x + y; // innerFunction 可以访问 outerFunction 的参数 x
    }
    return innerFunction; // 返回内部函数
}

// 创建一个闭包
var closure = outerFunction(5);

// 调用闭包,传入参数 3
var result = closure(3);

console.log(result); // 输出:8
拓展:实现链式调用
javascript 复制代码
function fun1(x) {
  let total = x;

  function sum(value) {
      total += value;
      return sum; // 返回自身以便链式调用
  }

  sum.toString = function () {
      return total;
  }

  return sum;
}

console.log(fun1(1)(2)(3).toString()); // 输出 6

说一说call apply bind的作用和区别,能手写一个吗

call:

用来改变调用函数的this指向,后面可以紧跟参数,立即执行

function.call(newThis,arg1,arg2...)

apply

用来改变调用函数的this指向,区别在于传递的参数是数组形式,立即执行

function.apply(newThis,[arg1,arg2...])

bind

也可以用来改变函数this指向,但是,它是返回一个新的函数,需要我们后续手动再次调用。

javascript 复制代码
// 示例函数
function greet(name) {
    console.log(`Hello, ${name}! My name is ${this.name}.`);
}

// 创建一个对象作为上下文
const context = {
    name: 'John'
};

// 使用 call() 方法调用函数,并指定上下文和参数列表
greet.call(context, 'Alice'); // 输出: "Hello, Alice! My name is John."

// 使用 apply() 方法调用函数,并指定上下文和参数数组
const args = ['Bob'];
greet.apply(context, args); // 输出: "Hello, Bob! My name is John."

// 使用 bind() 方法创建一个新的绑定函数
const boundGreet = greet.bind(context, 'Charlie');

// 稍后调用绑定函数
// 此时David是第二个参数,不是name,如果要显示,需要在greet函数添加后续参数
boundGreet('David'); // 输出: "Hello, Charlie! My name is John."
手写:
javascript 复制代码
// 手写实现 call 方法
Function.prototype.myCall = function(thisArg, ...args) {
    // 判断是否传入了有效的上下文,如果传入的上下文为 null 或者 undefined,则默认为全局对象(浏览器中为 window)
    thisArg = thisArg || window;

    // 将当前函数设置为传入的上下文的属性
    thisArg.__fn__ = this;

    // 调用函数
    const result = thisArg.__fn__(...args);

    // 删除临时属性
    delete thisArg.__fn__;

    return result;
};

// 手写实现 apply 方法
Function.prototype.myApply = function(thisArg, argsArray) {
    thisArg = thisArg || window;
    thisArg.__fn__ = this;
    
    // 使用扩展运算符将数组展开为参数列表
    const result = thisArg.__fn__(...argsArray);

    delete thisArg.__fn__;
    
    return result;
};

// 手写实现 bind 方法
Function.prototype.myBind = function(thisArg, ...args) {
    const fn = this;
    return function(...innerArgs) {
        return fn.apply(thisArg, args.concat(innerArgs));
    };
};

说一说new会发生什么,能手写一个吗

  1. 创建一个新的空对象。
  2. 将该空对象的原型链指向构造函数的原型对象(即 prototype 属性)。
  3. 将构造函数的执行上下文(this)绑定到这个新创建的对象。
  4. 执行构造函数内部的代码,以初始化新创建的对象。如果构造函数中有返回值,并且返回值是一个对象,那么这个对象将取代第一步中创建的新对象;如果返回值不是对象,则忽略这个返回值,返回第一步中创建的新对象。
  5. 如果构造函数没有返回值,那么会默认返回第一步中创建的新对象。
javascript 复制代码
function myNew(constructor, ...args) {
    // 创建一个新的空对象,继承构造函数的原型对象
    const obj = Object.create(constructor.prototype);
    
    // 将构造函数的执行上下文绑定到新对象上,并执行构造函数
    const result = constructor.apply(obj, args);
    
    // 如果构造函数有返回值且返回值是对象,则返回这个对象;否则返回新创建的对象
    return typeof result === 'object' ? result : obj;
}

// 示例构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 使用 myNew 关键字创建实例
const person1 = myNew(Person, 'Alice', 30);
console.log(person1); // 输出: Person { name: 'Alice', age: 30 }

说一说promise是什么与使用方法

Promise的作用:

Promise是异步微任务,解决了异步多层嵌套回调的问题,让代码的可读性更高,更容易维护。

Promise使用:

Promise是ES6提供的一个构造函数,可以使用Promise构造函数new一个实例,Promise构造函数接收一个函数作为参数,这个函数有两个参数,分别是两个函数 resolverejectresolve将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;reject则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例创建完成后,可以使用then方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,thencatch最终返回的也是一个Promise,所以可以链式调用。

Promise的特点:
  1. 对象的状态不受外界影响(Promise对象代表一个异步操作,有三种状态)。

    • pending(执行中)
    • Resolved(成功,又称Fulfilled)
    • rejected(拒绝) 其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise对象的状态改变,只有两种可能(状态凝固了,就不会再变了,会一直保持这个结果):

    • 从Pending变为Resolved
    • 从Pending变为Rejected
  3. resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数

  4. then 方法和 catch方法 只要不报错,返回的都是一个fullfilled状态的promise

加分回答 Promise的其他方法:
  • Promise.resolve() : 返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。

  • Promise.reject():返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法。

  • Promise.all():返回一个新的promise对象,该promise对象在参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。

  • Promise.any():接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。

  • Promise.race():当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

说一说axios的拦截器原理及应用

axios的拦截器可以在请求发送之前和响应返回之后进行处理,这样可以方便地对请求和响应进行统一处理,比如添加请求头、统一处理错误等。

拦截器的应用场景:
  1. 请求拦截器的应用场景

    • 在请求发送之前对请求进行处理,比如为每个请求添加相应的参数(如 token、时间戳等)。
    • 对请求进行统一的错误处理,比如请求超时、网络错误等。
    • 在发送请求前显示 loading 动画,请求完成后隐藏 loading 动画。
  2. 响应拦截器的应用场景

    • 在接收到响应后对响应进行处理,比如对返回的状态码进行判断,判断 token 是否过期。
    • 统一处理错误信息,比如处理后端返回的特定错误码。
    • 在接收到响应后隐藏 loading 动画,展示响应数据。
拦截器的原理

是通过创建一个数组(通常称为 interceptor chain,简称 chn),数组中保存了拦截器相应的方法以及 dispatchRequest 方法。

请求拦截器的方法会被放到 chn 数组的前面,

响应拦截器的方法会被放到 chn 数组的后面。

为了保证拦截器的执行顺序,需要使用 Promise 以出队列的方式对 chn 数组中的方法挨个执行。

一般来说,拦截器的执行顺序是这样的:

  1. 请求拦截器方法依次执行,按照定义顺序从前往后执行。
  2. dispatchRequest 方法执行。
  3. 响应拦截器方法依次执行,按照定义顺序从前往后执行。
拓展:

Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 Node.js 中。它具有以下特点:

  • 从浏览器中创建 XMLHttpRequests,从 Node.js 创建 HTTP 请求。
  • 支持 Promise API,使得使用异步请求更加方便和简洁。
  • 可拦截请求和响应,可以在发送请求之前或接收到响应之后对其进行处理。
  • 可转换请求数据和响应数据,例如,可以使用 JSON.stringify 将请求数据转换为 JSON 格式,使用 JSON.parse 将响应数据解析为 JavaScript 对象。
  • 可取消请求,当请求已经发送但未完成时,可以通过 cancelToken 取消请求。
  • 可自动转换 JSON 数据,无需手动解析 JSON 数据,Axios 会自动将 JSON 数据转换为 JavaScript 对象。
  • 客户端支持防御 XSRF(跨站请求伪造)攻击,可以通过配置 xsrfCookieNamexsrfHeaderName 来设置 XSRF 防御相关的选项。

说一说创建ajax过程

创建:
创建 XMLHttpRequest 对象

首先,需要创建一个 XMLHttpRequest 对象,用于发起 AJAX 请求。在现代浏览器中,可以直接使用 new XMLHttpRequest() 来创建 XMLHttpRequest 对象。

javascript 复制代码
const xhr = new XMLHttpRequest();
设置请求参数

在发送 AJAX 请求之前,通常需要设置一些请求参数,比如请求的 URL、请求的方法(GET、POST 等)、请求头、是否异步等。

javascript 复制代码
xhr.open('GET', 'https://api.example.com/data', true);
xhr.setRequestHeader('Content-Type', 'application/json');
设置回调函数

AJAX 请求是异步的,因此需要设置回调函数来处理请求的各个阶段,包括请求被发送、接收到响应、以及请求过程中发生的错误等。

javascript 复制代码
xhr.onreadystatechange = function() {
    if (xhr.readyState === XMLHttpRequest.DONE) {
        if (xhr.status === 200) {
            console.log('请求成功,返回数据为:', xhr.responseText);
        } else {
            console.error('请求失败,状态码为:', xhr.status);
        }
    }
};
发送请求

当所有参数都设置好之后,使用 send() 方法发送 AJAX 请求。

javascript 复制代码
xhr.send();
拓展:

POST请求需要设置请求头 readyState值说明

0:初始化,XHR对象已经创建,还未执行open

1:载入,已经调用open方法,但是还没发送请求

2:载入完成,请求已经发送完成

3:交互,可以接收到部分数据

4:数据全部返回

status值说明 200:成功 404:没有发现文件、查询或URl 500:服务器产生内部错误

为什么使用虚拟DOM,而不操作真实DOM

  1. 性能优化: 虚拟DOM可以批量更新和高效比较,减少了频繁操作真实DOM带来的性能损耗。通过比较虚拟DOM树和上一次渲染的虚拟DOM树的差异,可以最小化对真实DOM的操作,提高页面渲染的效率。

  2. 跨平台兼容性: 虚拟DOM可以使得框架或库在不同平台上运行时保持一致的行为,无需关心底层平台的DOM API差异。这样可以使得开发者更加专注于业务逻辑的实现,而不用考虑跨平台兼容性带来的问题。

  3. 简化复杂度: 直接操作真实DOM可能会涉及到复杂的DOM操作和手动维护DOM状态的问题。而虚拟DOM将复杂的DOM操作抽象为对虚拟DOM树的操作,简化了代码逻辑,提高了开发效率。

  4. 方便实现组件化: 虚拟DOM与组件化开发相结合,可以更方便地管理组件状态和组件间的通信。通过将组件状态存储在虚拟DOM树中,并通过比较虚拟DOM树的差异来更新组件状态,可以实现更灵活、可维护的组件化架构。

vue的diff理解

Vue 的 Virtual DOM 和 diff 算法是 Vue 实现响应式更新的核心机制之一。

Virtual DOM

在 Vue 中,每当状态发生变化时,Vue 会先生成一颗虚拟 DOM 树,即一个 JavaScript 对象表示的树形结构。这个虚拟 DOM 树会和上一次渲染时生成的虚拟 DOM 树进行对比,然后计算出最小的变更,并只对真实 DOM 进行最小化的修改,这样可以提高性能。

Diff 算法

Diff 算法是用来比较两棵树的算法,主要用于寻找变更并最小化变更操作的数量。Vue 中使用的是一种基于深度优先搜索的双端比较算法。它将新旧节点树进行逐层对比,并找出最少的变更来更新视图。

节点比较

对比两个节点的类型和 key(如果有),如果不同,则直接将旧节点替换为新节点。

子节点比较

对比子节点,会有以下几种情况:

子节点数量不同:直接替换子节点。

子节点相同位置的节点不同:递归对比子节点。

子节点顺序不同:尝试通过移动节点来匹配对应的位置。

遍历比较

递归地对比整棵树,找出需要更新的节点。

Diff 算法的优化

在实际应用中,为了进一步提高性能,Vue 在 Diff 算法中进行了一些优化:

Key 值

给每个节点设置一个唯一的 key 值,可以提高 diff 算法的性能,避免出现不必要的更新。

同层比较

在对比子节点时,Vue 会尽量保持节点的同层级比较,减少节点的跨层级移动,从而提高效率。

总的来说,Vue 的 diff 算法通过对比虚拟 DOM 树的变化来最小化 DOM 操作,从而提高渲染性能和用户体验。

什么是diff算法

Diff(差异化算法)是一种用于比较两个树形结构的算法,常用于前端框架中用于更新视图。在 Vue、React 等现代前端框架中,Diff 算法被用于比较虚拟 DOM 树的差异,从而高效地更新真实 DOM。

Diff 算法的基本原理是:通过比较两棵树的结构差异,找出最小的更新操作,然后将这些更新操作应用到真实 DOM 上,以实现视图的更新。

Diff 算法的目的是尽量减少更新操作的数量,从而提高渲染性能。在实际应用中,Diff 算法通常会结合一些优化策略,比如给每个节点添加唯一的 key 值、尽量保持同层级的比较等,以进一步提高性能。

TCP的三四

TCP:

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层协议,它负责在通信的两个应用程序之间提供可靠的数据传输服务。

TCP连接分为三次握手与四次挥手。

过程:

三次握手:

第一次:客户端发送SYN(同步)报文段给服务器

第二次:服务器端接收到SYN后,返回SYN+ACK(同步+确认)报文段给客户端,表示收到了客户端的请求,并且请求连接,指定了自己的初始序列号。

第三次:客户端收到回复后,发送ACK(确认)报文段给服务器,表示收到请求,并且建立连接。

此时双端建立了双向的数据传输通道进行数据传输。

四次挥手:

第一次:客户端发送 FIN 报文段 给服务器端,表明自己完成了数据的发送

第二次:服务器端发送ACK(确认)报文段,表示自己接收到了客户端的结束请求。

第三次:服务器端发送 FIN 报文段,表明自己也已经发送完数据。

第四次:客户端发送ACK(确认)报文段,表明收到了服务器端的结束请求。

通过这样的四次挥手过程,客户端和服务器完成了连接的关闭,释放了资源,并且可以安全地终止连接。

HTTP和HTTPS

HTTP(超文本传输协议)和HTTPS(安全超文本传输协议)是用于在客户端和服务器之间传输数据的协议。它们有以下主要区别:

  1. HTTP:

    • HTTP是一种不安全的协议,数据在传输过程中是明文的,容易被窃听和篡改。
    • HTTP使用端口号80进行通信。
    • HTTP在网络中传输的数据不经过加密,因此不适合传输敏感信息,例如用户登录凭据、银行卡信息等。
  2. HTTPS:

    • HTTPS是在HTTP的基础上添加了SSL/TLS加密层的安全协议,通过加密通道来保护数据的传输安全。
    • HTTPS使用端口号443进行通信。
    • HTTPS采用公钥加密和私钥解密的方式,确保数据在传输过程中是加密的,不易被窃听和篡改。
    • 通过数字证书,HTTPS还能够验证服务器的身份,防止中间人攻击。

浏览器输入URL之后发生了什么

1.URL解析

浏览器首先解析输入的URL,提取出其中的协议(例如HTTP、HTTPS)、主机名、端口号(如果有)、路径以及查询字符串等信息。

(端口用来;标识网络应用,实现进程间通信,提供网络服务,提高网络安全)

2.DNS解析

浏览器会检查缓存中是否有与主机名对应的IP地址,如果没有,则会进行DNS解析,将主机名解析成IP地址。这个过程涉及向DNS服务器发送请求,并等待响应。

3.TCP连接

浏览器通过HTTP或HTTPS协议与Web服务器建立TCP连接。如果URL中使用的是HTTPS协议,则还需要进行SSL/TLS握手,确保通信安全。

4.发送HTTP请求

浏览器向Web服务器发送HTTP请求,请求中包括请求方法(如GET、POST)、请求头(如User-Agent、Accept)、请求体(如表单数据或JSON数据)等信息。

5.服务器处理请求

Web服务器接收到浏览器发送的HTTP请求后,会根据请求的URL和方法执行相应的处理。处理的具体过程可能包括读取文件、查询数据库、执行脚本等操作。

6.服务器发送响应

Web服务器根据处理结果生成HTTP响应,响应中包括状态码、响应头(如Content-Type、Content-Length)、响应体(如HTML、CSS、JavaScript代码)等信息。

7.客户端接收并渲染

浏览器接收到服务器的HTTP响应后,会根据响应的内容进行渲染。如果响应的内容是HTML文档,则浏览器会解析HTML、加载CSS、执行JavaScript,并将结果显示在页面上。

8.绘制页面

浏览器根据解析后的内容绘制页面,并将页面呈现给用户。这个过程涉及布局计算、渲染树构建、绘制等操作。

9.TCP断开连接

当页面加载完成后,浏览器会关闭与Web服务器建立的TCP连接,释放资源。

相关推荐
twins352035 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
邵泽明36 分钟前
面试知识储备-多线程
java·面试·职场和发展
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
戊子仲秋1 小时前
【LeetCode】每日一题 2024_10_2 准时到达的列车最小时速(二分答案)
算法·leetcode·职场和发展
l1x1n02 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css
夜流冰2 小时前
工具方法 - 面试中回答问题的技巧
面试·职场和发展
penguin_bark3 小时前
LCR 068. 搜索插入位置
算法·leetcode·职场和发展