1.js中的深浅拷贝的区别,如何实现(浅拷贝是拷贝地址还是对象本身)
在JavaScript中,浅拷贝和深拷贝的区别主要涉及如何复制对象的属性,尤其是当对象的属性本身也是对象时。
浅拷贝 (Shallow Copy)
浅拷贝只复制对象的第一层属性,如果属性值本身是一个对象,浅拷贝只复制这个对象的引用(即内存地址),而不是对象本身。这意味着,如果原始对象的某个属性值是对象,并且被修改,那么拷贝的对象的相应属性也会受到影响。
如何实现浅拷贝:
(1)使用Object.assign
方法:
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
console.log(copy); // { a: 1, b: { c: 2 } }
copy.b.c = 3;
console.log(original.b.c); // 3
(2)使用扩展运算符(spread operator):
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
console.log(copy); // { a: 1, b: { c: 2 } }
copy.b.c = 3;
console.log(original.b.c); // 3
深拷贝 (Deep Copy)
深拷贝不仅复制对象的第一层属性,还会递归复制所有层级的属性,包括属性值为对象的情况。这意味着,深拷贝的结果是一个完全独立的新对象,原始对象的任何更改都不会影响到拷贝的对象。
如何实现深拷贝:
(1)使用JSON.parse
和JSON.stringify
:
const original = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(original));
console.log(copy); // { a: 1, b: { c: 2 } }
copy.b.c = 3;
console.log(original.b.c); // 2
上述方法不能复制函数、undefined和循环引用。
(2)使用库(如lodash的_.cloneDeep
方法):
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
const copy = _.cloneDeep(original);
console.log(copy); // { a: 1, b: { c: 2 } }
copy.b.c = 3;
console.log(original.b.c); // 2
2.==和===的区别,使用场景有哪些?
在JavaScript中,==
和 ===
是两种用于比较两个值的操作符,但它们在比较时的行为和规则有显著差异:
==
(等于)
- 类型转换:
==
是等值比较,它在比较前会进行类型转换(类型强制),如果比较的两个值不是同一类型,JavaScript会尝试将它们转换成同一类型,然后进行比较。 - 示例:
0 == '0'
→ true(数字0会被转换成字符串'0')2 == '2'
→ true1 == true
→ truenull == undefined
→ true
===
(恒等于)
- 无类型转换:
===
是严格等值比较,它不进行类型转换。如果两个值的类型不同,直接返回false;只有当两个值的类型相同且值相等时,才返回true。 - 示例:
0 === '0'
→ false2 === '2'
→ false1 === true
→ falsenull === undefined
→ false
使用场景
===
的使用场景:在大多数编程实践中,推荐使用===
,因为它不会进行隐式类型转换,这使得比较结果更加可预测和明确。这是尤其重要的,在处理复杂的数据结构和需要精确值比较的逻辑时,===
可以避免很多由于隐式类型转换导致的错误。==
的使用场景:==
可以在你确信进行类型转换是安全的或者必要的情况下使用。例如,在一些特定的情况下,你可能需要将null
和undefined
视为等价值,这时使用==
可以简化代码(null == undefined
)。但是,由于==
可能会导致难以追踪的逻辑错误,使用时需要格外小心。
3.讲讲闭包
闭包的定义
闭包是指那些能够访问自由变量的函数。所谓“自由变量”,是指在函数中使用的,但既不是函数参数也不是函数局部变量的变量。
一个函数在创建时会生成闭包,闭包包含该函数的函数体以及该函数可以访问的所有变量。
闭包的创建
闭包通常是在一个函数内部创建另一个函数时形成的:
function outer() {
var outerVar = "I am outside!";
function inner() {
console.log(outerVar); // 访问外部函数的变量
}
return inner;
}
var myInner = outer();
myInner(); // 输出: I am outside!
在这个例子中,inner
函数是在outer
函数内部定义的,并且inner
可以访问outer
函数作用域中的outerVar
变量。即使在outer
函数执行完毕后,inner
函数依旧能够访问outerVar
变量,这就是闭包的作用。
闭包的用途
闭包的一些常见用途包括:
- 数据封装:闭包可以帮助创建私有变量,提供对象数据的封装和保护。
- 模块化代码:闭包允许创建模块,这些模块可以包含私有函数和私有状态,只暴露公共的API。
- 实现回调和高阶函数:在异步编程中,闭包常用于存储状态,直到异4步操作完成。
4.讲讲异步(async,promise,原生的异步是什么)
在JavaScript中,异步编程是一种处理耗时操作(如文件读取、网络请求等)的方式,它允许程序继续执行其他任务,而不是停下来等待耗时操作完成。JavaScript的异步模型基于事件循环,支持回调函数、Promises、以及async/await
语法,这些都是用来处理异步操作的。
原生的异步
JavaScript的异步操作通常是通过以下几种技术实现的:
- 回调函数:这是实现异步操作的最早和最基本的方式。一个函数作为参数传递给另一个函数,当异步操作完成后,这个回调函数被调用。
- 事件监听:在DOM编程中常用,比如监听网络请求的
load
事件。
Promise
Promise是一个代表了异步操作最终完成或失败的对象。它有三种状态:
- Pending(等待中):异步操作还没有完成。
- Fulfilled(已成功):异步操作已经完成并且成功返回。
- Rejected(已失败):异步操作已经完成但失败了。
Promise的使用提供了比原生回调更好的错误处理和链式调用方法。例如,使用Promise处理一个HTTP请求:
function fetchData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(new Error("Request failed: " + xhr.statusText));
}
};
xhr.onerror = () => reject(new Error("Network error"));
xhr.send();
});
}
fetchData('https://api.example.com/data')
.then(data => console.log(data))
.catch(error => console.error(error));
async/await
async
和await
是基于Promises的语法糖,使异步代码看起来更像同步代码,从而简化了Promise代码的写法。一个async
函数自动将其返回值转换为一个Promise。await
关键字可以暂停async
函数的执行,等待Promise的解决,然后继续执行async
函数并返回结果。
async function fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
fetchData('https://api.example.com/data');
使用场景
- 回调函数:适用于简单的异步处理,如单一事件响应或简单的异步转换。
- Promises:适合需要链式处理多个异步操作的场景。
- async/await:适用于需要处理多个异步操作,特别是在逻辑复杂或条件多变的情况下。
5.讲讲AJAX
AJAX(Asynchronous JavaScript and XML)是一种在客户端和服务器之间异步交换数据和更新网页的技术,而无需重新加载整个网页。这使得应用程序可以快速响应用户的操作,提供更流畅的用户体验。AJAX 不是一种新的编程语言,而是一种使用现有标准的技术组合。
核心技术
AJAX 主要依赖以下几种技术:
- HTML/XHTML 和 CSS:用于标准的网页内容和样式表现。
- DOM (Document Object Model):用于动态显示和交互。
- XML:最初用作数据交换的格式,但现在通常被JSON所取代。
- XMLHttpRequest 对象:允许网页与服务器进行数据交换和更新。
- JavaScript:用来绑定以上技术。
XMLHttpRequest 对象
XMLHttpRequest
(XHR)对象是实现AJAX的核心。它为客户端提供了在发送请求到接收响应的过程中处理数据的能力。使用XHR对象,可以与服务器交换数据,从而更新网页的某一部分,而无需重载整个页面。
使用XHR请求示例:
function fetchData() {
var xhr = new XMLHttpRequest(); // 创建 XMLHttpRequest 对象
xhr.open('GET', 'https://api.example.com/data', true); // 配置请求类型、URL 和异步处理方式
xhr.onreadystatechange = function () { // 每当 readyState 改变时,就会调用这个函数
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText); // 在控制台打印响应文本
}
};
xhr.send(); // 发送请求
}
fetchData();
在这个示例中,当请求完成并且没有错误时(readyState
为 4,status
为 200),响应的内容会被打印到控制台。
JSON 与 AJAX
随着时间的推移,JSON(JavaScript Object Notation)因其简洁和易于解析的特性,逐渐成为替代XML作为数据交换格式的首选。它与JavaScript的兼容性非常好,使得处理由服务器返回的数据更加方便。
使用 AJAX 的好处
- 提高性能:只需更新页面的一部分,而不是每次都重新加载页面。
- 改善用户体验:减少了加载时间和页面闪烁,提供更平滑的交互体验。
- 异步操作:允许用户在数据请求处理的同时,继续其他操作,例如填写网页表单。
现代替代方案
虽然XMLHttpRequest
一直是实现 AJAX 的传统方式,但现代开发中更倾向于使用更简洁和强大的fetch
API。fetch
提供了一种更简单、更灵活的方式来处理网络请求,并支持Promises,使得异步操作更加容易管理。
6.跨域问题的解决方式有哪些
后端CORS、JSONP、搭建Node代理服务器、Nginx反向代理、postMessage、Websocket;
7.讲讲websocket(项目中如果出现断连应该怎么处理,怎么重连)
WebSocket 是一种网络通信协议,提供了一种在单个长连接上进行全双工、双向交互的通信方式。它允许服务器和客户端之间的信息即时传递,这在需要实时功能的应用程序(如在线游戏、实时交易平台、协作工具等)中非常有用。
WebSocket 基本工作原理
- 握手: 首先,客户端通过 HTTP 请求发起一个 WebSocket 连接。如果服务器支持 WebSocket,它会返回一个升级(Upgrade)响应,此时连接由 HTTP 升级为 WebSocket 连接。
- 数据交换: 一旦握手成功,客户端和服务器就可以通过这个建立的 WebSocket 连接自由地发送数据,直到其中一方关闭连接。
处理 WebSocket 断连
在实际项目中,WebSocket 连接可能因为多种原因断开,如网络波动、服务器重启等。为了确保应用的连续性和用户体验,处理断连和实现自动重连是非常重要的。
重连策略
- 监听连接关闭: 使用 WebSocket 的
onclose
事件监听器来检测连接何时被关闭。 - 执行重连: 在
onclose
事件中实现重连逻辑。一般来说,重连应当有延迟,并考虑到重连次数,避免无限重试。 - 增加延迟和退避策略: 在尝试重连时,建议使用指数退避策略(exponential backoff),即每次重连尝试之间的间隔时间逐渐增长,这可以减少对服务器的压力。
示例代码
let socket;
let retryInterval = 1000; // 初始重连间隔为 1000 毫秒
const maxInterval = 30000; // 最大重连间隔
function connect() {
socket = new WebSocket('ws://example.com/socket');
socket.onopen = function() {
console.log('WebSocket connection established');
retryInterval = 1000; // 重连成功后重置重连间隔
};
socket.onclose = function(e) {
console.log('WebSocket connection closed', e);
setTimeout(connect, retryInterval);
retryInterval = Math.min(maxInterval, retryInterval * 2); // 指数退避策略
};
socket.onerror = function(error) {
console.error('WebSocket error:', error);
socket.close(); // 确保触发 onclose 事件
};
socket.onmessage = function(event) {
console.log('Received:', event.data);
};
}
connect(); // 初始连接
8.vue中的生命周期
在 Vue.js 中,每个 Vue 实例在被创建之前都要经过一系列的初始化过程。在这个过程中,Vue 会运行一系列的配置,如设置数据监听、编译模板、挂载实例到DOM,并在数据变化时更新DOM。这些步骤自然形成了Vue的生命周期,它提供了多个事件钩子,允许用户在特定时刻添加自己的代码。
Vue 2 的生命周期钩子
Vue 2 中的主要生命周期钩子包括:
-
beforeCreate
- 这个钩子在实例初始化之后,数据观测(data observer)和event/watcher 事件配置之前被调用。
-
created
- 在实例创建完成后被立即调用。在这一步,实例已完成数据观测、属性和方法的运算,
$watch
/$event
事件回调已被配置。
- 在实例创建完成后被立即调用。在这一步,实例已完成数据观测、属性和方法的运算,
-
beforeMount
- 在挂载开始之前被调用:相关的
render
函数首次被调用。该钩子在服务器端渲染期间不被调用。
- 在挂载开始之前被调用:相关的
-
mounted
el
被新创建的vm.$el
替换,并挂载到实例上去之后调用该钩子。如果根实例挂载了一个文档内元素,当mounted
被调用时vm.$el
也在文档内。
-
beforeUpdate
- 数据更新时调用,发生在虚拟DOM打补丁之前。这里适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器。
-
updated
- 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以现在可以执行依赖于DOM的操作。
-
beforeDestroy
- 实例销毁之前调用。在这一步,实例仍然完全可用,这是解绑事件监听器、销毁子实例以及解除引用的一个好时机。
-
destroyed
- Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑,所有的事件监听器会被移除,所有的子实例也会被销毁。
Vue 3 生命周期钩子的变化
Vue 3 保留了 Vue 2 的大部分生命周期钩子,并引入了一些以 on
开头的新的生命周期函数,这些新的函数可以在 setup()
函数中使用,适用于Composition API:
- onBeforeMount 替代
beforeMount
- onMounted 替代
mounted
- onBeforeUpdate 替代
beforeUpdate
- onUpdated 替代
updated
- onBeforeUnmount 替代
beforeDestroy
- onUnmounted 替代
destroyed
这些钩子提供了更多的灵活性,尤其是在使用Composition API构建组件时。它们也更容易与其他组合逻辑集成,提高了代码的可重用性和整洁度。
9.vue的双向数据绑定原理是什么?
Vue.js 的双向数据绑定是其核心特性之一,使得开发者能够简化大量涉及到数据交互的复杂逻辑。Vue 实现双向绑定主要依赖于 JavaScript 的响应式系统,其中最关键的部分是利用了 ES5 的 Object.defineProperty() 方法来实现对对象属性的监听。
双向数据绑定的原理
1. 响应式系统
当你在 Vue 组件中定义 data
属性时,Vue 会遍历这些属性,并使用 Object.defineProperty()
将它们转换为 getter/setter。Vue 内部用这种方法来实现对数据的响应式(reactive)监听。
- Getter: 用于依赖收集,在属性被访问时收集当前属性依赖的组件。
- Setter: 在属性值修改时被调用,负责通知变化,触发视图的重新渲染。
2. 依赖收集
每个组件实例都有一个对应的 watcher 实例。当组件渲染时,会访问到组件依赖的数据,这时数据的 getter 函数就会被触发。Getter 函数负责将当前的 watcher 添加到该数据属性的依赖列表中。这样,当数据改变触发 setter 时,所有依赖于这个数据的 watcher 都会收到通知。
3. 派发更新
当数据发生变化时,setter 会被调用,setter 中会通知所有订阅者(watcher),告诉它们依赖的数据已经发生变化。每个 watcher 收到通知后,会重新执行,从而导致组件重新渲染。
v-model
的作用
在表单输入和应用状态之间创建双向绑定最常用的指令是 v-model
。v-model
在背后为不同的输入元素绑定不同的事件和属性。
- 对于文本框 (
<input>
,<textarea>
) 和单选按钮 (<input type="radio">
),v-model
绑定的是value
属性和input
事件。 - 对于复选框 (
<input type="checkbox">
),v-model
绑定的是checked
属性和change
事件。 - 对于下拉列表 (
<select>
),v-model
绑定的是value
属性和change
事件。
通过修改这些 DOM 元素的值,事件监听器会捕捉到变化并更新相应的 Vue 组件的数据。同时,当 Vue 组件的数据变化时,视图也会自动更新,从而反映出数据的最新状态。