1.沙箱隔离
前端沙箱隔离(Frontend sandbox isolation)是一种安全机制,用于将前端代码与主机环境隔离开来,以保护系统的安全性和稳定性。
在Web开发中,前端代码通常由JavaScript编写,而JavaScript是一种强大且灵活的语言,但它也可能存在一些安全风险。例如,恶意用户可能会通过前端代码执行跨站脚本攻击(XSS)或跨站请求伪造(CSRF)等攻击。
为了解决这些安全问题,前端沙箱隔离提供了一种隔离机制,使得前端代码不能直接访问和修改主机环境。它使用了一些技术来限制前端代码的权限,并提供一个受限的执行环境。
常见的前端沙箱隔离技术包括:
-
同源策略(Same Origin Policy):浏览器采用同源策略,限制来自不同源的页面之间的交互。这样,前端代码只能与同一源(域名、协议和端口号相同)的页面进行通信,防止跨域攻击。
-
沙箱环境(Sandboxed Environment):一些现代浏览器提供了沙箱环境,即在一个受限制的执行环境中运行前端代码。这意味着代码被隔离在一个受控制的环境中,无法访问敏感资源或执行危险操作。
-
内容安全策略(Content Security Policy):内容安全策略是一种通过HTTP头部或HTML标签来定义规则的机制,用于限制页面中可以加载和执行的资源。它可以阻止不信任的脚本或外部资源的加载,从而减少潜在的安全风险。
-
Web Worker:Web Worker 是一种在后台运行的 JavaScript 线程,它与主线程相互独立,可以在没有对 UI 的影响下执行复杂的计算任务。通过将耗时的计算任务放在 Web Worker 中执行,可以避免前端代码阻塞页面渲染,提高页面的响应性和安全性。
通过使用这些前端沙箱隔离技术,开发人员可以增加应用程序的安全性,使其更具可靠性和稳定性。同时,用户可以更加放心地使用网页应用而不担心受到恶意攻击的影响。
2.事件委托是将事件写在父级元素上
是的,事件委托(Event delegation)是一种常用的前端开发技术,它将事件处理程序绑定到父级元素上,而不是直接绑定到每个子元素上。
通过将事件处理程序绑定到父级元素上,可以利用事件冒泡(event bubbling)的原理来处理子元素的事件。当触发子元素上的事件时,事件将沿着DOM树向上传播到父级元素,而不仅仅是停留在子元素上。然后,父级元素可以根据事件的目标来确定要执行的操作。
使用事件委托的好处包括:
-
减少事件处理程序的数量:通过将事件处理程序绑定到父级元素上,而不是每个子元素上,可以减少需要维护的事件处理程序的数量。这对于动态添加或删除子元素的情况尤其有用。
-
提高性能和内存效率:事件委托利用了事件冒泡的机制,将事件处理集中在父级元素上。这样可以避免给每个子元素添加事件处理程序,节省内存并提高性能。
-
处理动态元素:如果页面上有动态生成的元素,直接为每个元素绑定事件可能无法生效。而通过事件委托,可以确保动态生成的元素也能够被正确处理。
使用事件委托时,需要在父级元素上监听相应事件,并在事件处理程序中判断事件的目标元素是否是我们所期望的子元素。如果是,则执行相应的操作;如果不是,则可以忽略该事件。
总结而言,事件委托是一种利用事件冒泡机制的技术,将事件处理程序绑定到父级元素上以处理子元素的事件。它能够减少事件处理程序的数量、提高性能和内存效率,并且适用于处理动态生成的元素。
3.对原型链的认识
原型链(Prototype chain)是 JavaScript 中一个重要的概念,它实现了对象之间的继承关系。在 JavaScript 中,每个对象都有一个原型(prototype),它可以是另一个对象或者 null。当访问一个对象的属性或方法时,如果该对象本身没有定义该属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到对应的属性或方法或者到达原型链的顶端(即原型为 null)。
以下是对原型链的一些认识:
-
原型对象(Prototype Object):每个 JavaScript 对象(除了 null)都有一个隐藏的内部属性[[Prototype]],指向其原型对象。可以通过
Object.getPrototypeOf(obj)
或者obj.__proto__
来访问对象的原型。 -
proto 属性:
__proto__
是对象实例上的属性,它指向对象的原型。通过obj.__proto__
可以获取对象的原型,也可以用来设置对象的原型(不推荐使用)。例如,obj.__proto__ = proto
可以将 obj 的原型设置为 proto。 -
构造函数(Constructor):在 JavaScript 中,构造函数是用于创建对象的函数,通过
new
关键字调用。构造函数自身也是一个对象,它有一个prototype
属性,指向构造函数的原型对象。构造函数通过this
关键字可以给新创建的对象添加属性和方法。 -
原型继承:当访问一个对象的属性或方法时,如果该对象本身没有定义,JavaScript 引擎会沿着原型链向上查找。这种机制实现了对象之间的继承关系,可以让子对象共享父对象的属性和方法。
-
原型链的终点:原型链的终点是 Object.prototype,它是所有 JavaScript 对象的最顶层原型对象。Object.prototype 的原型为 null。
-
使用原型链的好处:原型链实现了对象之间的继承关系,通过共享原型对象的属性和方法,可以节省内存,并且使代码更加简洁和易于维护。
需要注意的是,在 JavaScript 中,不推荐直接修改 __proto__
属性或者使用 obj.__proto__
来设置对象的原型。而应该使用 Object.create()
方法或者构造函数来创建具有指定原型的新对象。
总结而言,原型链是 JavaScript 中实现对象之间继承关系的机制,每个对象都有一个原型,通过原型链可以访问和共享原型对象的属性和方法。原型链的终点是 Object.prototype,它是所有对象的最顶层原型对象。原型链的概念在 JavaScript 中非常重要,对于理解和使用 JavaScript 的面向对象特性至关重要。
4.数据类型
JavaScript 中有一些常见的数据类型,包括:
-
基本数据类型(Primitive data types):
- 数字(Number):整数或浮点数,如
10
、3.14
。 - 字符串(String):一串文本字符,用引号(单引号或双引号)括起来,如
'Hello'
、"World"
。 - 布尔值(Boolean):表示真(true)或假(false)的值。
- null:表示一个空值或不存在的对象。
- undefined:表示未定义的值。
- Symbol(符号):表示唯一的标识符,用于创建对象的唯一属性键。
- 数字(Number):整数或浮点数,如
-
引用数据类型(Reference data types):
- 对象(Object):复合值,可以包含多个属性和方法。对象可以是内置对象(如数组、日期、正则表达式等)、宿主对象(由宿主环境提供的对象,如浏览器的 DOM 对象)或自定义对象。
- 数组(Array):一组有序的值,可以通过索引访问。数组可以包含不同类型的数据。
- 函数(Function):可执行的代码块,接收参数并返回一个值。函数也是对象的一种,可以包含属性和方法。
- 日期(Date):表示日期和时间的对象。
- 正则表达式(Regular Expression):描述一种字符串匹配规则的对象。
JavaScript 的数据类型是动态的,变量可以在不同的时间保存不同类型的值。通过 typeof
关键字可以获取一个值的类型。
例如:
let num = 10;
let str = "Hello";
let bool = true;
let n = null;
let u = undefined;
let sym = Symbol("foo");console.log(typeof num); // 输出: "number"
console.log(typeof str); // 输出: "string"
console.log(typeof bool); // 输出: "boolean"
console.log(typeof n); // 输出: "object" (typeof null 的结果是 "object" 是由于历史原因)
console.log(typeof u); // 输出: "undefined"
console.log(typeof sym); // 输出: "symbol"
需要注意的是,JavaScript 是一种动态类型语言,变量的类型是在运行时确定的,可以随时改变。因此,对于 JavaScript 开发来说,了解和正确使用不同的数据类型是至关重要的。
5.Symbol(符号)
Symbol 是 JavaScript 中的一种数据类型,引入于 ECMAScript 6(ES6)标准。它表示一个唯一且不可变的数据类型,用于创建对象的唯一属性键。
Symbol 值通过 Symbol 函数调用来创建,可以传入一个可选的描述字符串作为标识符的描述。每个通过 Symbol 函数创建的 Symbol 值都是唯一的,即使它们的描述相同。这意味着,可以将 Symbol 值用作对象的属性键,确保属性名的唯一性,避免命名冲突。
以下是一些使用 Symbol 的示例:
// 创建一个 Symbol
const symbol1 = Symbol();
console.log(symbol1); // 输出: Symbol()// 使用描述字符串创建一个 Symbol
const symbol2 = Symbol('foo');
console.log(symbol2); // 输出: Symbol(foo)// 作为对象的属性键
const obj = {};
const prop = Symbol('bar');
obj[prop] = 'value';
console.log(obj[prop]); // 输出: value// 遍历对象的 Symbol 属性键
for (let key in obj) {console.log(key); // 不会输出任何内容,Symbol 属性键不会被遍历
}
console.log(Object.getOwnPropertySymbols(obj)); // 输出: [Symbol(bar)]
值得注意的是,Symbol 属性键在 for…in 循环中不会被遍历,也不会出现在 Object.keys、Object.values、Object.entries 方法返回的结果中。如果需要获取对象的所有 Symbol 属性键,可以使用 Object.getOwnPropertySymbols
方法。
Symbol 还提供了一些内置的属性,如 Symbol.iterator、Symbol.toStringTag 等,用于指定对象的默认迭代器或自定义对象的类型标记。可以通过这些内置属性扩展 JavaScript 的语言特性。
Symbol 的主要作用是确保属性名的唯一性,尤其在对象的属性键比较复杂或存在命名冲突的情况下非常有用。它在 JavaScript 中广泛用于实现类似私有属性、符号常量等的概念。
6.ES5,ES6如何实现继承?
防抖(Debounce)和节流(Throttle)是在 JavaScript 中用于优化函数执行频率的两种常见技术。
- 防抖(Debounce):
- 当一个事件被触发后,延迟一定时间再执行相应的操作。如果在这段延迟时间内再次触发该事件,则重新计时。
- 主要用于处理频繁触发的事件,如窗口大小改变、输入框输入等,以减少函数的执行次数。
- 常见应用场景包括搜索框自动完成、无限滚动加载数据等。
下面是一个简单的防抖函数的实现示例:
function debounce(func, delay) {let timerId;return function (...args) {clearTimeout(timerId);timerId = setTimeout(() => {func.apply(this, args);}, delay);};
}// 使用防抖函数包装需要执行的函数
const debouncedFn = debounce(() => {console.log('Debounced function executed');
}, 200);// 在事件触发时调用防抖函数
debouncedFn(); // 在 200ms 后执行
debouncedFn(); // 重新计时,再次延迟 200ms 执行
- 节流(Throttle):
- 当一个事件被触发后,在固定时间间隔内只执行一次相应的操作。
- 主要用于限制函数的执行频率,尤其是处理持续触发的事件,如滚动事件、鼠标移动事件等。
- 常见应用场景包括按钮防重复点击、限制请求发送频率等。
下面是一个简单的节流函数的实现示例:
function throttle(func, delay) {let timerId;let lastExecutedTime = 0;return function (...args) {const currentTime = Date.now();if (currentTime - lastExecutedTime >= delay) {func.apply(this, args);lastExecutedTime = currentTime;} else {clearTimeout(timerId);timerId = setTimeout(() => {func.apply(this, args);lastExecutedTime = currentTime;}, delay - (currentTime - lastExecutedTime));}};
}// 使用节流函数包装需要执行的函数
const throttledFn = throttle(() => {console.log('Throttled function executed');
}, 200);// 在事件触发时调用节流函数
throttledFn(); // 立即执行
throttledFn(); // 在 200ms 后执行
防抖和节流可以根据不同的需求和场景选择合适的技术,通过控制函数的执行次数,提升页面性能和用户体验。
7.ES5,ES6如何实现继承?
在 ES5 中,可以使用原型链继承、构造函数继承和组合继承等方式来实现继承。而在 ES6 中,引入了 class 和 extends 关键字,使得实现继承更加简洁和易读。
以下是在 ES5 和 ES6 中实现继承的示例:
ES5 实现继承
-
原型链继承:
function Parent() {this.name = 'Parent'; }Parent.prototype.sayHello = function() {console.log('Hello, I am ' + this.name); };function Child() {this.age = 10; }Child.prototype = new Parent();var child = new Child(); child.sayHello(); // 输出: Hello, I am Child
-
构造函数继承:
function Parent(name) {this.name = name || 'Parent';this.sayHello = function() {console.log('Hello, I am ' + this.name);}; }function Child(name) {Parent.call(this, name);this.age = 10; }var child = new Child('Child'); child.sayHello(); // 输出: Hello, I am Child
-
组合继承(原型链继承 + 构造函数继承):
function Parent(name) {this.name = name || 'Parent'; }Parent.prototype.sayHello = function() {console.log('Hello, I am ' + this.name); };function Child(name) {Parent.call(this, name);this.age = 10; }Child.prototype = new Parent();var child = new Child('Child'); child.sayHello(); // 输出: Hello, I am Child
ES6 实现继承
使用 class 和 extends 关键字可以更简洁地实现继承:
class Parent {constructor() {this.name = 'Parent';}sayHello() {console.log('Hello, I am ' + this.name);}
}class Child extends Parent {constructor() {super();this.age = 10;}
}const child = new Child();
child.sayHello(); // 输出: Hello, I am Child
ES6 中的 class 和 extends 可以更直观地表示类之间的继承关系,而不需要手动设置原型链或调用构造函数。同时,通过 super 关键字可以在子类中调用父类的构造函数和方法,更方便地进行属性和方法的继承。
8.讲讲什么是作用域
作用域(Scope)是指在程序中定义变量、函数和对象时,这些标识符(Identifier)的可访问范围。简而言之,作用域决定了在代码中的哪些部分可以访问到变量、函数和对象。
在 JavaScript 中,有以下几种常见的作用域:
-
全局作用域(Global Scope):
- 全局作用域是在整个程序中都可访问的最外层作用域。
- 在全局作用域中声明的变量和函数可以被程序中的任何位置访问。
-
函数作用域(Function Scope):
- 函数作用域是在函数内部定义的变量和函数所具有的作用域。
- 在函数作用域中声明的变量和函数只能在函数内部访问,外部无法访问。
-
块级作用域(Block Scope):
- 块级作用域是在块({ })内部定义的变量所具有的作用域。
- 在 ES6 之前,JavaScript 中没有块级作用域,只有全局作用域和函数作用域。
- 在 ES6 中,引入了 let 和 const 关键字,用于声明块级作用域的变量。
作用域规定了变量的可访问范围和生命周期。当需要使用一个变量时,JavaScript 引擎会在当前作用域中查找变量,如果找到,则使用该变量;如果找不到,则会继续向上查找,直到找到全局作用域。这被称为作用域链(Scope Chain)。
作用域的理解对于编写和调试 JavaScript 代码非常重要,它有助于避免变量冲突、提高代码的可维护性,并且影响着变量的访问和生命周期。
9.块级作用域
块级作用域(Block Scope)是在块(由一对花括号 {} 包围的代码段)内部定义的变量所具有的作用域。在块级作用域中声明的变量只能在当前块内部访问,外部作用域无法访问。
在 ES6(ECMAScript 2015)之前,JavaScript 中只有全局作用域和函数作用域,没有块级作用域。这意味着使用 var 关键字声明的变量不受花括号的限制,仍然可以在外部作用域访问到。
而在 ES6 中,引入了 let 和 const 关键字来声明块级作用域的变量。
使用 let 声明的变量具有块级作用域:
function example() {if (true) {let x = 10; // 块级作用域内的变量console.log(x); // 输出: 10}console.log(x); // 报错: x is not defined
}example();
在上面的示例中,变量 x 使用 let 声明,在 if 块内部定义,只能在该块内部访问。在块外部访问变量 x,会导致 ReferenceError。
使用 const 声明的变量也具有块级作用域,并且具有常量的特性:
function example() {if (true) {const y = 20; // 块级作用域内的常量console.log(y); // 输出: 20y = 30; // 报错: Assignment to constant variable}
}example();
在上面的示例中,变量 y 使用 const 声明,也只能在 if 块内部访问。由于是常量,所以不能修改其值,否则会导致 TypeError。
块级作用域的引入使得 JavaScript 中变量的作用域更加清晰和可控,避免了变量污染和冲突问题,并且增加了对变量的细粒度控制。
10.单页面应用是什么?优缺点?如何弥补缺点
单页面应用(Single Page Application,SPA)是一种Web应用程序的架构模式,它通过使用动态加载内容和异步数据交互,使用户在一个单独的页面上与应用程序进行交互,而不需要每次跳转页面。
优点:
- 用户体验好:由于只需要加载一次页面,之后的页面切换都是通过动态加载内容实现,减少了页面刷新的延迟,用户体验更流畅。
- 前后端分离:前端负责渲染视图,后端负责提供接口和数据,各自独立开发,提高了开发效率和团队协作。
- 减少服务器负载:因为只有一个页面需要加载,减少了服务器传输的数据量和频率,降低了服务器的压力。
- 跨平台:单页面应用通常使用 JavaScript 编写,可以在多个平台上运行,例如浏览器、移动设备等。
缺点:
- 首次加载时间长:由于所有的资源都要一次性加载并缓存,首次加载时间会比较长,特别是当应用变得庞大时。
- SEO 不友好:搜索引擎爬虫难以获取到使用 JavaScript 动态生成的内容,对于SEO来说不友好。
- 内存占用较高:由于在切换页面时,之前加载的内容并不会被释放,容易导致内存占用过高。
- 浏览器兼容性:某些旧版本的浏览器对于一些 HTML5 特性支持不完善,可能导致兼容性问题。
如何弥补缺点:
- 优化首次加载时间:可以通过代码分割(code splitting)和资源压缩等手段来减少首次加载时间,以及使用缓存机制。
- 对于 SEO 不友好的问题,可以使用预渲染(prerendering)或服务器端渲染(Server-side Rendering,SSR)等技术来改善搜索引擎的爬取和索引。
- 合理释放资源:在进行页面切换时,需要合理地释放之前加载的资源,避免内存占用过高,可以通过垃圾回收机制实现。
- 对于浏览器兼容性问题,可以使用polyfill或使用适配和降级策略来解决旧版本浏览器的兼容性。
综合考虑,在开发和设计单页面应用时,需要权衡每个项目的需求和限制,并选择合适的技术和解决方案来弥补其缺点,以实现更好的用户体验和性能。
11.vuex中 mutation和action的区别和使用?
在Vuex中,Mutation和Action是两个核心概念,用于管理和修改应用程序的状态。
-
Mutation(变更状态):
- Mutation是用于修改Vuex中的状态(State)的方法。
- Mutation必须是同步函数,用于保证状态的可追踪性和可维护性。
- Mutation通过提交(commit)来调用,并且只能在Vuex的store中调用。
- Mutation需要定义在Vuex的store内部的mutations对象中。每个mutation都有一个关联的字符串类型的事件类型(type),以及一个处理函数(handler)。
// 在Vuex的store中定义一个mutation const store = new Vuex.Store({state: {count: 0},mutations: {increment(state) {state.count++}} })// 在组件中提交一个mutation store.commit('increment')
-
Action(异步操作):
- Action用于处理异步操作、封装业务逻辑以及触发Mutation来改变状态。
- Action可以包含任意异步操作,例如请求API、定时器等。
- Action通过派发(dispatch)来调用,并且可以在组件中使用
this.$store.dispatch
或映射辅助函数进行调用。 - Action需要定义在Vuex的store内部的actions对象中。每个action也有一个关联的字符串类型的事件类型(type),以及一个处理函数(handler)。
// 在Vuex的store中定义一个action const store = new Vuex.Store({state: {count: 0},mutations: {increment(state) {state.count++}},actions: {incrementAsync(context) {setTimeout(() => {context.commit('increment')}, 1000)}} })// 在组件中派发一个action store.dispatch('incrementAsync')
总结:
- Mutation用于同步地改变状态,而Action用于处理异步操作和封装复杂逻辑。
- Mutation通过提交(commit)来调用,Action通过派发(dispatch)来调用。
- Mutation必须是同步函数,而Action可以是异步操作。
- 在组件中使用
store.commit
来调用Mutation,在组件中使用store.dispatch
来调用Action。
在实际开发中,通常建议将异步操作放在Action中处理,确保状态管理的一致性,并保持Mutation的纯粹性。通过Action的封装和处理,可以使代码更加清晰和可维护。
12.Vue的虚拟Dom是什么?谈一谈对vue diff算法的认识?key的作用
Vue的虚拟DOM(Virtual DOM)是Vue框架用于提升性能和优化渲染的一种技术。
虚拟DOM是通过JavaScript对象模拟真实DOM的层次结构和属性。当数据发生变化时,Vue会通过比较新旧虚拟DOM的差异并将更改重新应用到真实DOM上,以避免直接操作真实DOM引起的性能损耗。虚拟DOM具有以下特点:
- 轻量快速:由于虚拟DOM是JavaScript对象,操作它的成本相对较低,比直接操作真实DOM快速且高效。
- 跨平台:虚拟DOM可以在不同平台上运行,例如浏览器、移动设备等。
- 高效更新:虚拟DOM会根据数据的变化生成新的虚拟DOM,并通过diff算法找出新旧虚拟DOM的差异,只更新差异部分,减少了不必要的重渲染,提高了性能。
Vue的diff算法是用于比较新旧虚拟DOM的差异,并仅更新变化的部分到真实DOM上。Vue的diff算法基于以下几个原则:
- 以深度优先遍历算法进行对比:从根节点开始,逐层对比子节点的差异。
- 比较相同层级节点:只比较同级别的节点,不进行跨级别的比较。
- 使用唯一的key标识节点:通过key属性,可以告诉Vue哪些节点是稳定的,哪些是需要重新创建的,优化了对比过程,提高效率。
- 相同组件类型的节点进行复用:如果新旧虚拟DOM的同一位置的节点类型相同,直接复用该节点,减少了节点的销毁和重建。
key的作用:
在虚拟DOM的diff算法中,key是用来标识虚拟DOM节点的唯一性和稳定性。使用key可以帮助Vue跟踪每个节点的身份,从而优化更新过程。具体作用如下:
- 提高性能:通过key,Vue可以对比新旧虚拟DOM中的节点,准确判断出哪些是新增的、删除的或移动的节点,避免不必要的更新操作,提高性能。
- 维护组件状态:当列表数据变化时,如果没有key,Vue会按照就地复用的原则,导致组件状态错乱。而通过设置唯一的key,可以确保复用正确的组件和维护组件的状态。
需要注意的是,key应该是稳定且唯一的,通常可以使用数据的唯一标识作为key。同时,避免在同一层级的兄弟节点中使用相同的key,这可能导致渲染错误。
总结:
虚拟DOM是Vue用于提升性能和优化渲染的技术,在数据变化时通过diff算法对比新旧虚拟DOM的差异,并只更新变化的部分到真实DOM上。key作为唯一标识节点的属性,在diff算法中起到标记节点身份和优化更新的作用。合理使用key可以提高渲染性能和组件状态的正确性。
13.异步操作放在created还是mouted?
在Vue中,异步操作可以放在created
或mounted
钩子函数中,具体取决于你的需求和操作类型。
created
钩子函数:在组件实例被创建之后立即调用。适合执行一些初始化操作,如获取数据、初始化变量等。如果异步操作不依赖于DOM元素,而是更多地关联到组件的数据或状态,那么可以将异步操作放在created
钩子函数中。
示例:
created() {// 执行异步操作fetchData().then(data => {// 处理数据}).catch(error => {// 处理错误});
}
mounted
钩子函数:在组件挂载到DOM之后调用。适合执行需要访问真实DOM元素的操作,比如初始化图表库、绑定第三方插件等。如果异步操作需要依赖已经渲染的DOM元素,并且操作涉及到DOM操作或尺寸计算等,那么可以将异步操作放在mounted
钩子函数中。
示例:
mounted() {// 调用第三方插件this.$nextTick(() => {initChart();});
}
需要注意的是,无论选择在created
还是mounted
中执行异步操作,都应该处理好异步操作的错误和取消。以及在组件销毁时清理相关资源,避免内存泄漏。
综上所述,如果异步操作不需要依赖DOM元素,可以放在created
钩子函数中。如果涉及到DOM操作或需要访问已渲染的DOM元素,可以放在mounted
钩子函数中。根据具体需求选择适当的钩子函数来执行异步操作。
13.vue子组件的生命周期?子元素在什么时候挂载?
Vue Router提供了多个钩子函数,可以在路由导航过程中执行相应的操作。以下是Vue Router中常用的钩子函数:
-
全局前置守卫:
beforeEach
: 在每个路由导航之前执行,可以用来进行全局的权限验证、登录状态检查等操作。
-
全局解析守卫:
beforeResolve
: 在每个路由导航解析之前执行,与beforeEach
类似。
-
全局后置钩子:
afterEach
: 在每个路由导航之后执行,通常用于记录页面浏览日志、页面滚动行为等操作。
-
路由独享的守卫:
beforeEnter
: 针对某个特定路由配置的前置守卫,在进入该路由前执行。
-
组件内的守卫:
beforeRouteEnter
: 在进入路由前,但还未进入该路由对应组件时执行,无法直接访问组件实例。beforeRouteUpdate
: 在当前路由改变,但仍然复用该组件时执行,可用于检测路由参数的变化。beforeRouteLeave
: 在离开当前路由时执行,可用于提示用户保存未保存的数据或执行其他操作。
这些钩子函数可以通过在路由配置中的路由对象上定义,也可以通过全局配置进行定义。例如:
const router = new VueRouter({routes: [{path: '/home',component: Home,beforeEnter(to, from, next) {// 路由独享的守卫// 检查用户权限if (checkPermission()) {next();} else {next('/login');}},},// ...],
});router.beforeEach((to, from, next) => {// 全局前置守卫// 检查登录状态、权限等if (isAuthenticated()) {next();} else {next('/login');}
});export default router;
通过使用这些钩子函数,你可以在路由导航过程中执行一些操作,如权限验证、重定向、记录日志等,以实现更灵活和可定制的路由控制逻辑。
14.vue子组件的生命周期?子元素在什么时候挂载?
在Vue中,子组件也具有自己的生命周期钩子函数。以下是子组件的生命周期钩子函数及其执行顺序:
-
beforeCreate:在实例被创建之前调用,此时组件的数据观测和事件配置尚未初始化。
-
created:在实例创建完成后调用,此时已经完成了数据观测、属性和方法的运算,但尚未挂载到真实的DOM。
-
beforeMount:在挂载开始之前被调用,此时模板编译已完成,但尚未将编译结果替换到真实的DOM中。
-
mounted:在挂载完成后被调用,此时组件已经被渲染到真实的DOM中,可以操作DOM元素。
-
beforeUpdate:在数据更新之前被调用,发生在虚拟DOM重新渲染和打补丁之前。
-
updated:在数据更新之后被调用,组件的更新已经同步到DOM中。
-
activated:在使用
keep-alive
组件时,子组件被激活时调用。 -
deactivated:在使用
keep-alive
组件时,子组件被停用时调用。 -
beforeDestroy:在实例销毁之前调用,此时实例仍然可用。
-
destroyed:在实例销毁之后调用,此时组件已经被销毁,清理工作已完成。
子组件的挂载发生在父组件使用该子组件时。当父组件渲染时,会触发子组件的创建和挂载过程。
具体来说,当父组件渲染时,会实例化子组件并调用子组件的生命周期钩子函数。子组件的beforeCreate
和created
钩子函数会在父组件渲染过程中被调用。然后,子组件被插入到父组件的DOM中,进行挂载过程,依次调用beforeMount
、mounted
钩子函数。
需要注意的是,子组件的生命周期是相对独立的,与父组件的生命周期并不完全一致。父组件的更新不会直接触发子组件的更新,子组件的更新由其自身的数据驱动。
综上所述,子组件的生命周期包括了创建、挂载、更新和销毁等阶段,子组件的挂载发生在父组件使用该子组件时。
15.vue的import和node的require区别?
import
和 require
都是模块加载的方式,但是二者有以下几个主要的区别:
-
语法
import
是 ES6 引入的模块化语法,使用import
来加载模块。require
是 Node.js 中引入的模块化方式,使用require
来加载模块。
-
功能
import
能够真正意义上实现静态导入,能够在编译阶段就确定模块之间的依赖关系,支持的功能更加强大,例如动态导入。require
是动态加载模块的,在执行时才会去加载模块,只能加载 CommonJS 规范的模块。
-
变量提升
import
加载的模块只有被调用时才会执行,变量不会提升,因此需要在文件头部引入。require
会将整个文件执行,模块中的变量会存在变量提升。
-
输出
import
导入的是模块的指定成员,可以通过解构的方式获取其中的成员。require
导入的是模块的完整对象,需要通过对象属性或方法来获取成员。
总体而言,import
更为灵活强大,逐渐取代了 require
的地位。在浏览器端,需要使用工具将 ES6 转换为 ES5 才能运行;在 Node.js 中,可以使用 import
代替 require
,但需要在文件扩展名为 .mjs
时才支持。
16.路由守卫的生命周期是怎样的
在 Vue Router 中,路由守卫是一种机制,用于在导航过程中对路由进行控制和处理。路由守卫可以帮助我们在跳转到不同的路由之前、期间或之后执行特定的逻辑。
Vue Router 提供了三种类型的路由守卫:全局守卫、路由独享的守卫和组件内的守卫。这些守卫函数都有特定的生命周期钩子函数,用于定义在特定阶段触发的逻辑。
以下是路由守卫的生命周期及对应的钩子函数:
-
全局前置守卫
beforeEach(to, from, next)
:在跳转路由之前被调用,可以用来进行权限验证、登录状态检查等操作。
-
全局解析守卫
beforeResolve(to, from, next)
:在路由解析过程中被调用,此时异步组件已经被解析完毕。
-
全局后置钩子
afterEach(to, from)
:在导航完成之后被调用,常用于页面的统计和记录。
-
路由独享的守卫
beforeEnter(to, from, next)
:在进入某个路由之前被调用,只对当前路由有效。
-
组件内的守卫
beforeRouteEnter(to, from, next)
:在进入路由组件之前被调用,此时组件实例还未被创建,无法访问组件实例的 this。beforeRouteUpdate(to, from, next)
:在当前路由复用的情况下,路由参数发生变化时被调用。beforeRouteLeave(to, from, next)
:在离开当前路由组件之前被调用,常用于弹出提示框确认是否离开页面。
这些钩子函数都可以接收三个参数:
to
:即将进入的目标路由对象from
:当前导航正要离开的路由对象next
:用于跳转到下一个钩子函数的回调函数
你可以在这些钩子函数中执行一些逻辑,如根据权限判断是否允许进入某个路由、记录日志等。通过调用 next()
方法,可以继续执行后续的钩子函数或导航到指定路由。
17.vuex如何解决数据丢失?
Vuex 是 Vue.js 的状态管理库,用于解决组件之间共享数据的问题。虽然 Vuex 本身并不能直接解决数据丢失的问题,但可以通过一些方法来确保数据在刷新或路由切换后不丢失。
-
持久化存储:使用插件如 vuex-persistedstate 将 Vuex 的数据持久化到本地存储(如 localStorage)中,在刷新或重新加载页面后可以从本地存储中恢复数据。这样确保了数据的持久性。
-
合理设计数据流:在设计应用程序的数据流时,遵循单向数据流的原则,确保数据的变化能够被正确保存和同步。通过定义好的 mutation 和 action 来修改和更新数据,可以更好地跟踪和管理数据的变化。
-
在合适的时机加载数据:在组件加载时,可以通过钩子函数(如 created、mounted)来触发对应的 action,从服务端获取数据并保存到 Vuex。这样可以保证每次组件加载时都能及时加载所需的数据。
-
路由导航守卫:可以利用路由的导航守卫钩子函数(如 beforeEach)来在路由切换前检查是否需要保存当前数据。在离开当前路由前,可以将需要保留的数据通过 mutation 存储到 Vuex 中,以便后续使用。
-
结合后端接口:在进行数据操作时,可以结合后端接口设计合适的数据保存和恢复机制。例如,在提交表单数据时,可以通过请求将数据保存到后端数据库,并在需要时从后端重新获取数据。
总的来说,Vuex 本身并不能直接解决数据丢失问题,但可以通过持久化存储、合理设计数据流、加载数据时机、路由导航守卫等方法来确保数据在刷新或路由切换后不丢失。
18.v先并行请求2个接口后,再请求第3个接口,如何处理?
在处理并行请求后再发起第三个接口请求时,你可以使用 Promise.all() 方法将这两个并行请求包装为 Promise,并在两个请求都完成后再发起第三个请求。下面是一个示例代码:
// 引入 axios 或其他 HTTP 请求库
import axios from 'axios';// 并行请求的接口1
const request1 = axios.get('接口1的URL');
// 并行请求的接口2
const request2 = axios.get('接口2的URL');// 使用 Promise.all() 包装并行请求
Promise.all([request1, request2]).then((responses) => {// 并行请求都成功完成后执行的逻辑// responses 是一个数组,包含了两个请求的响应对象,顺序与请求的顺序一致// 可以通过 responses[0] 和 responses[1] 获取对应的响应数据const response1 = responses[0];const response2 = responses[1];// 处理第三个接口的请求return axios.get('第三个接口的URL');}).then((response3) => {// 第三个接口请求成功后的逻辑// response3 是第三个接口的响应对象,包含了响应数据console.log(response3.data);}).catch((error) => {// 错误处理console.error(error);});
在上述代码中,我们首先创建了两个并行请求 request1
和 request2
,然后使用 Promise.all()
将它们包装为一个 Promise。当这两个请求都成功完成后,then
方法中的回调函数会被执行,我们可以在其中处理这两个请求的响应数据,然后再发起第三个接口的请求。最后,通过 then
方法处理第三个接口请求成功后的逻辑,或通过 catch
方法捕获任何错误。
19.说几个ES6新增的数组的方法
ES6 引入了一些有用的数组方法,下面列举了其中的几个:
Array.from()
:将类似数组或可迭代对象转换为真正的数组。
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const newArray = Array.from(arrayLike);
console.log(newArray); // ['a', 'b', 'c']
Array.of()
:根据传入的参数创建一个新数组,无论参数的类型和数量。
const newArray = Array.of(1, 2, 3, 'a', 'b');
console.log(newArray); // [1, 2, 3, 'a', 'b']
Array.prototype.find()
:返回数组中满足测试函数条件的第一个元素值。
const numbers = [1, 2, 3, 4, 5];
const found = numbers.find((element) => element > 3);
console.log(found); // 4
Array.prototype.findIndex()
:返回数组中满足测试函数条件的第一个元素的索引。
const numbers = [1, 2, 3, 4, 5];
const foundIndex = numbers.findIndex((element) => element > 3);
console.log(foundIndex); // 3
Array.prototype.includes()
:判断数组是否包含指定元素,返回布尔值。
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.includes(3)); // true
console.log(numbers.includes(6)); // false
这些是 ES6 中新增的一些有用的数组方法,它们提供了更加便捷和简洁的方式来操作和处理数组。这些方法可以提高开发效率并使代码更可读。
20.vue2生命周期
在 Vue 2 中,组件实例有以下生命周期钩子函数:
-
beforeCreate
:在实例初始化之后,数据观测 (data observer) 和事件/watcher 事件配置之前被调用。 -
created
:在实例创建完成后被立即调用。此时,实例已经完成数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还未开始,$el 属性尚不可用。 -
beforeMount
:在挂载开始之前被调用。相关的 render 函数首次被调用。 -
mounted
:实例被挂载后调用。此时,el 已经关联到实例的 vm. e l ,并进行 D O M 渲染。如果组件使用了 ‘ t e m p l a t e ‘ 选项,则只有在 ‘ m o u n t e d ‘ 钩子被调用后,整个模板才会在 ‘ el,并进行 DOM 渲染。如果组件使用了`template`选项,则只有在`mounted`钩子被调用后,整个模板才会在 ` el,并进行DOM渲染。如果组件使用了‘template‘选项,则只有在‘mounted‘钩子被调用后,整个模板才会在‘el` 内完全渲染。 -
beforeUpdate
:数据更新时调用,但是在 DOM 更新之前。 -
updated
:在数据更改导致虚拟 DOM 重新渲染和打补丁之后调用。 -
beforeDestroy
:实例销毁之前调用。在这一步,实例仍然完全可用。 -
destroyed
:实例销毁之后调用。当该钩子被调用时,Vue 实例的所有指令、过滤器、事件监听器等都已被解绑,子实例也被销毁。
此外,还有两个和异步任务相关的钩子函数:
beforeMount
和mounted
钩子函数中可以使用vm.$nextTick()
方法,在下次 DOM 更新循环结束之后执行一个回调。created
钩子函数中可以使用vm.$watch()
监听一个属性的变化,当监测到变化时执行回调。
这些生命周期钩子函数为我们提供了在组件不同阶段执行代码的机会,可以用于初始化数据、与外部 API 交互、在 DOM 更新后执行操作、清理内存等。
21.vue2生命周期 有几个
Vue 2 中有8个生命周期钩子函数,它们按照组件创建、挂载、更新和销毁的不同阶段进行调用。这些钩子函数分别是:
- beforeCreate:实例刚在内存中创建,此阶段无法访问到组件中的
data
和methods
。 - created:实例已经创建完成,可以访问到
data
和methods
,但此时尚未挂载到 DOM 上。 - beforeMount:在挂载开始之前调用,此时模板编译已完成,即将开始渲染组件。
- mounted:实例已经挂载到 DOM 上,此时可以进行 DOM 操作,如获取元素、绑定事件等。
- beforeUpdate:数据更新时调用,但是在 DOM 更新之前的阶段。
- updated:在数据变化导致的虚拟 DOM 重新渲染和打补丁之后调用。
- beforeDestroy:在组件销毁之前调用,可以进行善后工作,如清除计时器、解绑全局事件等。
- destroyed:组件销毁后调用,此时组件实例及其相关对象都会被销毁。
这些生命周期钩子函数为我们提供了对组件不同阶段进行操作的机会,可以在适当的时候执行特定的代码,以满足业务需求或进行资源清理。
22.vuex如何解决数据丢失?
Vuex 是 Vue.js 的状态管理库,用于集中管理组件之间共享的状态。对于数据丢失的问题,Vuex 本身并没有提供特定的解决方案,但可以通过一些策略来减少或避免数据丢失的风险。
以下是一些常见的方法和建议:
-
合理设计数据结构:在使用 Vuex 存储数据时,确保数据结构合理。遵循单一数据源的原则,将不同的模块、组件状态分开管理,并定义清晰的数据结构以避免混乱和数据丢失。使用对象、数组等数据类型时,确保正确的引用和拷贝,避免直接修改状态数据。
-
使用持久化插件:Vuex 的持久化插件(例如 vuex-persistedstate)可以将 Vuex 中的状态持久化到本地存储,如 localStorage 或 sessionStorage。通过将状态存储到本地,即使页面刷新或关闭再打开,数据也可以得到恢复,并避免数据丢失。
-
避免异步操作时的数据冲突:当多个组件同时进行异步操作,对于某个共享状态的修改,可能会导致数据冲突或丢失。在这种情况下,可以利用 Vuex 的 action 和 mutation 进行同步处理,避免同时对同一状态进行修改。
-
合理使用组件生命周期钩子:在组件的生命周期钩子函数中,可以通过订阅 store 中的状态变化,在特定时机保存数据到后端或本地存储,以确保数据的持久性。
-
错误处理和容错机制:在异步操作中要注意错误处理,捕获异常并采取相应的措施,例如回退到上一个有效的状态或提供用户友好的错误提示。
总之,Vuex 本身不能完全解决数据丢失的问题,但通过合理设计数据结构、使用持久化插件、避免冲突、利用生命周期钩子等方法,可以最大限度地减少数据丢失的风险,并确保应用的状态管理更加可靠。
23.宏任务和微任务有哪些?执行顺序?
宏任务和微任务是指在 JavaScript 引擎中执行的两类任务,它们的执行顺序有一定的规律。
宏任务是一些较为耗时的任务,例如输入、网络通信、计时器等。在每个宏任务执行完成后,JavaScript 引擎会检查是否有微任务需要执行。如果有,会依次执行所有微任务直到清空微任务队列,然后再进行下一个宏任务。也就是说,在一个宏任务中产生的所有微任务都会在这个宏任务结束之前执行完毕。
而微任务则是一些轻量级的任务,例如 Promise 的回调函数、MutationObserver 等。它们的执行优先级高于宏任务。因此,当发生宏任务和微任务同时存在的情况时,JavaScript 引擎会先执行所有微任务,再执行下一个宏任务,即先处理微任务再处理宏任务。
例如,在一段异步代码中,当异步操作成功返回时,Promise 的 then 回调将被放入微任务队列中,等待执行。而在当前宏任务结束后,JavaScript 就会去检查微任务队列,如果有待执行的微任务,就逐一执行它们。如果微任务队列为空,那么 JavaScript 就会取出下一个宏任务,继续执行。
总的来说,宏任务和微任务的执行顺序可以概括为:
- 执行当前宏任务;
- 检查微任务队列,依次执行所有微任务;
- 取出下一个宏任务,重复执行上述步骤。
需要注意的是,在每个宏任务中,只有当所有同步任务执行完毕,JavaScript 引擎才会考虑执行微任务,因此如果当前宏任务中存在循环或递归等耗时任务,可能会导致微任务无法及时执行,从而延迟了数据的变化和其他异步操作的执行。
24解决首次加载白屏
白屏问题通常是由于页面加载较慢或资源下载阻塞导致的。以下是一些常见的解决方案,可以帮助解决首次加载时的白屏问题:
-
优化代码和资源:对代码和资源进行优化,减少文件大小、请求次数和网络传输时间,以提高页面加载速度。可以压缩和合并 JavaScript 和 CSS 文件,使用图片压缩技术,延迟加载非关键资源等。
-
使用浏览器缓存:通过设置适当的缓存策略,让浏览器缓存静态资源,从而减少重复的网络请求。可以通过设置 HTTP 响应头中的
Cache-Control
和Expires
字段来控制缓存策略。 -
异步加载脚本:将页面中的一些 JavaScript 脚本标记为异步加载,这样可以让浏览器在加载其他资源时并行下载脚本文件。可以使用
<script async>
属性或动态创建<script>
标签并设置async
属性。 -
预加载关键资源:通过使用
<link rel="preload">
或<link rel="prefetch">
标签预加载关键资源,提前获取需要的资源,加快页面加载速度。 -
使用骨架屏或加载动画:在页面加载过程中展示一个简单的骨架屏或加载动画,给用户一种页面正在加载的反馈,缓解白屏带来的不好体验。
-
懒加载内容:对于长页面或图片较多的页面,将非首屏区域的内容或图片延迟加载,当用户滚动到对应区域时再加载,减少首次加载所需的资源和时间。
-
服务端渲染(SSR):使用服务端渲染技术生成首屏内容,减少客户端渲染的时间,提高页面加载速度。
通过上述方法可以有效地减少首次加载时的白屏问题,并提升用户的体验。根据具体情况选择适合的解决方案,并结合性能测试和优化工具来评估和改进页面加载性能。
25.promise是什么,有什么作用
Promise 是 JavaScript 中的一种异步编程解决方案。它是 ECMAScript 6 标准引入的一个对象,用于处理异步操作。
Promise 的主要作用是解决了传统回调函数(callback)带来的代码可读性差、回调地狱等问题,使得异步操作更加简洁、优雅和可管理。
Promise 对象有三个状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。当 Promise 对象状态变为 fulfilled 或 rejected 时,称为 Promise 的 settled 状态,此时可以调用对应的回调函数进行后续处理。
Promise 提供以下几个重要的方法:
-
Promise.resolve(value)
:返回一个已解析的 Promise 对象,状态为 fulfilled。 -
Promise.reject(reason)
:返回一个已拒绝的 Promise 对象,状态为 rejected。 -
Promise.prototype.then(onFulfilled, onRejected)
:添加处理 Promise 对象的回调函数。onFulfilled
在 Promise 对象状态变为 fulfilled 时执行,接收 Promise 返回的值作为参数;onRejected
在 Promise 对象状态变为 rejected 时执行,接收 Promise 抛出的错误作为参数。 -
Promise.prototype.catch(onRejected)
:添加处理 Promise 对象抛出错误的回调函数。相当于then(null, onRejected)
。 -
Promise.all(iterable)
:接收一个可迭代对象,返回一个新的 Promise 对象,只有当所有的 Promise 对象状态都变为 fulfilled 时,新的 Promise 对象才会变为 fulfilled,返回值为一个包含所有 Promise 对象结果的数组。 -
Promise.race(iterable)
:接收一个可迭代对象,返回一个新的 Promise 对象,只要有一个 Promise 对象状态变为 fulfilled 或 rejected,新的 Promise 对象就立即变为对应的状态,并返回该 Promise 对象的结果或错误。
通过使用 Promise,我们可以更好地处理异步操作,避免多层嵌套的回调函数,并且能够以链式调用的方式组织和处理异步任务。这使得代码结构更清晰、易读,并且便于错误处理和异常捕获。
26.什么是递归,递归有哪些优缺点
递归是指函数调用自身的一种编程技巧。在使用递归函数时,程序会反复执行同一个函数,每次执行都将问题分解为更小的子问题,直到问题无法再分解时得到结果。递归通常有一个基本结束条件(递归基)和一个递归条件(递推公式)。
递归的优点包括:
-
简洁:递归可以使代码结构更加简洁明了,提高代码可读性和可维护性;
-
实现问题分解:递归可以将复杂的问题分解为一组相似的子问题,使得问题的解决变得简单;
-
更好的表达能力:某些算法或数学运算使用递归表达能够更加简洁和自然。
递归的缺点包括:
-
低效:由于递归需要不断地进行函数调用,因此占用了更多的内存空间和计算资源,可能会导致程序执行效率低下;
-
可能引起栈溢出:递归需要使用函数栈来保存每个递归调用的状态,如果深度过大,可能会导致栈溢出异常;
-
可能陷入死循环:如果递归条件没有被正确设置,程序可能会陷入死循环,导致无法得到正确的结果。
因此,在使用递归时,需要慎重考虑,确保递归条件和递归基的设置正确,并且递归深度不会过大,避免引起程序性能或运行异常的问题。
27.mvvm和mvc
MVVM(Model-View-ViewModel)和 MVC(Model-View-Controller)都是常见的软件架构模式,用于组织应用程序的结构。
MVC 是一种设计模式,通常包含以下三个组件:
-
Model(模型):负责处理数据的操作和业务逻辑。
-
View(视图):负责展示数据给用户,并接收用户的输入。
-
Controller(控制器):接收用户的输入,根据用户的操作更新模型,并将更新后的数据传递给视图进行展示。
MVC 的主要思想是将应用程序分成三个独立的部分,每个部分有明确的职责,从而提高代码的可复用性和可维护性。
MVVM 是基于 MVC 的演化模式,它引入了一个新的组件 ViewModel(视图模型),主要包含以下三个部分:
-
Model(模型):与 MVC 中的 Model 相同,负责处理数据的操作和业务逻辑。
-
View(视图):与 MVC 中的 View 相同,负责展示数据给用户,并接收用户的输入。
-
ViewModel(视图模型):连接 View 和 Model,负责处理视图和模型之间的交互。它将视图显示的数据和用户的输入转化成模型理解的格式,并将模型的变化反馈给视图。
MVVM 的核心概念是数据绑定,它能够自动将视图和模型中的数据进行双向绑定,使得数据的变化能够自动更新到视图上,而用户的操作也能自动更新到模型中。这样可以减少在控制器/视图模块中的手动编写代码,提高开发效率。
总结来说,MVC 是一种较早期的软件架构模式,包含 Model、View 和 Controller 三个组件,负责分离数据、展示和用户交互的职责。而 MVVM 是基于 MVC 的演化模式,引入了 ViewModel 组件,通过数据绑定实现了视图和模型的自动同步。MVVM 更加强调数据驱动和解耦,适用于现代前端开发和响应式UI的场景。
28.常用的块与行属性内标签有哪些?有什么特征
常见的块级元素有:
<div>
:用于将文档分割成独立的区域,常用于包含其他 HTML 元素的容器。<p>
:表示段落,通常用于包含文本内容。<h1>
-<h6>
:表示标题,数字越小表示级别越高。<ul>
:表示无序列表,通常包含多个<li>
元素。<ol>
:表示有序列表,通常包含多个<li>
元素。<li>
:表示列表项,在有序列表或无序列表中使用。<table>
:表示表格,通常包含<tr>
(表示行)和<td>
(表示单元格)等子元素。
常见的行内元素有:
<span>
:用于给文本或其他行内元素添加样式或装饰。<a>
:表示超链接,用于创建链接到其他网页或位置的锚点。<strong>
:表示强调文本,通常以粗体显示。<em>
:表示强调文本,通常以斜体显示。<img>
:表示图像,用于插入图片。<input>
:表示输入控件,用于接收用户的输入。<button>
:表示按钮,用于触发特定的操作。
块级元素的特征:
- 占据一行或多行的空间,默认情况下会自动换行。
- 可以设置宽度、高度、边距和填充等属性来调整其布局。
- 可以包含其他块级元素和行内元素。
行内元素的特征:
- 在一行内显示,不会独占一行。
- 宽度和高度由内容决定,不能直接设置宽度和高度。
- 通常不会破坏文本的流动,可以与其他行内元素在同一行显示。
- 多个行内元素会根据空格和换行符之间的空隙进行排列。
需要注意的是,并非所有标签都严格按照块级元素或行内元素分类,部分元素即可作为块级元素,又可作为行内元素,这取决于其在特定上下文中的使用方式。
29.Css优先级
在 CSS 中,每个样式规则都有一个优先级,用于确定应用于元素的样式的优先级顺序。CSS 优先级是根据选择器的特定组合来计算的,通常由以下几个因素决定优先级的高低:
-
内联样式(Inline Styles):使用
style
属性直接在 HTML 元素上指定的样式具有最高优先级,将覆盖其他任何样式。 -
ID 选择器(ID Selectors):通过
#
符号指定的 ID 选择器具有比类选择器和标签选择器更高的优先级。 -
类选择器、属性选择器和伪类选择器(Class Selectors, Attribute Selectors, Pseudo-Class Selectors):通过
.
符号指定的类选择器、属性选择器(比如[attribute]
或[attribute=value]
)以及伪类选择器(比如:hover
、:first-child
)具有比标签选择器更高的优先级。 -
标签选择器(Tag Selectors)和伪元素选择器(Pseudo-Element Selectors):通过标签名称指定的选择器具有较低的优先级,如果多个标签选择器具有相同的优先级,则后定义的样式将被应用。
-
继承样式(Inherited Styles):继承的样式具有最低的优先级,即从父元素继承的样式。继承样式只在没有其他样式规则应用时才会起作用。
对于优先级的计算,可以使用以下规则:
- 每个选择器的 ID 数量乘以权重100。
- 每个选择器的类选择器、属性选择器和伪类选择器数量乘以权重10。
- 每个选择器的标签选择器数量乘以权重1。
- 计算所有选择器的权重之和,权重越高的样式规则优先级越高。
需要注意的是,内联样式具有最高的优先级,但是应该尽可能避免过多使用内联样式,而是使用外部样式表和选择器来管理样式,提高代码的可维护性和可扩展性。
29.Split()和 join()的区别?
split()
和 join()
是字符串方法,用于处理字符串的拆分和合并操作。
-
split()
方法:split()
方法可以将一个字符串拆分为一个字符串数组,根据指定的分隔符将原始字符串分隔成多个子字符串。split()
方法接收一个参数,即分隔符,用于指定在哪里进行拆分。如果没有提供分隔符参数,则默认使用空格作为分隔符。- 例如:
"Hello, World!".split(",")
将返回["Hello", " World!"]
,使用逗号作为分隔符进行拆分。
-
join()
方法:join()
方法可以将一个字符串数组(或可迭代对象)的所有元素合并为一个字符串,并使用指定的分隔符将它们连接起来。join()
方法接收一个参数,即分隔符,用于指定在连接字符串时使用的分隔符。如果没有提供分隔符参数,则默认不使用分隔符。- 例如:
["Hello", "World!"].join(", ")
将返回"Hello, World!"
,使用逗号和空格作为分隔符进行连接。
综上所述,split()
方法用于将一个字符串拆分为一个字符串数组,而 join()
方法用于将一个字符串数组合并为一个字符串。它们是互补的操作,可以在处理字符串时非常有用,例如在解析字符串或拼接字符串时使用。
30.Vuex 的 5 个核心属性是什么?
Vuex 是一个用于管理 Vue.js 应用程序状态的状态管理模式和库。它包含了以下 5 个核心属性:
-
state:
state
是应用程序的状态存储,类似于组件中的 data。它是一个响应式对象,用于保存应用程序的公共状态。 -
getters:
getters
是用于从state
中派生出其他状态的计算属性。可以将其视为对state
的一种包装,可以根据需要对数据进行转换、筛选或组合,并在组件中获取这些派生状态。 -
mutations:
mutations
是用于修改state
的唯一途径。每个mutation
都是一个函数,接收state
作为第一个参数,并可以接收额外的载荷(payload)参数。通过提交(commit
)一个mutation
来修改state
,保证所有的状态变更都被记录下来,便于跟踪。 -
actions:
actions
类似于mutations
,不同的是它可以包含异步操作。每个action
都是一个函数,接收一个上下文对象(context),其中包含了commit
、dispatch
和当前的state
。通过分发(dispatch
)一个action
来触发业务逻辑和异步操作,然后再提交(commit
)一个mutation
来修改state
。 -
modules:
modules
允许将store
拆分为模块,每个模块都有自己的state
、getters
、mutations
、actions
。模块化可以使状态管理更加灵活和可维护,并支持在跨模块之间共享状态。
这些核心属性组成了 Vuex 的基础架构,通过统一的状态管理机制,使得不同组件之间能够共享和同步数据,简化了复杂应用程序的状态管理。
31.什么原因会造成内存泄露
内存泄漏是指在程序中一些已分配的内存空间没有被正确释放或回收,导致这些内存无法再被程序使用,而无法被操作系统回收利用的情况。以下是一些常见的导致内存泄漏的原因:
-
对象引用问题:当一个对象不再需要时,如果还存在对该对象的引用,那么垃圾回收机制无法将其回收。例如,循环引用、全局变量、未释放的事件监听器等都可能导致对象无法被垃圾回收。
-
未释放的资源:一些资源需要手动释放,如打开的文件、数据库连接、网络连接等。如果在不再需要这些资源时没有正确释放或关闭它们,会造成内存泄漏。
-
慎重使用缓存:缓存可以提高性能,但如果缓存对象不被正确管理,会导致内存泄漏。例如,缓存中的对象长时间不被使用却一直保存在内存中,或者缓存的键或值没有被正确清理,都可能导致内存泄漏。
-
长生命周期的对象持有短生命周期对象的引用:如果一个长生命周期的对象持有一个短生命周期对象的引用,那么即使短生命周期对象不再使用,由于存在引用关系,它也无法被垃圾回收。
-
资源释放顺序错误:当多个资源之间存在依赖关系时,如果释放资源的顺序不正确,可能会导致一些资源无法正常释放,进而引发内存泄漏。
-
不合理的缓存或数据结构使用:某些缓存或数据结构的设计可能会导致内存泄漏,比如使用 HashMap 时没有正确处理键的生命周期,或者使用大量无用的缓存项等。
-
第三方库或框架的 bug:某些第三方库或框架本身存在内存泄漏的问题,使用时需要注意其文档或版本说明,及时升级修复问题的版本。
要避免内存泄漏,开发人员应注意及时释放资源、管理对象引用、合理使用缓存和数据结构,并进行正确的资源释放顺序。定期进行内存泄漏检测和性能优化也是很重要的。
32.第一次加载页面会触发哪几个钩子函数?
在 Vue.js 中,当第一次加载页面时,以下的钩子函数会被触发:
-
beforeCreate:在实例初始化之后,数据观测 (data observer) 和事件配置之前触发。此时,实例还未完成初始化,不能访问到
this
上的数据和方法。 -
created:在实例创建完成后触发。此时,可以访问到
this
上的数据和方法,并可以进行数据的初始化操作。通常在这个钩子函数中进行异步请求数据的操作。 -
beforeMount:在挂载开始之前被调用。此时,模板编译已完成,但尚未将生成的 DOM 替换到页面中。
-
mounted:在挂载完成后被调用。此时,组件已经被渲染到页面中,可以访问到真实的 DOM 元素。通常在这个钩子函数中进行其他库的初始化、绑定事件等操作。
这些钩子函数的触发顺序是:beforeCreate -> created -> beforeMount -> mounted。它们提供了一系列的生命周期阶段,可以在每个阶段做一些特定的操作,如数据初始化、异步请求、DOM 操作等。