Vue2和Vue3有许多不同之处,以下是一些主要的区别:
-
数据劫持方式:Vue2主要是通过Object.defineProperty劫持数据,对于新增的对象属性,使用数组修改元素,视图无法更新1。
-
性能和体积:Vue3的运行速度是Vue2的1.5倍左右,按需编译体积比Vue2要更小2。
-
类型推断:Vue3提供了更好的对TypeScript的支持2。
-
API和Hooks函数:Vue3引入了Composition API,这是一个新的编写组件的方式,可以更好地组织和复用代码逻辑23。
-
生态系统:虽然Vue3发布后,周边生态系统的支持并不完全,但现在主流组件库都已经发布了支持Vue3的版本2。
关于是否选择Vue2还是Vue3,尤雨溪(Vue的创始人)建议新学习者可以直接学Vue3,因为基础概念是一模一样的2。然而,对于正式项目,一些人认为目前还是Vue2更为稳定4。
Vue2和Vue3的响应式原理:
Vue2和Vue3的响应式原理有所不同,主要体现在数据劫持的方式上:
Vue2的响应式原理1234:
- Vue2的响应式是基于Object.defineProperty实现的,它会遍历data中的所有属性,将每个属性转为getter/setter1。
- getter用来收集依赖,setter用来执行notify,发布更新事件1。
- 但是,Vue2无法检测到对象属性的添加或删除1。对于数组,Vue2不能检测到利用索引直接设置一个数组项或修改数组的长度1。
Vue3的响应式原理567:
- Vue3的响应式是基于Proxy实现的,它可以解决Vue2响应式的一些缺陷6。
- 通过Proxy对象分别对get和set劫持,在取值和赋值中间分别插入劫持的方法,即track和trigger——依赖的跟踪和副作用的触发7。
这两种响应式原理都使得当数据发生改变时,Vue会通知到使用该数据的代码,例如,视图渲染中使用了数据,数据改变后,视图也会自动更新3。这就是Vue的响应式系统的核心机制
Vue2和Vue3的响应式原理各有其优缺点:
Vue2的响应式原理1234:
- 优点:
- Vue2的响应式系统简单易懂,易于使用1。
- 由于Vue2使用Object.defineProperty,它可以在不改变原始对象结构的情况下实现响应式1。
- 缺点:
- Vue2无法检测到对象属性的添加或删除1。对于数组,Vue2不能检测到利用索引直接设置一个数组项或修改数组的长度1。
- Object.defineProperty是ES5的特性,因此Vue2不支持IE8以及更低版本的浏览器1。
Vue3的响应式原理5:
- 优点:
- Vue3的响应式是基于
Proxy
实现的,它可以解决Vue2响应式的一些缺陷。例如,Vue3可以检测到对象属性的添加或删除。 Proxy
是ES6的特性,因此Vue3的兼容性更好。
- Vue3的响应式是基于
- 缺点:
Proxy
的使用可能会导致一些兼容性问题,因为一些老的浏览器可能不支持Proxy
。
详细介绍一下Object.defineProperty这个api
Object.defineProperty() 是 JavaScript 中的一个静态方法,它可以在对象上定义新的属性,或者修改对象上的现有属性,然后返回该对象1。
这个方法的语法如下:
Object.defineProperty(obj, prop, descriptor)
其中:
obj
:要在其上定义或修改属性的对象。prop
:要定义或修改的属性的名称。descriptor
:要定义或修改的属性的描述符。
Object.defineProperty() 允许精确地添加或修改对象上的属性。通过赋值创建的普通属性会在属性枚举(for…in,Object.keys()等)中显示出来,其值可能会被更改,也可能会被删除。这个方法允许改变这些额外的细节1。
默认情况下,使用 Object.defineProperty() 添加的属性是不可写的,不可枚举的,且不可配置的1。
此外,Object.defineProperty() 使用的是内部方法 [[DefineOwnProperty]],而不是 [[Set]],所以即使属性已经存在,它也不会调用 setter1。
在对象中存在的属性描述符主要有两种类型:数据描述符和访问器描述符。数据描述符是具有值的属性,该值可能可写或不可写。访问器描述符是由 getter-setter 函数对描述的属性。描述符必须是这两种类型之一,不能同时是两者1。
数据描述符和访问器描述符都是对象。他们共享以下可选键(请注意:这里提到的默认值是在使用 Object.defineProperty() 定义属性时的默认值)1:
configurable
:当此项设置为 false 时,此属性的类型不能在数据属性和访问器属性之间更改,且属性可能不被删除,其描述符的其他属性也不能被更改(但是,如果它是一个具有 writable: true 的数据描述符,那么值可以被更改,且 writable 可以被更改为 false)。默认为 false。enumerable
:当且仅当此属性在对应对象的属性枚举中显示时为 true。默认为 false。
数据描述符还有以下可选键1:
value
:与属性关联的值。可以是任何有效的 JavaScript 值(数字,对象,函数等)。默认为 undefined。writable
:当且仅当与属性关联的值可以使用赋值运算符更改时为 true。默认为 false。
访问器描述符还有以下可选键1:
get
:作为属性的 getter 的函数,或者如果没有 getter 则为 undefined。当访问属性时,此函数会被调用,没有参数,且 this 被设置为通过其访问属性的对象(由于继承,这可能不是定义属性的对象)。
http和https 的区别
HTTP和HTTPS主要有以下几个区别:
-
协议依赖:HTTP是基于TCP的应用层协议,信息传输是明文,存在安全风险。HTTPS则是在HTTP和TCP之间加入了SSL/TLS安全协议层,使得信息能够加密传输1234。
-
端口:HTTP使用的是80端口,而HTTPS使用的是443端口1234。
-
安全性:HTTP的信息传输是明文的,存在被窃听、篡改和冒充的风险。HTTPS通过SSL/TLS协议对信息进行加密传输,能有效防止这些风险1234。
-
证书:HTTPS需要向证书权威机构(CA)申请数字证书,以保证服务器的身份是可信的1234。
-
连接过程:HTTP连接建立相对简单,TCP三次握手后即可进行HTTP的报文传输。而HTTPS在TCP三次握手后,还需进行SSL/TLS的握手过程,才能进入加密报文传输1234。
-
性能:由于HTTPS需要进行加密和解密操作,因此会消耗更多的CPU和内存资源,相对于HTTP来说,HTTPS的页面加载速度会稍慢一些24。
总的来说,HTTPS提供了比HTTP更安全的数据传输环境,能有效防止数据在传输过程中被窃听、篡改或冒充。但同时,HTTPS也需要更多的系统资源和更复杂的握手过程。因此,对于一些对信息安全性要求较高的场景(如网银、电子商务等),通常会选择使用HTTPS,而对于一些对信息安全性要求不高,但对系统性能要求较高的场景(如新闻网站、论坛等),则可能会选择使用HTTP。
HTTP缓存
HTTP缓存是一种重要的技术,可以提高网页加载速度,减少服务器负载,以及节省网络带宽。以下是关于HTTP缓存的一些详细信息1:
-
基本概念:HTTP缓存会存储与请求关联的响应,并将存储的响应复用于后续请求。这样可以加快响应速度,减少服务器负载1。
-
缓存类型:在HTTP缓存标准中,有两种不同类型的缓存:私有缓存和共享缓存。私有缓存是绑定到特定客户端的缓存,通常是浏览器缓存。共享缓存位于客户端和服务器之间,可以存储能在用户之间共享的响应1。
-
缓存控制:HTTP提供了一系列的缓存控制策略,通过HTTP头部的Cache-Control字段来实现。例如,max-age指令可以定义一个资源被视为新鲜的最长时间1。
-
启发式缓存:即使没有给出Cache-Control,如果满足某些条件,响应也会被存储和重用。这称为启发式缓存1。
-
验证和更新:当缓存的资源过期时,客户端可以通过发送条件请求来验证资源是否已经改变。如果资源没有改变,服务器会返回一个304状态码,告诉客户端缓存的版本仍然有效1。
MVVM模式
MVVM(Model-View-ViewModel)是一种设计模式,它将应用程序的业务逻辑、用户界面和数据绑定分离。这种分离有助于提高代码的可维护性和可扩展性,同时也使得开发者能够更容易地实现跨平台的应用程序1。
以下是 MVVM 的主要组成部分231:
-
模型(Model):模型代表了业务逻辑和数据操作,例如从服务器获取数据、存储数据等31。
-
视图(View):视图负责渲染用户界面,包括HTML、CSS和JavaScript等,但不包括业务逻辑31。
-
视图模型(ViewModel):视图模型是视图和模型之间的桥梁,负责从模型中获取数据,并将其转换为视图可以使用的格式,同时也负责将视图中的用户交互事件转换为模型可以理解的操作31。
在 MVVM 架构中,视图模型扮演了非常重要的角色。它不仅仅是将模型的数据绑定到视图上,还可以实现一些业务逻辑,例如数据的验证、数据的格式化等3。视图模型还可以通过命令模式来实现用户交互事件的处理,从而将视图和模型完全解耦3。
在实际开发中,可以使用各种框架和库来实现 MVVM 架构,例如 Vue.js、AngularJS、React 等4。
遍历数组的方法
在JavaScript中,有多种方法可以遍历数组1:
- for循环:这是最基本的遍历方法,通过索引来访问数组的每个元素1。
var name = ['Peter','Stark','Jack'];
for(var i = 0; i < name.length; i++) {console.log(name[i]);
}
- while循环:这是另一种基本的遍历方法,可以从头到尾,也可以从尾到头遍历数组1。
var i = 0;
while (i < name.length) {console.log(name[i]);i++;
}
- for…in循环:这种方法可以遍历数组,但也会遍历非数字键名,所以不推荐用于遍历数组1。
var a = [1, 2, 3];
for (var key in a) {console.log(a[key]);
}
- for…of循环:这是ES6引入的新特性,可以直接遍历数组的元素1。
var arr = ['a','b','c'];
for(let item of arr) {console.log(item);
}
- forEach方法:这是数组的内置方法,可以遍历数组的每个元素。但是,你不能使用break语句中断循环,也不能使用return语句返回到外层函数1。
var arr = [1,2,3,4];
arr.forEach(function(item) {console.log(item);
});
- map方法:这也是数组的内置方法,它会创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果1。
var arr = [1,2,3,4];
var newArray = arr.map(x => x * x);
console.log(newArray);
map和forEach
map() 和 forEach() 都是 JavaScript 中数组的方法,用于遍历数组。但是,它们在使用和结果上有一些重要的区别12:
- 返回值:map() 方法会创建一个新的数组,其中的每个元素都是原数组中对应元素经过函数处理后的结果。也就是说,map() 会返回一个新的数组12。而 forEach() 方法则没有返回值,它仅仅是对数组中的每个元素执行一次给定的函数12。
const array = [1, 2, 3, 4, 5];
const mapResult = array.map(x => x * x); // 返回新数组:[1, 4, 9, 16, 25]
const forEachResult = array.forEach(x => x * x); // 返回undefined
- 链式调用:由于 map() 返回一个新的数组,因此可以链式调用其他数组方法,如 reduce()、filter() 等12。而 forEach() 由于返回 undefined,因此不能链式调用其他数组方法12。
const array = [1, 2, 3, 4, 5];
const mapChain = array.map(x => x * x).reduce((total, value) => total + value); // 返回55
// const forEachChain = array.forEach(x => x * x).reduce((total, value) => total + value); // 报错:Cannot read property 'reduce' of undefined
- 原数组的改变:map() 方法不会改变原数组,而 forEach() 方法则可能会改变原数组12。
const array = [1, 2, 3, 4, 5];
array.map((item, index, input) => {input[index] = item * 10;
}); // 原数组不变
array.forEach((item, index, input) => {input[index] = item * 10;
}); // 原数组被改变:[10, 20, 30, 40, 50]
总的来说,如果你需要对数组中的每个元素进行操作,并需要一个新的数组作为结果,那么 map()
是一个好的选择。如果你只是需要遍历数组,并对每个元素执行某些操作,而不需要结果数组,那么 forEach()
可能更适合你。希望这个解释能帮助你理解 map()
和 forEach()
的区别。
闭包
闭包(Closure)是计算机科学中的一个概念,特别在函数式编程中使用频繁。在JavaScript等支持一等函数的编程语言中,闭包是一种实现词法绑定的技术1。
闭包是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在JavaScript中,闭包会随着函数的创建而被同时创建1。
以下是一个简单的闭包示例:
function outerFunction(outerVariable) {return function innerFunction(innerVariable) {console.log('outerVariable:', outerVariable);console.log('innerVariable:', innerVariable);}
}const newFunction = outerFunction('outside');
newFunction('inside'); // logs: outerVariable: outside, innerVariable: inside
在这个例子中,innerFunction
是一个闭包,因为它可以访问 outerFunction
的作用域,包括它的参数 outerVariable
。
闭包在JavaScript中有许多用途,包括数据封装和私有化、函数工厂、以及在异步编程中创建回调函数等。
浏览器的渲染过程
浏览器的渲染过程是一个复杂的过程,涉及到多个步骤12:
-
获取资源:浏览器首先通过网络请求获取HTML、CSS和JavaScript等资源12。
-
解析HTML:浏览器解析HTML文档,构建DOM(Document Object Model)树12。
-
解析CSS:浏览器解析CSS样式信息,构建CSSOM(CSS Object Model)树12。
-
构建渲染树:浏览器将DOM树和CSSOM树合并,构建渲染树12。
-
布局:浏览器计算渲染树中每个节点的位置和大小,这个过程也被称为“重排”12。
-
绘制:浏览器遍历渲染树,使用UI后端层将每个节点绘制到屏幕上,这个过程也被称为“重绘”12。
-
合成:浏览器会将多个层合成在一起,然后显示在屏幕上12。
以上就是浏览器的基本渲染过程。需要注意的是,这个过程可能会因为用户的交互、JavaScript的执行等因素而被打断,然后重新开始12。此外,现代的浏览器还会使用各种优化技术来提高渲染的效率12。
路由有几种模式?
在前端开发中,路由主要有两种模式1:
- Hash模式:这种模式下,路由的变化会通过URL的hash(#后面的部分)来实现。当hash值改变时,页面不会重新加载,但会触发hashchange事件,我们可以通过监听这个事件来实现前端路由1。这种模式的优点是兼容性好,可以支持到IE81。
window.location.hash = 'home'; // 设置hash值
window.addEventListener('hashchange', function() {console.log(window.location.hash); // 监听hash变化
});
- History模式:这种模式下,路由的变化是通过HTML5的History API(pushState、replaceState)来实现的。这种模式可以提供类似于普通页面跳转的用户体验,URL没有#符号,看起来更美观1。但这种模式需要服务器的支持,且只能支持到IE101。
window.history.pushState(null, null, 'home'); // 添加一个新的状态到历史状态栈
window.history.replaceState(null, null, 'home'); // 替换当前的历史状态
window.addEventListener('popstate', function() {console.log(window.location.href); // 监听历史状态变化
});
具体实现方式
在前端路由的实现中,无论是Hash模式还是History模式,都需要监听URL的变化,并根据URL的变化来更新页面的内容。以下是这两种模式的具体实现方式12:
- Hash模式:在Hash模式下,我们可以使用window.location.hash来获取和设置URL的hash部分。当hash变化时,会触发hashchange事件,我们可以通过监听这个事件来更新页面的内容12。
window.addEventListener('hashchange', function() {var currentHash = window.location.hash;// 根据currentHash更新页面内容
});
- History模式:在History模式下,我们可以使用History API的pushState和replaceState方法来改变URL,而不会触发页面的重新加载。当用户点击浏览器的前进或后退按钮时,会触发popstate事件,我们可以通过监听这个事件来更新页面的内容12。
window.addEventListener('popstate', function() {var currentState = window.history.state;// 根据currentState更新页面内容
});
原型和原型链的区别
原型(Prototype)和原型链(Prototype Chain)是JavaScript中非常重要的概念,它们之间有着密切的关系,但也有明显的区别123。
原型(Prototype):在JavaScript中,每个函数都有一个特殊的属性叫做prototype,这个属性是一个指向原型对象的指针。当一个函数被用作构造函数来创建实例时,那些实例会自动获得原型对象上的属性和方法12。
function Person() {}
Person.prototype.name = 'Tom';
var person1 = new Person();
console.log(person1.name); // 输出:Tom
原型链(Prototype Chain):当试图访问一个对象的属性时,如果在该对象上没有找到该属性,那么JavaScript会在该对象的原型对象上查找,如果还是没有找到,那么就会继续在原型对象的原型上查找,这样一层一层地向上查找,直到找到该属性或者查找到null(即已经查找到原型链的顶端)为止,这种查找过程就形成了一种链式结构,被称为原型链123。
function Person() {}
Person.prototype.name = 'Tom';
var person1 = new Person();
console.log(person1.toString()); // 输出:[object Object]
在这个例子中,toString
方法并不在person1
和Person.prototype
上,但是在Person.prototype
的原型(即Object.prototype
)上,所以通过原型链,person1
可以访问到toString
方法。
总的来说,原型是每个JavaScript函数都有的一个属性,它指向一个对象,这个对象包含了通过该函数作为构造函数创建的所有实例共享的属性和方法。而原型链则是一种查找机制,当试图访问一个对象的属性时,JavaScript会首先在该对象上查找,如果没有找到,就会沿着原型链向上查找,直到找到该属性或者查找到原型链的顶端为止。
react
React是一个用于构建用户界面的JavaScript库,起源于Facebook的内部项目,用来架设Instagram的网站,并于2013年5月开源1。React主要用于构建UI,很多人认为React是MVC中的V(视图)1。React拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它1。
以下是React的一些主要特点21:
-
组件化:React通过使用组件——描述部分用户界面的、自包含的逻辑代码段——来实现此目的。这些组件可以组合在一起以创建完整的UI2。
-
声明式编程:React采用声明式编程模式,这意味着你只需要描述你希望的最终状态,React会自动确保UI匹配这个状态2。
-
虚拟DOM:React引入了一种叫做虚拟DOM的机制,它是对真实DOM的抽象。当组件的状态改变时,React会先在虚拟DOM上进行改变,然后再将最终的差异应用到真实的DOM上,从而提高性能2。
-
单向数据流:React实现了单向数据流,这使得组件之间的交互更加清晰和易于理解2。
-
JSX:React引入了一种叫做JSX的语法,它允许你在JavaScript中编写类似HTML的代码2。
-
生态系统:React有一个庞大的社区和丰富的生态系统,包括各种工具、库和框架,如Redux、React Router等2。
React不仅可以用于Web开发,还可以与其他库一起使用以渲染到特定环境。例如,React Native可用于构建移动应用程序;React 360可用于构建虚拟现实应用程序2。
常用的hooks
在React中,有几个常用的Hooks123:
- useState:这是一个状态Hook,它允许你在函数组件中添加状态。例如,你可以使用useState来跟踪一个输入框的值,并在用户输入时更新状态123。
const [count, setCount] = useState(0);
- useEffect:这是一个副作用Hook,它允许你在函数组件中执行副作用。例如,你可以使用useEffect来在组件挂载时获取数据,或在组件更新时更新DOM123。
useEffect(() => {document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
- useContext:这是一个共享状态Hook,它允许你在函数组件中获取context的值。例如,你可以使用useContext来获取主题颜色,并在组件中使用该值123。
const theme = useContext(ThemeContext);
- useReducer:这是一个action钩子,它允许你在函数组件中使用reducer函数来管理复杂的状态逻辑123。
const [state, dispatch] = useReducer(reducer, initialArg, init);
useRef
useRef 是 React 中的一个 Hook,它可以让你在函数组件中创建一个可变的引用1234。
以下是 useRef 的一些主要用途1234:
- 访问DOM元素:你可以使用 useRef 创建一个引用,并将其作为 ref 属性添加到 JSX 元素上。然后,你可以通过 ref.current 访问到这个DOM元素1234。
const inputElement = useRef();
// ...
<input type="text" ref={inputElement} />
- 存储可变值:useRef 返回的对象有一个 current 属性,你可以把它当作一个“盒子”来存储任何可变值。这个值可以在渲染之间保持不变,而且改变它不会触发组件重新渲染1234。
const intervalRef = useRef(0);
// ...
intervalRef.current = intervalId;
- 跟踪之前的状态或属性:由于 useRef 的值在渲染之间保持不变,你可以使用它来存储组件之前的状态或属性1234。
const prevCountRef = useRef();
useEffect(() => {prevCountRef.current = count;
}, [count]); // Update ref.current value if count changes
手写题
节流防抖
深拷贝
全排列 X