普通问题
说一说 严格模式和 非严格模式
关键词:
严格模式的使用,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加密
客户端发送支持的TLS版本和加密算法列表: 客户端向服务器发送一个Hello消息,包含支持的TLS版本和加密算法列表。
服务器选择TLS版本和加密算法: 服务器收到客户端的Hello消息后,选择与客户端相匹配的TLS版本和加密算法,并生成一个证书,包含服务器的公钥。
服务器发送证书给客户端: 服务器将包含公钥的证书发送给客户端。
客户端验证服务器证书: 客户端收到服务器的证书后,验证证书的有效性。这包括检查证书的签发机构是否受信任、证书是否过期、证书中包含的域名是否与实际域名匹配等。
客户端生成预主密钥并使用服务器公钥加密: 客户端生成一个随机的预主密钥,并使用服务器的公钥进行加密,然后将加密后的预主密钥发送给服务器。
服务器使用私钥解密预主密钥: 服务器收到客户端发送的预主密钥后,使用自己的私钥进行解密。
客户端和服务器生成主密钥: 客户端和服务器使用约定好的加密算法,以客户端发送的预主密钥和服务器解密后的预主密钥为输入,生成共享的主密钥。
客户端和服务器确认握手: 客户端和服务器互相发送消息,确认握手过程已经完成。
客户端和服务器开始安全通信: 客户端和服务器使用生成的主密钥进行安全通信,包括加密和解密数据。
很基础的重点
什么是原型链,说说相关的理解
首先要明白一点:原型存在于对象中的。
接下来阐述什么原型:
在js中,每个构造函数内部都有一个prototype属性,该属性的值是个对象,该对象包含了该构造函数所有实例共享的属性和方法。当我们通过构造函数创建对象的时候,在这个对象中有一个指针,这个指针指向构造函数的prototype的值,我们将这个指向prototype的指针称为原型。或者用另一种简单却难理解的说法是:js中的对象都有一个特殊的[[Prototype]]内置属性,其实这就是原型。
关系:
所有的引用类型(包括数组,对象,函数)都有隐性原型属性(proto), 值也是一个普通的对象。
所有的函数,都有一个 prototype 属性,值也是一个普通的对象。
所有的引用类型的proto属性值都指向构造函数的 prototype 属性值
所以得出以下 关系:
构造函数.prototype===原型
原型.constructor===构造函数
实例对象.proto===原型
说一说你对闭包的理解
闭包的核心概念有三个要素:
- 函数(Function):闭包是一个函数,它可以执行特定的代码逻辑。
- 函数定义时的环境(Environment):闭包还包含了在函数定义时所处的作用域中的变量。
- 函数返回值(Return Value):闭包可以返回一个函数或者值,这取决于函数的具体实现。
闭包的主要特点包括:
- 记忆状态:闭包可以记住定义时的上下文信息,即使在定义之后,这些上下文已经不再存在。
- 封装性:闭包将变量和函数绑定在一起,形成一个封闭的单元,避免了全局变量的污染。
- 延迟执行:闭包中的函数可以延迟执行,等待外部条件满足后再执行。
javascriptfunction 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
拓展:实现链式调用
javascriptfunction 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会发生什么,能手写一个吗
- 创建一个新的空对象。
- 将该空对象的原型链指向构造函数的原型对象(即
prototype
属性)。- 将构造函数的执行上下文(
this
)绑定到这个新创建的对象。- 执行构造函数内部的代码,以初始化新创建的对象。如果构造函数中有返回值,并且返回值是一个对象,那么这个对象将取代第一步中创建的新对象;如果返回值不是对象,则忽略这个返回值,返回第一步中创建的新对象。
- 如果构造函数没有返回值,那么会默认返回第一步中创建的新对象。
javascriptfunction 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构造函数接收一个函数作为参数,这个函数有两个参数,分别是两个函数
resolve
和reject
,resolve
将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;reject
则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例创建完成后,可以使用then
方法分别指定成功或失败的回调函数,也可以使用catch
捕获失败,then
和catch
最终返回的也是一个Promise,所以可以链式调用。Promise的特点:
对象的状态不受外界影响(Promise对象代表一个异步操作,有三种状态)。
- pending(执行中)
- Resolved(成功,又称Fulfilled)
- rejected(拒绝) 其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。
一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise对象的状态改变,只有两种可能(状态凝固了,就不会再变了,会一直保持这个结果):
- 从Pending变为Resolved
- 从Pending变为Rejected
resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数
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的拦截器可以在请求发送之前和响应返回之后进行处理,这样可以方便地对请求和响应进行统一处理,比如添加请求头、统一处理错误等。
拦截器的应用场景:
请求拦截器的应用场景:
- 在请求发送之前对请求进行处理,比如为每个请求添加相应的参数(如 token、时间戳等)。
- 对请求进行统一的错误处理,比如请求超时、网络错误等。
- 在发送请求前显示 loading 动画,请求完成后隐藏 loading 动画。
响应拦截器的应用场景:
- 在接收到响应后对响应进行处理,比如对返回的状态码进行判断,判断 token 是否过期。
- 统一处理错误信息,比如处理后端返回的特定错误码。
- 在接收到响应后隐藏 loading 动画,展示响应数据。
拦截器的原理
是通过创建一个数组(通常称为 interceptor chain,简称 chn),数组中保存了拦截器相应的方法以及
dispatchRequest
方法。请求拦截器的方法会被放到
chn
数组的前面,响应拦截器的方法会被放到
chn
数组的后面。为了保证拦截器的执行顺序,需要使用 Promise 以出队列的方式对
chn
数组中的方法挨个执行。一般来说,拦截器的执行顺序是这样的:
- 请求拦截器方法依次执行,按照定义顺序从前往后执行。
dispatchRequest
方法执行。- 响应拦截器方法依次执行,按照定义顺序从前往后执行。
拓展:
Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 Node.js 中。它具有以下特点:
- 从浏览器中创建 XMLHttpRequests,从 Node.js 创建 HTTP 请求。
- 支持 Promise API,使得使用异步请求更加方便和简洁。
- 可拦截请求和响应,可以在发送请求之前或接收到响应之后对其进行处理。
- 可转换请求数据和响应数据,例如,可以使用 JSON.stringify 将请求数据转换为 JSON 格式,使用 JSON.parse 将响应数据解析为 JavaScript 对象。
- 可取消请求,当请求已经发送但未完成时,可以通过 cancelToken 取消请求。
- 可自动转换 JSON 数据,无需手动解析 JSON 数据,Axios 会自动将 JSON 数据转换为 JavaScript 对象。
- 客户端支持防御 XSRF(跨站请求伪造)攻击,可以通过配置
xsrfCookieName
和xsrfHeaderName
来设置 XSRF 防御相关的选项。
说一说创建ajax过程
创建:
创建 XMLHttpRequest 对象:
首先,需要创建一个 XMLHttpRequest 对象,用于发起 AJAX 请求。在现代浏览器中,可以直接使用
new XMLHttpRequest()
来创建 XMLHttpRequest 对象。
javascriptconst xhr = new XMLHttpRequest();
设置请求参数:
在发送 AJAX 请求之前,通常需要设置一些请求参数,比如请求的 URL、请求的方法(GET、POST 等)、请求头、是否异步等。
javascriptxhr.open('GET', 'https://api.example.com/data', true); xhr.setRequestHeader('Content-Type', 'application/json');
设置回调函数:
AJAX 请求是异步的,因此需要设置回调函数来处理请求的各个阶段,包括请求被发送、接收到响应、以及请求过程中发生的错误等。
javascriptxhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { console.log('请求成功,返回数据为:', xhr.responseText); } else { console.error('请求失败,状态码为:', xhr.status); } } };
发送请求:
当所有参数都设置好之后,使用
send()
方法发送 AJAX 请求。
javascriptxhr.send();
拓展:
POST请求需要设置请求头 readyState值说明
0:初始化,XHR对象已经创建,还未执行open
1:载入,已经调用open方法,但是还没发送请求
2:载入完成,请求已经发送完成
3:交互,可以接收到部分数据
4:数据全部返回
status值说明 200:成功 404:没有发现文件、查询或URl 500:服务器产生内部错误
为什么使用虚拟DOM,而不操作真实DOM
性能优化: 虚拟DOM可以批量更新和高效比较,减少了频繁操作真实DOM带来的性能损耗。通过比较虚拟DOM树和上一次渲染的虚拟DOM树的差异,可以最小化对真实DOM的操作,提高页面渲染的效率。
跨平台兼容性: 虚拟DOM可以使得框架或库在不同平台上运行时保持一致的行为,无需关心底层平台的DOM API差异。这样可以使得开发者更加专注于业务逻辑的实现,而不用考虑跨平台兼容性带来的问题。
简化复杂度: 直接操作真实DOM可能会涉及到复杂的DOM操作和手动维护DOM状态的问题。而虚拟DOM将复杂的DOM操作抽象为对虚拟DOM树的操作,简化了代码逻辑,提高了开发效率。
方便实现组件化: 虚拟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(安全超文本传输协议)是用于在客户端和服务器之间传输数据的协议。它们有以下主要区别:
HTTP:
- HTTP是一种不安全的协议,数据在传输过程中是明文的,容易被窃听和篡改。
- HTTP使用端口号80进行通信。
- HTTP在网络中传输的数据不经过加密,因此不适合传输敏感信息,例如用户登录凭据、银行卡信息等。
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连接,释放资源。