js 面试题总结

js 面试题总结

文章目录

近百道面试题

1、实现 子元素 在父元素中垂直居中的方式

有多种方法可以实现子元素在父元素中垂直居中的方式,以下是其中几种常用的方法:

  1. 使用 Flexbox 布局:

父元素设置为 flex 容器,并使用 align-items 属性将子元素垂直居中。

.parent {
  display: flex;
  align-items: center; /* 子元素垂直居中 */
}
  1. 使用表格布局:

将父元素设置为 table,子元素设置为 table-cell,并使用 vertical-align 属性将子元素垂直居中。

.parent {
  display: table;
}

.child {
  display: table-cell;
  vertical-align: middle; /* 子元素垂直居中 */
}
  1. 使用绝对定位和 transform:

使用绝对定位将子元素相对于父元素居中,结合 transform 属性进行位移变换。

.parent {
  position: relative;
}

.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 子元素垂直居中 */
}
  1. 使用 Grid 布局:

父元素设置为 grid 容器,并使用 place-items 属性将子元素垂直居中。

.parent {
  display: grid;
  place-items: center; /* 子元素垂直居中 */
}

以上是常用的几种方法,选择适合你项目需求和浏览器兼容性的方式来实现子元素在父元素中垂直居中。

2、实现 子元素 在父元素中水平 垂直居中的方式

1.可以使用 Flexbox 布局。以下是一种基本的方法:

.parent {
  display: flex;
  justify-content: center; /* 子元素水平居中 */
  align-items: center; /* 子元素垂直居中 */
}

以上代码将父元素设置为 flex 容器,使用 `justify-content` 属性将子元素水平居中,使用 `align-items` 属性将子元素垂直居中。

2.使用绝对定位和 transform:

.parent {
  position: relative;
}

.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 子元素水平垂直居中 */
}

3.使用表格布局:

将父元素设置为 table,子元素设置为 table-cell,并使用 vertical-align 和 text-align 属性控制对齐方式。

.parent {
  display: table;
  width: 100%;
  height: 100%;
}

.child {
  display: table-cell;
  vertical-align: middle; /* 子元素垂直居中 */
  text-align: center; /* 子元素水平居中 */
}

4.使用 Grid 布局:

.parent {
  display: grid;
}

.child {
  justify-self: center; /* 子元素水平居中 */
  align-self: center; /* 子元素垂直居中 */
}
3、描述 Keepealive 的作用,有哪些钩子函数,如何控制组件级存列表?

KeepAlive 组件是 Vue.js 中的一个内置组件,用于缓存和复用组件实例。它可以将动态组件进行缓存,在组件切换时保留其状态,提高应用性能。

作用

  1. 缓存组件状态:使用 KeepAlive 包裹的组件在切换时会被缓存,保留组件的状态,包括数据、DOM 状态以及生命周期状态。
  2. 复用组件实例:每次切换到被 KeepAlive 包裹的组件时,并不会重新创建组件实例,而是直接使用之前缓存的组件实例。

常用的钩子函数:

  1. activated:在组件被激活时调用,每次切换到该组件时都会触发。可以用来执行组件激活时需要进行的逻辑操作,例如数据加载或动画效果的启动。
  2. deactivated:在组件失活时调用,每次从该组件切换出去时都会触发。可以用来执行组件失活时需要进行的清理操作,例如取消定时器或停止正在进行的动画。

控制组件级存储:

可以通过 <keep-alive> 的特殊属性 includeexclude 控制具体哪些组件需要被缓存,以及哪些组件不需要被缓存。

  • include:指定要缓存的组件名称数组,只有包含在该数组中的组件才会被缓存。
  • exclude:指定不需要缓存的组件名称数组,这些组件将不会被缓存。

示例如下:

<keep-alive :include="['ComponentA', 'ComponentB']" :exclude="['ComponentC']">
  <router-view></router-view>
</keep-alive>

以上代码中,只有名为 “ComponentA” 和 “ComponentB” 的组件会被缓存,而名为 “ComponentC” 的组件不会被缓存。其他组件则按需创建和销毁。

使用 includeexclude 可以灵活地控制组件级别的缓存策略,根据具体需求来决定哪些组件需要保持状态并进行复用。

4、请写出判断对象是数组的三个方法

判断对象是否为数组的三个常用方法如下:

  1. 使用 Array.isArray() 方法:
if (Array.isArray(obj)) {
  // obj 是一个数组
}
  1. 使用 instanceof 运算符:
if (obj instanceof Array) {
  // obj 是一个数组
}
  1. 使用 Object.prototype.toString.call() 方法:
if (Object.prototype.toString.call(obj) === '[object Array]') {
  // obj 是一个数组
}

4.通过 constructor 判断

if (obj.constructor === Array) {
  // obj 是一个数组
}

例子:

var listInfo = [1,2,3];
console.log(this.listInfo) // [1,2,3]
console.log(Array.isArray(this.listInfo)) // true
console.log(this.listInfo instanceof Array) //true
console.log(Object.prototype.toString.call(this.listInfo)) // [object Array]
console.log(this.listInfo.constructor) // ƒ Array() { [native code] }
console.log(this.listInfo.constructor === Array); // true

注意: instanceof.constructor 必须确保这个数组是 Arry 构造函数创建出来的 否则会报错

这些方法都可以判断给定的对象是否为数组。对于这三种方法,推荐使用 Array.isArray(),因为它是专门用于判断对象是否为数组的方法,且在不支持ES5的环境中也能正常工作。

5、请说出下面代码的执行结果
var myObj1 = {
  name: '小王',
  myAge: this.age,
  sayName: function(add, front) {
    console.log(this.name + '今年' + this.age + '在' + add + '做' + front);
  }
};

var heros = {
  name: '小张',
  age: '20'
};
myObj1.sayName.call(heros, '上海', '前端');
myObj1.sayName.apply(heros,['上海','前端']);
myObj1.sayName.bind(heros)('上海','前端');
myObj1.sayName.bind(heros,'上海','前端')();
myObj1.sayName.bind(heros,['上海','前端'])()



 小张今年20在上海做前端
 小张今年20在上海做前端
 小张今年20在上海做前端
 小张今年20在上海做前端
 小张今年20在上海,前端做undefined
 
 考察的 

下面是对每个调用方法的说明:
1、 `myObj1.sayName.call(heros, '上海', '前端')`:使用 `call` 方法调用 `sayName` 方法,并将 `heros` 对象作为方法的上下文(`this`)绑定,同时传递 `'上海'` 和 `'前端'` 作为参数。 立即执行

2 `myObj1.sayName.apply(heros, ['上海', '前端'])`:使用 `apply` 方法调用 `sayName` 方法,并将 `heros` 对象作为方法的上下文(`this`)绑定,同时以** 数组 **形式传递 `['上海', '前端']` 作为参数。  立即执行
3、myObj1.sayName.bind(heros)('上海','前端'); 将 heros 作为参数 传入 bind 方法,然后在后面的调用中又依次传入 上海、前端
4、、 `myObj1.sayName.bind(heros, '上海', '前端')()`:使用 `bind` 方法创建一个新的函数,并将 `heros` 对象作为方法的上下文(`this`)绑定,同时传递 `'上海'` 和 `'前端'` 作为参数。然后立即调用新创建的函数。
5、myObj1.sayName.bind(heros,['上海','前端'])()  将heros 作为改变this 指向的值传入,再把 ['上海','前端'] 这个数组作为第一个替换值传入, 所以 add =‘上海,前端’  ,front =undefined 

知识点:

在 JavaScript 中,`call`、`apply` 和 `bind` 都是用于改变函数的执行上下文(即函数内部的 `this` 指向)的方法。它们的主要区别在于参数的传递方式和是否立即执行函数。

1. `call` 方法:
   - 语法:`function.call(thisArg, arg1, arg2, ...)`。
   - 作用:`call` 方法调用一个函数,并将指定的对象作为函数的执行上下文(`this`)。可以通过 `call` 方法实现继承、借用其他对象的方法或更改函数内部的上下文。
   - 参数:
     - `thisArg`:被绑定到函数的执行上下文(`this`)的对象。
     - `arg1`, `arg2`, ...:函数调用时所需的参数列表。
   - 返回值:函数的返回结果。

2. `apply` 方法:
   - 语法:`function.apply(thisArg, [argsArray])`。
   - 作用:`apply` 方法调用一个函数,并将指定的对象作为函数的执行上下文(`this`)。与 `call` 方法类似,不同之处在于参数的传递方式,`apply` 使用 ·数组·来传递参数。
   - 参数:
     - `thisArg`:被绑定到函数的执行上下文(`this`)的对象。
     - `[argsArray]`:一个包含函数调用时所需参数的数组。
   - 返回值:函数的返回结果。

3. `bind` 方法:
   - 语法:`function.bind(thisArg, arg1, arg2, ...)`。
   - 作用:`bind` 方法.创建. 一个新的函数,该函数和原来的函数具有相同的函数体,但执行上下文(`this`)永久地被绑定到 `bind` 方法的第一个参数指定的对象。
   - 参数:
     - `thisArg`:被绑定到新函数的执行上下文(`this`)的对象。
     - `arg1`, `arg2`, ...:在调用新函数时要传递给原函数的固定参数。
   - 返回值:返回一个绑定了执行上下文和固定参数的新函数,并不会立即执行。

在给定的代码示例中,我们使用了 `call`、`apply` 和 `bind` 来改变 `myObj1.sayName` 函数的执行上下文,并传递了额外的参数。

- `myObj1.sayName.call(heros, '上海', '前端')`:调用 `sayName` 函数,使其以 `heros` 对象作为执行上下文(`this`),并传递 `'上海'` 和 `'前端'` 作为参数。结果会打印出类似于 "小张今年20在上海做前端" 的内容。
- `myObj1.sayName.apply(heros, ['上海', '前端'])`:类似于 `call` 方法,但是参数通过数组进行传递。
- `myObj1.sayName.bind(heros, '上海', '前端')()`:创建一个新的函数,并将 `heros` 对象作为执行上下文(`this`),同时传递 `'上海'` 和 `'前端'` 作为参数。由于使用了括号 `()`,新函数会立即执行,并打印出相应的内容。

需要注意的是,在使用 `call`、`apply` 和 `bind` 时,要确保参数和参数列表的正确配对,并根据具体需求选择合适的方法来改变函数的执行上下文。
6、过滤器如何使用? 请写一个数字保两个小数的过滤器

在 JavaScript 中,你可以使用过滤器(Filter)来对数据进行格式化和处理。过滤器通常用于将原始数据转化为需要的形式,比如在显示数据时对数字进行格式化。

下面是一个示例代码,实现了一个保留两位小数的数字过滤器:

// 定义数字保留两位小数的过滤器
function twoDecimalFilter(value) {
  if (typeof value !== 'number') return value; // 非数字类型直接返回

  return value.toFixed(2); // 使用toFixed方法保留两位小数
}

// 示例使用
let number1 = 12.3456;
let number2 = 3.7;
let string = 'abc';

console.log(twoDecimalFilter(number1)); // 输出 "12.35"
console.log(twoDecimalFilter(number2)); // 输出 "3.70"
console.log(twoDecimalFilter(string));  // 输出 "abc"

上述代码中,twoDecimalFilter 是一个自定义的过滤器函数。它接受一个参数 value,判断该值是否为数字类型,如果是,则使用 toFixed 方法将其保留两位小数并返回;如果不是数字,则直接返回原值。

在使用过滤器时,只需调用过滤器函数并传入要格式化的数据即可。在上面的示例中,我们分别对 number1number2string 进行过滤,并输出结果。

请注意,在实际开发中,可能会使用框架或库来提供更便捷和灵活的过滤器机制,比如在 Vue.js 中使用过滤器。以上示例仅演示了一个简单的自定义过滤器的实现方式。

上述要求用vue的过滤器实现

使用 Vue 的过滤器可以更方便地对数据进行格式化和处理。下面是一个使用 Vue 过滤器的示例,实现保留两位小数的数字过滤器:

html复制代码<!-- 在 Vue 模板中定义过滤器 -->
<div id="app">
  <p>{{ number1 | twoDecimal }}</p>
  <p>{{ number2 | twoDecimal }}</p>
  <p>{{ string | twoDecimal }}</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<script>
// 创建 Vue 实例
new Vue({
  el: '#app',
  data: {
    number1: 12.3456,
    number2: 3.7,
    string: 'abc'
  },
  filters: {
    twoDecimal(value) {
      if (typeof value !== 'number') return value;
      
      return value.toFixed(2);
    }
  }
});
</script>

在上述代码中,我们创建了一个 Vue 实例,并在该实例中定义了一个过滤器 twoDecimal。在模板中,通过使用管道操作符 | 将要过滤的数据与过滤器名称相连,在输出时会自动应用过滤器。

在这个示例中,number1number2string 是 Vue 实例的数据属性,分别代表要过滤的数字和字符串。通过 {{ number | twoDecimal }} 的形式,将数值绑定到对应的模板插值处,同时应用名为 twoDecimal 的过滤器,从而实现保留两位小数的效果。

运行以上代码,你将看到页面上输出的数值已按照过滤器的设定进行了格式化。

7、在vue里面 什么是自定义指令,请注册一个全局的自定义指令。

在 Vue 中,自定义指令(Custom Directive)是一种扩展 Vue 的能力,用于对 DOM 元素进行低层次的操作和交互。通过自定义指令,我们可以直接操作 DOM、添加事件监听器、进行样式操作等。

要注册一个全局的自定义指令,你可以使用 Vue.directive 方法。下面是一个示例代码,演示如何注册一个名为 my-directive 的全局自定义指令:

<div id="app">
  <input v-my-directive />
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<script>
// 注册全局自定义指令
Vue.directive('my-directive', {
  bind: function(el, binding, vnode) {
    // 指令绑定时的处理逻辑
    el.style.backgroundColor = 'yellow';
    el.addEventListener('focus', function() {
      el.style.color = 'red';
    });
  },
  inserted: function(el, binding, vnode) {
    // 指令插入到 DOM 中的处理逻辑
  },
  update: function(el, binding, vnode, oldVnode) {
    // 组件更新时的处理逻辑
  },
  unbind: function(el, binding, vnode) {
    // 指令解绑时的处理逻辑
    el.removeEventListener('focus');
  }
});

// 创建 Vue 实例
new Vue({
  el: '#app',
});
</script>

在上述代码中,我们使用 Vue.directive 方法注册了一个名为 my-directive 的全局自定义指令。该指令包含了几个生命周期钩子函数,例如 bindinsertedupdateunbind,你可以根据需要实现相应的逻辑。

在示例代码中,bind 钩子函数用来在指令绑定时设置元素的背景颜色为黄色,并添加一个焦点事件监听器,当聚焦到输入框时,将文字颜色设置为红色。

在 Vue 实例的模板中,我们通过 v-my-directive 指令将这个自定义指令应用到了一个 <input> 元素上。当 Vue 渲染该模板时,my-directive 指令就会被触发,执行对应的逻辑。

请注意,以上示例代码是注册一个全局自定义指令的基本用法,你可以根据实际需求进行扩展和修改。

8、请说出下面代码的输出结果
const arr1 =[
	{
		id:1,
		name:'name1
	},{
		id:2
		name:'name2
	}
]

const arr2 =[...arr1] // 复制数组,浅拷贝
arr1[0].name='namel-undate' // 会改变复制后的数组
console.log(arr2[0].name)  // 

const obj ={
	a:'aa'
	b:bb'
}
const obj2 = {...obj}  // 复制对象形成新对象
obj.a='aa-update' // 不会影响新对象
console.log(obj.a)
console.log(obj2.a)

对于更新后的代码如下:

const arr1 = [{
  id: 1,
  name: 'name1'
}, {
  id: 2,
  name: 'name2'
}];

const arr2 = [...arr1];
arr1[0].name = 'namel-update';
console.log(arr2[0].name);

与之前相同,arr1是一个包含两个对象元素的数组。通过扩展运算符[...arr1]arr1数组的元素复制给了新数组arr2。同样地,这仍然是浅拷贝,意味着修改arr1中的第一个对象的name属性会影响到arr2中的对应元素。因此,console.log(arr2[0].name)将输出namel-update

const obj = {
  a: 'aa',
  b: 'bb'
};
const obj2 = {...obj};
obj.a = 'aa-update';
console.log(obj.a);
console.log(obj2.a);

在这段代码中,我们使用了对象展开语法{...obj}来将obj对象进行复制。这种方式会创建一个新的对象,并将原始对象中的属性复制到新的对象中。因此,obj2是一个独立的对象,其属性与原始对象obj相同但值不共享。

由于我们将obj对象的a属性值修改为'aa-update',所以console.log(obj.a)将输出'aa-update'。而console.log(obj2.a)将输出原始的属性值'aa',因为obj2是在修改之前复制的对象。

综上所述,使用对象展开语法{...obj}可以实现对象的浅拷贝,并且复制后的对象与原始对象的属性值不共享。

9、介绍下浅拷贝和深拷贝

浅拷贝和深拷贝是两种常用的对象或数组复制方法,它们在复制过程中处理引用类型数据的方式不同。

  1. 浅拷贝:
    • 浅拷贝是指创建一个新对象或数组,将原始对象或数组的值复制到新对象或数组中。当复制引用类型数据时,只复制了引用地址,而不是创建新的独立副本。
    • 浅拷贝后的新对象或数组与原始对象或数组共享相同的引用值,修改其中任何一个的属性或元素会影响到另一个。
    • 常见的浅拷贝方式包括扩展运算符[...arr]{...obj}Object.assign()等。
  2. 深拷贝:
    • 深拷贝是指完全复制一个对象或数组及其所有嵌套的对象或数组,得到一个全新且独立的副本。这样两个对象/数组互不干扰,修改其中一个不会影响另一个。

    • 深拷贝复制了所有层级的数据,包括基本类型和引用类型数据,递归地遍历复制每个对象或数组的属性或元素。

    • 常见的实现深拷贝的方式包括循环递归、JSON序列化与反序列化、使用第三方库(如Lodash或深拷贝工具函数)等。

    • 实现深拷贝的方式有多种。以下是几种常见的方法:

      1. 手动递归遍历:
        这是一种基本的深拷贝方法,通过递归遍历对象或数组的每个属性,并创建完全独立的副本。

      2. JSON 序列化和反序列化:
        这种方法利用 JavaScript 中的 JSON 对象提供的序列化(JSON.stringify)和反序列化(JSON.parse)功能,将对象转换为字符串再解析为新对象,实现深拷贝。但需要注意该方法无法复制函数、正则表达式等特殊对象。

      3. 使用第三方库:

        • Lodash:Lodash 是一个流行的 JavaScript 实用工具库,其中提供了 _.cloneDeep 方法来进行深拷贝。
        • jQuery:jQuery 的 extend 方法可以实现深拷贝,例如 $.extend(true, {}, obj)。
        • Immutable.js:Immutable.js 是一个持久化数据结构的库,它通过使用树结构和哈希映射实现高效的数据共享。这个库的数据结构天生支持深拷贝。

      需要注意的是,虽然这些方法都可以实现深拷贝,但在处理特定类型的数据或大型对象时可能会存在性能问题。在选择使用哪种方法时,请考虑到数据的复杂性、大小和需要拷贝的深度,并根据实际情况选择最适合的深拷贝方式。

在选择使用浅拷贝还是深拷贝时,需要根据具体需求和数据结构来决定。如果对象/数组的属性值不包含引用类型数据、或者对共享值的修改无影响,那么可以使用浅拷贝;如果需要完全独立且不受影响地操作复制后的对象/数组,那么应该使用深拷贝。

10、websocket 如何监听心跳

WebSocket 是一种基于 HTTP 协议进行双向通信的协议,通信的两端可以同时发送和接收数据。在 WebSocket 连接中,心跳包用于维持连接的存活状态,可以通过定时发送特定格式的数据包来检测连接是否处于正常工作状态。

具体地,可以通过在服务器端设置一个定时器,在一定时间间隔内发送心跳包。客户端也可以实现类似的逻辑,在一定时间内没有收到服务端的消息,则认为连接出现了问题需要重新连接或重连。WebSocket 还支持 PING 和 PONG 控制帧,可以使用这些帧来检测连接是否存活。当客户端发送 PING 帧时,如果服务端成功接收到,则会回复一个 PONG 帧,客户端可以通过检查是否收到 PONG 帧来判断连接状态。在大部分情况下,开启心跳机制是很重要的,因为它可以避免因长时间没有通信而导致的连接中断,保持 WebSocket 的稳定性和可靠性。

前端在监听 WebSocket 心跳时,可以通过在客户端实现定时器来检测连接是否存活。具体地,可以在连接建立成功后,设置一个定时器,定期发送心跳包到服务器,并在服务器端回复的限定时间内未收到相应的数据包,则认为连接异常,需要重新连接或重连。

同时,WebSocket 还提供了 onclose 事件和 onerror 事件,用于监听连接的关闭和错误。当 WebSocket 断开连接或发生错误时,这两个事件都会触发,可以在事件回调中实现自定义逻辑,比如进行重新连接或提示用户等。需要注意的是,在监听 WebSocket 事件时,也要考虑到接口文档中相关的限制条件,避免违反相关法律法规

11、前端 http 如何转 https

在前端应用中,可以通过以下步骤将 HTTP 转换为 HTTPS:

获取 SSL 证书:获取可信的 SSL 证书是使用 HTTPS 的前提条件。可以通过商业 CA 颁发或自签署证书的方式获得 SSL 证书。

在服务器上部署 SSL 证书:一旦 SSL 证书准备好了,就需要在 HTTP 服务器(如 Nginx、Apache 等)上部署证书。这里假设已经正确部署了 SSL 证书。

修改源代码:在 Web 应用程序的源代码中,需要将所有 http:// 替换为 https://。可以使用全局搜索替换的方法,也可以在部分需要保护的地方手动修改。

重定向 HTTP 请求到 HTTPS:使用服务器级别的 URL 重定向,可以将未加密的请求强制重定向到 HTTPS URL。例如,在 Nginx 中,可以在 server block 中添加以下配置段:

server {
listen 80;
server_name example.com;
rewrite ^(.*)$ https://$server_name$1 permanent;
}
这些配置将启用监听80端口,并将所有HTTP流量重定向到匹配的 HTTPS地址。

测试 HTTPS 连接:完成前面的步骤后,应该测试 Web 应用程序是否正确地使用 HTTPS 实现形式。通常,HTTPS 的开发和调试可能会遇到各种问题,包括混合内容、证书错误等。

需要注意的是,对于某些特殊情况,比如不想购买 SSL 证书、只需要加密部分内容等情况,也可以使用一些其它方法来实现 HTTPS 化。但无论使用何种方法,都应该遵循安全性最佳实践并谨慎处理您的用户数据。

12、前端 同步可以调用异步吗,异步调异步呢

前端同步代码是会阻塞 UI 线程的,如果在同步 JavaScript 代码中调用异步函数,由于异步函数通常需要等待返回结果,那么可能会使页面长时间无响应。因此,在同步 JavaScript 代码中调用异步函数会导致页面卡死甚至崩溃,不可取。

同时,在异步 JavaScript 代码中调用异步函数也需要小心。虽然异步函数通常会使用 Promise 或回调方式来处理异步操作,并且在执行时不会阻塞 UI 线程,但是在多层嵌套异步调用时,可能会出现回调地狱问题,通过 async / await 方式可以有效解决这个问题。

为了避免上述问题,建议在编写JavaScript 代码时,谨慎考虑同步和异步调用的方式,尽量避免长时间阻塞UI线程,并通过适当的方式来处理异步回调嵌套和错误捕获等相关问题。

13、视频在安卓可以正常播放在 ios 无法正常播放?

视频在安卓可以正常播放而在iOS上无法正常播放,这可能是由于使用了一种不受 iOS 设备支持的视频编解码格式所致。

iOS 上支持播放的视频编解码格式包括 H.264、HEVC(H.265)、MPEG-4 Part 2 和 Motion JPEG 等。而在 Android 上,通常支持更多的视频编解码格式,包括 VP9、AV1 等开源编解码格式。因此,如果使用了 iOS 不支持的视频编解码格式,将导致视频无法在 iOS 设备上正常播放。

为了确保视频能够在所有设备上正常播放,建议尽可能使用受广泛支持的视频编解码格式。如果需要使用某些特定编解码格式,则需要进行额外处理,例如,在视频上传时,将其转换为不同的格式以适应不同的设备。

同时,也可以考虑使用跨平台的视频播放库,例如 Video.js 或 Jwplayer 等,它们通常会自动检测设备并使用相应的编解码格式进行播放,从而最大限度地提高视频的兼容性。

14、前端 如何对后台接口进行封装

在前端进行后台接口封装可以提高代码的可读性、稳定性和重用性,同时也能更好地维护后台接口,降低与后端的耦合度。以下是一些推荐的封装方式:

1。封装 Ajax 请求函数:将常见的 Ajax 请求封装成函数,在函数内部处理请求、响应错误和数据转换等逻辑,以减少冗余代码和复杂度。

2。封装 API 接口对象:将后端 API 接口封装到一个对象中,每个接口用一个方法表示,并统一处理请求和响应。这种方式方便集中管理所有 API 接口,易于调用和测试。

3.封装数据模型类:将后端的接口数据结构封装为一个前端数据模型类,提供相应的方法进行数据操作和业务处理。这种方式可以有效抽象出基础业务逻辑和处理过程,并实现数据缓存、数据加工等功能。

4.中间件封装:使用中间件框架如 axios-interceptors 等,对 HTTP 请求和响应进行拦截和处理,实现许多共同的需求,例如鉴权、数据缓存和统一错误处理等。

5.Hook 封装:使用 React 的 Hook 监听组件生命周期或者自定义 Hook,对接口进行处理,使 Hooks 像普通函数一样调用接口,并且 Hooks 也可以处理接口并返回值。

15、promise 原理

Promise 是 ES6 新增的一种异步编程方式,它是一种对象,用于表示异步操作的最终完成(或失败)及其结果值。Promise 的核心理念是将异步操作放入 Promise 中,并返回一个代表着该异步操作结果的 Promise 对象,以便在异步操作完成时进行处理。

Promise 的基本原理如下:

Promise 对象有三种状态:pending、fulfilled 和 rejected。初始状态为 pending,待执行完成后会变成 fulfilled(已解决)或 rejected(已拒绝)。

Promise 对象通过 then 方法注册回调函数,当异步操作执行完毕后通过 resolve 方法或 reject 方法确定最终状态,并将结果作为参数传给回调函数。

then 方法可以链式调用,每个 then 方法中的回调函数都会接受上一个 then 方法中回调函数返回值作为参数。

如果在 Promise 构造函数中发生错误,Promise 对象就会立即进入被拒绝(rejected)状态,并且抛出异常。

基本语法:

js
new Promise((resolve, reject) => {
// 执行异步操作
if (/* 异步操作成功 */) {
resolve(result); // 将结果传递给回调
} else {
reject(error); // 将错误信息传递给回调
}
}).then(result => { // 注册回调函数
// 处理异步操作成功后的结果
}).catch(error => { // 捕捉 Promise 抛出的异常
// 处理异步操作失败后的错误
});
该 Promise 对象会调用一个函数,该函数有两个参数:resolve 和 reject。执行结果成功时调用 resolve 方法将结果传递给 then 函数回调,执行结果失败调用 reject 方法向 catch 函数传递错误信息。then 函数中接收到上一个 promise 函数的执行结果,回调处理该结果,catch 函数接收前面抛出的错误,并进行处理。

以上方式都能有效封装前端与后端之间的接口,提升开发效率与维护便利性,根据实际项目需求进行选择即可。

16、uniapp 如何自动化打包

UniApp 提供了通过命令行自动化构建打包的方式。具体步骤如下:

1.在项目根目录下,使用命令行进入到 HBuilderX 安装目录中的 uni-cli/bin 目录下。

2.运行以下命令行,安装打包所需依赖:

npm install

3.在 uniapp 项目目录下,修改 package.json 文件的 scripts 字段,添加如下脚本代码:
“scripts”: {
“build:h5”:“cross-env NODE_ENV=production uni-build --h5”
}
4.修改完 package.json 后,在命令行运行以下命令:
npm run build:h5
此时会在项目根目录的 dist 目录下生成 h5 平台所需的文件。其它平台的操作方法与 h5 平台类似,在 scripts 中添加对应的命令即可。

需要注意的是,在使用自动化构建打包前,需要先进行配置相关打包参数等内容,例如 app 配置及 manifest.json 配置等。此外,若需要生成离线包,则需要在 HBuilderX 的菜单栏中选择 「发行」-「云端打包」,登录后按照页面的提示操作即可。

17、微信小程序凭证?

在微信小程序中,每个小程序必须拥有自己的 AppID 和 AppSecret,这两个参数被称为小程序的凭证,用于进行认证和接口调用。

*AppID:是一个字符串,是小程序的唯一标识符,与开发者帐号相关联。
*AppSecret:是一个字符串,是小程序的加密密钥,与App ID相关联。App Secret用来进行小程序后端的身份验证,如果泄露此密码,可能会导致恶意用户获取您小程序的数据或者搞乱各种API等操作。
AppID 是小程序的唯一标识符,可通过微信公众平台申请获得。而 AppSecret 则是用于对接口进行签名的密钥,需要开发者在开发者中心中手动设置。

在使用微信小程序的开放接口时,需要使用小程序的凭证进行接口调用,同时还需要针对不同的接口填写相应的参数。具体接口调用方法可以参考微信小程序开发文档,文档中提供了详细的接口说明和调用示例。

18、公众号怎么实现微信支付

要在公众号中实现微信支付,需要进行以下步骤:

1.在微信商户平台注册账号并进行实名认证,获取商户号和密钥等信息。

2.使用商户号和密钥申请微信支付接口的api密钥和证书。

3.在公众号开发者后台中,设置支付授权目录和JSAPI支付安全域名,并开通JSAPI支付功能。

4.实现支付功能:使用微信支付接口的统一下单 API,向微信支付服务器发起请求获取预支付交易会话标识(prepay_id)。

5.客户端调用JSAPI方法,拉起微信支付界面,用户确认支付后完成支付过程。

具体的步骤和操作细节可以参考微信官方文档中提供的支付接口开发指南,以及相关的示例代码和接口文档。在实现支付功能时还需要注意数据加密和处理异常情况等问题。另外建议开发者严格按照微信支付平台的标准接入流程进行开发和测试,并在审核前仔细检查相关功能是否符合规范要求。

19、前端 实现支付宝支付

在前端实现支付宝支付,需要进行以下步骤:

1.在支付宝开发者平台创建应用并进行认证,获取应用ID和密钥等信息。

2.采用支付宝提供的SDK或接口,将应用与支付宝账号关联。

3.实现支付功能,使用支付宝提供的统一收单下单并支付页面接口,向支付宝服务器发送下单请求,并获得一个唯一的订单号和支付宝跳转链接。

4.将跳转链接进行二维码生成或者页面渲染,展示给用户以完成支付过程。

具体的步骤和操作细节可以参考支付宝官方文档中提供的支付接口开发指南,以及相关的示例代码、接口文档。在实现支付功能时还需要注意数据加密和处理异常情况等问题。另外建议开发者严格按照支付宝平台的标准流程进行开发和测试,并在审核前仔细检查相关功能是否符合规范要求。

20、前端如何识别 后端返回链接 是否是图片
  1. 前端可以通过以下方式判断后端返回的链接是否是有效的图片链接:

利用JavaScript的Image对象进行预加载,如果加载成功,则说明该链接是有效的图片链接。

function checkImage(url, success, error) {
var img = new Image();
img.onload = success;
img.onerror = error;
img.src = url;
}
checkImage(‘https://example.com/image.jpg’, function() {
console.log(‘Valid image URL’);
}, function() {
console.log(‘Invalid image URL’);
});

2.判断链接是否以常见的图片格式结尾,例如.jpg、.png、.gif等。

function isImageUrl(url) {
    return (url.match(/\.(jpeg|jpg|gif|png)$/) != null);
}

console.log(isImageUrl(‘https://example.com/image.jpg’)); // true
以上两种方式都可以判断链接是否是有效的图片链接,同时建议在展示图片前对数据进行合法性检查和类型判断。另外需要注意的是,由于网络原因或服务器问题等,链接可能会返回404或其他错误码,因此就算链接是有效的图片链接也不能100%保证一定能够展示出来,请开发者在使用时进行细致的测试和异常处理。

21、前端怎么解决页面白屏

检查网络连接是否正常,确保 CDN 或静态资源能够正常加载。

检查代码中是否有语法错误、逻辑错误或其他代码问题,可以使用开发者工具进行调试,并检查控制台报错信息。

确认浏览器版本是否支持所使用的技术,如是否支持 ES6、CSS Grid等。如果不支持,建议降级或用 polyfill 处理。

预加载相关资源,例如预加载 HTML、CSS、JS 文件。

将关键 CSS 和 JS 文件放在 head 标签中,以便在文档加载时就能够渲染。

合理、简化页面结构和样式,尽量减少 HTTP 请求次数和响应容量。

使用浏览器工具对页面加载过程进行分析,找到具体瓶颈并进行优化。

22、dom树加载机制

DOM(文档对象模型)树是浏览器解析 HTML 文档后所形成的一种树形结构,表示页面中所有 HTML 元素及其层级关系。浏览器在加载页面时会先经过以下阶段:

解析 HTML 文件:当浏览器请求一个 HTML 页面时,它会读取并解析文件内容,构建出 DOM 树。

构建 DOM 树:解析 HTML 文件时,遇到标签、属性和文本节点等元素,都会在内存中创建相应的节点对象,并按照层级关系构建 DOM 树,节点对象具有父子关系,DOM 树是由多个节点所组成的树形结构。

生成 Render Tree:浏览器在解析 DOM 树时,会将 CSS 样式与每个节点(除非该节点对应的 CSS 尚未下载)结合起来,生成一个带有样式信息的 Render 树,该树表示了网页的渲染结构。

布局(Layout):最终的Render树事先排版布局,即确定每个节点在屏幕上的准确位置和大小。同时,还需要确定节点的颜色、字体、边框、背景色、图像等外观效果,以便后续的绘制工作。布局过程会根据盒模型、文档流等特性进行计算。

绘制(Painting):在已经确定好的节点位置上进行绘制,即显示出文本、图片、背景等视觉效果。

以上就是浏览器加载页面时DOM树的加载机制涉及的一系列流程和步骤。

CSS 盒模型(Box Model)是用于描述和布局 HTML 元素的一种模型。它表示每个元素都被包含在一个矩形框中,该框由内容区域、内边距(padding)、边框(border)和外边距(margin)组成。

盒模型的组成部分如下:

1. 内容区域(Content):
   - 包含元素的实际内容,如文本、图像等。
   - 通过设置 `width` 和 `height` 可以调整内容区域的尺寸。

2. 内边距(Padding):
   - 内边距是内容区域与边框之间的空白区域。
   - 可以使用 CSS 的 `padding` 属性为元素设置内边距。

3. 边框(Border):
   - 边框是围绕内容区域和内边距的线条或样式。
   - 可以使用 CSS 的 `border` 属性来定义边框的样式、宽度和颜色。

4. 外边距(Margin):
   - 外边距是元素与其周围元素之间的空白区域。
   - 可以使用 CSS 的 `margin` 属性设置元素的外边距。

在标准盒模型中,元素的总宽度计算公式为:`总宽度 = 内容区域宽度 + 左内边距 + 右内边距 + 左边框 + 右边框 + 左外边距 + 右外边距`。同样,总高度的计算也是类似的。

CSS 盒模型提供了灵活的布局控制,使用不同的属性和值可以实现各种布局效果。可以通过设置盒模型的属性来调整元素的尺寸、间距和边框样式,以满足设计需求。
23、前端页面渲染机制

前端页面渲染机制主要涉及的是浏览器如何对网页进行解析、构建和渲染的过程。具体来说,它通常包括以下几个阶段:

构建 DOM 树:当浏览器接收到 HTML 页面时,会按照标签的层次关系依次解析文件内容,并根据标签生成对应的 DOM 元素节点。

解析 CSS 样式:在 CSS 阶段中,浏览器会处理样式表信息,解析出每个元素所对应的应用样式,并将其合并组成一个规则列表。

生成 Render 树:Render 树是一个包含每个节点和其对应样式属性信息的树形结构,与 DOM 树类似。它的每个节点都代表了文档中相应的可视化元素。

布局(Layout):在 Render 树构建完成后,浏览器会进入布局阶段,计算出每个节点的位置和尺寸信息,并确定它们在屏幕上的呈现方式。

绘制(Painting):布局完成后,浏览器就可以进入绘制阶段,将页面内容绘制到屏幕上。

总的来说,浏览器在渲染页面时,会从 DOM 树、CSS 样式、JavaScript 脚本等多个方面对页面进行处理。在实际应用中,为了提高页面的性能,还需要注意避免网络请求、资源重复加载等问题。

25、介绍下http\https 协议

HTTP(Hypertext Transfer Protocol)是一种应用层协议,它是 Web 技术的基础之一。通过 HTTP 协议,客户端和服务端之间可以有效地传输数据和通信。HTTP 协议支持多种请求方法,如 GET、POST 等,同时还定义了可接受的请求头域以及状态码等规范。

HTTPS(HyperText Transfer Protocol Secure)是在 HTTP 协议之上添加了加密和认证机制的协议。在 HTTPS 中,服务器需要使用 SSL 或 TLS 协议对数据进行加密处理,并通过数字证书验证身份,保证服务器和客户端之间的通信安全性。

具体来说,简单介绍一下 HTTP 和 HTTPS 的区别:

安全性:HTTP 是明文传输协议,不提供任何加密方法。而 HTTPS 利用加密技术保证信息传输过程中的机密性和完整性,更为安全。

数据传输方式:HTTP 以明文方式传输数据,所有数据都可以在网络上被监听和窃取。而 HTTPS 则通过 SSL/TLS 加密来保障数据的安全传输。

默认端口:HTTP 默认使用端口号为80,HTTPS 默认使用端口号为443。

证书要求:HTTPS 涉及到证书的验证和签发,在使用 HTTPS 前需要申请SSL证书,并且要求使用 HTTPS 的网站必须拥有有效的数字证书。

综上所述,HTTP 协议是一种常用的协议,在访问普通网站的过程中使用较多;而对于数据传输要求更高、安全性更为重要的网站,则选择使用 HTTPS 协议。

26、get、post怎么传参

无论是 GET 还是 POST 请求方式都可以通过参数来传递数据。一般来说,GET 请求方式适合请求简单的数据,而 POST 适合提交复杂数据或者包含文件上传等操作。

GET 请求方式传参 在 GET 请求方式中,参数一般写在 URL 的末尾,使用 ? 来分隔 URL 和参数,多个参数之间使用 & 符号来连接。例如:
http://example.com/path/to/file?key1=value1&key2=value2
在实际代码中,可以使用 JavaScript 中的 fetch 函数(客户端)或者 PHP 中的 $_GET (服务端获取 GET 参数)进行数据的传递和获取。

POST 请求方式传参 在 POST 请求方式中,参数不会跟在 URL 后面像 GET 方式一样,而是作为数据在请求体中进行提交。提交的数据可以是字符串、JSON 对象、FormData 对象等。例如:
const data = {
key1: ‘value1’,
key2: ‘value2’
};

fetch(‘http://example.com/path/to/file’, {
method: ‘POST’,
body: JSON.stringify(data),
headers: {
‘Content-Type’: ‘application/json’
}
})
在 PHP 中,可以使用 $_POST (服务端获取 POST 参数) 或 file_get_contents(‘php://input’) 等方法获取 POST 数据。

需要注意的是,GET 方法提交的数据长度有限制,且数据会直接暴露在 URL 上,不适合传输敏感信息;而 POST 方法提交的数据可以存储大量数据,也更为安全,但是需要服务器进行处理。因此,在实际开发中,需要根据具体情况合理选择 GET 或 POST 方式来传递参数。

27、vuex 数据传参导致白屏

Vuex 是 Vue.js 的一个状态管理库,主要用于 Vue 应用中的数据共享问题。Vuex 存放在集中式存储中的状态是响应式的,能够做到在组件间传递和共享数据。

关于Vuex 数据传参导致白屏的问题,可能存在以下原因:

Vuex 数据传递出现了循环依赖,导致系统进入死循环,最终导致白屏。可以通过检查数据流向来判断是否有这种情况。

在使用 Vuex 进行数据传递时,没有及时调用异步操作需要的命令,导致白屏。这种情况下需要检查代码,确保异步操作的正确执行。

由于异步操作或其他原因,Vuex 中的数据未成功加载,但系统却尝试渲染视图,导致出现白屏。此时需要确保数据正常加载以后再进行视图渲染。

针对以上问题,可以针对具体的情况分别处理。例如,在处理循环依赖的问题时,可以修改代码结构、减少数据依赖关系等方式来解决;在处理异步操作相关问题时,需要仔细考虑应该何时调用异步操作,并且要编写良好的错误处理机制,避免出现无法处理的异常情况;对于数据加载问题,可以使用 Vue.js 提供的生命周期函数来确保数据加载完成后再进行视图渲染。

28、WebSocket 线程过多,通常是因为服务器无法在实时处理所有客户端请求所致。想要解决这个问题,可以尝试以下几种方法:
  1. 调整 WebSocket 同时连接的数量限制:我们可以调整 WebSocket 的同时连接数。例如,如果服务器只支持 1000 个连接,并且正在处理第 1001 个连接,则新的连接会被拒绝。通过调整同步连接数的限制,可以降低线程并发量和资源消耗。

  2. 根据消息的价值和重要性进行数据分类和筛选:服务器可以根据不同类型的消息内容,将其分配到不同的线程中进行处理,以避免单一线程处理太多数据导致负载过大。这可以通过使用消息队列等技术实现。

  3. 水平扩展服务器:如果以上两种方法无法解决问题,则可以使用水平扩展服务器的方法来增加系统的集群节点。这种方法可以通过添加更多服务器同时处理客户端请求来提高服务器的吞吐量和并发性能。

总之,处理 WebSocket 过多线程的方法有很多,具体取决于您的应用场景和实际需求。

29、在 Canvas 中处理音频通常需要结合 Web Audio API 来实现。Web Audio API 是一个用于处理和控制音频的高级 API,它提供了许多函数和接口,可以很容易地在 JavaScript 中创建音频模型并控制其行为。

下面是展示如何使用 Web Audio API 在 Canvas 上处理音频的基本步骤:

  1. 加载音频文件:使用 JavaScript 的 Audio 对象来加载音频文件。

  2. 创建音频上下文对象:创建一个 AudioContext 对象。该对象将被用于从音频文件中读取数据并传递到 Web Audio API。

  3. 解码音频数据:调用 AudioContext 对象的 decodeAudioData() 方法解码音频数据。

  4. 创建音频节点:根据需要创建各种类型的音频节点,例如 SourceNode、GainNode、DestinationNode 等。节点将构成一个音频处理图谱,每个节点都会执行一些特定的音频操作。

  5. 将音频节点连接起来:使用 connect() 方法将音频节点与其他节点连接起来形成所需的音频处理图谱。由此形成的处理图可以通过简单的组合来实现从音频文件读取数据并对其进行处理。

  6. 开始播放音频:调用 start() 方法启动音频轨道中的播放或停止音频播放 。

在 Canvas 中处理音频的能力不仅限于上述步骤。实际上, Canvas 有一些内置功能可用于处理和操作音频,例如使用图形来控制音频播放、操纵声音的可视化效果和创建有趣的交互式体验。具体取决于您的需求和想要实现的功能。

30、 在 Webpack 中配置 rem

想要在 Webpack 中配置 rem,可以采用以下步骤:

  1. 安装 postcss-loader 和 postcss-pxtorem 插件

在终端中输入以下命令安装相关插件:

npm install postcss-loader postcss-pxtorem -D
  1. 配置 postcss-loader

在 webpack.config.js 文件中的 module.rules 数组中添加 postcss-loader,如下所示:

module.exports = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'postcss-loader']
      }
    ]
  }
};
  1. 配置 postcss-pxtorem

在项目根目录创建 postcss.config.js 文件,并配置 postcss-pxtorem 插件,如下所示:

module.exports = {
  plugins: [
    require('postcss-pxtorem')({
      rootValue: 75,   // 根字体大小
      propList: ['*'],  // 全部选择器都需要转换
      mediaQuery: false // 是否开启媒体查询模式
    })
  ]
};
  1. 设置 HTML 文档的 font-size

在 index.html 的 head 标签内设置 HTML 文档的 font-size,如下所示:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack Project</title>
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
    <style>
      html {
        font-size: 100px; /* 此处设置为100px,与 rootValue 相同 */
      }
    </style>
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

最后,重新运行 Webpack 即可实现在 CSS 中使用 rem。

31、Webpack 解决样式兼容问题

为了解决样式兼容问题,可以考虑在 Webpack 中配置 PostCSS 插件 Autoprefixer,它可以自动添加浏览器前缀,从而简化前端编写 CSS 样式时的兼容性处理工作。

使用 Autoprefixer 需要以下步骤:

  1. 安装 postcss-loader 和 autoprefixer

执行以下命令来安装所需依赖:

npm install postcss-loader autoprefixer -D
  1. 在 webpack.config.js 配置文件中添加 loader

在 module.rules 数组中添加处理 CSS 文件的 loader,连同 Autoprefixer 插件一起使用。下面是示例代码:

module.exports = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              plugins: [require('autoprefixer')]
            }
          }
        ]
      }
    ]
  }
};
  1. 在 package.json 文件中添加 browerslist 配置

建议将 Autoprefixer 和 Babel 一起配合使用来兼容更多 JavaScript 特性和一些旧版浏览器。将目标浏览器放在项目根目录的 package.json 文件的 browserslist 配置项中。例如:

"browserslist": [
  "> 1%",
  "last 2 versions"
]

这条指令的意思是我们需要支持全球占有率 1% 及以上的浏览器,并且支持最新的两个主要浏览器版本。

经过上述步骤,就完成了 Autoprefixer 的配置。在接下来开发和打包过程中,Autoprefixer 将自动解析 CSS,并根据你的 browserslist 配置添加相应的前缀,从而帮助解决跨浏览器兼容性问题。

32、Vite

Vite 是 Vue.js 创始人尤雨溪开发的一款面向现代浏览器的轻量级前端开发服务器和构建工具。它提供了快速的冷启动,内置开箱即用支持 Vue 单文件组件 (SFC) 的 devServer,并能够在开发期间基于 ES Module 进行非常快速的模块热更新 (HMR),从而解决了 Webpack 打包重启时间过长的问题。

下面是 Vite 的主要特点:

  1. 快速冷启动:通过 ES Module 的特性,在服务启动时只需要编译当前需要的文件,而不需要像 Webpack 那样一次性编译整个项目,从而提升了冷启动速度。

  2. 实时模块热替换:基于 Vue 3 的 Reactivity API 实现,可以更精准地定位需要替换的模块,同时也提供了对 CSS 和自定义模块类型的支持,可以加快前端开发的速度。

  3. 内置优化插件:通过内置的预处理插件以及针对生产环境的打包优化,可以帮助用户轻松地实现优化产品代码的目的。

  4. 零配置:默认配置下就可以直接使用,无需进行额外的配置。

  5. 可扩展性:Vite 提供了 API 和插件体系,方便用户根据需求扩展、优化相关功能。

使用 Vite 可以轻松实现项目开发和构建,其兼容多种框架 (Vue / React / Preact / LitElement),支持 TypeScript、SCSS、LESS 等多个前端开发技术栈,并且提供了快速开发、更佳的开发体验和更快的构建效率。

33、vite 的基本使用

使用 Vite 是非常简单的,下面介绍如何使用 Vite 来搭建 Vue.js 应用程序。

  1. 安装

首先需要全局安装 @vite/cli:

npm install -g vite 或者 yarn global add vite
  1. 创建项目

使用以下命令创建一个新的 Vue 项目:

npm init vite-app my-project 或者 yarn create vite-app my-project
  1. 安装依赖

进入项目目录并安装依赖:

cd my-project
npm install 或者 yarn
  1. 运行项目

执行以下命令运行项目:

npm run dev 或者 yarn dev

然后打开浏览器访问 http://localhost:3000 即可查看应用程序。

  1. 构建项目

使用以下命令构建项目:

npm run build 或者 yarn build

构建完成后,所有打包好的文件都会生成在 dist 目录下。

以上就是使用 Vite 搭建 Vue.js 应用程序的基本流程。同时 Vite 也支持其他前端框架,只需按照相应文档进行相应设置即可。

34、vue 的 object.prototype 为什么监听不到数组 (对象) 里面变化?

Vue 在实现数据双向绑定时,使用了 Object.defineProperty() 方法来为对象上的属性添加 getter 和 setter,从而实现数据响应式。

但是,Object.defineProperty() 方法只能劫持对象的属性,而不能劫持整个对象或数组。这就意味着当你改变数组中的某一个元素时,它是不会触发响应式更新的,因为 Vue 并没有劫持数组元素本身的 setter。

由于数组是一种比较特殊的对象类型,在 JavaScript 中,最好使用 Array 类型提供的方法对其进行操作。这些数组的方法可以触发响应式更新,例如 push、pop、splice 等等。所以推荐在 Vue 中,我们尽量使用这些数组方法来修改数组中的元素,从而保证数据响应式。

总结一下,Vue 监听不到对象里面的数组变化,是因为 Object.defineProperty() 方法只能劫持对象的属性,而不能劫持整个对象或数组,但是 Vue 提供了一些数组的方法,可以触发响应式更新。因此,我们在Vue使用中,建议使用这些数组方法来修改数组中的元素。

35、那在vue中怎么做到监听数组 (对象) 里面变化呢?

在 Vue 中,要监听数组内部元素的变化,可以使用一个名为 Vue.set 或者 this.$set 的方法来实现。这个方法会将新增的元素转换为响应式数据,并触发界面的重新渲染。

Vue.set(obj, propertyName/index, value) 方法接收 3 个参数:

  • obj:需要被添加属性的对象或数组
  • propertyName/index:需要添加的属性名或索引值
  • value:新添加的属性值

例如:

// 数组增加元素
this.$set(this.itemList, 1, newItem);

// 对象增加属性
this.$set(this.userInfo, 'name', '张三');

除此之外,Vue 还提供了一种 Watch 深度监测的方法,即开启深度监测。开启后,当你改变对象或数组中的属性时,Vue 将递归地遍历对象以追踪所有属性,并绑定Getter和Setter。这个选项默认关闭,由于其性能影响,我们不应该在所有属性都需要检测的情况下开启它,仅在必要时才考虑使用。开启方式如下:

var vm = new Vue({
  data: {
    users: [{ id: 0, name: 'Peter' }]
  },
  watch: {
    users: {
      deep: true,
      handler: function(newUsers, oldUsers) {
        console.log('users changed!')
      }
    }
  }
})

代码中的 { deep: true } 表示开启深度监测,handler 函数会在 users 数组发生变化时被调用。

总之,使用 Vue.set 和深度监控可以实现数组(对象)的监听,但是开销比较大。建议尽量使用数组提供的方法来代替直接对数组进行操作,以获得更好的性能和用户体验。

1、用 $set() 和 Object.assign() 方法可以给对象增加响应式数据
2、数组的 pop(),push() 等方法可以实现响应式 主要是因为 Vue 使用了特殊的技巧来拦截数组的变异方法(mutation methods)。

Vue 的响应式系统在创建响应式对象时会将数组的原型进行改写,并劫持了数组变异方法,比如 push()pop()shift()unshift()splice()sort()reverse() 等。当调用这些数组方法时,Vue 能够观察到数组的变化,并通知相关的依赖进行更新。

具体的实现原理如下:

  1. 当创建一个响应式数组时,Vue 会将数组的原型指向一个改写过的数组原型 Array.prototype
  2. 改写过的数组原型包含了对数组变异方法的重新定义,以使它们能够触发依赖更新。
  3. 当使用 push() 方法向数组中添加新元素时,Vue 能够捕捉到这个变化,并在内部执行相应的操作:
    • 添加新元素到数组中。
    • 将新元素设置为响应式,并进行依赖追踪。
    • 触发视图更新,重新渲染包含该数组的组件。

通过以上步骤,Vue 实现了对数组变异方法的拦截与处理,使得数组的变化能够被响应式系统检测到并进行相应的更新操作,从而实现了数组的响应式。

需要注意的是,Vue 的这种拦截和劫持机制只能处理使用变异方法改变的数组数据,对于直接通过索引修改数组元素或者改变数组长度的操作,Vue 是无法检测到的。为了解决这个问题,Vue 提供了一些特殊的方法来处理这类情况,比如使用 Vue.set 或者替换整个数组来触发更新等方式。

35、路由 history 原理,服务端如何配置?

路由 history 模式通过 HTML5 的 History API 实现,它使用 window.history.pushState() 方法动态改变浏览器的当前 url,同时也是监听 popstate 事件来更新页面视图。

与 hash 模式不同,history 模式可以更改 URL 中的 pathname 和 search 字段,而不会在最后添加一个 # 符号。例如,对于 URL http://example.com/path/user,如果用户点击了链接/update,浏览器将会发送一个跳转请求,并把显示路径更改为 http://example.com/path/update。

服务端需要根据实际情况进行配置。当在服务端启用历史模式时,任何链接都应指向主页。否则,当用户在浏览器中输入 URL 时,服务器将返回 404 错误,这是因为服务端不知道如何处理该 URL。

配置方式分为以下两种:

  1. 使用服务器框架。如果在使用一些服务器框架(例如 Express)来构建你的应用程序,则需要捕获到所有的路由并渲染 index.html 文件。这个文件将包含您的应用程序,当应用程序启动时,它将自动渲染正确的组件作为响应。
const express = require('express')
const path = require('path')

const app = express()

app.use(express.static(path.resolve(__dirname, 'public')))

app.get('*', (req, res) => {
  res.sendFile(path.resolve(__dirname, 'public', 'index.html'))
})

app.listen(3000)
  1. 配置 Nginx 服务器。另一种是使用 Nginx 或其他支持 Rewrite 规则的 web 服务器来配置。
location / {
    try_files $uri /index.html;
}

以上示例是在 Nginx 配置文件中添加了一个转发规则,将所有请求重定向到 index.html 文件并让 Vue Router 接管路由。

总之,路由 history 模式通过 HTML5 的 History API 实现,它可以更改 URL 的 pathname 和 search字段。服务端需要根据实际情况进行配置,比如使用服务器框架或 Nginx 等服务器,以捕获到所有的路由并渲染 index.html 文件,并让 Vue Router 接管路由。

36、关于 Tree.js 的教程,以下是一个基本的介绍:

安装和引入 Tree.js 库:
首先需要安装 Tree.js 库并将其引用到你的网站上。可以在官方网站下载最新版的库文件或者使用 NPM 安装,然后在 HTML 页面中添加引用。

创建渲染器(Renderer) 和场景(Scene) 对象:
使用 Tree.js 创建一个 Renderer(渲染器)对象,这个对象将负责把 3D 场景布置在浏览器中。同时创建一个 Scene(场景)对象作为一个容器,把所有需要呈现的 3D 对象都放在其中。

添加相机(Camera):
添加 Camera(相机)对象即可确定场景视角。将相机放置在一个合适的位置以便捕捉想要呈现的场景。

创建和添加几何图形(Geometry):
Tree.js 提供了许多内置几何模型类,例如 BoxGeometry(立方体几何)、SphereGeometry(球体几何)等,您也可以使用自定义几何模型。然后可以使用材质来设置图形的颜色纹理等。

光源和阴影:
在 Tree.js 中,有不同类型的光源,包括环境光、点光源、半球光、方向光、聚光等。在场景中添加适当的光源可以增强图形的真实感,并且还可以使用阴影,让图形看起来更加立体。

动画和交互:
Tree.js 支持各类交互和动画行为,例如旋转、移动、缩放、改变颜色等。同时,还支持事件处理程序,可以监听用户输入、键盘等。

渲染:
最后,调用 Renderer 对象的 Render 方法即可将整个 3D 场景内部渲染出来。

这里只是对 Tree.js 的一个简单介绍,想要深入学习还需要参考相关文档和示例代码。

步骤:

Tree.js 是一个基于 WebGL 的 3D 图形开发的 JavaScript 库。它提供了丰富的工具和函数,使您可以轻松地创建复杂的动画和交互式 3D 应用程序。下面是 Tree.js 的详细教程:

  1. 安装和引入 Tree.js

首先,在 HTML 文件中引入 Tree.js 库

<script src="https://cdn.rawgit.com/mrdoob/three.js/master/build/three.min.js"></script>

如果您的项目使用 npm 包管理器管理依赖,也可以通过以下命令安装 Three.js:

npm install three

然后在你的项目中,以如下方式引用:

import * as THREE from 'three';
  1. 创建场景

在开始创建 3D 场景之前,需要先初始化一个 Scene 对象,指定背景颜色等属性。

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf4f6f9);
  1. 添加相机

相机是观察者看到 3D 场景的窗口。在 Three.js 中有许多类型的相机,例如透视相机、正交相机等,可以根据需求选择一个合适的相机。

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 5);

// 或者使用 OrthographicCamera:
const camera = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 1, 1000);
camera.position.set(0, 0, 5);
  1. 添加渲染器

渲染器负责通过 WebGL 技术将场景呈现到作为 HTML 元素的画布中。

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement); // 将 canvas 元素添加到 document.body 中
  1. 添加几何体和材质

在 Three.js 中创建 3D 对象需要使用 Geometry(几何体)和Material(材质),它们决定了对象的形状和外观。

// 创建立方体几何体和基本红色材质
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });

// 创建网格对象
const cube = new THREE.Mesh(geometry, material);

// 将网格对象添加到场景中
scene.add(cube);
  1. 添加光源

当场景变得复杂

37、v-model 指令的修饰符

​ .nummber 自动将用户的输入值转为数值类型
​ .trim 自动过滤用户输入的首民空白字料
​ .Lazy 只有在输入数据结束才会调用,不会在输入时调用

38、小程序代码的构成

pages 用来存放所有小程序的页面9
utils 用来存放工具性质的模块 (例如:格式化时间的自定义模块)
app.js 小程序项目的入口文件
app.json 小程序项目的全局配置文件
app.wxss 小程序项目的全局样式文件
project.config.json 项目的配置文件
sitemap.json 用来配置小程序及其页面是否允许被微信索引

39.小程序页面的组成部分

小程序官方建议把所有小程序的页面,都存放在 pages 目录中,以单独的文件夹存在
其中,每个页面由 4 个基本文件组成,它们分别是:
1、js 文件 (页面的脚本文件,存放页面的数据、事件处理函数等)
2、 json 文件(当前页面的配置文件,配置窗口的外观、表现等)
3、.wxml 文件(页面的模板结构文件)
4、.wxss 文件(当前页面的样式表文件)

能够使用WXML模板语法染页面结构
wx:if、wx:elif、wx:else、hidden、wx:for、wx:key
能够使用wXSS样式美化页面结构
rpx 尺寸单位、@import 样式导入、全局样式和局部样式
@能够使用 app.json 对小程序进行全局性配置
pages、window、tabBar、style@
能够使用 page,json 对小程序页面进行个性化配置
对单个页面进行个性化配置、就近原则
能够知道如何发起网络数据请求
wx.request() 方法、onLoad()事件

40、微信小程序开发流程:

注册小程序账号:在微信公众平台注册一个小程序账号,注册完成后,可以获取到 AppID 和 AppSecret,这是后续开发的必备信息。
创建小程序项目:使用微信开发者工具创建一个新的小程序项目,填写小程序的名称、AppID 等基本信息,并选择对应的开发语言(小程序支持多种语言开发)。
开发页面:在微信开发者工具中开发小程序页面,包括页面的布局、样式、交互逻辑等。
开发接口:在服务器端开发小程序需要的接口,比如获取数据、提交表单等功能。
测试和调试:开发完成后需要进行测试和调试,确保功能正常。
提交审核:当开发完成后,需要提交审核,经过审核后即可发布到小程序平台供用户使用。
公众号网页开发流程:
注册公众号账号:在微信公众平台注册一个公众号账号,注册完成后,可以获取到 AppID 和 AppSecret,这是后续开发的必备信息。
开发网页:在本地开发网页,包括页面的布局、样式、交互逻辑等。
接入微信JS-SDK:通过引入微信JS-SDK,可以在网页中调用微信的API,比如获取用户信息、分享等。
进行网页授权:通过网页授权,可以获取用户的微信身份信息,实现个性化的功能。
部署网页:将网页部署到服务器上,并配置服务器地址、令牌(Token)、消息加密密钥(EncodingAESKey)等信息。
验证服务器:配置完成后,微信公众平台会向服务器发送一个验证请求,需要在服务器中进行验证,并返回验证信息。
测试和调试:部署完成后需要进行测试和调试,确保功能正常。
提交审核:当开发完成后,需要提交审核,经过审核后即可发布到公众号中供用户使用。
总体来说,微信小程序开发和公众号网页开发都需要涉及多个方面,包括前端、后端、数据库等,需要开发者具备一定的技术能力和经验。同时,还需要对微信公众平台的规则和限制有一定的了解,确保开发的功能符合相关要求。

41、Vuex:

每一个?Vuex?应用的核心就是?store(仓库),它包含着你的应用中大部分的状态?(state)。 状态管理有5个核心:state、getter、mutation、action、module。

State 1、单一状态树,定义应用状态的默认初始值,页面显示所需的数据从该对象中进行读取。 2、Vuex?使用单一状态树,用一个对象就包含了全部的应用层级状态。它便作为一个“唯一数据源”而存在。这也意味着,每个应用将仅仅包含一个?store?实例。 3、单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。 4、不可直接对?state?进行更改,需要通过?Mutation?方法来更改。 5、由于?Vuex?的状态存储是响应式的,从?store?实例中读取状态最简单的方法就是在计算属性中返回某个状态:

Getter 1、可以认为是?store?的计算属性,对?state?的加工,是派生出来的数据。 2、就像?computed?计算属性一样,getter?返回的值会根据它的依赖被缓存起来,且只有当它的依赖值发生改变才会被重新计算。 3、可以在多组件中共享?getter?函数,这样做还可以提高运行效率。 4、在?store?上注册?getter,getter?方法接受以下参数: state,?如果在模块中定义则为模块的局部状态 5、getters,?等同于?store.getters

Mutation 1、Vuex中store数据改变的唯一方法就是mutation 2、通俗的理解,mutations?里面装着改变数据的方法集合,处理数据逻辑的方法全部放在?mutations?里,使数据和视图分离。

Action action?类似于?mutation?,不同在于: 1、action?提交的是?mutation,通过?mutation?来改变?state?,而不是直接变更状态。 2、action?可以包含任意异步操作。

Module 1、由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store?对象就有可能变得相当臃肿。 2、为了解决以上问题,Vuex?允许我们将?store?分割成模块(module)。每个模块拥有自己的?state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

42、Vuex

四大核心属性,以及一个子模块管理属性,state存放数据,Mutations同步修改state里面的数据,actions异步修改数据,但需要调用Mutations里面的方法修改,getters数据过滤器,对数据进行整理,还有一个modules,当数据过多和复杂时,将数据用模块化分开,在住文件中用modules引用

Leon:
简单回答

store,存储变量 mutation,发起变量更改,记录更改 action,发起异步变量更改,记录更改 getter,计算属性 module,模块划分

43、浏览器中的Event Loop是什么,它有什么作用?

答案:Event Loop是浏览器提供的一种机制,用于执行JavaScript代码。它主要作用在于监听任务队列,将其中挂起的任务分配到可用的线程上去执行。

44、请简述HTML5中nav标签的作用。

答案:nav标签用于定义导航栏,通常包含一个或多个链接,用户通过点击这些链接可以跳转到不同的页面或功能。

45、在Vue.js中,computed与watch有什么区别?

答案:computed属性会在其依赖数据发生改变时自动更新,而watch则需要手动监听数据的变化。computed适用于处理复杂的计算,并且数据源发生变化时需要经过一些额外的操作;而watch则适用于监听某个特定数据的变化,并且需要执行一些副作用操作(如发送网络请求)。

46、请简述React中Virtual DOM的作用。

答案:Virtual DOM是React中的一种机制,它是一个轻量级的JavaScript对象,用于表示真实DOM节点的结构。由于Virtual DOM比真实DOM更容易操作和修改,所以React会首先对Virtual DOM进行操作并计算出最小变化集合,然后再将这些变化同步到真实DOM节点上,从而减少不必要的页面重绘。

47、如何在JavaScript中判断一个变量是否是数组?

答案:可以使用Array.isArray()方法来判断一个变量是否是数组类型:

复制代码
  var arr = [];
  if (Array.isArray(arr)) {
    console.log("arr is an array");
  }
48、简述ES6中let和const关键字的作用及区别。

答案:let和const都是块级作用域的声明方式,用于替代var关键字。它们的主要区别在于const声明的变量必须被初始化,并且在后续的操作中不能被重新赋值;而let声明的变量则可以被重新赋值。

49、在JavaScript中,如何遍历一个对象的属性?

答案:可以使用for-in循环来遍历一个对象的所有可枚举属性:

复制代码
  var obj = {a: 1, b: 2, c: 3};
  for (var prop in obj) {
    console.log(prop + ": " + obj[prop]);
  }
50、请简述React中props与state的区别。

答案:props和state都是React组件中用于存储组件数据的属性,但它们的作用略有不同。props通常是由父组件向子组件传递的静态数据,子组件无法修改它们,只能读取。而state则是组件内部维护的动态数据,只能通过setState()方法来修改,但这些修改会触发组件的重新渲染。

51、如果希望在React应用中使用Ajax请求,请列出至少两种可以实现该功能的库或插件。

答案:常用于在React应用中进行Ajax请求的第三方库包括Axios、jQuery和Fetch等。

52、如何在JavaScript中创建自定义异常?

答案:通过Error对象可以轻松地创建自定义异常,可以像下面这样定义并抛出一个名为"CustomError"的异常:

复制代码
  function CustomError(message) {
    this.message = message;
    this.name = "CustomError";
  }
    throw new CustomError("Something went wrong");
53、在JavaScript中如何判断一个变量是否为null或undefined?

答案:需要使用“==null”运算符来检测一个变量是否为null或undefined,代码如下:

复制代码
  var a;
  if (a == null) {
    console.log("a is null or undefined");
  }
54、如何让一个DOM元素在点击时触发某个事件?

答案:可以使用addEventListener()方法来在DOM元素上注册事件监听器:

复制代码
  var btn = document.querySelector("#myBtn");
  btn.addEventListener("click", function() {
    // do something
  });
55、在React中,shouldComponentUpdate()生命周期方法的作用是什么?

答案:shouldComponentUpdate()方法用于控制组件是否需要重新渲染。如果该方法返回false,则React将不会重新渲染该组件,从而提高页面的性能。

56、如何在JavaScript中判断一个变量是否为NaN?

答案:可以使用isNaN()函数来判断一个变量是否为NaN:

复制代码
  var num = NaN;
  if (isNaN(num)) {
    console.log("num is NaN");
  }
57、在CSS中,请简述盒模型(Box Model)的概念。

答案:CSS中所有元素都被视为一个箱子,包含content、padding、border和margin四个部分。盒模型通过这四个部分来定义一个元素的大小、边距和边框等属性。

58、请简述React中的组件生命周期。

答案:React中的组件生命周期包括以下几个阶段:

Mounting(挂载):组件被创建并插入到DOM中;
Updating(更新):组件的state或props发生改变,需要重新渲染;
Unmounting(卸载):组件从DOM中移除;
Error Handling(错误处理):组件在渲染期间发生错误,需要进行错误处理。

59、在JavaScript中如何获取当前时间?

答案:可以使用Date对象来获取当前时间,代码如下:

复制代码
  var now = new Date();
  console.log(now.toDateString());
60、在React中,请简述Refs的作用。

答案:Refs是用于在React组件中获取DOM节点或组件实例的方法。它可以通过React.createRef()方法或回调函数的方式来创建,并且可以在组件中

61、什么是闭包?请举例说明。

答:闭包是指函数与其相关引用环境组合的一种特殊情况。简单来说,就是一个函数能够访问在函数定义时不在其作用域内的变量。例如:

javascript
复制代码
function outerFunction() {
  let number = 10;

  function innerFunction() {
    console.log(number);
  }

  return innerFunction;
}

let closure = outerFunction();

closure(); // 输出 10
62、如何判断一个变量是否为数组类型?

答:使用Array.isArray()方法。

61、常见的HTTP状态码有哪些?请分别说明各自的含义。

答:常见的HTTP状态码有以下几个:

200 OK 表示请求成功。
201 Created 表示请求成功并创建了新资源。
204 No Content 表示请求成功但无返回内容。
400 Bad Request 表示客户端发送的请求有误。
401 Unauthorized 表示客户端需要身份验证才能访问该资源。
403 Forbidden 表示服务器拒绝提供该资源。
404 Not Found 表示请求的资源不存在。
500 Internal Server Error 表示服务器发生错误。

62、如何检测一个对象是否包含某个属性?

答:使用in运算符或者Object.hasOwnProperty()方法。

63、解释一下JavaScript中的事件循环。

答:事件循环指的是JavaScript在执行代码时处理异步操作的一种机制。当有异步操作时,它们会被放到事件队列中,等待JavaScript引擎空闲时去处理。事件循环由三个部分组成:调用栈、事件队列和微任务队列。当调用栈为空时,JavaScript开始处理事件队列中的事件。处理完每一个事件后,JavaScript还会执行微任务队列中所有的任务。

64、请说明ES6中let和const关键字的区别。

答:let和const都用于声明变量,但是它们之间有几个重要的区别:

let声明的变量可以修改,const声明的变量不能修改。
let声明的变量在同一个作用域内不能重复定义,const声明的变量也不能重复定义,而且一旦赋值就不能改变。
使用let声明的变量没有定义提升,而使用const声明的变量也没有定义提升,并且必须在声明的同时就进行赋值。
const声明的变量必须在初始化时赋值,否则会抛出异常。

65、jQuery中的on()和bind()方法有什么区别?

答:bind()方法是jQuery早期的事件绑定方法,而on()方法是新版jQuery引入的更强大的事件绑定方法。相比之下,on()方法具有以下优势:

on()方法支持多个事件同时绑定到同一个元素上,而bind()方法只能绑定一个。
on()方法可以为后添加的元素绑定事件,而bind()方法不支持这种方式。
on()方法还可以使用命名空间对事件进行管理。

66、JavaScript中的typeof操作符会返回哪些值?请说明各自的含义。

答:JavaScript中的typeof操作符可以返回以下几种值:

“undefined” 表示该变量未定义。
“boolean” 表示该变量是布尔类型。
“number” 表示该变量是数值类型。
“string” 表示该变量是字符串类型。
“object” 表示该变量是对象类型或null类型(注意,null被认为是一个空对象)。
“function” 表示该变量是函数类型。

67、请解释一下浏览器的同源策略。

答:浏览器的同源策略指的是一个网页的脚本只能读取来自同一来源的数据。同源是指协议、域名和端口号都相同。如果两个页面的URL有任何一个部分不同,就会被视为不同源。同源策略的目的是保护用户隐私和安全。

68、请解释一下JavaScript中的this关键字。

答:在JavaScript中,this关键字引用的是当前执行代码所处的上下文对象。具体来说,this指向的是函数调用时的环境,取决于函数的调用方式。如果在全局作用域中使用this,则它会指向全局对象window;如果在独立的函数中使用this,则它会指向全局对象或undefined;如果在对象的方法中使用this,则它会指向该对象;如果使用call、apply或bind方法,可以手动设置this的值。

69、请解释一下JavaScript中的原型继承。

答:JavaScript中的原型继承是通过原型链实现的一种继承机制。每个JavaScript对象都有一个指向另一个对象的内部链接,这个链接就是该对象的原型。如果在对象上访问一个属性或方法时,如果该对象本身不存在该属性或方法,JavaScript会沿着原型链查找,直到找到具有该属性或方法的对象为止。可以使用Object.create()方法来创建一个新对象并将其原型设为某个现有对象。

70、请解释一下CSS中的盒模型。

答:CSS中的盒模型指的是元素在文档流中所占据的空间,包括元素的内容区域、内边距、边框和外边距等。根据标准盒模型和IE盒模型的区别,盒模型可以分为两种不同的模式,即标准(content-box)和IE(border-box)盒模型。标准盒模型计算元素尺寸时不包含内边距和边框的宽度,而IE盒模型将内边距和边框的宽度纳入了元素的尺寸计算。

71、什么是小程序?

答:小程序是一种轻量级的应用程序,可以在移动设备上直接使用,无需下载安装。它结合了Web开发的灵活性和原生应用的性能,提供了一种更好的用户体验。

72、小程序有哪些优势?

答:小程序具有以下优势:

  • 无需下载安装,用户可以直接打开使用。
  • 跨平台支持,可以在多个操作系统(如iOS、Android)上运行。
  • 快速加载,提供更好的用户体验。
  • 提供丰富的API和能力,如地理位置、文件操作、相机等。
  • 方便的分享和传播,可以通过二维码等方式进行推广。
73、小程序和传统网页开发有何不同?

答:小程序和传统网页开发相比有几个区别:

  • 小程序基于特定的框架和开发规范,如微信小程序基于WXML、WXSS和JavaScript进行开发。
  • 小程序拥有更多原生能力和API,如访问硬件设备、发送消息等。
  • 小程序有独立的运行环境,相比网页更加封闭,对域名和资源的访问有一定限制。
74、请说明小程序的页面生命周期及其执行顺序。

答:小程序的页面生命周期包括onLoad、onShow、onReady、onHide、onUnload等。执行顺序如下:

  • onLoad:页面加载时触发,仅执行一次。
  • onShow:页面显示时触发,每次打开页面都会执行。
  • onReady:页面初次渲染完成时触发,仅执行一次。
  • onHide:页面隐藏时触发,当用户切换到其他页面时执行。
  • onUnload:页面卸载时触发,如返回上一页或小程序关闭。
75、如何实现小程序之间的通信?

答:小程序之间的通信可以通过以下方式实现:

  • 路由跳转:使用navigator组件进行页面跳转,并传递参数。
  • Storage API:使用Storage API在本地存储数据,在不同页面之间进行传递。
  • 全局变量:可以将数据保存在App全局对象中,在不同页面之间共享数据。
  • 事件监听:使用事件总线模式,通过触发事件和监听事件来实现页面间的通信。
76、什么是uni-app?

答:uni-app是一个基于Vue.js的开发框架,可以同时构建多个平台的应用程序,包括iOS、Android、Web、微信小程序等。通过uni-app,开发者可以使用一套代码编写跨平台的应用,并具备原生应用的性能和体验。

77、uni-app有哪些主要特点?

答:uni-app具有以下主要特点:

  • 一套代码,多端运行:开发者只需编写一次代码,即可同时发布到多个平台。

  • 原生级性能:通过底层的渲染优化和性能调优,uni-app实现了原生应用的性能表现。

  • 开发门槛低:基于Vue.js进行开发,对于熟悉Vue.js的开发者来说上手较快。

  • 生态丰富:uni-app拥有庞大的插件市场和活跃的社区支持,提供了丰富的插件和组件库。

  • 跨平台支持:可以使用同一份代码开发多个平台的应用,提高开发效率。

  • 组件化开发:采用Vue.js的组件化开发思想,可以快速构建复杂的UI界面。

  • 内置原生能力:具备访问设备功能、调用原生API等能力。

  • 良好的性能表现:通过编译生成原生代码,提升应用的运行效率。

  • 大量插件和扩展:拥有丰富的插件和扩展库,方便开发各种功能和特效。

78、uni-app的工作原理是什么?

答:uni-app的工作原理可以简述为:使用前端技术栈(Vue.js + Webpack)开发页面,通过编译器将代码转换为各个平台所需的代码,再通过各个平台的运行时环境将代码渲染为可执行的应用。

79、如何在uni-app中进行页面跳转?

答:在uni-app中,可以使用navigateTo、redirectTo、switchTab等API进行页面跳转。同时也可以通过标签结合Vue Router实现页面间的导航。

80、uni-app支持哪些第三方插件和组件库?

答:uni-app支持大量的第三方插件和组件库,包括但不限于以下:

  • vant-weapp:基于微信小程序封装的轻量级组件库。
  • uView UI:一款基于uni-app的高质量UI框架。
  • uni-ui:uni-app官方推荐的组件库,提供了丰富的UI组件。
  • Lodash:一个JavaScript实用工具库,提供了许多常用的函数和方法。
  • axios:一个基于Promise的HTTP客户端,用于发送异步请求。
81、uni-app的页面生命周期有哪些?

答:uni-app的页面生命周期包括onLoad、onShow、onReady、onHide、onUnload等。与小程序的页面生命周期类似。

82、如何进行uni-app之间的页面通信?

答:uni-app之间的页面通信可以采用以下方式:

  • 路由跳转:使用uni-app提供的路由API进行页面跳转,并可传递参数。
  • Vuex状态管理:通过Vuex实现不同页面之间的数据共享和通信。
  • Event Bus事件总线:创建一个全局的事件总线对象,不同页面通过发布-订阅模式进行通信。
  • Storage API:使用uni-app提供的Storage API在本地存储数据,在不同页面之间进行传递。
83、uni-app如何实现原生功能调用?

答:uni-app通过封装了一套API来实现原生功能的调用。可以使用uni-app提供的API,如拍照、获取地理位置、调用相应等设备能力。也可以使用插件或原生扩展来实现对原生功能的调用。

84、VueX 使用教程
  1. 安装 Vuex

需要使用 npm 安装 Vuex ,在终端输入如下命令:

npm install vuex --save
  1. 创建一个 store

在项目中引入 Vuex 后,在 src 目录下新建一个 vuex 文件夹,然后在其中创建一个 index.js 文件,代码如下:

// 引入 Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 创建仓库
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  getters: {
    getCount: state => {
      return state.count
    }
  }
})

// 导出 store
export default store

在这段代码中,我们创建了一个名为 store 的 Vuex 实例,并编写了 state、mutations、actions、getters 四种选项来管理应用的状态。

  • state:存储应用的数据状态。
  • mutations:类似于事件,更改 state 中的值,是 Vuex 唯一允许修改 state 的方法。
  • actions:用于处理异步操作,提交 mutation,通常在 actions 中进行异步请求获取数据等操作,再调用 mutations 中的方法更新 state。actions 可以包含任意异步操作,但不能直接操作 state,必须通过 mutations 等同步操作来修改状态。
  • getters:用于获取数据,在组件内部使用 computed 对象计算属性时,可以通过 getters 获取 store 中的计算结果。
  1. 在 main.js 中引入 store

在 src 目录下找到 main.js 文件,将以下代码添加到文件开头:

import Vue from 'vue'
import App from './App.vue'
import store from './vuex'

new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

这段代码将 store 引入了全局中,并在 Vue 实例中注册该 store,这样就使整个应用程序都可以访问到 store 中的状态和其他特性。

  1. 在组件中使用 Vuex

在组件中使用 Vuex 需要做如下两步操作:

  • 引入 mapState、mapMutations、mapActions、mapGetters 等方法;
  • 将 state、mutations、actions、getters 映射到当前组件中;

下面是一个基本的组件示例,演示如何在组件中使用 state 和 mutations。

<template>
  <div class="counter">
    <h1>{{ count }}</h1>
    <button @click="increment">add</button>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'

export default {
  name: 'Counter',
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapMutations(['increment'])
  }
}
</script>

在模板中,我们使用 {{ count }} 显示 state 中存储的数据,使用 @click="increment" 触发 mutations 中的 increment 方法来更新状态。在 scripts 中,我们使用 mapState 将 count 映射到 computed 中,使用 mapMutations 将 increment 映射到 methods 中。

  1. 在组件中使用 actions 和 getters

在组件中使用 actions 和 getters 的方法与上述使用 state 和 mutations 的方法类似,在此不再赘述。以下是示例代码:

<script>
import { mapActions, mapGetters } from 'vuex'

export default {
  name: 'Counter',
  computed: {
    ...mapGetters(['getCount'])
  },
  methods: {
    ...mapActions(['incrementAsync'])
  }
}
</script>

在模板中,我们使用 {{ getCount }} 显示 getters 中计算的数据,使用 @click="incrementAsync" 触发 actions 中的 incrementAsync 方法来更新状态。在 scripts 中,我们使用 mapGetters 将 getCount 映射到 computed 中,使用 mapActions 将 incrementAsync 映射到 methods 中。

总结:

Vuex 是一个用于 Vue.js 应用程序的状态管理库,它允许我们集中式地处理应用程序的所有状态。通过创建和修改 store 实例中的 state、mutations、actions、getters 等选项,我们可以方便地管理应用程序的状态,并在组件中轻松使用这些状态。

真实实例

1、vue的双向绑定原理是什么?里面的关键点在哪里?

Vue的双向绑定原理是通过数据劫持和发布-订阅模式实现的。关键点在于通过Object.defineProperty()方法来劫持对象的属性访问,当数据发生变化时,触发相应的监听器,并更新视图。

2、实现水平垂直居中的方式?

实现水平垂直居中的方式有多种。其中一种常用的方式是使用Flex布局,设置容器为display: flex;justify-content: center;align-items: center;

3、常用伪元素有哪一些?

常用的伪元素有::before::after,它们在元素的内容前后添加虚拟元素,可以通过CSS样式进行修饰。

4、移动端如何适配不同屏幕尺寸?

移动端适配可以采用响应式布局或者使用媒体查询等方式进行适配。常见的做法是使用百分比、rem单位或者CSS框架(如Bootstrap)来实现灵活布局和缩放效果。

5、本地存储有哪一些?他们三者有什么区别?

常见的本地存储方式有Cookie、LocalStorage和SessionStorage。它们的区别在于作用域、存储容量和数据有效期等方面。Cookie存储在客户端浏览器中,有大小限制且过期时间可以设置;LocalStorage和SessionStorage则是HTML5提供的Web Storage API,存储在浏览器中,LocalStorage没有过期时间限制且容量较大,而SessionStorage在会话结束后自动清除。

6、JS的数据类型?如何判断js的数据类型?

JS的数据类型包括基本数据类型(Number、String、Boolean、Null、Undefined、Symbol)和引用数据类型(Object)。可以使用typeof操作符判断基本数据类型,使用instanceof操作符或Object.prototype.toString.call()方法判断引用数据类型。

7、说一下ES6的新特性有哪些?

ES6的新特性包括箭头函数、解构赋值、模板字符串、let和const关键字、默认参数、扩展运算符、Promise、类与继承、模块化等。

es7 新特性

1.新增了includes的数组查找方法
使用场景:确认数组中有我们需要查找的内容



在ES7之前,我们接触到的数组方法可以通过filter或者indexOf

如果使用filter 可以直接筛选之后输出,如果是indexOf则根据返回值来判断是否在当前数组中。

如果返回值 >= 0 则证明是有值的,如果是负值 则没有包含需要查询的数值。



 如果查询的数值没有,返回undefined



 但是indexOf的缺陷也很明显





 没错,不能检测NaN

那么ES7横空出世的includes正好可以解决这个问题



相对于indexOf返回的数字,includes返回的是布尔值



2.取幂运算符 **
当我们需要将一个数进行乘方时,通常使用Math库中的pow()函数。但是,ES7中提供了新的幂运算符(**),使得我们可以更方便地进行乘方运算。

语法格式为:

base ** exponent

其中,base表示底数,exponent表示指数。例如,23次方可以表示为2 ** 3,结果为8需要注意的是,幂运算符的优先级高于加减乘除运算符,因此需要使用括号来控制运算顺序。例如,(2 + 3) ** 2的结果为25,而2 + 3 ** 2的结果为17
8、let、const、var三者有什么区别?

三者都用于声明变量,但区别在于作用域和可修改性。var声明的变量具有函数作用域,而letconst声明的变量具有块级作用域。另外,letvar声明的变量可以被修改,而const声明的变量是常量,不可被修改。

9、数组去重有哪些办法?

除了ES6新增的构造函数Set具有天然去重功能和数组迭代外,常见的数组去重方法还包括使用indexOf()方法、利用对象属性唯一性等方式进行去重。

10、说一下深拷贝和浅拷贝,如何自己实现一个深拷贝?

深拷贝是指在内存中完全复制一个对象,包括其所有的属性和嵌套对象,而浅拷贝只复制对象的引用。实现深拷贝可以通过递归遍历对象的属性,并创建新的对象来实现。

11、Vue的生命周期有哪一些?说一下它们每个阶段做什么操作?

Vue的生命周期包括:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy和destroyed。在beforeCreate阶段,Vue实例被初始化,数据观测和事件配置之前;在created阶段,Vue实例完成数据观测、属性和方法的运算;在beforeMount阶段,模板编译成虚拟DOM之前;在mounted阶段,虚拟DOM挂载到实际DOM后;在beforeUpdate阶段,数据更新之前;在updated阶段,数据更新完成;在beforeDestroy阶段,开始销毁Vue实例之前;在destroyed阶段,Vue实例销毁完成。

12、组件通讯方式有哪一些?

组件通讯方式包括父子组件通讯(props和$emit)、兄弟组件通讯(通过共同的父组件传递数据或使用事件总线)和跨层级通讯(使用Vuex或provide/inject)。

13、Vuex有几个属性及作用?

Vuex有state、getters、mutations、actions和modules等属性。state是存储共享状态的地方,getters用于获取state中的数据,mutations用于修改state的数据(同步操作),actions用于处理异步操作或提交mutations,而modules可以将store分割成模块化的部分。

14、Vue的监听属性和计算属性有什么区别?

Vue的监听属性通过watch选项来实现,可监听数据的变化并执行相应的回调函数;而计算属性通过computed选项来定义,它根据依赖的数据自动计算得出结果,计算属性具有缓存机制,在依赖数据未改变时直接返回缓存结果。

15、说一下防抖和节流。怎么实现?

防抖和节流是解决频繁触发事件时的性能优化手段。防抖是在一定时间内只执行最后一次操作,常用的实现方式是使用setTimeout延迟执行;节流是在一定时间间隔内只执行一次操作,常用的实现方式是使用定时器控制执行频率。

16、Vue的导航守卫有哪一些?
Vue的导航守卫包括以下几种:
  1. beforeEach:在路由切换开始之前调用,可以用来进行全局的前置守卫验证。

  2. beforeResolve:在导航被确认之前调用,可以用来进行延迟加载的路由组件的初始化。

  3. afterEach:在每次路由切换完成之后调用,可以用来进行全局的后置处理操作。

  4. 路由独享的守卫:

    • beforeEnter:在单个路由配置中定义,只对该路由有效。
  5. 组件内的守卫:

    • beforeRouteEnter:在进入路由对应的组件前调用,可以访问不到组件实例,需要使用 next 方法来传递组件实例给回调函数。

    • beforeRouteUpdate:在路由参数发生变化时调用,例如从 /user/1 导航到 /user/2。

    • beforeRouteLeave:在离开当前路由时调用,可以用来确认是否离开或取消导航。

这些导航守卫可以让你控制路由的跳转行为,在路由切换前进行验证、权限控制或其他必要的操作。

分模块面试题

一、HTML 篇

1. 简述一下你对 HTML 语义化的理解?

用正确的标签做正确的事情。

html 语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析;即使在没有样式 CSS 情况下也以一种文档格式显示,并且是容易阅读的;

搜索引擎的爬虫也依赖于 HTML 标记来确定上下文和各个关键字的权重,利于 SEO;

使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。

2. 标签上 title 与 alt 属性的区别是什么?

alt 是给搜索引擎识别,在图像无法显示时的替代文本;

title 是关于元素的注释信息,主要是给用户解读。

当鼠标放到文字或是图片上时有 title 文字显示。(因为 IE 不标准)在 IE 浏览器中 alt 起到了 title 的作用,变成文字提示。

在定义 img 对象时,将 alt 和 title 属性写全,可以保证在各种浏览器中都能正常使用。

3. iframe的优缺点?
优点
  1. 页面和程序分离,几乎不会受到外界任何js或者css的影响, 便于使用
  2. 可以通过iframe嵌套通用的页面, 提高代码的重用率, 比如页面的头部样式和底部版权信息
  3. 重新加载页面时, 不需要重载iframe框架页的内容, 增加页面重载速度.
  4. iframe可以解决第三方内容加载缓慢的问题.

解决加载缓慢的第三方内容如图标和广告等的加载问题

Security sandbox

并行加载脚本

缺点:
  1. 会产生很多页面,不容易管理
  2. iframe框架的内容无法被搜索引擎捕获, 所以iframe不适用于首页
  3. iframe兼容性较差
  4. iframe有一定的安全风险
  5. iframe会阻塞主页面的Onload事件

6、即时内容为空,加载也需要时间

没有语意

4. href 与 src?
  • href (Hypertext Reference)指定网络资源的位置,从而在当前元素或者当前文档和由当前属性定义的需要的锚点或资源之间定义一个链接或者关系。(目的不是为了引用资源,而是为了建立联系,让当前标签能够链接到目标地址。)

  • src source(缩写),指向外部资源的位置,指向的内容将会应用到文档中当前标签所在位置。

  • href与src的区别

  1. 请求资源类型不同:href 指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的联系。在请求 src 资源时会将其指向的资源下载并应用到文档中,比如 JavaScript 脚本,img 图片;

  2. 作用结果不同:href 用于在当前文档和引用资源之间确立联系;src 用于替换当前内容;

  3. 浏览器解析方式不同:当浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。

二、CSS 篇

1、介绍一下 CSS 的盒子模型?

有两种, IE 盒子模型、W3C 盒子模型;

盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);

区 别: IE 的 content 部分把 border 和 padding 计算了进去;

2. css 选择器优先级?

!important > 行内样式(比重1000)> ID 选择器(比重100) > 类选择器(比重10) > 标签(比重1) > 通配符 > 继承 > 浏览器默认属性

3、垂直居中几种方式?
  1. 单行文本: line-height = height

  2. 图片: vertical-align: middle;

  3. absolute 定位: top: 50%;left: 50%;transform: translate(-50%, -50%);

  4. flex: display:flex;margin:auto

4、简明说一下 CSS link 与 @import 的区别和用法?
  1. link 是 XHTML 标签,除了加载CSS外,还可以定义 RSS 等其他事务;@import 属于 CSS 范畴,只能加载 CSS。

  2. link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载。

  3. link 是 XHTML 标签,无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不支持。

  4. link 支持使用 Javascript 控制 DOM 去改变样式;而@import不支持。

5、rgba和opacity的透明效果有什么不同?

opacity 会继承父元素的 opacity 属性,而 RGBA 设置的元素的后代元素不会继承不透明属性。

6、display:none和visibility:hidden的区别?
  • display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。

  • visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。

  • display:none 会导致页面回流和重绘,visibility:hidden 只会导致重绘

7、position的值, relative和absolute分别是相对于谁进行定位的?
  • relative:相对定位,相对于自己本身在正常文档流中的位置进行定位。

  • absolute:生成绝对定位,相对于最近一级定位不为static的父元素进行定位。

  • fixed: (老版本IE不支持)生成绝对定位,相对于浏览器窗口或者frame进行定位。

  • static:默认值,没有定位,元素出现在正常的文档流中。

  • sticky:生成粘性定位的元素,容器的位置根据正常文档流计算得出。

8、画一条0.5px的直线?

考查的是css3的transform

height: 1px;

transform: scale(0.5);
9、calc, support, media各自的含义及用法?

1、@support 主要是用于检测浏览器是否支持CSS的某个属性,其实就是条件判断,如果支持某个属性,你可以写一套样式,如果不支持某个属性,你也可以提供另外一套样式作为替补。

2、calc() 函数用于动态计算长度值。 calc()函数支持 “+”, “-”, “*”, “/” 运算;

3、@media 查询,你可以针对不同的媒体类型定义不同的样式。

10、1rem、1em、1vh、1px各自代表的含义?
1、rem

rem是全部的长度都相对于根元素元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。

2、em

子元素字体大小的em是相对于父元素字体大小

元素的width/height/padding/margin用em的话是相对于该元素的font-size

3、vw/vh

全称是 Viewport Width 和 Viewport Height,视窗的宽度和高度,相当于 屏幕宽度和高度的 1%,不过,处理宽度的时候%单位更合适,处理高度的 话 vh 单位更好。

4、px

px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。

一般电脑的分辨率有{19201024}等不同的分辨率

19201024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素

11、画一个三角形?

这属于简单的css考查,平时在用组件库的同时,也别忘了原生的css

<div class="a"></div>

.a {

  width: 0;

  height: 0;

  border-width: 100px;

  border-style: solid;

  border-color: transparent #0099CC transparent transparent;

  transform: rotate(90deg); /*顺时针旋转90°*/

}

三、HTML / CSS 混合篇

1、HTML5、CSS3 里面都新增了那些新特性?
HTML5
  • 新的语义标签

  • article 独立的内容。

  • aside 侧边栏。

  • header 头部。

  • nav 导航。

  • section 文档中的节。

  • footer 页脚。

  • 画布(Canvas) API

  • 地理(Geolocation) API

  • 本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失;

  • sessionStorage 的数据在浏览器关闭后自动删除

  • 新的技术webworker, websocket, Geolocation

  • 拖拽释放(Drag and drop) API

  • 音频、视频API(audio,video)

  • 表单控件,calendar、date、time、email、url、searc

CSS3
  • 2d,3d变换

  • Transition, animation

  • 媒体查询

  • 新的单位(rem, vw,vh 等)

  • 圆角(border-radius),阴影(box-shadow),对文字加特效(text-shadow),线性渐变(gradient),旋转(transform)transform:rotate(9deg) scale(0.85,0.90) translate(0px,-30px) skew(-9deg,0deg);//旋转,缩放,定位,倾斜

  • Rgba

2、BFC 是什么?

BFC 即 Block Formatting Contexts (块级格式化上下文),它属于普通流,即:元素按照其在 HTML 中的先后位置至上而下布局,在这个过程中,行内元素水平排列,直到当行被占满然后换行,块级元素则会被渲染为完整的一个新行,除非另外指定,否则所有元素默认都是普通流定位,也可以说,普通流中元素的位置由该元素在 HTML 文档中的位置决定。

可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。

只要元素满足下面任一条件即可触发 BFC 特性

  • body 根元素

  • 浮动元素:float 除 none 以外的值

  • 绝对定位元素:position (absolute、fixed)

  • display 为 inline-block、table-cells、flex

  • overflow 除了 visible 以外的值 (hidden、auto、scroll)

3、常见兼容性问题?

浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一。

Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,

可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决.

四、JS 篇

1、JS 数据类型 ?

数据类型主要包括两部分:

基本数据类型: Undefined、Null、Boolean、Number 和 String

引用数据类型: Object (包括 Object 、Array 、Function)

ECMAScript 2015 新增:Symbol(创建后独一无二且不可变的数据类型 )

2、判断一个值是什么类型有哪些方法?

typeof 运算符

instanceof 运算符

Object.prototype.toString 方法

3、null 和 undefined 的区别?

null 表示一个对象被定义了,值为“空值”;

undefined 表示不存在这个值。

(1)变量被声明了,但没有赋值时,就等于undefined。 (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。 (3)对象没有赋值的属性,该属性的值为undefined。 (4)函数没有返回值时,默认返回undefined。

4、怎么判断一个变量arr的话是否为数组(此题用 typeof 不行)?
arr instanceof Array

arr.constructor == Array

Object.prototype.toString.call(arr) ==[Object Array]
5、“ ===”、“ ==”的区别?

==,当且仅当两个运算数相等时,它返回 true,即不检查数据类型

===,只有在无需类型转换运算数就相等的情况下,才返回 true,需要检查数据类型

6、 “eval是做什么的?

它的功能是把对应的字符串解析成 JS 代码并运行;

应该避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 语句,一次执行)。

7、箭头函数有哪些特点?

不需要function关键字来创建函数

省略return关键字

改变this指向

8、var、let、const 区别?

var 存在变量提升。

let 只能在块级作用域内访问。

const 用来定义常量,必须初始化,不能修改(对象特殊)

9、new操作符具体干了什么呢?

1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。

2、属性和方法被加入到 this 引用的对象中。

3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。

10、JSON 的了解?

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。

它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小

{‘age’:‘12’, ‘name’:‘back’}

11、document.write 和 innerHTML 的区别?

document.write 只能重绘整个页面

innerHTML 可以重绘页面的一部分

12、ajax过程?

(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.

(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.

(3)设置响应HTTP请求状态变化的函数.

(4)发送HTTP请求.

(5)获取异步调用返回的数据.

(6)使用JavaScript和DOM实现局部刷新.

13、请解释一下 JavaScript 的同源策略?

概念:同源策略是客户端脚本(尤其是Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。

这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。

指一段脚本只能读取来自同一来源的窗口和文档的属性。

14、介绍一下闭包和闭包常用场景?

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包常见方式,就是在一个函数的内部创建另一个函数

使用闭包主要为了设计私有的方法和变量,闭包的优点是可以避免变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念。

闭包有三个特性:

函数嵌套函数

函数内部可以引用外部的参数和变量

参数和变量不会被垃圾回收机制回收

应用场景,设置私有变量的方法

不适用场景:返回闭包的函数是个非常大的函数

闭包的缺点就是常驻内存,会增大内存使用量,使用不当会造成内存泄漏

15、javascript的内存(垃圾)回收机制?

垃圾回收器会每隔一段时间找出那些不再使用的内存,然后为其释放内存

一般使用标记清除方法(mark and sweep), 当变量进入环境标记为进入环境,离开环境标记为离开环境

垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了

还有引用计数方法(reference counting), 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。

在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。

16、JavaScript原型,原型链 ? 有什么特点?
1、任何对象都有 proto 隐式原型, 等于 构造函数 的 prototype

const obj = {}

obj.proto === Object.prototype // true

2、任何函数都有 prototype 显示原型 等于 原型对象(就是一个普通对象包含公共属性)

(通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性)

function Person () {}

Person.prototype = 原型对象

Person.prototype.constructor === Person // true

const person1 = new Person

person1.__proto__ === Person.prototype // true

person1.constructor == Person // true
3、对象还具有 constructor 属性,指向构造函数(Person.prototype.constructor == Person)
4、原型链是依赖于__proto__, 查找一个属性会沿着 proto 原型链向上查找,直到找到为止。
5、特殊

// 原型链最终点是 null

Object.prototype.proto === null // true

obj.proto.proto === null // true

每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,

如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,

于是就这样一直找下去,也就是我们平时所说的原型链的概念。

关系:instance.constructor.prototype = instance.proto

特点

JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

17、用js递归的方式写1到100求和?
function add(num1, num2) {

  const num = num1 + num2;

  if(num2 === 100) {

   	return num;

  } else {

	  return add(num, num2 + 1)

  }

}

var sum = add(1, 2);     
18、事件队列(宏任务微任务)

可以分为微任务(micro task)队列和宏任务(macro task)队列。

微任务一般比宏任务先执行,并且微任务队列只有一个,宏任务队列可能有多个。另外我们常见的点击和键盘等事件也属于宏任务。

下面我们看一下常见宏任务和常见微任务。

常见宏任务:
setTimeout()

setInterval()

setImmediate()
常见微任务:
promise.then()、promise.catch()

new MutaionObserver()

process.nextTick()
微任务和宏任务的本质区别。

宏任务特征:有明确的异步任务需要执行和回调;需要其他异步线程支持。

微任务特征:没有明确的异步任务需要执行,只有回调;不需要其他异步线程支持。

setTimeout(function () {

  console.log("1");

}, 0);

async function async1() {

  console.log("2");

  const data = await async2();

  console.log("3");

  return data;

}

async function async2() {
  return new Promise((resolve) => {
    console.log("4");
    resolve("async2的结果");
  }).then((data) => {
    console.log("5");
    return data;
  });
}

async1().then((data) => {

  console.log("6");

  console.log(data);

});

new Promise(function (resolve) {

  console.log("7");

 resolve()

}).then(function () {

  console.log("8");

});

 

// 2 4 7 5 8 3 6 async2的结果 1
19、async/await

async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。是Generator函数的语法糖,并对Generator函数进行了改进。

改进:
  • *内置执行器,无需手动执行 next() 方法。

  • *更好的语义

  • *更广的适用性:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

  • *返回值是 Promise,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用。

  • *async 隐式返回 Promise 作为结果的函数,那么可以简单理解为,await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。

20、JavaScript 是单线程的,浏览器是多进程的
  • *每打开一个新网页就会创建一个渲染进程

  • *渲染进程是多线程的

  • *负责页面渲染的 GUI 渲染线程

  • *负责JavaScript的执行的 JavaScript 引擎线程,

  • *负责浏览器事件循环的事件触发线程,注意这不归 JavaScript 引擎线程管

  • *负责定时器的定时触发器线程,setTimeout 中低于 4ms 的时间间隔算为4ms

  • *负责XMLHttpRequest的异步 http 请求线程

  • *GUI 渲染线程与 JavaScript 引擎线程是互斥的

  • *单线程JavaScript是因为避免 DOM 渲染的冲突,web worker 支持多线程,但是 web worker 不能访问 window 对象,document 对象等。

五、Vue 篇

1、谈谈你对MVVM开发模式的理解?
  • *MVVM分为Model、View、ViewModel三者。

  • *Model 代表数据模型,数据和业务逻辑都在Model层中定义;

  • *View 代表UI视图,负责数据的展示;

  • *ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;

  • *Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。

这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。

2、v-if 和 v-show 有什么区别?

v-if 是真正的条件渲染,会控制这个 DOM 节点的存在与否。因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。

当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。

3、说说你对 SPA 单页面的理解,它的优缺点分别是什么?

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页> 面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 > HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;

基于上面一点,SPA 相对对服务器压力小;

前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一> 加载,部分页面按需加载;

前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所> 有的页面切换需要自己建立堆栈管理;

SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

4、你使用过 Vuex 吗?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

  • (1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  • (2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

每一个 Vuex 应用的核心就是 store(仓库),它包含着你的应用中大部分的状态 (state)。 状态管理有5个核心:state、getter、mutation、action、module。

State => 基本数据,定义了应用状态的数据结构,可以在这里设置默认的初始状态。
  1. 单一状态树,定义应用状态的默认初始值,页面显示所需的数据从该对象中进行读取。

  2. 2、Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。它便作为一个“唯一数据源”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。

  3. 单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

  4. 不可直接对 state 进行更改,需要通过 Mutation 方法来更改。

  5. 由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:

Getter => 从基本数据派生的数据,允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

​ 1、可以认为是 store 的计算属性,对 state 的加工,是派生出来的数据。
​ 2、就像 computed 计算属性一样,getter 返回的值会根据它的依赖被缓存起来,且只有当它的依 赖值发生改变才会被重新计算。
​ 3、可以在多组件中共享 getter 函数,这样做还可以提高运行效率。
​ 4、在 store 上注册 getter,getter 方法接受以下参数: state, 如果在模块中定义则为模块的局部状态
​ 5、getters, 等同于 store.getters

Mutation => 是唯一更改 store 中状态的方法,且必须是同步函数。

1、Vuex中store数据改变的唯一方法就是mutation

2、通俗的理解,mutations 里面装着改变数据的方法集合,处理数据逻辑的方法全部放在 mutations 里,使数据和视图分离。

Action => 像一个装饰器,包裹mutations,使之可以异步。用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
  • Action action 类似于 mutation ,不同在于:

1、action 提交的是 mutation,通过 mutation 来改变 state ,而不是直接变更状态。

2、action 可以包含任意异步操作。

Module => 模块化Vuex,允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

1、由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

2、为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

总结:

四大核心属性,以及一个子模块管理属性,state存放数据,Mutations同步修改state里面的数据,actions异步修改数据,但需要调用Mutations里面的方法修改,getters数据过滤器,对数据进行整理,还有一个modules,当数据过多和复杂时,将数据用模块化分开,在住文件中用modules引用

5、Class 与 Style 如何动态绑定?
  • Class 可以通过对象语法和数组语法进行动态绑定:
对象语法:
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>

data: {

	isActive: true,

 	hasError: false

}
数组语法:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

data: {

 activeClass: 'active',

 errorClass: 'text-danger'

}
  • Style 也可以通过对象语法和数组语法进行动态绑定:
对象语法:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

data: {

 activeColor: 'red',

 fontSize: 30

}
数组语法:
<div v-bind:style="[styleColor, styleSize]"></div>

data: {

 styleColor: {

   color: 'red'

  },

 styleSize:{

   fontSize:'23px'
  }
}
6、怎样理解 Vue 的单向数据流?

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。

这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。

这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

7、computed 和 watch 的区别和运用的场景?
  • computed:是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
  • watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:

当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

8、直接给一个数组项赋值,Vue 能检测到变化吗

由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:

  • *当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

  • *当你修改数组的长度时,例如:vm.items.length = newLength

为了解决第一个问题,Vue 提供了以下操作方法:

// Vue.set

Vue.set(vm.items, indexOfItem, newValue)

// vm.$set,Vue.set的一个别名

vm.$set(vm.items, indexOfItem, newValue)

// Array.prototype.splice

vm.items.splice(indexOfItem, 1, newValue)

为了解决第二个问题,Vue 提供了以下操作方法:

// Array.prototype.splice

vm.items.splice(newLength)
9、谈谈你对 Vue 生命周期的理解?
生命周期是什么?

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

各个生命周期的作用

生命周期 描述

  • beforeCreate 组件实例被创建之初,组件的属性生效之前

  • created 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用

  • beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用

  • mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子

  • beforeUpdate 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前

  • updated 组件数据更新之后

  • activited keep-alive 专属,组件被激活时调用

  • deadctivated keep-alive 专属,组件被销毁时调用

  • beforeDestory 组件销毁前调用

  • destoryed 组件销毁后调用

10、Vue 的父组件和子组件生命周期钩子函数执行顺序?

Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

  • 加载渲染过程 :

父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  • 子组件更新过程 :

父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  • 父组件更新过程 :

父 beforeUpdate -> 父 updated

  • 销毁过程 :

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

11、父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vue

<Child @mounted="doSomething"/>

 

// Child.vue

mounted() {

 this.$emit("mounted");

}

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue

<Child @hook:mounted="doSomething" ></Child>

 

doSomething() {

  console.log('父组件监听到 mounted 钩子函数 ...');

},

 

//  Child.vue

mounted(){

  console.log('子组件触发 mounted 钩子函数 ...');

},   

 

/ 以上输出顺序为:

// 子组件触发 mounted 钩子函数 ...

// 父组件监听到 mounted 钩子函数 ...

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

12、谈谈你对 keep-alive 的了解?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

  • *一般结合路由和动态组件一起使用,用于缓存组件;
  • *提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
  • *对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
13、组件中 data 为什么是一个函数?

为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?

因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,

如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

14、v-model 的原理?

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

1、text 和 textarea 元素使用 value 属性和 input 事件;

2、checkbox 和 radio 使用 checked 属性和 change 事件;

3、select 字段将 value 作为 prop 并将 change 作为事件。

  • 以 input 表单元素为例:
<input v-model='something'>
    

 //相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
  • 如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
父组件:

<ModelChild v-model="message"></ModelChild>

子组件:

<div>{{value}}</div>

props:{

  value: String

},

methods: {

 test1(){

   this.$emit('input', '小红')

 },

},
15、Vue 组件间通信有哪几种方式?

Vue 组件间通信只要指以下 3 类通信:****父子组件通信、隔代组件通信、兄弟组件通信****,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。

(1)props / $emit 适用 父子组件通信

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。

(2)ref 与 $parent / $children适用 父子组件通信

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例

$parent / $children:访问父 / 子实例

(3)EventBus ($emit / $on)适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

(4) a t t r s / attrs/ attrs/listeners适用于 隔代组件通信

a t t r s : 包 含 了 父 作 用 域 中 不 被 p r o p 所 识 别 ( 且 获 取 ) 的 特 性 绑 定 ( c l a s s 和 s t y l e 除 外 ) 。 当 一 个 组 件 没 有 声 明 任 何 p r o p 时 , 这 里 会 包 含 所 有 父 作 用 域 的 绑 定 ( c l a s s 和 s t y l e 除 外 ) , 并 且 可 以 通 过 v − b i n d = " attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind=" attrsprop()(classstyle)prop(classstyle)vbind="attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。

l i s t e n e r s : 包 含 了 父 作 用 域 中 的 ( 不 含 . n a t i v e 修 饰 器 的 ) v − o n 事 件 监 听 器 。 它 可 以 通 过 v − o n = " listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=" listeners(.native)vonvon="listeners" 传入内部组件

(5)provide / inject适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

(6)Vuex适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

16、使用过 Vue SSR 吗?说说 SSR?

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。

服务端渲染 SSR 的优缺点如下:

(1)服务端渲染的优点:

更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;

更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

(2) 服务端渲染的缺点:

更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;

更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。

17、vue-router 路由模式有几种?

vue-router 有 3 种路由模式:hash、history、abstract,对应的源码如下所示:

 
switch (mode) {

 case 'history':

  this.history = new HTML5History(this, options.base)

  break

 case 'hash':

  this.history = new HashHistory(this, options.base, this.fallback)

  break

 case 'abstract':

  this.history = new AbstractHistory(this, options.base)

  break

 default:

  if (process.env.NODE_ENV !== 'production') {

   assert(false, `invalid mode: ${mode}`)

  }

}

其中,3 种路由模式的说明如下:

hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;

history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;

abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

19、能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?
(1)hash 模式的实现原理

早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 ‘#search’:

https://www.word.com#search

hash 路由模式的实现主要是基于下面几个特性:
  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;

  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;

  • 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;

  • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:

history.pushState() 和 history.repalceState()
  • 这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。

  • 唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

window.history.pushState(null, null, path);

window.history.replaceState(null, null, path);
  • history 路由模式的实现主要基于存在下面几个特性:
  1. pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;

  2. 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);

  3. history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

19、Vue 框架怎么实现对象和数组的监听?

Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据

即:

  • 输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。

  • Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。

  • 其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:
  1. 实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

  2. 实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

  3. 实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。

  4. 实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

20、Vue 是如何实现数据双向绑定的?

如果被问到 Vue 怎么实现数据双向绑定,大家肯定都会回答 通过 Object.defineProperty() 对数据进行劫持,但是 Object.defineProperty() 只能对属性进行数据劫持,不能对整个对象进行劫持。

同理无法对数组进行劫持,但是我们在使用 Vue 框架中都知道,Vue 能检测到对象和数组(部分方法的操作)的变化,那它是怎么实现的呢?我们查看相关代码如下:

 /**

  \* Observe a list of Array items.

  */

 observeArray (items: Array<any>) {

  for (let i = 0, l = items.length; i < l; i++) {

   observe(items[i])  // observe 功能为监测数据的变化

  }

 }



 

 /**

  \* 对属性进行递归遍历

  */

 let childOb = !shallow && observe(val) // observe 功能为监测数据的变化

通过以上 Vue 源码部分查看,我们就能知道 Vue 框架是通过遍历数组递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。

21、Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?
  • 受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。

  • 由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

  • 但是 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)来实现为对象添加响应式属性,那框架本身是如何实现的呢?

我们查看对应的 Vue 源码:vue/src/core/instance/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {

 // target 为数组

 if (Array.isArray(target) && isValidArrayIndex(key)) {

  // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误

  target.length = Math.max(target.length, key)

  // 利用数组的splice变异方法触发响应式

  target.splice(key, 1, val)

  return val

 }

 // key 已经存在,直接修改属性值

 if (key in target && !(key in Object.prototype)) {

  target[key] = val

  return val

 }

 const ob = (target: any).__ob__

 // target 本身就不是响应式数据, 直接赋值

 if (!ob) {

  target[key] = val

  return val

 }

 // 对属性进行响应式处理

 defineReactive(ob.value, key, val)

 ob.dep.notify()

 return val

}

我们阅读以上源码可知,****vm.$set 的实现原理****是:

如果目标是数组,直接使用数组的 splice 方法触发相应式;

如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)
22、虚拟 DOM 的优缺点?
优点:

保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;

无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;

跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

23、虚拟 DOM 实现原理?

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;

  • diff 算法 — 比较两棵虚拟 DOM 树的差异;

  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

24、Vue 中的 key 有什么作用?

1、key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。

2、Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。

所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速!

3、更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。

更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:

function createKeyToOldIdx (children, beginIdx, endIdx) {

 let i, key

 const map = {}

 for (i = beginIdx; i <= endIdx; ++i) {

  key = children[i].key

  if (isDef(key)) map[key] = i

 }

 return map

}
25、你有对 Vue 项目进行哪些优化?
(1)代码层面的优化
  • v-if 和 v-show 区分使用场景

  • computed 和 watch 区分使用场景

  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if

  • 长列表性能优化

  • 事件的销毁

  • 图片资源懒加载

  • 路由懒加载

  • 第三方插件的按需引入

  • 优化无限列表性能

  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化
  • Webpack 对图片进行压缩

  • 减少 ES6 转为 ES5 的冗余代码

  • 提取公共代码

  • 模板预编译

  • 提取组件的 CSS

  • 优化 SourceMap

  • 构建结果输出分析

  • Vue 项目的编译优化

(3)基础的 Web 技术的优化
  • 开启 gzip 压缩

  • 浏览器缓存

  • CDN 的使用

  • 使用 Chrome Performance 查找性能瓶颈

26、对于 vue3.0 特性你有什么了解的吗?

Vue 3.0 的目标是让 Vue 核心变得更小、更快、更强大,因此 Vue 3.0 增加以下这些新特性:

(1)监测机制的改变

3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:

  • 只能监测属性,不能监测对象

  • 检测属性的添加和删除;

  • 检测数组索引和长度的变更;

  • 支持 Map、Set、WeakMap 和 WeakSet。

  • 新的 observer 还提供了以下特性:

  1. 用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。

  2. 默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。

  3. 更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。

  4. 不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。

  5. 更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染。

(2)模板

模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。

同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。

(3)对象式的组件声明方式

vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。

3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。

此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。

现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。

(4)其它方面的更改

vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其他的更改:

  • 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。

  • 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。

  • 基于 treeshaking 优化,提供了更多的内置功能。

27、响应式原理(变化侦测)

使用发布订阅模式将数据劫持和模板编译结合,实现双向绑定

1、observer: 封装 Object.defineProperty 方法用来劫持对象属性的getter和setter,以此来追踪数据变化。

2、读取数据时触发getter来收集依赖(Watcher)到Dep。

3、修改数据时触发setter,并遍历依赖列表,通知所有相关依赖(Watcher)

4、Dep 类为依赖找一个存储依赖的地方,用来收集和管理依赖,在getter中收集,在setter中通知。

5、Watcher 类就是收集的依赖,实际上是一个订阅器,Watcher会将自己的实例赋值给window.target(全局变量)上,然后去主动访问属性,触发属性的getter,getter中会将此Watcher收集到Dep中,Watcher的update方法会在Dep的通知方法中被调用,触发更新。

6、Observer 类用来将一个对象的所有属性和子属性都变成响应式的,通过递归调用defineReactive来实现。

7、由于无法侦测对象上新增/删除属性,所以提供 $set 和 $delete API5。

28、Object.defineProperty怎么用, 三个参数?,有什么作用啊?

Object.defineProperty 是 JavaScript 中用来定义对象属性的方法。它接受三个参数:

  1. 对象 (obj):要定义属性的对象。
  2. 属性名 (prop):要定义或修改的属性名。
  3. 属性描述符 (descriptor):一个包含属性特性的对象。

属性描述符 (descriptor) 对象中可以包含以下可选键:

  • value:属性的值。
  • writable:属性是否可写,默认为 false
  • enumerable:属性是否可枚举,默认为 false,即不会出现在 for...in 循环和 Object.keys() 结果中。
  • configurable:属性是否可配置,默认为 false,即不允许改变属性的特性或删除属性。

Object.defineProperty 的作用是控制属性的各种特性。通过使用该方法,可以定义、修改或重新定义对象的属性,并且可以设置属性的可写性、可枚举性和可配置性。这使得我们能够更精确地控制对象属性的行为。

例如,下面的代码示例展示了如何使用 Object.defineProperty 定义一个只读属性:

const obj = {};

Object.defineProperty(obj, 'name', {
  value: 'John',
  writable: false,
});

console.log(obj.name);  // 输出:"John"
obj.name = 'Jane';      // 无效,属性是只读的,赋值操作被忽略
console.log(obj.name);  // 输出:"John"

在上述示例中,通过定义的属性描述符将属性 name 设置为只读 (writable: false),因此任何对该属性的赋值操作都会被忽略。

29、vue2和vue3的响应式原理都有什么区别呢?

vue2 用的是 Object.defindProperty 但是vue3用的是Proxy

Object.defindProperty 缺点:
  • 一次只能对一个属性进行监听,需要遍历来对所有属性监听

  • 对于对象的新增属性,需要手动监听

  • 对于数组通过push、unshift方法增加的元素,也无法监听

Proxy就没有这个问题,可以监听整个对象的数据变化,所以用vue3.0会用Proxy代替definedProperty。
30、Vue的patch diff 算法

patch将新老VNode节点进行比对,然后将根据两者的比较结果进行最小单位地修改视图,而不是将整个视图根据新的VNode重绘。patch的核心在于diff算法,这套算法可以高效地比较virtual DOM的变更,得出变化以修改视图。

diff算法核心是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。

  • 同层级比较(只比较同一层级,不跨级比较)

  • tag 不相同,则直接删除重建,不在深度比较

  • tag 和 key,两个都相同,则认为是相同节点,会进行深度比较

31、Vue 模板编译原理

Vue 的模板编译原理可以概括为以下几个步骤:

  1. 解析:Vue 将模板字符串转换为抽象语法树 (AST)。它使用 HTML 解析器将模板解析为 DOM 树,并使用指令解析器将指令和表达式解析为对应的 AST 节点。

  2. 优化:Vue 对生成的 AST 进行静态节点标记和静态根节点提升的优化过程。静态节点是在编译阶段确定不需要改变的节点,可以被直接复用,从而提高渲染性能。静态根节点提升将多次出现的静态子树提升到 render 函数外部,避免每次重新创建静态子树。

  3. 代码生成:Vue 将优化后的 AST 通过代码生成器转换为渲染函数。渲染函数是一个 JavaScript 函数,它会在组件实例发生变化时被调用,生成最终的虚拟 DOM 并更新真实 DOM。

  4. 渲染:每当组件的状态发生变化时,Vue 会调用渲染函数来重新生成虚拟 DOM,并计算出与上一次渲染结果的差异 (Diff)。然后,Vue 会将差异应用到真实 DOM 上,完成视图的更新。

通过模板编译的过程,Vue 实现了将模板转换为可复用的渲染函数的能力。这种方式使得开发者可以使用类似于传统模板引擎的语法来编写组件的模板,并在运行时将其转换为高效的渲染函数,实现数据驱动视图的更新。

32、Vue原理总结

1、【模板编译】将template模板,经过编译系统后生成VNode,(模板字符串→AST→Render函数)

2、【渲染】然后再通过渲染系统来将VNode生成真实DOM(document.createElement && Mount挂载到真实DOM节点上)

3、【响应式】通过响应式系统对数据进行监听,当数据发生改变时,触发依赖项(组件)

4、【diff & Patch】组件内收到通知后,会通过diff算法对比VNode的变化,尽可能复用代码,找出最小差异,保证性能消耗最小。

5、【渲染】拿到需要新增/删除/修改的VNode后,逐一去操作真实DOM进行修改(通过选择器选择到对应真实DOM节点进行修改)

六、Webpack 篇

1、谈谈你对Webpack的理解(Webpack是什么?)

Webpack 是一个 静态模块打包器,可以分析各个模块的依赖关系,项目中的所有资源皆为模块,通过分析模块间的依赖关系,在其内部递归构建出一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle。最终编绎输出模块为 HTML、JavaScript、CSS 以及各种静态文件(图片、字体等)。

webpack 就像一条生产线,要经过一系列处理流程(loader)后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。

插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。

webpack的主要作用如下:

模块打包 可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。

编译兼容 在前端的“上古时期”,手写一堆浏览器兼容代码一直是令前端工程师头皮发麻的事情,而在今天这个问题被大大的弱化了,通过webpack的Loader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less,.vue,.jsx这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。

能力扩展 通过webpack的Plugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。

2、Webpack的打包过程/打包原理/构建流程?

Webpack 的打包过程可以大致分为以下几个步骤:

  1. 解析配置:Webpack 会读取并解析项目根目录下的配置文件(例如:webpack.config.js),获取打包相关的配置信息。

  2. 入口分析:根据配置中指定的入口文件(entry),Webpack 会从入口开始递归地解析模块依赖关系。Webpack 会根据入口文件及其所依赖的文件,构建起一个完整的模块依赖图(Dependency Graph)。

  3. 模块转换:Webpack 在解析每个模块时,会根据配置中的规则(loaders)对模块进行相应的转换操作。Loaders 可以将模块源代码转换成 Webpack 可以处理的有效模块代码,例如将 ES6 语法转换为 ES5、将 Sass 编译为 CSS 等。

  4. 模块组装:经过模块转换后,Webpack 会得到一组已转换的模块。这些模块通过加载器产生的中间代码(通常是 CommonJS 或 ES Module 格式),被组装成一个或多个 Chunk。Chunk 是由模块之间的依赖关系构成的一个代码块。

  5. 输出生成:在组装完成后,Webpack 将根据配置中的出口设置(output)来确定输出的文件名和路径,并将 Chunk 内容写入指定的输出文件中。同时,根据需要,可以对输出文件进行压缩、混淆、代码分割等优化处理。

  6. 构建完成:Webpack 在整个构建过程中,会生成一系列的构建信息、警告和错误提示,以便开发者进行问题排查和优化。最终,Webpack 将输出构建结果的统计信息,包括文件大小、模块数量等。

总的来说,Webpack 的打包原理是通过解析配置文件、分析入口文件依赖、应用加载器转换模块、组装模块成 Chunk,并将结果进行输出。在这个过程中,Webpack 提供了丰富的插件和工具,使得开发者能够根据需求自定义构建流程,并实现各种功能,例如代码拆分、按需加载、缓存优化等。

3、loader的作用

webpack中的loader是一个函数,主要为了实现源码的转换,所以loader函数会以源码作为参数,比如,将ES6转换为ES5,将less转换为css,然后再将css转换为js,以便能嵌入到html文件中。

默认情况下,webpack只支持对js和json文件进行打包,但是像css、html、png等其他类型的文件,webpack则无能为力。因此,就需要配置相应的loader进行文件内容的解析转换。

4、有哪些常见的Loader?他们是解决什么问题的?
常用的loader如下:
  • image-loader:加载并且压缩图片文件。

  • less-loader:加载并编译 LESS 文件。

  • sass-loader:加载并编译 SASS/SCSS 文件。

  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性,使用css-loader必须要配合使用style-loader。

  • style-loader:用于将 CSS 编译完成的样式,挂载到页面的 style 标签上。需要注意 loader 执行顺序,style-loader 要放在第一位,loader 都是从后往前执行。

  • babel-loader:把 ES6 转换成 ES5

  • postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀。

  • eslint-loader:通过 ESLint 检查 JavaScript 代码。

  • vue-loader:加载并编译 Vue 组件。

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)

  • url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)。

  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试。

5、plugin的作用

plugin是一个类,类中有一个apply()方法,主要用于Plugin的安装,可以在其中监听一些来自编译器发出的事件,在合适的时机做一些事情。

webpack中的plugin赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在webpack的不同阶段(钩子 / 生命周期),贯穿了webpack整个编译周期。目的在于「解决 loader 无法实现的其他事」。

6、有哪些常见的Plugin?他们是解决什么问题的?
  • html-webpack-plugin:可以复制一个有结构的html文件,并自动引入打包输出的所有资源(JS/CSS)

  • clean-webpack-plugin:重新打包自动清空 dist 目录

  • mini-css-extract-plugin:提取 js 中的 css 成单独文件

  • optimize-css-assets-webpack-plugin:压缩css

  • uglifyjs-webpack-plugin:压缩js

  • commons-chunk-plugin:提取公共代码

  • define-plugin:定义环境变量

7、Webpack中Loader和Plugin的区别
运行时机

1.loader运行在编译阶段

2.plugins 在整个周期都起作用

使用方式

Loader:1.下载 2.使用

Plugin:1.下载 2.引用 3.使用

loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中;plugin赋予了webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader无法实现的其他事。

在运行时机上,loader 运行在打包文件之前;plugin则是在整个编译周期都起作用。

在配置上,loader在module.rules中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性;plugin在 plugins中单独配置,类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入。

8、webpack的热更新是如何做到的?说明其原理?

热更新的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上webpack-dev-server与浏览器之间维护了一个websocket,当本地资源发生变化时,webpack-dev-server会向浏览器推送更新,并带上构建时的hash,让客户端与上一次资源进行对比。客户端对比出差异后会向webpack-dev-server发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向webpack-dev-server发起 jsonp 请求获取该chunk的增量更新。

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader和vue-loader都是借助这些 API 实现热更新。

详细:

1、在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。

2、webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。

3、webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念

4、webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,

同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。

webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。

5、决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。

9、如何解决循环依赖问题

Webpack 中将 require 替换为 webpack_require,会根据 moduleId 到 installedModules 找是否加载过,加载过则直接返回之前的 export,不会重复加载。

10、如何提高Webpack构建速度
1、代码压缩
  • JS 压缩

    webpack 4.0默认在生产环境的时候是支持代码压缩的,即mode=production模式下。实际上webpack 4.0默认是使用terser-webpack-plugin这个压缩插件,在此之前是使用 uglifyjs-webpack-plugin,两者的区别是后者对 ES6 的压缩不是很好,同时我们可以开启 parallel参数,使用多进程压缩,加快压缩。

  • CSS 压缩

    CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等。可以使用另外一个插件:css-minimizer-webpack-plugin。

  • HTML 压缩
使用HtmlWebpackPlugin插件来生成 HTML 的模板时候,通过配置属性minify进行 html 优化。

module.exports = {

plugin:[

 new HtmlwebpackPlugin({

  minify:{

   minifyCSS: false, // 是否压缩css

   collapseWhitespace: false, // 是否折叠空格

   removeComments: true // 是否移除注释

  }

 })

 ]

}
2、图片压缩

配置image-webpack-loader

3、Tree Shaking

Tree Shaking是一个术语,在计算机中表示消除死代码,依赖于 ES Module 的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)。在webpack实现Tree shaking有两种方案:

usedExports:通过标记某些函数是否被使用,之后通过 Terser 来进行优化的

module.exports = {

  ...

  optimization:{

​    usedExports

  }

 }

使用之后,没被用上的代码在webpack打包中会加入unused harmony export mul注释,用来告知Terser在优化时,可以删除掉这段代码。

sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用

sideEffects用于告知webpack compiler哪些模块时有副作用,配置方法是在package.json中设置sideEffects属性。如果sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports。如果有些文件需要保留,可以设置为数组的形式,如:

"sideEffecis":[

  "./src/util/format.js",

  "*.css" // 所有的css文件

]
4、缩小打包域

排除webpack不需要解析的模块,即在使用loader的时候,在尽量少的模块中去使用。可以借助 include和exclude这两个参数,规定loader只在那些模块应用和在哪些模块不应用。

5、减少 ES6 转为 ES5 的冗余代码

使用bable-plugin-transform-runtime插件

6、提取公共代码

通过配置CommonsChunkPlugin插件,将多个页面的公共代码抽离成单独的文件

7、其他

组件懒加载、路由懒加载、开启gzip、公共的第三方包上cdn、配置cache缓存Loader对文件的编译副本、配置resolve提高文件的搜索速度(@: src)

七、性能优化篇

1、浏览器缓存优化

为了让浏览器缓存发挥最大作用,该策略尽量遵循以下五点就能发挥浏览器缓存最大作用。

  • 「考虑拒绝一切缓存策略」:Cache-Control:no-store

  • 「考虑资源是否每次向服务器请求」:Cache-Control:no-cache

  • 「考虑资源是否被代理服务器缓存」:Cache-Control:public/private

  • 「考虑资源过期时间」:Expires:t/Cache-Control:max-age=t,s-maxage=t

  • 「考虑协商缓存」:Last-Modified/Etag

缓存策略通过设置HTTP报文实现,在形式上分为**「强缓存/强制缓存」和「协商缓存/对比缓存」**。为了方便对比,笔者将某些细节使用图例展示,相信你有更好的理解。

整个缓存策略机制很明了,先走强缓存,若命中失败才走协商缓存。若命中强缓存,直接使用强缓存;若未命中强缓存,发送请求到服务器检查是否命中协商缓存;若命中协商缓存,服务器返回304通知浏览器使用本地缓存,否则返回最新资源。

有两种较常用的应用场景值得使用缓存策略一试,当然更多应用场景都可根据项目需求制定。

  • 「频繁变动资源」:设置Cache-Control:no-cache,使浏览器每次都发送请求到服务器,配合Last-Modified/ETag验证资源是否有效

  • 「不常变化资源」:设置Cache-Control:max-age=31536000,对文件名哈希处理,当代码修改后生成新的文件名,当HTML文件引入文件名发生改变才会下载最新文件

2、渲染层面性能优化

**「渲染层面」**的性能优化,无疑是如何让代码解析更好执行更快。因此笔者从以下五方面做出建议。

  • 「CSS策略」:基于CSS规则

  • 「DOM策略」:基于DOM操作

  • 「阻塞策略」:基于脚本加载

  • 「回流重绘策略」:基于回流重绘

  • 「异步更新策略」:基于异步更新

上述五方面都是编写代码时完成,充满在整个项目流程的开发阶段里。因此在开发阶段需时刻注意以下涉及到的每一点,养成良好的开发习惯,性能优化也自然而然被使用上了。

渲染层面的性能优化更多表现在编码细节上,而并非实体代码。简单来说就是遵循某些编码规则,才能将渲染层面的性能优化发挥到最大作用。

**「回流重绘策略」**在渲染层面的性能优化里占比较重,也是最常规的性能优化之一。上年笔者发布的掘金小册《玩转CSS的艺术之美》使用一整章讲解回流重绘,本章已开通试读,更多细节请戳这里。

CSS策略
  • 避免出现超过三层的嵌套规则

  • 避免为ID选择器添加多余选择器

  • 避免使用标签选择器代替类选择器

  • 避免使用通配选择器,只对目标节点声明规则

  • 避免重复匹配重复定义,关注可继承属性

DOM策略
  • 缓存DOM计算属性

  • 避免过多DOM操作

  • 使用DOMFragment缓存批量化DOM操作

  • 阻塞策略

  • 脚本与DOM/其它脚本的依赖关系很强:对

3、性能优化六大指标

六大指标基本囊括大部分性能优化细节,可作为九大策略的补充。笔者根据每条性能优化建议的特征将指标划分为以下六方面。

  • 「加载优化」:资源在加载时可做的性能优化

  • 「执行优化」:资源在执行时可做的性能优化

  • 「渲染优化」:资源在渲染时可做的性能优化

  • 「样式优化」:样式在编码时可做的性能优化

  • 「脚本优化」:脚本在编码时可做的性能优化

  • 「V8引擎优化」:针对V8引擎特征可做的性能优化

八、其他杂项篇

1、常见的浏览器内核有哪些?

主要分成两部分:渲染引擎(layout engineer或Rendering Engine)和JS引擎

  • 渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。

  • JS引擎则:解析和执行javascript来实现网页的动态效果。

最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。

常见内核

  • Trident 内核:IE, MaxThon, TT, The World, 360, 搜狗浏览器等。[又称 MSHTML]

  • Gecko 内核:Netscape6 及以上版本,FF, MozillaSuite / SeaMonkey 等

  • Presto 内核:Opera7 及以上。 [Opera内核原为:Presto,现为:Blink;]

  • Webkit 内核:Safari, Chrome等。 [ Chrome的:Blink(WebKit 的分支)]

2、网页前端性能优化的方式有哪些?
  • 1.压缩 css, js, 图片

  • 2.减少 http 请求次数, 合并 css、js 、合并图片(雪碧图)

  • 3.使用 CDN

  • 4.减少 dom 元素数量

  • 5.图片懒加载

  • 6.静态资源另外用无 cookie 的域名

  • 7.减少 dom 的访问(缓存 dom)

  • 8.巧用事件委托

  • 9.样式表置顶、脚本置低

3、网页从输入网址到渲染完成经历了哪些过程?

大致可以分为如下7步:

  1. 输入网址;

  2. 发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;

  3. 与web服务器建立TCP连接;

  4. 浏览器向web服务器发送http请求;

  5. web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);

  6. 浏览器下载web服务器返回的数据及解析html源文件;

  7. 生成DOM树,解析css和js,渲染页面,直至显示完成;

4、线程与进程的区别?
  • 一个程序至少有一个进程,一个进程至少有一个线程.

  • 线程的划分尺度小于进程,使得多线程程序的并发性高。

  • 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

  • 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

  • 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

5、HTTP常见的状态码?
  • 100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息

  • 200 OK 正常返回信息

  • 201 Created 请求成功并且服务器创建了新的资源

  • 202 Accepted 服务器已接受请求,但尚未处理

  • 301 Moved Permanently 请求的网页已永久移动到新位置。

  • 302 Found 临时性重定向。

  • 303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。

  • 304 Not Modified 自从上次请求后,请求的网页未修改过。

  • 400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。

  • 401 Unauthorized 请求未授权。

  • 403 Forbidden 禁止访问。

  • 404 Not Found 找不到如何与 URI 相匹配的资源。

  • 500 Internal Server Error 最常见的服务器端错误。

  • 503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。

6、图片懒加载?

当页面滚动的时间被触发 -> 执行加载图片操作 -> 判断图片是否在可视区域内 -> 在,则动态将data-src的值赋予该图片

7、移动端性能优化?
  1. 尽量使用css3动画,开启硬件加速

  2. 适当使用touch时间代替click时间

  3. 避免使用css3渐变阴影效果

  4. 可以用transform: translateZ(0) 来开启硬件加速

  5. 不滥用float。float在渲染时计算量比较大,尽量减少使用

  6. 不滥用web字体。web字体需要下载,解析,重绘当前页面

  7. 合理使用requestAnimationFrame动画代替setTimeout

  8. css中的属性(css3 transitions、css3 3D transforms、opacity、webGL、video)会触发GUP渲染,耗电

8、TCP 传输的三次握手、四次挥手策略
三次握手:

为了准确无误地吧数据送达目标处,TCP协议采用了三次握手策略。用TCP协议把数据包送出去后,TCP不会对传送后的情况置之不理,他一定会向对方确认是否送达,握手过程中使用TCP的标志:SYN和ACK

  • 发送端首先发送一个带SYN的标志的数据包给对方

  • 接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息

  • 最后,发送端再回传一个带ACK的标志的数据包,代表“握手”结束

如在握手过程中某个阶段莫明中断,TCP协议会再次以相同的顺序发送相同的数据包

断开一个TCP连接需要“四次挥手”
  • 第一次挥手:主动关闭方发送一个FIN,用来关注主动方到被动关闭方的数据传送,也即是主动关闭方告诫被动关闭方:我已经不会再给你发数据了(在FIN包之前发送的数据,如果没有收到对应的ACK确认报文,主动关闭方依然会重发这些数据)。但是,此时主动关闭方还可以接受数据

  • 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号收到序号 +1(与SYN相同,一个 FIN占用一个序号)

  • 第三次挥手:被动关闭方发送一个 FIN。用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会给你发送数据了

  • 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手

9、HTTP 和 HTTPS,为什么HTTPS安全?
  • HTTP协议通常承载与 TCP协议之上,在HTTP和TCP之间添加一个安全协议层(SSL或TSL),这个时候,就成了我们常说的HTTPS

  • 默认HTTP的端口号为80,HTTPS的端口号为443

  • 因为网络请求需要中间有很多的服务器路由的转发,中间的节点都可能篡改信息,而如果使用HTTPS,密钥在你和终点站才有,https之所有说比http安全,是因为他利用ssl/tls协议传输。包含证书,流量转发,负载均衡,页面适配,浏览器适配,refer传递等,保障了传输过程的安全性

10、axios和fetch区别对比

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,它本身具有以下特征

  • 从浏览器中创建 XMLHttpRequest

  • 支持 Promise API

  • 客户端支持防止CSRF

  • 提供了一些并发请求的接口(重要,方便了很多的操作)

  • 从 node.js 创建 http 请求

  • 拦截请求和响应

  • 转换请求和响应数据

  • 取消请求

  • 自动转换JSON数据

fetch优势:
  • 语法简洁,更加语义化

  • 基于标准 Promise 实现,支持 async/await

  • 同构方便,使用 isomorphic-fetch

  • 更加底层,提供的API丰富(request, response)

  • 脱离了XHR,是ES规范里新的实现方式

fetch存在问题
  • fetch是一个低层次的API,你可以把它考虑成原生的XHR,所以使用起来并不是那么舒服,需要进行封装。

  • fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。

  • fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})

  • fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费

  • fetch没有办法原生监测请求的进度,而XHR可以

九、主观题篇

1、你都做过什么项目呢?具体聊某一个项目中运用的技术.

注意:用心找自己做的项目中自己感觉最拿出来手的(复杂度最高,用的技术最多的项目),描述的时候尽可能往里面添加一些技术名词

布局我们用html5+css3

我们会用reset.css重置浏览器的默认样式

JS框架的话我们选用的是jQuery(也可能是Zepto)

我们用版本控制工具git来协同开发

我们会基于gulp搭建的前端自动化工程来开发(里面包含有我们的项目结构、我们需要引用的第三方库等一些信息,我们还实现了sass编译、CSS3加前缀等的自动化)

我们的项目中还用到了表单验证validate插件、图片懒加载Lazyload插件

2、你遇到过比较难的技术问题是?你是如何解决的?
3、常使用的库有哪些?常用的前端开发工具?开发过什么应用或组件?
4、除了前端以外还了解什么其它技术么?你最最厉害的技能是什么?
5、对前端开发工程师这个职位是怎么样理解的?它的前景会怎么样?

前端是最贴近用户的程序员,比后端、数据库、产品经理、运营、安全都近。

  • 1、实现界****面交互****

  • 2、提升用户体验

  • 3、有了Node.js,前端可以实现服务端的一些事情

  • 前端是最贴近用户的程序员,前端的能力就是能让产品从 90分进化到 100 分,甚至更好,

  • 参与项目,快速高质量完成实现效果图,精确到1px;

  • 与团队成员,UI设计,产品经理的沟通;

  • 做好的页面结构,页面重构和用户体验;

  • 处理hack,兼容、写出优美的代码格式;

  • 针对服务器的优化、拥抱最新前端技术。

6、你的优点是什么?缺点是什么?

新面试题

1、flex布局周边属性

在flex布局中,有几个重要的周边属性可以调整弹性容器和弹性项目之间的间距和对齐方式。以下是这些属性的介绍:

  1. justify-content:用于设置弹性项目在主轴上的对齐方式。它可以接受以下值:
    • flex-start:将弹性项目靠近主轴起始端对齐。
    • flex-end:将弹性项目靠近主轴结束端对齐。
    • center:将弹性项目居中对齐。
    • space-between:将弹性项目平均分布在主轴上,首个项目靠近起始端,最后一个项目靠近结束端,项目之间的间距相等。
    • space-around:将弹性项目平均分布在主轴上,包括首尾项目两侧,项目之间和项目与容器之间的间距相等。
  2. align-items:用于设置弹性项目在交叉轴上的对齐方式。它可以接受以下值:
    • flex-start:将弹性项目靠近交叉轴起始端对齐。
    • flex-end:将弹性项目靠近交叉轴结束端对齐。
    • center:将弹性项目在交叉轴上居中对齐。
    • stretch:将弹性项目拉伸以填充交叉轴。
    • baseline:将弹性项目的基线对齐。
  3. align-content:用于设置多行弹性容器在交叉轴上的对齐方式。它可以接受以下值:
    • flex-start:将多行弹性容器靠近交叉轴起始端对齐。

    • flex-end:将多行弹性容器靠近交叉轴结束端对齐。

    • center:将多行弹性容器在交叉轴上居中对齐。

    • space-between:将多行弹性容器平均分布在交叉轴上,首行靠近起始端,末行靠近结束端,行与行之间间距相等。

    • space-around:将多行弹性容器平均分布在交叉轴上,包括首尾两侧,行与行之间和容器与首尾行之间的间距相等。

    • stretch:如果有剩余空间,将行高拉伸以填充交叉轴。

4、flex-direction

​ flex-direction 设置主轴 的方向。

  • ​ row: 默认值。作为一行,水平地显示弹性项目
  • ​ column: 作为列,垂直地显示弹性项目。
  • ​ row-reverse: 等同行,但方向相反。
  • ​ column-reverse: 等同列,但方向相反。
5、flex-wrap

​ 设置是否换行展示

  • wrap: 允许换行
  • nowrao: 不允许换行 默认值
  • wrap-reverse 会在需要时换行,以反方向。

以上就是flex布局中常用的周边属性,它们可以帮助您控制弹性容器和弹性项目的对齐和间距。

6、flex-flow属性是以下属性的简写属性:
2、mixins.,extends 混入

在CSS预处理器(如Sass、Less)中,"mixins"和"extends"是两个用于代码复用的关键概念。

  1. Mixins(混入):混入是一种将一组CSS属性集合定义为可重用代码块的机制。通过定义混入,可以在需要的地方引用它,并将其中的样式应用到相应的元素上。混入通常用来避免重复编写相似的样式代码,提高代码的复用性和可维护性。

    下面是一个SCSS语法的混入示例:

    @mixin button-styles {
      display: inline-block;
      padding: 10px 20px;
      background-color: #f1f1f1;
      color: #333;
      border-radius: 4px;
    }
    
    .my-button {
      @include button-styles;
    }
    

    在上述示例中,通过定义了一个名为"button-styles"的混入,其中包含了一组按钮样式属性。然后,通过"@include"指令将该混入引入到".my-button"选择器内部,从而实现了样式的复用。

  2. Extends(扩展):扩展是一种将一个CSS选择器的样式继承到另一个选择器上的机制。通过扩展,可以使多个选择器共享相同的样式规则,减少代码冗余。扩展会将被扩展选择器的样式直接复制到扩展选择器上,形成一种继承关系。

    下面是一个Less语法的扩展示例:

    .my-button {
      display: inline-block;
      padding: 10px 20px;
      background-color: #f1f1f1;
      color: #333;
      border-radius: 4px;
    }
    
    .my-button-primary {
      &:extend(.my-button);
      background-color: blue;
      color: white;
    }
    

    在上述示例中,".my-button-primary"选择器通过"&:extend(.my-button)“指令来扩展”.my-button"选择器的样式。这样,".my-button-primary"将会继承".my-button"的所有样式,并可以在此基础上进行修改或添加新的样式。

总之,通过混入和扩展机制,CSS预处理器可以实现代码块的复用和样式的继承,提高了CSS代码的效率和可维护性。

3、无感登录 怎么实现

无感登录(Seamless login)是一种用户在使用应用程序或网站时无需手动输入用户名和密码进行认证的登录方式,通常通过利用持久性凭证、单点登录或社交媒体授权等方式来实现。以下是实现无感登录的一些常见方法:

  1. 持久性凭证(Persistent Tokens):该方法通过在用户设备上存储持久性凭证(如长期有效的令牌或 Cookie),使得用户下次访问应用程序时可以自动识别并进行登录。这种凭证可以使用加密技术保证安全性。

  2. 单点登录(Single Sign-On, SSO):SSO 是一种身份验证机制,允许用户通过一次登录即可访问多个关联应用程序或网站。用户在通过认证后会获得一个令牌,该令牌可以在不同的应用程序之间共享,避免了重复登录的繁琐过程。

  3. 社交媒体授权(Social Media Authorization):通过允许用户使用其社交媒体账户(如Facebook、Google等)进行登录,应用程序可以获取到用户授权的信息,并将其作为有效的身份验证凭据使用。

在实现无感登录时,需要考虑以下几点安全性问题:

  • 身份验证凭证的安全性:持久性凭证在传输和存储过程中需要进行加密,并采取合适的措施防止被窃取或滥用。
  • 用户隐私保护:在收集和使用用户信息时,需要明确告知用户哪些信息会被收集,并遵守相关的数据隐私法规。
  • 安全漏洞和风险评估:定期进行安全性评估,修复潜在的安全漏洞,确保用户的登录信息和数据不受到未授权访问。

综上所述,实现无感登录可以结合持久性凭证、单点登录和社交媒体授权等技术手段,同时务必关注用户隐私和系统安全,确保用户登录过程的便利性和安全性。

4、vue 自定义指令都有哪些属性

在Vue中,自定义指令可以通过钩子函数来扩展元素和组件的行为。以下是Vue自定义指令的常见钩子函数和属性:

  1. bind:指令第一次绑定到元素时调用。
    • el: 指令所绑定的元素。
    • binding: 一个包含以下属性的对象:
      • value: 指令的绑定值。
      • modifiers: 一个包含修饰符的对象。
      • oldValue: 上一个绑定的值,仅在 update 和 componentUpdated 钩子中可用。
      • expression: 绑定值的字符串表示。
    • vnode: Vue编译生成的虚拟节点。
  2. inserted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已插入文档)。
    • 同bind钩子函数的参数。
  3. update:被绑定元素所在的模板更新时调用,但可能发生在其子元素更新之前。
    • 同bind钩子函数的参数。
  4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
    • 同bind钩子函数的参数。
  5. unbind:指令与元素解绑时调用。
    • 同bind钩子函数的参数。

除了钩子函数中的属性外,还可以在自定义指令中使用一些全局属性和方法,如Vue.directive用于注册全局自定义指令,以及elcomponentInstance等属性。

需要注意的是,不同的钩子函数中可用的参数可能会有所不同,请根据实际需要选择合适的钩子函数和属性来实现自定义指令的功能。

5、keep-alive 中的属性,max 超过设置的数值10后如何处理

在Vue的<keep-alive>组件中,可以使用max属性来限制缓存的组件实例数量。当缓存的组件实例数量超过max设置的数值10时,<keep-alive>组件会按照LRU(Least Recently Used)的方式移除最不活跃的组件实例,以保持缓存数量不超过设定值。

具体处理方式如下:
  1. 当新的组件实例被缓存时,会检查当前缓存的组件实例数量是否已达到或超过max设置的数值10。
  2. 如果当前数量未超过max,新的组件实例将被添加到缓存中,不会触发删除操作。
  3. 如果当前数量已达到或超过max<keep-alive>组件会根据最近访问时间和使用情况,移除最不活跃的组件实例,释放资源空间,并将新的组件实例添加到缓存中。

这样做的目的是控制缓存组件的数量,避免因缓存过多导致内存占用过高,同时保持较常用的组件实例在缓存中,提高系统性能和用户体验。

需要注意的是,超出max设置的数值后的删除行为是自动触发的,并且无法通过配置进行更改。如果需要更细粒度地控制组件实例的缓存和销毁行为,可以考虑自定义缓存策略或动态调整max的值。

6、vue computed 在哪两个勾子函数中间 打印

在Vue的生命周期中,computed属性的计算是在beforeMountmounted勾子函数之间进行的。

具体的生命周期顺序如下:
  1. beforeCreate:实例刚在内存中创建,数据观测 (data observer) 和 event/watcher 事件配置之前。
  2. created:实例已经完成了数据观测 (data observer),属性和方法的运算,watch/event 事件回调。但是还未挂载到DOM上。
  3. beforeMount:在将实例挂载到DOM之前,此时computed属性的计算尚未开始。
  4. mounted:实例已挂载到DOM上。这个阶段可以访问到DOM元素,进行一些初始化操作,例如使用外部插件、操作DOM等。
  5. beforeUpdate:在数据更新之前,发生在虚拟DOM重新渲染和打补丁之前。这里可以在更新之前访问现有的DOM。
  6. updated:重新渲染和打补丁之后,组件已更新。在这个钩子函数中,可以操作DOM,但要避免无限循环更新。
  7. beforeUnmount(Vue 3中为beforeUnmount):实例销毁之前调用。在这里可以进行必要的清理工作。
  8. unmounted(Vue 3中为unmounted):组件实例被销毁后调用。在这个阶段,组件的数据绑定和监听器都会被移除,可以进行最后的清理工作。

根据上述顺序,在beforeMountmounted之间打印computed属性的值,可以在组件挂载到DOM之前获取计算属性的结果。

7、for in 和 for of的区别

for...infor...of都是用于遍历数据的循环语句,但它们有以下几个区别:

  1. 遍历的目标不同:
    • for...in:用于遍历对象的可枚举属性。它会遍历对象自身及其原型链上的所有可枚举属性(包括继承而来的属性)。
    • for...of:用于遍历可迭代对象(例如数组、字符串、Map、Set等)。它可以遍历对象内部的元素或值。
  2. 遍历的值不同:
    • for...in:返回的是属性名或键名(字符串类型)。在遍历对象时,可以通过获取属性值,进一步操作属性对应的值。
    • for...of:返回的是集合中的元素或值。在遍历可迭代对象时,直接获取对象的元素或值,而不需要访问键名。
  3. 遍历顺序不同:
    • for...in:遍历对象属性时,不保证顺序,因为它是根据原始插入顺序遍历属性,但在一些情况下可能会以其他顺序遍历。(例如数字属性会按照升序排列)
    • for...of:遍历可迭代对象时,可以按照元素在集合中的顺序进行迭代。
  4. 使用场景不同:
    • for...in:适用于遍历对象属性,例如需要遍历对象所有的键名。
    • for...of:适用于遍历可迭代对象中的元素或值,例如遍历数组元素、字符串字符等。
示例代码说明:
const obj = { a: 1, b: 2 };

// for...in 遍历对象属性
for (let key in obj) {
  console.log(key); // 输出:a  b
  console.log(obj[key]); // 输出:1  2
}

const arr = [1, 2, 3];

// for...of 遍历数组元素
for (let value of arr) {
  console.log(value); // 输出:1  2  3
}

综上所述,for...in用于遍历对象属性,而for...of用于遍历可迭代对象的元素或值。

8、nextTick 原理
nextTick是Vue中的一个异步方法,用于在DOM更新后执行回调函数。它的原理如下:
  1. nextTick方法的调用时机通常是在Vue的更新队列被添加后的微任务中执行。更新队列会在数据变化后触发,并对组件进行重新渲染。

  2. 当数据发生变化时,Vue会将需要更新的组件添加到一个异步的更新队列中,而不是立即进行DOM更新。

  3. 在下一次事件循环的微任务阶段,Vue会开始遍历更新队列并执行相应的更新操作。

  4. 在每个组件更新完成之后,nextTick方法注册的回调函数会被调用。这确保了回调函数在DOM更新后执行。

  5. 由于使用微任务的方式,在nextTick的回调函数中执行的代码会在浏览器进行渲染之前被执行,从而可以获取到最新的DOM结构。

总结来说,nextTick利用Vue的异步更新机制,在DOM更新后执行回调函数,以便在更新完成后进行其他操作,例如访问已更新的DOM元素或进行其它相关处理。

示例代码:
// 模拟Vue中的异步更新和nextTick方法
const queue = []; // 更新队列

function updateComponent() {
  // 执行组件的更新操作...
  // ...
  
  // 更新完成后执行nextTick注册的回调函数
  nextTick();
}

function nextTick(callback) {
  if (typeof callback === 'function') {
    queue.push(callback);
  }

  Promise.resolve().then(() => {
    for (let i = 0; i < queue.length; i++) {
      if (typeof queue[i] === 'function') {
        queue[i]();
      }
    }
    queue.length = 0; // 清空队列
  });
}

// 假设数据发生了变化,触发组件更新
updateComponent();

// 注册nextTick的回调函数
nextTick(() => {
  console.log('DOM更新完成后执行');
});

上述代码演示了一个简化的nextTick实现,通过异步更新队列和微任务,来保证在DOM更新完成后执行回调函数。

9、axios 中断请求

axions 中断请求有两种方式:

方式一

使用 CancelToken.souce 工厂方法创建一个 cancel token,代码如下:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('https://mdn.github.io/dom-examples/abort-api/sintel.mp4', {
  cancelToken: source.token
}).catch(function (thrown) {
  // 判断请求是否已中止
  if (axios.isCancel(thrown)) {
    // 参数 thrown 是自定义的信息
    console.log('Request canceled', thrown.message);
  } else {
    // 处理错误
  }
});

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
复制代码

中止后的网络请求变成如下所示:

图片

我们再来看看初始时和中止后的 souce 状态:

图片

可以看到,初始时和中止后的 source 状态并没还有发生改变。那么我们是如何判断请求的中止状态呢?axios 为我们提供了一个 isCancel() 方法,用于判断请求的中止状态。isCancel() 方法的参数,就是我们在中止请求时自定义的信息。

图片

方式二

通过传递一个 executor 函数到 CancelToken 的构造函数来创建一个 cancel token:

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// 取消请求
cancel('Operation canceled by the user.');
复制代码

浏览器运行结果与方式一一致,此处不再赘述。

10、部署时候用hash刷新白屏问题

部署时使用哈希(hash)作为文件名的方式,可以有效解决浏览器缓存问题,确保用户获取到最新的文件版本。然而,使用哈希刷新后可能会导致白屏问题,这主要是因为以下原因:

  1. 缓存问题:
    当文件名中的哈希发生变化时,浏览器将视为一个新的文件,并下载最新的文件。但在某些情况下,浏览器可能仍然会从缓存中加载旧文件,导致页面出现错误或白屏。

  2. 引用路径问题:
    如果在 HTML 或 CSS 文件中引用了被哈希的资源文件,而没有及时更新对应的路径,浏览器将无法正确地加载新文件,从而导致白屏问题。

为解决哈希刷新导致的白屏问题,可以尝试以下方法:
  1. 强制刷新页面:
    可以通过按下 Ctrl + F5(Windows)或 Command + Shift + R(Mac)的组合键来强制刷新页面,忽略缓存并重新加载所有文件。

  2. 更新引用路径:
    确保在 HTML 或 CSS 文件中对哈希文件的引用路径是正确的。可以修改对应文件引用的路径,或者使用相对路径或绝对路径来确保能够正确加载最新的文件。

  3. 清除浏览器缓存:
    如果问题仍然存在,可以尝试清除浏览器缓存。具体的步骤取决于所使用的浏览器,可以通过浏览器设置或按照特定浏览器的教程进行操作。

  4. 使用版本号代替哈希:
    考虑在文件名中使用带有版本号的方式来解决缓存问题,而不是使用哈希。每次更新文件时,修改文件名中的版本号,确保浏览器能够正确加载最新的文件。

请根据具体情况尝试这些方法,并确保在部署前进行充分测试,以避免出现白屏问题。

11、tab页用路由模式咋实现

在前端开发中,可以使用路由库或框架来实现tab页的路由模式。以下是一般的实现步骤:

  1. 配置路由:
    首先,您需要配置路由以定义每个tab对应的URL路径和组件。这可以通过路由库或框架的配置文件完成。

  2. 创建tab组件:
    创建一个tab组件,用于显示和切换不同的标签页。该组件可以包含tab标题和一个用于渲染对应标签页内容的容器。

  3. 处理tab切换事件:
    在tab组件中,为每个tab标题添加点击事件处理程序,以便在用户点击tab时触发路由切换操作。您可以使用路由库或框架提供的方法来进行导航操作,将当前URL更新为对应的tab路由。

  4. 渲染对应标签页内容:
    当路由发生变化时,根据当前URL路径获取到对应的标签页组件,并将其渲染到tab组件的内容容器中。这通常由路由库或框架自动完成。

请注意,在实际开发中,具体的实现方式可能会因所使用的前端框架或库而有所差异。您可以参考相关框架或库的文档,查找适合您项目的具体实现方法。

同时,以下是几个常用的前端路由库或框架供您参考:

  • React Router:适用于React应用的流行路由库。
  • Vue Router:Vue.js官方提供的路由库。
  • Angular Router:Angular框架自带的路由模块。
12、vuex中b.module里面咋接收根目录下面的state

在 Vuex 的模块中,如果需要访问根目录下的 state,你可以使用 rootState 参数。下面是一个示例:

在根目录的 state 中有一个名为 count 的状态,而在 vuex 的 b 模块里需要获取这个状态的值。

// 在 b 模块的 getters、actions 或者 mutations 中可以通过 rootState 参数访问根目录下的 state

// b.js(b 模块)
const b = {
  // ...
  getters: {
    getCountFromRoot(state, getters, rootState) {
      return rootState.count;
    }
  },
  // ...
};

export default b;

通过 rootState.count 即可在 getCountFromRoot getter 中访问到根目录下的 count 状态。

13、A跳转B再跳转C,返回时怎么直接到A

在路由跳转中要实现从 C 页面返回到 A 页面,可以使用路由导航的方法进行跳转和返回操作。以下是一种可能的解决方案:

  1. 从页面 A 跳转到页面 B:

    • 可以使用路由库或框架提供的导航方法,比如 router.push<router-link> 组件来实现从 A 跳转到 B。
  2. 从页面 B 跳转到页面 C:

    • 同样使用路由导航方法实现从 B 跳转到 C。
  3. 在页面 C 中直接返回到页面 A:

    • 可以使用路由库或框架提供的导航方法进行返回操作。一种常见的方法是使用 router.go(-2),其中 -2 表示返回两个页面的历史记录,即返回到页面 A。

具体代码示例(以 Vue Router 为例):

在组件 A 中进行跳转到组件 B 的操作:

// 在组件 A 中调用路由导航方法跳转到组件 B
this.$router.push({ path: '/b' });

在组件 B 中进行跳转到组件 C 的操作:

// 在组件 B 中调用路由导航方法跳转到组件 C
this.$router.push({ path: '/c' });

在组件 C 中进行直接返回到组件 A 的操作:

// 在组件 C 中调用路由导航方法返回到组件 A
this.$router.go(-2);

以上代码示例假设你的路由路径配置中存在名为 /b/c 的路由,且组件 A、B、C 分别与对应的路由关联。

希望对你有所帮助!如果还有其他问题,请随时提问。

14、前端怎么部署配置

前端部署和配置依赖于你使用的前端框架和工具。下面是一般性的步骤来帮助你进行前端部署和配置:

  1. 打包静态资源:在部署前,首先需要将前端代码打包成静态资源。大多数前端框架和构建工具(如Webpack、Parcel、Rollup等)都提供了打包功能。运行相应的打包命令,它们会将你的源代码编译、压缩和打包成最终的静态文件。

  2. 选择合适的服务器:选择适合你的项目的服务器。常用的选择包括:

    • 静态文件服务器:如果你的前端代码只包含静态文件(HTML、CSS、JavaScript),可以使用简单的静态文件服务器,如Nginx、Apache等。
    • 后端服务器:如果你的前端代码与后端API服务集成在同一服务器上,你可以使用类似Express.js、Django等后端框架来托管前端静态文件。
  3. 配置服务器:根据你选择的服务器类型,对服务器进行相应的配置:

    • 静态文件服务器:在服务器上设置静态文件目录,并确保服务器能够访问到静态资源文件。
    • 后端服务器:根据框架和服务器配置,将静态文件目录设置为前端打包生成的路径,并确保服务器能够正确地处理前端路由。
  4. 上传静态资源:将打包生成的静态资源上传到服务器。具体的上传方法和工具取决于你使用的服务器和部署方式:

    • 如果是使用静态文件服务器,可以通过FTP、SCP或其他文件传输工具将静态资源文件上传到指定目录。
    • 如果是与后端服务器集成,可以将打包生成的静态资源文件与后端代码一起上传至对应的服务器路径。
  5. 配置域名和端口:根据你的需求,为部署的前端应用配置域名和端口。这涉及到DNS解析和服务器端口配置,具体操作可以参考相关文档或咨询服务提供商。

以上是一个简单的前端部署和配置流程的概述。实际的部署过程可能因具体项目和环境而有所不同。确切的步骤和指令取决于你所使用的前端框架、工具和服务器类型。

希望对你有所帮助!如有其他问题,请随时提问。

15、sass和less用到哪些属性,配置啥的

Sass和Less是两种常用的CSS预处理器,它们为CSS提供了更强大的功能和易用性。下面是它们的一些主要属性和配置:

Sass:
  • 变量:使用$符号定义变量,并可以在样式中多次引用。
  • 嵌套规则:可以将选择器嵌套在父选择器内,简化代码结构。
  • 混合器(Mixins):类似于函数,可以重用一组样式,并在需要的地方进行调用。
  • 继承:使用@extend关键字实现样式的继承,减少重复代码。
  • 导入外部文件:使用@import导入外部Sass文件,可以模块化管理样式。
Less:
  • 变量:使用@符号定义变量,并可以在样式中多次引用。
  • 嵌套规则:可以将选择器嵌套在父选择器内,提高可读性和维护性。
  • 混合器(Mixins):类似于函数,可以重用一组样式,并在需要的地方进行调用。
  • 继承:使用&:extend关键字实现样式的继承,减少重复代码。
  • 导入外部文件:使用@import导入外部Less文件,可以模块化管理样式。
配置方面:
  • 安装和编译:需要安装Sass或Less的编译工具,例如Sass使用的是Ruby的sass gem,而Less使用的是Less.js或者其他构建工具。
  • 编译选项:可以配置编译工具的选项,例如输入文件路径、输出文件路径、编译模式(开发环境或生产环境)、自动补全浏览器前缀等。
  • 文件结构组织:根据项目需求,可以设置Sass或Less文件的目录结构,并使用适当的文件命名规范。
  • 导入顺序:在主文件中使用@import导入其他Sass或Less文件时,要注意导入顺序,确保样式按正确顺序加载。

需要注意的是,具体的属性和配置细节可能会因为不同版本和工具的使用而有所变化。建议查阅相关文档或官方指南以获取最新和准确的信息。

Node

1、什么是Node.js?它与前端开发有什么关系?

答案: Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,可以让JavaScript代码在服务器端运行。与传统的前端开发相比,Node.js使得开发者可以使用JavaScript开发后端应用程序。

2、Node.js的特点是什么?它适用于哪些场景?

答案: Node.js具有以下几个特点:

  • 非阻塞式I/O:Node.js采用事件驱动、非阻塞的I/O模型,能够处理大量并发请求。
  • 单线程:Node.js使用单线程模型来处理请求,避免了多线程之间的同步问题。
  • 轻量高效:由于采用了事件驱动和单线程模型,Node.js拥有较低的资源消耗和快速的响应能力。

Node.js适用于构建高并发、实时性要求高的网络应用,如聊天室、实时通讯应用、推送服务、大规模数据处理等

3、如何创建一个基本的HTTP服务器并监听特定端口?

答案和解析:

const http = require('http');

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, World!');
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/`);
});

通过使用Node.js的内置模块http,创建一个HTTP服务器并通过createServer方法传入回调函数来处理请求。在回调函数中,设置响应状态码、头部信息和返回内容。最后,使用listen方法指定监听的端口,并在回调函数中输出服务器运行信息。

4、什么是模块(Module)?如何在Node.js中使用模块?

答案: 在Node.js中,模块是可以重用的代码块,用于组织和封装相关功能。每个文件就是一个模块,模块里面定义的变量、函数或类默认只在当前模块内可见,不同模块之间可以通过导入和导出的方式进行交互。

在Node.js中,可以使用require函数来导入其他模块的导出部分,并将其赋值给一个变量,然后可以通过该变量访问被导入模块的内容。例如:

// 导入名为myModule的模块
const myModule = require('./myModule');

// 使用myModule模块中导出的函数
myModule.myFunction();
5、require()函数的作用是什么?它如何加载和导出模块?

答案: require()函数用于在Node.js中加载模块,并返回模块的导出部分(exports)。它接收一个模块标识符作为参数,可以是相对路径或模块名。

在加载模块时,Node.js会根据传入的标识符解析模块的位置。如果是相对路径,则根据当前模块的位置进行解析;如果是模块名,则按照一定的规则搜索模块并加载。

模块可以通过exports对象来导出其功能。在模块内部,可以将需要导出的变量、函数或类赋值给exports对象的属性。例如:

// myModule.js
exports.myFunction = () => {
  console.log('Hello from myModule!');
};

其他模块在使用该模块时,可以通过require()函数导入,并访问其中导出的内容。

6、Node.js中常用的内置模块有:
  • http:用于创建基于HTTP协议的服务器和客户端。
  • fs:提供文件系统相关功能,例如读取和写入文件。
  • path:用于处理文件路径的工具函数。
  • os:提供操作系统相关的信息和操作。
  • events:用于处理事件的模块。
7、处理异步操作以避免回调地狱的方式:
  • 使用Promise对象:通过Promise对象可以更优雅地处理异步操作,链式调用then()方法处理成功的结果,使用catch()方法捕获错误。
  • 使用async/await:结合async函数和await关键字,可以以同步的方式编写异步代码,避免回调嵌套。
  • 使用异步库或工具函数:如Async.js、Bluebird等第三方库,提供了强大的异步控制流程管理工具。
8、包管理器是管理和安装软件包(包括库、框架和工具)的工具。Node.js中常用的包管理器有:
  • npm(Node Package Manager):是Node.js官方提供的默认包管理器。
  • Yarn:是Facebook开发的快速可靠的替代npm的包管理器。
9、读取和写入文件的代码示例:
const fs = require('fs');

// 读取文件
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// 写入文件
fs.writeFile('file.txt', 'Hello, world!', (err) => {
  if (err) throw err;
  console.log('File written successfully.');
});
10、如何处理表单提交数据?请简述一下Express框架的用途和优势。

在Express框架中,可以使用中间件(如body-parser)来解析请求体,获取表单提交的数据。通过Express提供的路由功能,可以根据不同的HTTP请求方法和URL路径,编写对应的处理函数来处理表单提交的数据。处理函数可以访问请求对象(req)中的参数和数据,并进行相应的处理逻辑,最后返回响应给客户端。

Express框架是一个基于Node.js的快速、灵活的Web应用程序框架,它简化了Web应用程序的开发过程。它的主要用途包括路由管理、中间件支持、静态文件服务、模板引擎等。相比原生的Node.js开发,Express提供了更简洁、高效的API和丰富的插件生态系统,使得开发Web应用变得更加容易。

Express框架的优势包括:
  1. 简单易学:Express提供了简洁而直观的API,易于学习和上手。
  2. 快速灵活:Express具有良好的性能,可灵活扩展和定制,满足各种项目需求。
  3. 中间件支持:通过中间件机制,可以方便地实现功能扩展、错误处理、身份验证等。
  4. 大型社区:Express拥有庞大的开发者社区和插件生态系统,提供了丰富的解决方案和支持。
11、如何处理路由请求?请介绍一下RESTful API的设计原则。

在Express框架中,可以使用app.get()app.post()等方法定义路由来处理不同的HTTP请求。根据请求的URL路径和请求方法,配置对应的路由处理函数,完成具体的业务逻辑。以RESTful API为例,其设计原则包括:

1、使用合适的HTTP方法:
  • GET:用于获取资源
  • POST:用于创建新资源
  • PUT/PATCH:用于更新资源
  • DELETE:用于删除资源
2、使用语义化的URL路径:
  • 使用名词表示资源,避免使用动词
  • 使用URL路径传递参数,避免使用查询字符串
3、返回合适的HTTP状态码:
  • 200 OK:成功响应
  • 201 Created:资源创建成功
  • 400 Bad Request:请求参数错误
  • 404 Not Found:资源不存在
  • 500 Internal Server Error:服务器端错误
4、使用JSON格式进行数据交互:
  • 请求和响应的数据格式通常使用JSON来标准化表示
12、Node.js中的事件驱动编程是什么意思?请给出一个具体的例子。

事件驱动编程是一种编程范式,基于事件和事件处理函数之间的关系来组织代码逻辑。在Node.js中,许多核心模块和第三方模块都使用事件驱动机制。例如,Node.js中的EventEmitter是一个基于事件的类,允许对象发出特定事件并监听相应的事件进行处理。

以下是一个示例:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

myEmitter.on('event', () => {
  console.log('触发了event事件');
});

myEmitter.emit('event');

在这个例子中,创建了一个自定义的MyEmitter类,它继承了EventEmitter类,并通过on方法监听了名为"event"的事件。当使用emit方法触发"event"事件时,其中注册的事件处理函数会被调用,打印出"触发了event事件"。

13、什么是中间件(Middleware)?它在Node.js中的作用是什么?

中间件是一种在请求和响应的处理过程中,位于应用程序和服务器之间的函数或功能模块。在Node.js中,Express框架广泛使用中间件机制来实现各种功能,如路由处理、错误处理、身份验证等。

中间件在请求到达路由处理函数之前或响应发送给客户端之前,对请求和响应进行预处理、转换或执行其他操作。它可以修改请求和响应对象、调用下一个中间件函数,或终止请求-响应周期。

通过使用中间件可以实现以下功能:

  • 路由处理
  • 请求参数解析
  • 静态文件服务
  • 错误处理
  • 身份验证
  • 日志记录
  • 安全性处理等
14、要使用Node.js连接和操作数据库,一般需要按照以下步骤进行:
  1. 安装相关数据库驱动:首先根据需要选择合适的数据库,然后使用npm(Node.js包管理工具)安装相应的数据库驱动模块,例如MongoDB可以使用npm install mongodb命令安装。

  2. 引入数据库驱动模块:在代码中引入所需的数据库驱动模块,例如MongoDB可以使用require('mongodb')进行引入。

  3. 建立数据库连接:创建一个到数据库的连接,提供数据库的连接配置信息,例如服务器地址、端口号、认证凭证等。

  4. 执行数据库操作:使用数据库驱动模块提供的API进行数据查询、插入、更新、删除等操作。

下面是一个简单的示例代码,以连接MongoDB数据库为例:

const MongoClient = require('mongodb').MongoClient;

// 连接配置
const url = 'mongodb://localhost:27017';
const dbName = 'mydb';

// 创建连接实例
const client = new MongoClient(url, { useUnifiedTopology: true });

// 连接数据库
client.connect(function(err) {
  if (err) {
    console.error('Failed to connect to database', err);
    return;
  }
  
  console.log('Connected successfully to database');
  
  const db = client.db(dbName);
  
  // 在此执行数据库操作
  // ...
  
  // 关闭连接
  client.close();
});
15、关于WebSocket的解释和与HTTP通信的区别如下:

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时数据传输,而不需要在每个请求-响应周期中建立新的连接。

与HTTP相比,WebSocket具有以下区别:

  1. 连接方式:HTTP是无状态的请求-响应协议,在每个请求中都需要重新建立连接。而WebSocket在初始化阶段通过HTTP协议进行握手,完成后建立了一条持久的连接,可以在任意时刻双向传输数据。

  2. 传输效率:由于WebSocket建立了持久连接,避免了多次创建和关闭连接的开销,因此在频繁通信和大量数据传输场景下更高效。

  3. 支持实时性:WebSocket支持服务器主动推送数据给客户端,使得实时性要求较高的应用(如聊天应用、股票行情等)能够及时更新数据,而不需要客户端不断轮询或发送请求。

需要注意的是,WebSocket协议作为一个独立的协议存在,与HTTP协议并没有本质的关系,但WebSocket的握手过程使用了HTTP协议来完成初始连接的建立。

16、前端常见的解决跨域的方式

前端常见的跨域处理方式包括以下几种:

  1. JSONP(JSON with Padding):JSONP是一种利用<script>标签可以跨域加载资源的特性来实现跨域请求的方法。通过在页面中动态创建一个带有回调函数参数的<script>标签,服务器返回包裹在回调函数中的数据,从而实现跨域数据获取。但是JSONP只支持GET请求。

  2. CORS(Cross-Origin Resource Sharing):CORS是一种在服务器上设置响应头以允许跨域请求的机制。服务器需要在响应中添加一些特定的HTTP头部,如Access-Control-Allow-Origin,Access-Control-Allow-Methods等,指示哪些源、方法或标头允许跨域请求。

  3. 代理服务器:前端可以设置代理服务器来转发请求,将跨域请求发送到同源的后端服务器上,然后由后端服务器代为请求目标API,并将结果返回给前端。这样前端通过访问自己的服务器实际上是同源请求,避免了跨域问题。

  4. WebSocket:WebSocket协议本身支持跨域通信,不受同源策略限制,因此可以作为一种跨域请求的解决方案。通过建立WebSocket连接后,客户端和服务器之间可以进行实时双向通信。

  5. 使用反向代理:在部署环境中,可以通过使用反向代理服务器将前端应用和后端API部署在同一个域名下,实现同源访问。常用的反向代理工具有Nginx和Apache等。

17、如何通过 vuex 实现登录验证?

通过Vuex可以实现登录验证的功能。下面是一种使用Vuex进行登录验证的基本思路:

  1. 创建Vuex Store:首先,在Vuex中创建一个store,用于存储用户的登录状态和相关信息。

  2. 定义State:在store中定义一个state,包括用户是否已登录的布尔值(例如isLoggedin)和用户信息对象(例如userInfo)等。

  3. 定义Mutations:在store中定义mutations,用于修改state中的数据。添加一个mutation,例如setLoggedin,用于设置用户的登录状态,另外还可以添加其它需要修改state的mutations。

  4. 定义Actions:在store中定义actions,用于异步操作和调用mutations修改state。添加一个action,例如login,用于处理用户登录的逻辑。在此action中可以发送登录请求到服务器进行验证,并根据返回结果调用对应的mutation来修改用户的登录状态和用户信息。

  5. 在组件中使用:在需要进行登录验证的组件中,可以使用mapState将state中的数据映射到组件的计算属性或者直接使用this.$store.state来获取登录状态和用户信息。通过mapMutations将mutations映射到组件的方法中,可以在组件中触发对应的mutation来修改state。

这只是一个基本的示例,具体实现要根据项目需求而定。在Vuex中结合路由守卫和axios等工具,可以更加灵活地实现登录验证功能,保护需要登录才能访问的页面。

需要注意的是,登录验证是一个涉及安全性的敏感操作,要确保在处理登录验证时遵循安全规范,例如对用户输入进行合法性校验、使用安全的传输方式等。

18、router 和route 区别

在Vue.js中,"router"和"route"是两个相关但不同的概念。

  1. Router(路由器):Router是Vue Router库提供的功能之一,用于管理应用程序的路由。它是一个全局配置,用于定义应用程序的路由规则,并且允许用户在不同的URL路径下加载不同的组件。通常,在Vue.js应用中,我们会创建一个router实例,配置各种路由规则,然后将其与Vue实例关联起来。这样,当用户在浏览器地址栏输入特定的URL或者通过页面跳转时,Vue Router会根据路由规则来动态地渲染对应的组件。

  2. Route(路由):Route表示当前活动的路由信息,是在组件中使用的一个对象。当路由发生变化时,Vue Router会将当前的路由信息注入到组件内部的 r o u t e 属 性 中 , 从 而 让 组 件 可 以 获 取 有 关 当 前 路 由 的 信 息 。 例 如 , route属性中,从而让组件可以获取有关当前路由的信息。例如, routeroute对象包含了当前的路径(path)、参数(params)、查询参数(query)、元信息(meta),以及一些导航相关的方法(push、replace、go等)。通过访问$route对象,我们可以在组件内部根据路由信息进行条件渲染、处理页面逻辑、获取参数等操作。

总结来说,"router"是整个Vue Router库,用于管理应用程序的路由规则;而"route"是指当前活动的路由信息对象,包含了有关当前路由的详细信息,可以在组件中使用。

19、created 生命周期不可以千什么事情

在Vue.js中,created生命周期函数是在组件实例创建完成后立即调用的钩子函数。在created生命周期阶段,组件实例已经完成了数据观测(data observer)和事件初始化(event initialization),但尚未挂载到DOM中。

在created生命周期函数中,可以进行以下操作:

  1. 初始化数据:可以进行一些数据的初始化操作,例如设置默认值、请求数据等。

  2. 进行异步操作:可以使用异步请求数据,例如发送网络请求或者从本地存储中读取数据。

  3. 订阅事件/监听器:可以在created生命周期函数中订阅全局事件或者其他组件的自定义事件,并定义对应的处理函数。

  4. 调用方法/执行逻辑:可以调用组件内部的方法,执行一些逻辑操作,对数据进行处理等。

  5. 注册插件:可以在created生命周期函数中注册Vue插件,以扩展Vue功能。

需要注意的是,在created生命周期函数中不能进行DOM相关的操作,因为此时组件还没有被挂载到真实的DOM中。如果需要在DOM操作相关的代码,则需要在mounted生命周期函数中执行。

总的来说,created生命周期函数适合进行组件的初始化和数据准备的操作,但不适合进行直接涉及DOM的操作。

20、数据拦截是发生在哪个生命周期的? 什么情况下不会对属性数据拦截,要怎么解决?

在Vue.js中,数据拦截(data interception)是发生在组件实例的创建过程中,具体是在初始化组件实例时进行的,而不是在某个具体的生命周期函数中。

Vue.js通过使用Observer来监听和拦截数据的变化。当Vue实例创建时,会将组件中的data对象进行递归遍历并使用Object.defineProperty对每个属性进行拦截。这样,当数据发生变化时,Vue能够捕捉到,并执行相应的更新操作。

然而,也有一种情况下会导致属性数据不被拦截,即当数据已经存在于组件实例data对象中,但在创建组件实例之前就已经被定义或修改。这种情况可能发生在通过原型继承或动态添加属性时。

要解决这个问题,可以采用以下两种方法:

  1. 在组件实例创建之前,确保所有的数据属性都已经在data对象中进行了初始化。这样,属性就能够被正常拦截和观测。

  2. 对于需要后期添加或修改的属性,可以使用Vue.set()或this.$set()方法进行手动拦截。这些方法可以在组件实例创建后动态地向响应式的对象中添加新的属性,并确保其被拦截和观测。

总结来说,数据拦截发生在组件实例的创建过程中,但在组件实例创建之前已经定义或修改的属性可能不会被拦截。我们可以通过在data对象中初始化所有的属性,或使用Vue.set()或this.$set()方法来解决这个问题。

21、css bfc

BFC是指块级格式化上下文(Block Formatting Context),它是CSS中的一种渲染机制。

BFC是一个独立的渲染区域,其中的元素按照一定的规则进行布局和渲染。在BFC中,每个元素都按照垂直方向依次放置,两个相邻的块级元素之间的垂直边距会发生重叠。

触发BFC的条件包括:
  1. 根元素(HTML)
  2. 设置了浮动(float属性不为none)
  3. 设置了绝对定位(position为absolute或fixed)
  4. display的值为inline-block、flex或grid
  5. overflow的值不为visible
BFC具有以下特性:
  1. 内部的元素在垂直方向上排列,从顶部开始,一直到底部。
  2. 阻止垂直外边距的重叠。
  3. BFC区域不会与浮动元素重叠。
  4. 清除浮动,使得父元素可以正确地包裹浮动的子元素。
  5. 计算容器的高度时,会包含浮动元素。
BFC在实际的页面布局中有多种应用,例如:
  1. 清除浮动:当父元素包含浮动的子元素时,可以将父元素触发BFC,以清除浮动后造成的布局问题。
  2. 防止外边距折叠:当相邻的块级元素垂直外边距重叠时,可以触发BFC,使它们不再重叠。
  3. 实现多栏布局:通过将容器触发BFC,可以让其中的内容按照列布局进行排列。

通过使用BFC,我们可以更好地控制元素在布局中的行为和相互关系,提供更灵活和可靠的页面布局解决方案。

22、什么是圣杯布局?

圣杯布局(Holy Grail Layout)是一种用于创建三栏等高布局的技术。它由Matthew Levine提出,并在CSS中被广泛应用。

在圣杯布局中,有一个主要内容区域,以及两个侧边栏。这三栏的高度都应当相等,并且主要内容区域应当排在第一位,然后是左侧边栏,最后是右侧边栏。这样可以实现侧边栏优先加载、语义化结构和SEO友好的页面布局效果。

实现圣杯布局的关键是使用浮动和负边距来进行定位。具体步骤如下:

  1. 将三个栏目用
    等容器包装起来,并设置宽度为100%。
  2. 通过浮动将左侧和右侧的侧边栏移动到左右两侧。
  3. 为主要内容区域添加左右内边距,以避免被侧边栏覆盖。
  4. 使用负边距将左侧侧边栏移动到主要内容的前面,并设置足够大的宽度,使其占据正常位置。
  5. 使用负边距将右侧侧边栏移动到主要内容的后面,并设置足够大的宽度,使其占据正常位置。
  6. 使用相对定位将三个栏目包装容器还原到正常位置,避免宽度受到影响。

通过以上步骤,就可以实现三栏等高布局的圣杯布局效果。同时,可以使用盒模型的box-sizing属性,以及适当的CSS选择器来进一步完善和定制布局。

需要注意的是,在实际应用中,为了兼容不同浏览器和设备,可能需要进行额外的调整和兼容性处理,例如使用CSS Flexbox或Grid布局等。

23、什么是双飞翼布局

双飞翼布局(Double-Flight Layout)是一种用于创建三栏等高布局的技术,与圣杯布局类似。它由淘宝Tangram团队提出,并通过使用CSS浮动和负边距来实现。

在双飞翼布局中,同样有一个主要内容区域和两个侧边栏。这三栏的高度都应当相等,并且主要内容区域优先加载、语义化结构和SEO友好。

与圣杯布局不同的是,双飞翼布局采用了更为简洁的HTML结构。具体步骤如下:

  1. 将三个栏目用
    等容器包装起来,并设置宽度为100%。
  2. 对主要内容区域设置左右内边距,以防止被侧边栏覆盖。
  3. 使用浮动将主要内容区域置于最前面。同时,通过设置左右外边距为对应侧边栏的宽度,使其腾出位置给侧边栏。
  4. 在主要内容区域内创建一个子容器,在其中放置实际的内容。这个子容器可以设置左右内边距,避免内容贴近浏览器窗口边界。
  5. 通过负边距将两个侧边栏移动到主要内容区域的两侧,实现三栏并排布局。
  6. 对侧边栏进行浮动,并设置相应的宽度。
  7. 通过使用相对定位使得三个栏目容器回归正常位置。

通过以上步骤,就可以实现双飞翼布局。同样,为了兼容不同浏览器和设备,可能需要进行额外的调整和兼容性处理,例如使用CSS Flexbox或Grid布局等。

24、http 缓存

HTTP缓存是一种用于优化Web性能的机制,它通过在客户端和服务器之间存储和重复使用资源来减少网络请求和响应的数量,从而提高页面加载速度并减少带宽消耗。

HTTP缓存可以分为两种类型:强缓存和协商缓存。

强缓存是指浏览器在第一次请求资源时,将资源的响应标记为可缓存,并在一定时间内直接从本地缓存中获取该资源,而无需再次发送请求到服务器。常用的强缓存策略有两个HTTP头字段:

  • Expires:指定资源的过期时间,是一个具体的时间点。
  • Cache-Control:指定资源的缓存策略,包括max-age(相对时间)和s-maxage(对共享缓存有效)等参数。

如果资源命中了强缓存,浏览器就会直接从缓存中读取并使用该资源,不再发送请求到服务器。

协商缓存是指浏览器在第一次请求资源时,向服务器发送请求并携带缓存标识(如Last-Modified、ETag),服务器根据这些标识判断资源是否已经发生变化。如果资源未发生变化,则服务器返回304 Not Modified状态码,告知浏览器直接使用缓存的资源。如果资源发生变化,服务器返回新的资源和200 OK状态码。

常用的协商缓存策略有两个HTTP头字段:

  • Last-Modified / If-Modified-Since:响应中包含资源的最后修改时间,请求中可以携带这个值,服务器根据它来判断资源是否已经变化。
  • ETag / If-None-Match:响应中包含资源的唯一标识符,类似于无法被修改的版本号,请求中可以携带这个值,服务器根据它来判断资源是否已经变化。

如果资源命中了协商缓存,服务器返回304 Not Modified状态码,浏览器就会从

除了HTTP缓存,还有其他几种常见的缓存技术。以下是其中一些:

  1. 数据库缓存:将数据库查询结果或计算结果存储在缓存中,以提高数据检索和处理的速度。常用的数据库缓存工具包括Redis和Memcached。

  2. CDN缓存:内容分发网络(CDN)通过将静态资源(如图片、CSS和JavaScript文件)缓存在全球各地的边缘节点上,以减少远程请求并加快内容传输速度。

  3. 应用程序级缓存:应用程序级缓存是指缓存应用程序运行时产生的数据或计算结果,避免频繁的数据访问或计算操作。可以使用内存缓存(如Redis、Memcached)或分布式缓存(如Hazelcast)来实现。

  4. 操作系统缓存:操作系统会将最近使用的文件、页面和执行代码等存储在内存中,以加快访问速度。操作系统缓存能够有效降低磁盘IO的压力,并提高系统的响应性能。

这些缓存技术可以根据需求和场景进行选择和组合使用,以提高系统的性能和用户体验。

浏览器有以下几种缓存方式:
  1. 强缓存:通过设置HTTP响应头中的Expires或Cache-Control字段来控制缓存的有效期。当浏览器发送请求时,会先检查缓存是否过期,如果没有过期,则直接使用缓存中的数据,不会发送请求到服务器。
  2. 协商缓存:通过设置HTTP响应头中的Last-Modified和ETag字段来标识资源的版本信息。当浏览器发送请求时,会携带If-Modified-Since和If-None-Match字段,服务器根据这些字段来判断资源是否发生变化。如果资源没有变化,则返回304 Not Modified,浏览器直接使用缓存中的数据。
  3. Service Worker缓存:Service Worker是一种在浏览器后台运行的脚本,可以拦截网络请求并自定义缓存策略。开发者可以通过编写Service Worker脚本来实现离线缓存、推送通知等功能。
  4. 内存缓存:浏览器会将一部分资源存储在内存中,以提高访问速度。内存缓存的容量较小,一般只能存储一些常用的资源。
  5. 硬盘缓存:浏览器会将一部分资源存储在硬盘中,以便在下次访问时直接读取。硬盘缓存的容量较大,可以存储较多的资源。

每种浏览器的缓存大小限制不同,具体大小取决于浏览器的版本和配置。一般来说,内存缓存的容量较小,通常在几十MB到几百MB之间。而硬盘缓存的容量较大,可以达到几个GB甚至更大。开发者可以通过浏

25、http 1.0 和 2.0 的区别

HTTP/1.0和HTTP/2.0是两个不同版本的超文本传输协议(HTTP)。它们之间有以下主要区别:

  1. 连接复用:在HTTP/1.0中,每个请求都需要建立一个新的TCP连接,完成后立即关闭。而在HTTP/2.0中,引入了多路复用技术,可以在单个TCP连接上同时进行多个请求和响应,减少了连接建立和关闭的开销,并提高了性能。

  2. 头部压缩:HTTP/2.0使用了HPACK算法对头部信息进行压缩,减少了传输的数据量。相比之下,HTTP/1.0的头部信息没有进行压缩,导致每个请求的头部数据较大。

  3. 服务器推送:HTTP/2.0引入了服务器推送机制,服务器可以在客户端请求之前主动将一些与请求相关的资源推送给客户端,从而减少了额外的请求数量,提高了页面加载速度。

  4. 优化的二进制传输:HTTP/2.0将数据传输格式从文本(HTTP/1.0)改为二进制格式,通过分帧、流和优先级等机制,提供了更高效的数据传输方式,减少了延迟并增加了吞吐量。

  5. 错误处理:在HTTP/2.0中,错误信息以帧的形式直接传输,而在HTTP/1.0中需要通过完整的响应来传递错误信息。

综上所述,HTTP/2.0相较于HTTP/1.0在性能和效率方面有显著改进,并提供了更多的功能和特性,促进了更快速、更高效的网络通信。

26、介绍下 bfs,dfs

BFS(广度优先搜索)和DFS(深度优先搜索)是两种常见的图遍历算法,用于在图或树中搜索特定节点或遍历所有节点。

1、广度优先搜索(BFS):
  • 从起始节点开始,逐层地向外遍历,先访问离起始节点最近的节点。
  • 使用队列数据结构对节点进行存储和扩展(按照队列的FIFO原则)。
  • 每次从队列中取出一个节点,并将其未访问过的相邻节点加入队列。
  • 重复执行上述步骤,直到遍历完整个图或找到目标节点。
2、深度优先搜索(DFS):
  • 从起始节点开始,沿着一条路径尽可能深入地访问节点,直到不能继续为止,然后回溯到上一个节点继续探索其他路径。
  • 使用栈数据结构对节点进行存储和回溯(按照栈的LIFO原则)。
  • 以递归或显式栈来实现DFS算法。
  • 对每个未访问过的相邻节点递归或压入栈中,继续深度搜索直到找到目标节点或遍历完整个图。
选择使用BFS还是DFS取决于具体的应用场景和问题要求:
  • BFS适合用于寻找最短路径或离起点最近的节点,如迷宫问题、单词变换等。
  • DFS适合用于遍历或搜索所有可能的路径,如生成括号序列、图的连通性判断等。

需要注意的是,BFS和DFS算法都需要标记已访问的节点,以防止无限循环或重复访问。

27、输入网址发生了什么

当你输入一个网址并按下回车时,发生了以下过程:

  1. URL解析:浏览器会解析输入的网址(URL),包括协议(如HTTP、HTTPS)、域名(如www.example.com)和路径(如/page)等信息。

  2. DNS解析:浏览器将域名转换为对应的IP地址,这个过程称为DNS解析。首先,浏览器查找本地DNS缓存,如果找到匹配的IP地址,则直接使用该IP地址。如果没有找到,浏览器将发送请求给DNS服务器,以获取域名对应的IP地址。

  3. 建立TCP连接:浏览器与服务器之间需要建立一个TCP连接。它使用三次握手过程来确保双方的连接可靠。握手完成后,客户端(浏览器)和服务器之间建立起了一个TCP连接。

  4. 发送HTTP请求:浏览器构建一个HTTP请求报文,包含请求方法(如GET、POST)、请求头部、请求体等信息。然后,浏览器将请求报文发送给服务器。

  5. 服务器处理请求:服务器收到HTTP请求后,根据请求的内容进行相应的处理。可能的处理操作包括查询数据库、读取文件、处理业务逻辑等。

  6. 服务器发送HTTP响应:服务器生成一个HTTP响应报文,包含响应状态码、响应头部、响应体等信息。然后,服务器将响应报文发送回客户端(浏览器)。

  7. 浏览器渲染:浏览器接收到HTTP响应后,根据响应的内容进行解析和渲染。它会解析HTML、CSS和JavaScript等资源,并将其呈现在屏幕上,以展示网页内容给用户。

  8. 断开TCP连接:在数据传输完成后,浏览器和服务器之间的TCP连接会被断开,释放网络资源。

以上是一个简单的描述,实际情况可能还涉及到缓存机制、重定向、身份验证等其他步骤,具体的过程可能会因使用的协议、服务器配置和网页内容等因素而有所不同。

28、websocket 主题

WebSocket是一种网络通信协议,它允许在客户端和服务器之间建立双向通信的连接。它的主要特点是实时性和低延迟。使用WebSocket,可以实现实时数据传输、推送通知、即时聊天等功能。

至于"主题",在WebSocket中没有直接称为"主题"的概念。但是,可以通过在消息中添加特定的标识符或自定义数据字段来实现类似主题的功能。这样,不同的消息可以根据标识符或数据字段进行区分和处理,以实现不同主题或频道的消息传递。

29、前端怎么做公共模块的抽离

在前端开发中,抽离公共模块是一种常见的优化方式,可以提高代码的可维护性和复用性。以下是几种常用的方法来进行公共模块的抽离:

  1. 函数封装:将一段常用的功能逻辑封装成函数,并以参数形式暴露出来,在不同的地方进行调用。这样可以避免代码重复,提高代码的复用性。

  2. 组件抽离:将一部分功能相似或通用的代码抽离成组件,通过引入和使用组件来实现公共模块的复用。例如,可以将页面中多处使用的头部、导航栏等抽离成组件。

  3. 工具类/插件封装:将一些常用的工具函数或第三方插件封装成库,供整个项目或多个项目共享使用。这样可以提高代码的复用性,并且便于在不同的项目中统一管理和更新。

  4. 模块化管理:使用模块化开发的方式,将公共的逻辑和功能封装成独立的模块,通过导入和使用模块来实现代码复用。可以使用像ES Modules、CommonJS等模块化规范,或者使用打包工具(如Webpack、Rollup)进行模块的打包和管理。

  5. 配置文件提取:将一些通用的配置项(如API接口地址、样式主题等)提取出来,放在单独的配置文件中进行管理。不同的模块可以共享和引用这些配置项,方便统一管理和修改。

以上是几种常见的方法来进行公共模块的抽离,根据具体需求和项目情况选择适合的方式进行抽离,以提高代码的可维护性和复用性。

30、组件封装的时候如果在用到别人封装的组件库里面还想再用到他封装之前的方法或者插槽应该怎么用

当你在使用别人封装的组件库时,想要在其中的组件中使用该库之前的方法或插槽,可以通过以下几种方式来实现:

  1. 组件扩展:有些组件库提供了组件扩展的能力,允许你在其基础上进行自定义和修改。你可以根据组件库的文档查找相关的扩展方式,在扩展的过程中保留原有的方法或插槽。
  2. 插槽/作用域插槽:如果组件库支持插槽功能,你可以在使用组件时通过插槽的方式插入额外的内容,包括之前的方法或插槽。具体的插槽用法可以参考组件库的文档,了解如何使用插槽并嵌套之前的方法或插槽。
  3. 组件组合:在某些情况下,你可以创建一个新的组件,利用组合的方式将别人封装的组件与之前的方法或插槽进行组合。在新组件中,你可以引入别人封装的组件,并在组件中使用之前的方法或插槽。

需要注意的是,具体的使用方式取决于所使用的组件库的实现和文档说明。建议查阅组件库的官方文档或示例,以了解如何在其组件中使用之前的方法或插槽。

31、自己在mui基础上封装组件怎么让别人在使用自己封装的组件的时候还能调用mui原生的方法和api

在封装基于 MUI 的组件时,你可以通过以下方式让别人能够在使用你的封装组件时仍然调用 MUI 原生的方法和 API:

  1. 透传属性(Prop Forwarding):将 MUI 组件所支持的属性透传到你封装的组件上。这样,在使用你封装的组件时,可以直接将 MUI 组件所支持的属性传递给你的组件,从而实现对 MUI 原生方法和 API 的调用。
  2. 插槽(Slots):如果 MUI 组件支持插槽功能,你可以在封装的组件中提供相应的插槽,以便用户可以在使用你的组件时放置 MUI 原生组件或其他内容,并通过插槽来实现与原生方法和 API 的交互。
  3. 扩展(Extend):某些情况下,你可能需要通过继承 MUI 组件来扩展你封装的组件,以保留并增加 MUI 原生的方法和 API。通过继承 MUI 组件,你可以在你的组件中使用 MUI 组件的方法和 API,并添加自定义的功能和样式。

总之,要让别人能够调用 MUI 原生的方法和 API,在封装的组件中要考虑如何透传属性、使用插槽和扩展 MUI 组件的功能。请仔细阅读 MUI 的文档,了解组件的属性、插槽和扩展机制,并根据需要进行相应的封装。

32、跨一层级的插槽怎么使用

跨一层级的插槽可以通过作用域插槽(Scoped Slots)来实现。作用域插槽允许你将父组件中的内容传递到子组件,并在子组件中进行处理和展示。

以下是使用跨一层级插槽的一般步骤:

  1. 在父组件中定义插槽:
    <template>
      <child-component>
        <template v-slot:slotName="slotProps">
          <!-- 在这里编写要传递给子组件的内容和插槽相关代码 -->
        </template>
      </child-component>
    </template>
    
  2. 在子组件中接收并使用插槽内容:
    <template>
      <div>
        <!-- 在子组件中使用插槽内容和处理逻辑 -->
        <slot name="slotName" :slotProps="propsToSendToSlot"></slot>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          propsToSendToSlot: {} // 可选:传递给插槽的属性/数据对象
        };
      }
    };
    </script>
    

在上述示例中,父组件中的<template v-slot:slotName>部分定义了跨一层级的插槽,并命名为slotName。子组件中的<slot>标签中也使用了相同的插槽名slotName,并通过:slotProps属性传递父组件中的属性或数据对象(propsToSendToSlot)。

请根据实际情况,将代码中的slotName替换为你想要的插槽名称,并根据需要传递父组件的属性或数据对象。这样,父组件中的内容将会传递到子组件,并在子组件中进行展示和处理。

需要注意的是,作用域插槽的具体使用方式可能会因框架或组件库而异,以上示例是基于 Vue.js 的语法。如果你在使用其他框架或库,请参考相应的文档来了解如何使用跨一层级的插槽。

33、长列表怎么做性能优化

对于长列表的性能优化,可以考虑以下几个方面:

  • 虚拟滚动:使用虚拟滚动技术来渲染可见区域的内容,而不是将整个列表一次性全部渲染出来。这可以减少 DOM 元素的数量,提升页面的加载速度和渲染性能。常见的虚拟滚动实现方式包括使用组件库提供的虚拟滚动组件或手动计算并设置列表项的位置。

  • 延迟加载:如果列表项中包含大量内容(如图片、视频等),可以考虑延迟加载非可见区域的内容。通过监听滚动事件,当列表项进入可见区域时再开始加载相关资源,避免一次性加载过多数据而影响性能。

  • 重用 DOM 元素:在滚动过程中,不断创建和销毁大量的 DOM 元素会导致性能下降。这时可以通过重用已存在的 DOM 元素来减少创建和销毁的操作。例如,使用对象池或缓存列表项的 DOM 元素,在需要显示新的列表项时,从对象池或缓存中取出一个元素并更新其内容。

  • 分批加载:将长列表分成多个较小的批次进行加载和渲染,可以减轻主线程负担,提升用户体验。例如,在滚动过程中,先加载和渲染可见区域的列表项,然后在空闲时间继续加载下一个批次的列表项。

  • 使用合适的事件处理机制:对于涉及用户交互的列表,选择合适的事件处理机制也是性能优化的一部分。如对于滚动事件,可以使用debouncethrottle函数来限制事件触发频率,避免过多的重绘操作。

  • 优化渲染性能:在渲染列表项时,尽量减少不必要的嵌套和计算,避免过多的布局调整和样式计算。可以利用 CSS 技术进行样式的批量更新或动画性能的优化。

34、怎么优化首屏的加载速度

要优化首屏的加载速度,可以考虑以下几个方面:

  • 压缩和优化资源:对静态资源如CSS、JavaScript和图片进行压缩和优化,以减少文件大小并提高加载速度。使用合适的压缩工具和技术,例如Gzip压缩、图像优化工具等。
  • 延迟加载:推迟非必要的资源加载,只加载首屏可见内容所需的最小必需资源。这可以通过延迟加载图片、懒加载(Lazy Loading)技术以及按需加载组件或模块来实现。
  • 异步加载:将页面中的JavaScript代码进行异步加载,避免阻塞页面渲染。可以使用async或defer属性来标记脚本标签,或者通过动态创建和插入脚本元素来实现异步加载。
  • 缓存机制:有效利用浏览器缓存,使重复访问的资源可以从缓存中加载而不是重新请求服务器。合理设置缓存头,如过期时间、命中策略等,确保静态资源可以被长时间缓存。
  • 代码优化:优化JavaScript和CSS代码,去除冗余、无用的代码,并采用更高效的算法和数据结构。避免在首屏加载时执行复杂或耗时的操作,尽量减少JavaScript执行时间。
  • DNS预解析:在HTML文档中添加DNS预解析标签,让浏览器在加载页面时提前解析域名,减少DNS查询时间,加快资源获取速度。
  • 懒加载字体:延迟加载自定义字体文件,使其在内容需要显示时才进行加载,避免阻塞其他资源的加载。
  • 使用CDN加速:使用内容分发网络(CDN)来加速资源的加载,将静态文件分发至全球各个节点,使用户可以从最近的节点加载资源,减少响应时间。
  • 优化服务器和网络环境:确保服务器端的响应时间尽可能短,并采用高速稳定的网络连接,减少数据传输延迟。
  • 响应式设计:针对不同设备和屏幕尺寸优化页面布局和内容展示,减少无意义的加载和渲染。

通过综合考虑以上因素并进行相应的优化调整,可以显著提高首屏的加载速度,提升用户体验。

35、小程序 怎么做响应式布局

在小程序中实现响应式布局可以借助以下方法:

  1. 百分比布局:使用百分比来设置组件的宽度和高度,使其相对于父元素或屏幕宽度进行自适应调整。例如,将一个容器的宽度设置为100%即可使其自适应填充父容器的宽度。

  2. 弹性盒子布局(Flexbox):使用flex布局模型可以轻松实现弹性盒子布局,它可以自动根据可用空间分配子元素的大小和位置。通过设置容器的display属性为"flex",并结合各种flex属性(如flex-direction、justify-content和align-items等),可以创建具有响应能力的布局。

  3. 媒体查询(Media Queries):使用媒体查询可以根据设备的屏幕尺寸、像素密度或方向等条件来应用不同的样式。你可以在样式表中使用@media规则,并根据需要定义不同的CSS样式,从而针对不同的屏幕尺寸提供自定义的布局和样式。

  4. 尺寸单位选择:在设置长度、宽度和字体大小等属性时,使用相对单位(如百分比、rpx)而非固定像素值。这样可以确保布局在不同设备上按比例缩放,适应屏幕大小的变化。

  5. 利用小程序框架提供的响应式布局支持:一些小程序框架(如uni-app、Taro等)提供了内置的响应式布局支持,可以根据不同设备自动调整页面布局。你可以参考相应框架的文档,学习如何使用其提供的响应式布局特性。

以上方法可以帮助你实现小程序的响应式布局,根据不同设备和屏幕尺寸提供更好的用户体验。根据具体需求选择合适的方法,并在开发过程中进行实践和调试。

36、1rpx

1rpx(rpx = responsive pixel)是小程序中的一个相对长度单位,它用于实现在不同设备像素密度下的自适应布局。在小程序中,屏幕宽度被分为750rpx,即1rpx等于屏幕宽度的1/750。

使用1rpx作为单位可以使布局在不同设备上按比例缩放,以适应不同像素密度的屏幕。例如,如果要将一个容器的宽度设置为屏幕宽度的一半,可以将宽度设置为375rpx(750rpx / 2),则在屏幕宽度为750px的设备上,该容器的宽度将为375px;在屏幕宽度为375px的设备上,该容器的宽度也将为375px。

在编写样式时,可以直接使用rpx作为单位进行设置,例如:

.container {
  width: 300rpx;
  height: 200rpx;
  font-size: 24rpx;
}

需要注意的是,在使用rpx单位时,小程序开发工具会根据实际屏幕像素密度进行转换,并生成相应的样式表。因此,开发者无需手动处理不同设备像素密度的差异,只需关注基于rpx单位编写的样式即可。

37、vue怎么做权限管理

在Vue中进行权限管理的一种常见方法是通过路由守卫(Route Guards)来控制页面的访问权限。下面是一个简单的示例:

  1. 创建路由配置文件(例如router.js),定义需要进行权限控制的路由和对应的权限要求:
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Admin from './views/Admin.vue'

Vue.use(Router)

const router = new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/admin',
      name: 'admin',
      meta: { requiresAuth: true }, // 添加requiresAuth字段表示需要登录后才能访问
      component: Admin
    }
  ]
})

export default router
  1. 在主组件中编写路由守卫来进行权限判断,例如在App.vue中:
<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  mounted() {
    this.checkAuth()
  },
  methods: {
    checkAuth() {
      this.$router.beforeEach((to, from, next) => {
        if (to.meta.requiresAuth && !this.isAuthenticated()) {
          // 判断to路由是否需要登录访问,并检查用户是否已认证
          next('/') // 如果未认证,重定向到登录页面或其他合适的处理
        } else {
          next() // 已认证,放行路由
        }
      })
    },
    isAuthenticated() {
      // 判断用户是否已认证的逻辑,可以根据实际情况进行修改
      // 例如从本地存储或后台接口获取认证状态
      return localStorage.getItem('isAuthenticated') === 'true'
    }
  }
}
</script>

在上述代码中,通过to.meta.requiresAuth判断访问的路由是否需要登录权限,如果需要但用户未认证,则重定向到登录页面或其他处理。isAuthenticated方法用于判断用户是否已经认证,你可以根据实际需求来实现该方法。

当用户试图访问需要权限的页面时,Vue Router会调用路由守卫,在守卫中进行权限验证和控制访问。

以上是一个简单的权限管理示例,实际的权限管理方案可能更加复杂,包括不同的角色、权限级别等。你可以根据自己的需求扩展和修改上述代码,以实现更全面的权限管理功能。

38、小程序怎么做权限管理

在小程序中进行权限管理可以通过以下几个步骤实现:

  1. 定义权限列表:首先,你需要确定你的小程序有哪些功能或页面需要进行权限管理。将这些功能或页面列出,并分配相应的权限级别。

  2. 登录认证:为了进行权限管理,用户通常需要登录认证。你可以使用微信提供的登录接口(wx.login)获取用户的唯一标识(如OpenID或UnionID),并将其与你的后台系统关联起来进行认证。

  3. 前端权限控制:在小程序前端,你可以通过条件判断和界面控制来限制和授权用户访问特定的功能或页面。

    • 条件判断:在关键性业务逻辑处,比如点击某个按钮触发某个操作之前,判断用户的权限等级、是否已登录等条件。
    • 界面控制:根据用户的权限等级,决定是否显示或禁用某些功能按钮、导航菜单等。
  4. 后端权限校验:为了保证数据的安全性,建议在与后端交互时进行进一步的权限校验。后端可以根据登录认证获取到的用户标识,对请求的接口进行权限验证,确保用户只能访问其具备权限的数据和功能。

  5. 用户角色管理:如果你的小程序有多个角色(如管理员、普通用户等),可以根据不同的角色分配不同的权限级别。这样可更细粒度地控制每个角色的操作权限。

除了上述步骤,你还可以采用以下技术手段来增强小程序的权限管理:

  • 数据加密:对敏感数据进行加密处理,确保数据传输和存储的安全性。
  • 接口鉴权:在后端接口设计中引入接口鉴权机制,通过令牌、签名等方式验证请求的合法性。
  • 数据访问控制:通过数据库的读写权限控制,限制用户对数据的访问和修改。

需要注意的是,权限管理涉及到用户隐私和数据安全,应该进行谨慎设计和合理使用,避免出现安全漏洞。同时,建议按照最佳实践和相关安全规范进行开发和部署,保护用户和系统的安全。

39、小程序从A页面到B页面再到C页面 怎么在不刷新页面的情况下返回A页面

在小程序中实现页面的跳转和返回可以使用小程序提供的导航组件和API来完成。如果你希望在不刷新页面的情况下返回A页面,可以尝试以下方法:

  1. 使用wx.navigateTo进行页面跳转:在从A页面跳转到B页面时,使用wx.navigateTo API进行页面跳转。这个方法会将当前页面加入导航栈中,并打开新的页面。

  2. 使用wx.navigateBack进行页面返回:在从C页面返回A页面时,你可以使用wx.navigateBack API进行页面返回。这个方法会关闭当前页面并返回到上一个页面。

    • 通过设置delta参数为大于1的值,例如wx.navigateBack({ delta: 2 }),可以一次性返回多个页面。具体的值取决于你想要返回的层级数。
    • 如果你不知道当前页面的层级数,可以使用wx.getCurrentPages API获取当前页面栈,并根据需要计算delta的值。
  3. 避免使用wx.reLaunchwx.redirectTowx.reLaunchwx.redirectTo这两个API会替换当前页面或直接关闭当前页面,从而刷新整个页面栈。如果你希望保持页面栈的状态并实现无刷新返回,应避免使用这两个API。

综上所述,通过合理地使用wx.navigateTowx.navigateBack,你可以在小程序中实现在不刷新页面的情况下返回A页面。

40、h5页面缓存怎么实现

在H5页面中实现缓存可以通过以下几种方式实现:

  1. 使用浏览器缓存:H5页面可以利用浏览器的缓存机制来进行页面缓存。当用户首次访问页面时,页面资源(如HTML、CSS、JavaScript和图片等)会被浏览器缓存下来。之后再次访问同一页面时,浏览器会直接从缓存中加载资源,提高页面加载速度。

    • 页面缓存由浏览器自动处理,不需要开发者额外操作。
    • 可以通过设置响应头的Cache-Control和Expires字段来控制缓存的策略,如设置为公共缓存、私有缓存、强制缓存等。
  2. 使用LocalStorage或SessionStorage:H5提供了LocalStorage和SessionStorage两个Web Storage对象,可以在本地存储数据。这些存储是永久性(LocalStorage)或会话级别(SessionStorage)的,并在页面刷新或关闭后仍然保持。

    • 可以使用localStorage.setItem(key, value)方法将数据存储到LocalStorage中,使用localStorage.getItem(key)方法获取数据。
    • SessionStorage的使用方式与LocalStorage类似,只是其数据是在单个会话中保存,当浏览器窗口关闭后会自动清除。
    • H5页面可以在加载时先检查LocalStorage或SessionStorage中是否存在缓存数据,如果存在则优先使用缓存数据,否则再请求服务器资源。
  3. 使用Service Worker:Service Worker 是一种在浏览器后台运行的脚本,可以拦截和处理网络请求。它可以用于实现高级缓存策略,包括离线访问和资源缓存。

    • Service Worker 可以通过捕获网络请求并将其缓存到本地,当用户再次访问相同资源时,直接从本地缓存中加载,而无需再次向服务器发起请求。
    • Service Worker 的使用需要在页面注册和配置,并编写相应的脚本逻辑。

需要注意的是,在进行页面缓存时,要根据实际需

41、h5页面从A页面到B页面再到C页面 怎么在不刷新页面的情况下返回A页面

在H5页面中,你可以使用浏览器的历史记录(History API)来实现在不刷新页面的情况下返回A页面的功能。以下是一种实现方式:

  1. 从A页面跳转到B页面:你可以使用window.location.href或者window.location.replace方法进行页面跳转至B页面。

  2. 从B页面跳转到C页面:同样使用window.location.href或者window.location.replace方法,将页面跳转至C页面。

  3. 在C页面返回A页面:通过调用window.history.go(-2)方法,可以返回到A页面。其中-2表示要返回的历史记录步数。

    • 如果需要返回到A页面的前一个页面(B页面),可以使用window.history.go(-1)
    • 如果不知道具体的历史记录步数,也可以通过window.history.length获取当前历史记录的长度,并根据需要计算返回的步数。

这样操作后,页面会回退到A页面,而不会刷新整个页面。请注意,这种方式只适用于单页应用(SPA)或者页面之间没有表单数据或重要状态需要保留的情况。如果需要在页面间传递数据或者保存状态,建议使用其他方法,例如URL参数、本地存储、全局状态管理等。

知识点:

在计算机编程领域中,"history模式""hash模式"通常用于描述前端路由的不同实现方式。

1. History模式:
   - 优点:使用 history 模式可以去除 URL 中的哈希符号(#),使 URL 更加清晰可读,并且更接近传统的网站 URL 结构。
   - 缺点:使用 history 模式需要服务器配置来支持。如果用户直接访问 history 模式下的 URL,会收到 404 错误,因此需要在服务器上进行一些特殊的配置。
   - 用法:可以通过调用浏览器的 `pushState` 方法或使用前端框架(如Vue Router、React Router等)来启用 history 模式。

2. Hash模式:
   - 优点:使用 hash 模式无需对服务器进行特殊配置,可以直接在普通的静态服务器上部署。同时,hash 模式兼容性较好,在较旧的浏览器中也能正常工作。
   - 缺点:URL 中使用了特殊字符 "#",可能会引起搜索引擎的误判,且对用户而言,URL 可能不够直观易懂。
   - 用法:通过修改 URL 的 hash 片段来实现页面切换,通常是监听浏览器的 "hashchange" 事件,再根据新的 hash 值来处理相应的逻辑。

选择使用哪种模式,取决于具体需求和场景。如果需要兼容性好且无需额外服务器配置,则可以选择 hash 模式。而如果希望 URL 更加直观可读,并愿意进行服务器配置来支持,那么可以选择 history 模式。


要在不刷新页面的情况下从 C 页面返回 A 页面,可以使用 history 模式来实现。 History 模式允许通过修改 URL 的路径来切换页面,同时不会触发页面的完全刷新。

具体步骤如下:
1.A 页面中设置路由链接或按钮,点击时导航到 B 页面。
2.B 页面中同样设置路由链接或按钮,点击时导航到 C 页面。
3.C 页面中设置路由链接或按钮,点击时执行前端代码,调用浏览器的 `history.back()` 方法,即可返回到 A 页面,而无需刷新页面。

需要注意的是,使用 history 模式进行页面切换时,需要确保服务器已配置了相关路由规则,以确保直接访问特定路径时能正确加载对应的页面。

42、比较两个对象是否相同

要比较两个对象是否相同,可以使用以下方法:

  1. 浅比较:使用===运算符进行比较。它会直接比较两个对象的引用是否指向同一个内存地址。如果两个对象引用地址相同,则认为它们是相同的。

    const obj1 = { name: 'Alice', age: 25 };
    const obj2 = { name: 'Alice', age: 25 };
    
    console.log(obj1 === obj2);  // false
    

    在上面的例子中,尽管obj1obj2的属性值相同,但它们分别位于不同的内存地址,因此返回的结果是false

  2. 深比较:如果想要比较两个对象的属性值是否相同,需要逐个比较每个属性的值。这可以通过递归遍历对象的属性来实现,或者使用一些库/函数来辅助实现,例如lodashisEqual()函数。

    const obj1 = { name: 'Alice', age: 25 };
    const obj2 = { name: 'Alice', age: 25 };
    
    console.log(_.isEqual(obj1, obj2));  // true
    

    在上面的示例中,_.isEqual()函数会深度比较obj1obj2的属性值,并返回true

请注意,在深比较时要考虑到对象的嵌套层级和属性值的数据类型等因素。如果对象内部存在循环引用或复杂的嵌套结构,比较过程可能会更加复杂。

43、vueX 跟普通的 window注册的全局变量有什么区别

VueX 是 Vue.js 框架中用于应用程序状态管理的官方库,而普通的全局变量是通过将数据绑定到 window 对象上实现的。

以下是 VueX 和普通全局变量之间的一些区别:

  1. 集中式状态管理:VueX 提供了一个集中式的状态管理机制。它通过创建一个单一的全局状态存储对象(Store)来管理应用程序的状态。这个存储对象允许在组件之间共享和响应数据,并提供了一些特定的方法来修改状态、响应状态变化以及进行高级状态管理,例如模块化划分等。相比之下,普通的全局变量需要手动管理和同步状态的更改。

  2. 响应式更新:VueX 的状态存储对象是响应式的,当状态发生变化时,相关的组件会自动更新。这使得在组件中直接使用存储对象中的状态更加方便,不需要手动监听和管理变化。然而,对于普通的全局变量,需要手动设置并监听变量的变化通知组件进行更新。

  3. 严格的状态变更规则:VueX 强制遵循严格的状态变更规则,即只能通过提交 mutation 来修改状态,从而方便跟踪状态变化的来源。这样可以确保状态的可维护性和追踪性,以及防止不恰当的状态修改。而普通的全局变量没有这种限制,可以在任何地方直接修改。

  4. 插件扩展:VueX 提供了一些插件机制,可以在存储对象上执行额外的操作,例如持久化、调试工具等。这些插件可以轻松地与 VueX 集成,并为应用程序提供更多功能。对于普通的全局变量,这些功能需要手动实现或使用第三方库。

总的来说,VueX 是一个专门用于状态管理的库,它通过集中式的状态存储对象和响应式更新机制,提供了更便捷、可维护和可扩展的方式来管理应用程序的状态。相比之下,普通的全局变量需要手动处理状态的管理和更新,缺乏强制规则和插件扩展等特性。

44、不考虑写法差异 vueX 比一般设置全局变量的方式好在那里?

VueX 比一般设置全局变量的方式好在以下几个方面:

  1. 集中管理:VueX 提供了一个单一的全局状态存储对象(Store),将应用程序的状态集中管理。这使得状态的修改和获取更加方便,组件之间可以轻松地共享状态,而无需手动处理全局变量的传递和同步。

  2. 响应式更新:VueX 的状态存储对象是响应式的,当状态发生变化时,相关的组件会自动更新。这意味着组件可以直接订阅并使用状态,无需手动监听变量的变化或触发更新操作。这样可以减少开发者的工作量,并提高代码的简洁性和可读性。

  3. 严格的状态变更规则:VueX 强制遵循严格的状态变更规则,即只能通过提交 mutation 来修改状态。这样可以确保状态的可维护性和追踪性,以及防止不恰当的状态修改。相比之下,使用普通的全局变量可能会导致状态的随意修改和难以跟踪,增加代码的复杂性和维护成本。

  4. 插件扩展:VueX 提供了插件机制,允许在存储对象上执行额外的操作,例如持久化、调试工具等。这些插件可以轻松地与 VueX 集成,并为应用程序提供更多功能和增强工具。相比之下,使用普通的全局变量需要手动实现或依赖第三方库来实现类似的扩展功能。

综上所述,VueX 提供了一种结构化、可维护和可扩展的方式来管理应用程序的状态,通过集中管理、响应式更新、严格的状态

45、请写出下列代码执行结果
function f1(){console.time('time span')}
function f2(){console.timeEnd('time span');}
setTimeout(f1,100);
setTimeout(f2,200);
function waitForMs(n){
var now = Date.now();
 while(Date.now()-now < n){}
}
waitForMs(500)


A、tine apan:700.077ms
B、time apan:0.066ms
C、time apan:500.077ms
D、time apan:100.077ms

这四个选项那个是上述代码的正确选项?

根据您提供的代码和选项,正确的选项应该是:

C、time span: 500.077ms

在这段代码中,先使用setTimeout函数延迟100毫秒后调用f1函数开始计时,然后再延迟200毫秒后调用f2函数结束计时。而在waitForMs函数中,使用一个循环进行500毫秒的阻塞等待。

因此,在最终输出结果中,time span 表示计时开始到计时结束的时间差,因为f1函数的计时开始与f2函数的计时结束之间有一个等待时间为500毫秒的操作,则应该是500.077ms。
46、判断基础类型之间的比较
请选择结果为真的表达式
Anull instanceof Object
Bnull == undefined
CNaN==NaN
Dfalse == undefined



在给出选项的表达式中,正确的是:

Bnull == undefined

这是因为在JavaScript中,使用"=="进行比较时会进行类型转换。而null和undefined在相等比较时是相等的,它们被认为是同一种特殊的空值,因此该表达式结果为真。

其他选项的表达式结果为假:
Anull instanceof Object:虽然null是一个特殊值,但它不是Object的实例,因此该表达式结果为假。
CNaN==NaN:NaN (Not-a-Number)代表非数字,与任何其他值(包括自身)进行相等比较时,结果都会返回falseDfalse == undefined:将布尔值false与undefined进行相等比较时,它们的类型不同,因此该表达式结果为假。
47、HTML5新增的inputtype类型有哪些?

A、image B、color C、date D、range E、tel

HTML5新增的input type类型有以下几种:

B、color:用于选择颜色的输入控件。
C、date:用于选择日期的输入控件。
D、range:用于选择范围的输入控件,可以拖动滑块来选择值。
E、tel:用于输入电话号码的输入控件。

而A选项的image不是HTML5新增的input type类型,它实际上是用于显示图像按钮的,而不是输入类型。




48、HTML5 input type类型 都有那些?
HTML5新增的input type类型包括以下几种:

1. text:文本输入框
2. password:密码输入框
3. email:电子邮件输入框
4. number:数字输入框
5. search:搜索输入框
6. tel:电话号码输入框
7. url:URL输入框
8. date:日期输入框
9. month:月份输入框
10. week:周数输入框
11. time:时间输入框
12. datetime-local:本地日期时间输入框
13. color:颜色选择器
14. range:范围选择器
15. checkbox:复选框
16. radio:单选按钮
17. file:文件上传控件
18. submit:提交按钮
19. reset:重置按钮
20. button:普通按钮
21. image:图像按钮(用于显示图像)
22. hidden:隐藏输入字段

这些是HTML5中常用的input type类型,可以根据不同的需求选择适合的类型。
49、可以实现选择某个元素的所有奇数项的选择器是?

A、:first-of-type B、:nth-child(2n+1) C、:nth-of-type(2n+1) D、:nth-last-of-type(3n)

选择某个元素的所有奇数项的选择器是C:nth-of-type(2n+1)。

选择器C:nth-of-type(2n+1)表示选择该类型元素中的奇数位置项。其中,n代表一个整数,从0开始计数。当n为0时,选择第1个、第3个、第5个…的元素;当n为1时,选择第2个、第4个、第6个…的元素;以此类推。所以,:nth-of-type(2n+1)选择器可以实现选择某个元素的所有奇数项。

A:first-of-type选择器:
:first-of-type选择器表示选择某个元素在父元素下的同类型元素中的第一个出现的元素。该选择器不考虑位置,只关注元素类型。例如,使用:first-of-type选择器可以选择某个父元素下的第一个子元素,无论它在父元素中的位置如何。

B:nth-child(2n+1)选择器:
:nth-child(2n+1)选择器表示选择某个元素在父元素下的所有子元素中的奇数项。这个选择器会根据元素在父元素中的位置进行计算。其中,n代表一个整数,从0开始计数。当n为0时,选择第1个、第3个、第5个…的元素;当n为1时,选择第2个、第4个、第6个…的元素;以此类推。

D:nth-last-of-type(3n)选择器:
:nth-last-of-type(3n)选择器表示选择某个元素在父元素下的同类型元素中,距离最后一个同类型元素的位置是3的倍数的元素。其中,n代表一个整数,从0开始计数。该选择器从最后一个同类型元素开始向前计算位置,每隔3个元素选择一次。

50、下面的执行结果
var a = 100;

function test() {
  console.log(a);
  var a = 10;
  console.log(a);
}

test();
// undefined  10
在修正后的代码中,我们将函数定义和调用包裹在正确的语法结构中。此外,console,log被更正为console.log来输出控制台日志。请注意,在test函数内部,我们声明了一个局部变量a并对其进行赋值,这样它就会覆盖全局变量a。因此,第一个console.log(a)输出的将是在函数作用域内的值 undefined,而第二个console.log(a)输出的是函数作用域内重新赋值后的值 10
51、下列程序运行结果符合预期的是:
var a={}, b = Object.prototype;
[a.prototype=== b, Object.getPrototypeOf(a) === b]

A:[false,true] B:[true,true] C:[false,false] D:other


运行结果符合预期的是B: [false, true]。

解释如下:
- `a.prototype === b`:这个表达式中,对于对象`a`来说,它的原型对象应该通过`Object.getPrototypeOf(a)`来获取,而不是直接访问`.prototype`属性。因此,`a.prototype`会返回`undefined`,即使对象`a`没有显式定义原型对象。另外,全局变量`Object.prototype`表示所有对象的原型对象,所以`a.prototype === b`返回`false`- `Object.getPrototypeOf(a) === b`:这个表达式中,使用了`Object.getPrototypeOf()`方法来获取对象`a`的原型对象,并将其与`b`进行比较。由于对象`a`的原型对象是`Object.prototype`,所以返回的结果为`true`。

因此,符合预期的答案是B[false, true]`Object.getPrototypeOf()`是一个内建函数,用于获取指定对象的原型对象。它接受一个对象作为参数,然后返回该对象的原型对象。

对象的原型对象可以通过 `__proto__` 属性或者 `Object.getPrototypeOf()` 方法获得。原型对象又包含了一组属性和方法,这些属性和方法可以被继承到该对象本身。

下面是使用`Object.getPrototypeOf()`的示例:

```javascript
const obj = {};
const prototype = Object.getPrototypeOf(obj);
console.log(prototype); // 输出: {}
```

在上面的示例中,我们创建了一个空对象 `obj`,然后使用`Object.getPrototypeOf()`方法获取了它的原型对象 `prototype`。由于`obj`是一个普通对象,它的原型对象是 `Object.prototype`,所以`prototype`的值为空对象 `{}`。

需要注意的是,`Object.getPrototypeOf()`只能用于获取对象的原型对象,而不能用于获取其他类型的原型,比如基本类型(如数字、字符串)的原型对象。
52、可以实现只选择与div相邻的p元素的选择器是?
A.div>b B、div,p C、div p D、div ~d E、div+p
E、div+p选择器可以实现只选择与div相邻的p元素。该选择器表示选择紧接在div元素后面的第一个p元素。它要求p元素是紧跟在div元素之后的兄弟元素,并且它们有相同的父元素。
53、下列程序符合预期的是
(function() {
var x=y=1;
})();
console.log(y)
console.log(x)


符合预期的输出结果是:
1
ReferenceError: x is not defined

解释如下:
在这段代码中,自执行函数创建了一个局部作用域。变量 x 使用了 var 关键字进行声明并赋值为 1,因此它是在局部作用域内有效的局部变量。而变量 y 则没有使用 varlet 进行声明,因此它被隐式地声明为全局变量,并且也被赋值为 1。

由于自执行函数是立即执行的,所以在执行过程中,内部的变量 x 和全局的变量 y 都被赋值为 1。因此,在自执行函数外部调用 console.log(y) 输出结果为 1,而调用 console.log(x) 会导致引用错误(ReferenceError),因为变量 x 是在自执行函数的局部作用域内定义的,并不在外部作用域中可见。

这样,正确的输出结果应该是:
1
ReferenceError: x is not defined

54、请编写一个JavaScript 函数 parseQueryString,它的用途是把 URL参数解析为一个对象

下面是一个 JavaScript 函数 parseQueryString 的实现,它将解析 URL 参数并返回一个包含参数键值对的对象:

function parseQueryString(url) {
  var queryString = url.split('?')[1]; // 获取问号后面的参数部分
  var params = {};

  if (queryString) {
    var keyValuePairs = queryString.split('&'); // 将参数字符串拆分为键值对数组

    keyValuePairs.forEach(function(keyValuePair) {
      var pair = keyValuePair.split('='); // 拆分键值对
      var key = decodeURIComponent(pair[0]); // 解码键
      var value = decodeURIComponent(pair[1]); // 解码值

      if (params.hasOwnProperty(key)) {
        // 如果参数键已存在,将值转换为数组并追加新值
        if (!Array.isArray(params[key])) {
          params[key] = [params[key]];
        }
        params[key].push(value);
      } else {
        params[key] = value; // 添加新的参数键值对
      }
    });
  }

  return params;
}
使用示例:
var url = "http://example.com/?name=John&age=30&hobbies=reading&hobbies=sports";

var params = parseQueryString(url);

console.log(params);
输出结果:
{
  name: "John",
  age: "30",
  hobbies: ["reading", "sports"]
}

上述函数会将 URL 参数解析为一个对象,其中参数名作为对象的属性,参数值作为属性的值。如果同一个参数名出现多次,则参数值将被存储为数组。

需要注意的是,这个示例只针对简单的参数解析,并且没有考虑一些复杂的情况(例如参数值包含特殊字符或嵌套对象等)。如果需要更完整和健壮的 URL 解析功能,可以使用现有的第三方库,如 query-stringURLSearchParams 等。

55、decodeURIComponent

decodeURIComponent 是 JavaScript 中的一个全局函数,用于解码使用 encodeURIComponent 编码的字符串。

在 JavaScript 中,当需要将字符串作为 URL 的一部分发送时,需要使用 encodeURIComponent 对特殊字符进行编码。例如空格会被编码为 %20,问号会被编码为 %3F 等。而 decodeURIComponent 则可以对这些编码后的字符串进行解码,恢复原始的字符串表示。

下面是 decodeURIComponent 函数的示例用法:

var encodedString = '%E4%BD%A0%E5%A5%BD%2C%20%E4%B8%96%E7%95%8C%EF%BC%81';

var decodedString = decodeURIComponent(encodedString);

console.log(decodedString);

输出结果:

你好, 世界!

在上述示例中,我们通过 decodeURIComponent 将经过 encodeURIComponent 编码的字符串解码为原始的字符串。

需要注意的是,如果传递给 decodeURIComponent 的字符串不是有效的编码格式,或者包含了无效的编码序列,那么函数可能会抛出异常。因此,在使用 decodeURIComponent 进行解码之前,最好确保输入的字符串是由 encodeURIComponent 或类似方法进行的正确编码。

Electron 面试题

  1. Electron是一个用于构建跨平台桌面应用程序的开源框架,它使用HTML、CSS和JavaScript技术。
  2. Electron的主要特点包括跨平台能力(支持Windows、Mac和Linux)、易于开发(使用Web技术)、强大的功能扩展性和兼容性。
  3. 与传统的Web应用相比,Electron应用具有更接近原生应用的外观和行为,可以访问操作系统级别的API,并且在离线环境下也可以运行。
  4. 创建一个基本的Electron应用程序需要安装Node.js和npm,然后使用命令行工具创建和初始化项目,编写主进程和渲染进程的代码,并使用Electron运行应用程序。
  5. 主进程是应用程序的控制中心,负责处理窗口管理、系统级别的功能和与渲染进程的通信。而渲染进程则类似于浏览器中的页面,负责展示界面和处理用户交互。
  6. 可以通过使用Electron提供的API来与操作系统进行交互,例如创建系统托盘菜单、使用原生对话框或执行操作系统命令等。
  7. Electron应用程序可以使用打包工具(如Electron Builder)来将源代码和依赖项打包成可执行文件,并结合软件分发渠道进行发布。
  8. Electron的优点包括跨平台能力、开发效率高、生态系统丰富;缺点可能包括应用程序体积较大和性能相对较低。
  9. 相对于其他类似框架,Electron有着更广泛的社区支持和活跃的开发生态系统,并且在一些知名应用中得到了广泛应用。
  10. 在Electron应用程序中,需要注意安全问题,比如防止恶意代码注入、用户数据保护等,可以通过使用Electron提供的安全机制和最佳实践来确保应用程序安全
11、Electron 怎么使用

以下是使用Electron构建应用程序的基本步骤:

  1. 确保你已经安装了Node.js和npm(Node包管理器)。你可以在官方网站上下载并按照说明进行安装。
  2. 打开命令行工具,创建一个新的项目文件夹,并进入该文件夹:
    mkdir my-electron-app
    cd my-electron-app
    
  3. 在项目文件夹下初始化你的应用程序,并创建一个 package.json 文件来管理依赖项:
    npm init -y
    
  4. 安装Electron作为开发依赖项:
    npm install electron --save-dev
    
  5. 创建一个主进程文件 main.js ,这将是你的应用程序的入口点。在这个文件中,你可以设置窗口、处理系统事件和与渲染进程通信等:
    // main.js
    
    const { app, BrowserWindow } = require('electron');
    
    function createWindow() {
      const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          nodeIntegration: true // 允许在渲染进程中使用Node.js API
        }
      });
    
      win.loadFile('index.html'); // 加载显示界面的HTML文件
    
      // 在窗口准备好后显示
      win.once('ready-to-show', () => {
        win.show();
      });
    }
    
    app.whenReady().then(createWindow); // 应用准备就绪后创建窗口
    
  6. 创建一个渲染进程文件 index.html ,用于定义你的应用程序界面:
    <!-- index.html -->
    
    <!DOCTYPE html>
    <html>
    <head>
      <title>My Electron App</title>
    </head>
    <body>
      <h1>Hello, Electron!</h1>
    </body>
    </html>
    
  7. package.json 文件中添加一个脚本命令,用于运行你的应用程序:
    // package.json
    
    {
      "name": "my-electron-app",
      "version": "1.0.0",
      "scripts": {
        "start": "electron ."
      }
    }
    
  8. 运行以下命令启动应用程序:
    npm start
    

现在,你应该能够看到一个新的Electron窗口,里面显示着 “Hello, Electron!”。这只是一个基本示例,你可以根据需要进行扩展和定制,使用Electron提供的API来实现更多功能。

请记住,这只是一个简单的入门指南,并不包括所有可能的细节和高级用法。对于更复杂的应用程序开发,请参考Electron官方文档和其他

1、什么是 Electron?
  • Electron 是一个开源框架,用于构建跨平台的桌面应用程序。它使用 HTML、CSS 和 JavaScript 技术栈来创建像原生应用一样运行的应用程序。
2、Electron 是如何工作的?
  • Electron 使用 Chromium 渲染引擎来呈现用户界面,并通过 Node.js 提供对操作系统底层功能的访问。它将网页和本地代码结合在一起,允许开发者使用 Web 技术来构建强大的桌面应用程序。
3、Electron 应用的主进程和渲染进程有什么区别?
  • 主进程是应用程序的入口点,负责创建和管理渲染进程。它可以访问本地资源和操作系统级别的功能。每个 Electron 应用只能有一个主进程。
  • 渲染进程是由主进程创建的浏览器窗口中的进程。每个窗口都有自己的渲染进程。渲染进程负责显示和处理网页内容,但不能直接访问操作系统的原生资源。
4、如何与主进程进行通信?
  • 渲染进程可以通过 IPC(进程间通信)机制与主进程进行通信。主进程和渲染进程可以通过 IPC 的事件、消息发送和共享数据来交换信息。
5、Electron 支持的操作系统是哪些?
  • Electron 可以在 Windows、macOS 和 Linux 上运行,从而实现跨平台的桌面应用程序开发。
6、如何打包 Electron 应用?
  • Electron 提供了多种打包工具和框架,例如 Electron Forge、Electron Packager 和 Electron Builder。这些工具可以将 Electron 应用打包为可执行文件或安装程序,以便在不同操作系统上分发和安装应用。
7、怎样优化 Electron 应用的性能?
  • 避免在渲染进程中加载过多资源或进行复杂的计算。
  • 使用 Chrome DevTools 来检测内存泄漏和性能问题。
  • 合理管理渲染进程的生命周期,如使用窗口重用等。
  • 使用原生模块或 C++ 扩展来提高性能关键部分的执行效率。
  • 针对特定平台进行优化,如针对 macOS 的 App Nap 或 Windows 的 High DPI 设置。

这些问题涵盖了 Electron 的基本概念、核心原理和常见的开发考虑因素。希望这些问题和答案能帮助你准备好 Electron 面试。记得根据你所申请的职位和角色做进一步的学习和准备。

8、如何在 Electron 中使用本地模块(Native Modules)?
  • 在 Electron 中,可以通过使用 Node.js 的原生模块来访问本地功能。这需要使用 node-gyp 工具将 C++ 代码编译为可用于 Electron 的模块。
9、如何处理 Electron 应用程序的主窗口关闭事件?
  • 可以通过监听 window.on('close') 事件来处理主窗口的关闭事件。在监听事件的回调函数中,可以执行必要的清理操作,并决定是否允许关闭窗口。
10、如何在 Electron 应用中实现自动更新(Auto Update)功能?
  • 可以使用 Electron 提供的自动更新模块(如 electron-updater)来实现自动更新功能。该模块可以从服务器端下载并安装应用程序的最新版本。
11、如何在 Electron 应用程序中使用本地存储?
  • Electron 应用可以使用浏览器提供的 Web Storage API(如 localStorage 和 sessionStorage)。此外,还可以使用 Node.js 的文件系统 API 访问本地文件系统进行数据存储。
12、如何创建自定义菜单栏和上下文菜单(Context Menu)?
  • 可以使用 Electron 提供的 MenuMenuItem 类来创建自定义菜单栏和上下文菜单。可以通过定义菜单项和子菜单项来构建菜单,并使用 IPC 将特定操作与菜单项关联起来。
13、如何调试 Electron 应用程序?
  • 可以使用 Chrome DevTools 来调试渲染进程。通过在主进程中启用 --inspect 标志,可以调试主进程。
14、如何处理 Electron 应用程序的多窗口通信?
  • 可以使用 Electron 提供的 IPC(进程间通信)机制,在不同的窗口之间进行通信。可以通过主进程作为中介来实现
15、如何处理 Electron 应用的安全性?
  • 在 Electron 应用中,安全性非常重要。以下是几种处理安全性的方法:
    • 使用最新版本的 Electron 和相关依赖库,以获得最新的安全补丁和修复。
    • 验证和过滤所有从用户界面传递到主进程的输入,以防止恶意代码注入或跨站脚本攻击(XSS)。
    • 注意将敏感信息存储在安全的地方,如使用 Node.js 的 crypto 模块对密码进行加密。
    • 避免在渲染进程中执行危险的操作,如禁止使用 eval() 函数、限制对远程资源的加载等。
    • 使用 Electron 提供的安全策略和 API,如设置 Content Security Policy(CSP)限制页面内容、使用 Webview 标签时审查远程内容等。
16、如何实现自动更新 Electron 应用程序?
  • Electron 提供了自动更新功能来简化应用程序的部署和更新流程。以下是一种常用的自动更新方式:
    • 使用 Electron Builder 或其他打包工具配置自动更新的参数。
    • 在服务器上托管应用程序的更新文件,并提供一个下载地址或版本 API 给应用程序。
    • 应用程序在启动时,检查最新的版本号,并与当前版本进行比较。
    • 如果有可用的更新,应用程序会下载新版本的文件并提示用户安装更新。
17、如何调试 Electron 应用?
  • Electron 提供了多种调试工具和技术来帮助开发者调试应用程序。以下是一些常用的调试方法:
    • 使用 Chrome DevTools 来调试渲染进程代码,包括查看 DOM、调试 JavaScript 和分析性能。
    • 在主进程中使用 Node.js 的调试工具来调试主进程代码。
    • 使用 Electron 命令行工具启动应用程序时,可以添加 --inspect--remote-debugging-port 参数来启用远程调试。

Vue3

1、Vue 3中的Composition API是什么?它与Options API有何不同?

答案:Composition API是Vue 3中引入的一种新的组织代码的方式,它通过函数式的形式来组合和重用逻辑。与Options API相比,Composition API更灵活、可组合性更强,并且更利于代码的重用和维护。

2、Vue 3中的响应式数据是如何实现的?

答案:Vue 3通过使用refreactive函数来创建响应式数据。ref用于创建包装简单值的响应式引用,而reactive则用于创建包装复杂对象的代理,并使其内部属性成为响应式。

3、Vue 3中的Teleport组件是用来做什么的?

答案:Teleport组件是Vue 3中新增的一个特性,用于将子组件在DOM中的位置移动到任意指定的目标位置。这对于创建弹出窗口、模态框等场景非常有用。

4、Vue 3中的v-model指令有何变化?

答案:在Vue 3中,v-model指令仍然用于双向绑定数据,但工作原理略有变化。现在,v-model指令直接绑定到组件实例上的value属性,而不再依赖于组件内部通过props和$emit来传递数据。

5、Vue 3中的Suspense组件是用来做什么的?

答案:Suspense组件是Vue 3中新增的一个特性,用于处理异步组件的加载状态。它可以在异步组件加载完成之前显示指定的fallback内容,以提供更好的用户体验。

6、Vue 3中的组合式API和混入(Mixin)有何区别?

答案:组合式API是一种更灵活、可组合性更强的代码组织方式,它通过函数形式将逻辑进行组合和重用。而混入是一种在Vue中共享组件选项的方式,在多个组件之间共享相同的逻辑或功能。与混入不同,组合式API避免了命名冲突,并提供了更明确的逻辑组合方式。

7、在Vue 3中,如何处理全局状态管理?

答案:在Vue 3中,可以使用新引入的provideinject API来处理全局状态管理。通过在父组件上使用provide提供数据,然后在子组件上使用inject来访问这些数据,实现跨层级组件间的状态共享。

8、Vue 3中的动态组件有何变化?

答案:在Vue 3中,动态组件的用法没有变化,仍然使用<component>元素和:is属性来切换动态组件。但由于编译器的改进,Vue 3生成的代码更小、更高效。

9、Vue 3中支持哪些新的生命周期钩子函数?

答案:Vue 3中新增了两个生命周期钩子函数:beforeUnmountonVnodeMountedbeforeUnmount在组件实例销毁之前调用,而onVnodeMounted在虚拟节点挂载后调用。

10、Vue 3中的模板语法有哪些改进?

答案:Vue 3中的模板语法相对于Vue 2来说并没有太大变化,但有一些值得注意的改进。例如,引入了v-bind="$attrs"用于绑定组件的非Prop属性,以及v-model指令可以在自定义组件中使用modelValueupdate:modelValue来实现双向绑定。

11、如何在Vue 3中处理异步操作?

答案:在Vue 3中,可以使用Promise、async/await或使用第三方库(如axios)来处理异步操作。异步操作可以在setup()函数中进行,也可以封装为自定义的异步方法供组件使用。

12、Vue 3中支持哪种方式进行组件通信?

答案:在Vue 3中,组件通信可以通过Props、事件、Provide/Inject、全局状态管理等方式实现。具体选择哪种方式取决于应用的需求和组件之间的关系。

13、Vue 3中怎么做页面过渡效果?

答案:在Vue 3中,页面过渡效果可以使用<transition><transition-group>组件来实现。它们可以通过设置不同的类名实现过渡效果,并且支持自定义过渡的各个阶段。

14、Vue 3中的编译器和运行时的区别是什么?

答案:在Vue 3中,编译器(Compiler)负责将模板编译为渲染函数,在开发环境下会包含更多的警告和友好的错误信息。而运行时(Runtime)则是组件实例的执行环境,在生产环境下会去除编译器的代码以减小包体积。

当然,这里还有几个Vue 3面试题供您参考:

15、Vue 3中的Teleport是什么?它有什么作用?

答案:Teleport是Vue 3中引入的一种新组件,它可以将组件的内容插入到DOM中的任意位置。它的作用是允许你将组件的内容渲染到DOM结构中的其他位置,而不受组件层级的限制。

16、Vue 3中的Composition API与Options API相比有哪些优势?

答案:Composition API相较于Options API具有以下优势:

  • 更好的逻辑复用和组合性,使用函数形式进行逻辑组合和重用。
  • 更清晰的代码结构,将相关逻辑放在同一个函数中。
  • 更好的类型推导和IDE支持。
  • 更好的响应式能力,可以对响应式状态进行细粒度的控制。
17、Vue 3中的Fragment是什么?它的作用是什么?

答案:Fragment是Vue 3中引入的一种新语法,它允许我们在模板中使用多个根元素,而无需包裹在额外的容器元素中。它的作用是简化模板的编写,并提高代码的可读性。

请注意,以上答案仅为简要回答,如果您需要更详细的解释或有其他问题,请随时提问。记住,我不能回答任何触及禁止事项的问题。****

当然,这里还有更多的Vue 3面试题供您参考:

18、Vue 3中的v-model指令如何使用?它是怎样实现双向数据绑定的?

答案:在Vue 3中,可以使用v-model指令来实现双向数据绑定。它通常用于表单元素,如输入框、复选框、单选框等。在组件内部,我们需要使用emits选项声明一个名为update:modelValue的事件,并在相应的表单元素上绑定v-model指令,将值与父组件进行双向绑定。

19、Vue 3中的Suspense和错误边界是什么?它们有什么作用?

答案:Suspense是Vue 3中引入的一种新特性,用于处理异步组件的加载状态。它允许我们在异步组件加载时显示一个占位符,直到异步组件加载完成并显示内容。错误边界是一种机制,用于捕获和处理组件树中发生的 JavaScript 错误,以防止整个应用崩溃。它们的作用是提高应用的性能和用户体验,增强对异步加载和错误处理的支持。

20、Vue 3中的响应式原理是什么?它与Vue 2相比有何改进?

答案:Vue 3中的响应式原理基于Proxy和Reflect实现。它通过拦截对象的操作来跟踪依赖和触发更新,提供了更可靠和高效的响应式系统。与Vue 2相比,Vue 3的响应式系统具有更好的性能、更小的包体积和更好的类型推导支持。

请记住,如果您需要更多解释或有其他问题,请随时提问。但请注意,我不能回答任何触及禁止事项的问题。

当然,这里还有更多的Vue 3面试题供您参考:

21、Vue 3中的Teleport与Portal有何区别?

答案:Vue 3中的Teleport与Portal概念类似,都可以将组件的内容插入到DOM的其他位置。但两者的实现方式略有不同,Teleport使用了新的Teleport组件来实现,而Portal是通过普通的组件结构和slot来实现。另外,Teleport相对于Portal提供了更细粒度的控制,可以在任意位置进行渲染。

22、Vue 3中的递归组件是什么?它在哪些场景下常用?

答案:递归组件是指在组件的模板中调用自身的一种特殊组件。它在处理树状结构的数据或需要无限嵌套的场景时非常有用,比如文件夹树、评论回复等。递归组件通过递归调用自身来展示嵌套结构,并且可以通过定义递归结束条件来避免无限循环。

23、Vue 3中的Fragments和Portals在性能上有何改进?

答案:Vue 3中的Fragments和Portals在性能上有所改进。Fragments在渲染时不会创建额外的父级包装元素,减少了不必要的DOM层级,提升了性能。而Portals利用了Teleport的机制,在插入和移动组件时,可以更快地更新目标位置的DOM,减少了不必要的重渲染。

请注意,以上答案仅为简要回答,如果您需要更详细的解释或有其他问题,请随时提问。记住,我不能回答任何触及禁止事项的问题。

当然,请继续参考更多的Vue 3面试题:

24、Vue 3中的Composition API和Options API有何区别?

答案:Composition API是Vue 3引入的一种新的组合式API风格,它允许开发者根据逻辑相关性组织代码。相比之下,Options API是Vue 2中主要使用的API风格,将相关的选项(如data、methods、computed等)放在一个对象中。Composition API更加灵活、可组合和易于阅读,在处理复杂逻辑和代码重用方面更具优势。

25、Vue 3中的全局状态管理工具是什么?它与Vue 2中的Vuex有何区别?

答案:Vue 3中的全局状态管理工具是@vue/reactivity库,它提供了一些函数和类,用于创建响应式状态和触发更新。与之相比,Vue 2中的Vuex是一个专门为Vue.js设计的状态管理库,提供了集中式的状态管理方案,包括状态定义、状态获取和状态修改等功能。相对而言,@vue/reactivity库更加轻量级且与其他生态系统更加兼容。

26、Vue 3中的动画过渡是如何实现的?请提供一个简单的示例。

Vue 3中的动画过渡可以通过<transition>组件和<transition-group>组件来实现。

下面是一个简单的示例,展示了如何使用<transition>组件实现元素的淡入淡出效果:

<template>
  <div>
    <button @click="toggleShow">Toggle Show</button>
    <transition name="fade">
      <p v-if="show">这是一个会淡入淡出的元素</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: false,
    };
  },
  methods: {
    toggleShow() {
      this.show = !this.show;
    },
  },
};
</script>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
</style>

在上述代码中,我们使用了<transition>组件将希望具有动画效果的元素包裹起来。通过设置name属性为"fade",我们定义了过渡的名称,同时也对应了CSS过渡类名的前缀。

然后,在<style>标签中,我们通过定义.fade-enter-active.fade-leave-active样式类来控制动画的持续时间和过渡属性。同时,通过定义.fade-enter.fade-leave-to样式类来控制初始状态和目标状态之间的过渡效果。

最后,点击按钮时,通过修改show的值来控制元素的显示与隐藏,从而触发淡入淡出的动画效果。

27、history 模式和hash模式的区别 优缺点 和分别的用法

在计算机编程领域中,"history模式"和"hash模式"通常用于描述前端路由的不同实现方式。

  1. History模式:

    • 优点:使用 history 模式可以去除 URL 中的哈希符号(#),使 URL 更加清晰可读,并且更接近传统的网站 URL 结构。
    • 缺点:使用 history 模式需要服务器配置来支持。如果用户直接访问 history 模式下的 URL,会收到 404 错误,因此需要在服务器上进行一些特殊的配置。
    • 用法:可以通过调用浏览器的 pushState 方法或使用前端框架(如Vue Router、React Router等)来启用 history 模式。
  2. Hash模式:

    • 优点:使用 hash 模式无需对服务器进行特殊配置,可以直接在普通的静态服务器上部署。同时,hash 模式兼容性较好,在较旧的浏览器中也能正常工作。
    • 缺点:URL 中使用了特殊字符 “#”,可能会引起搜索引擎的误判,且对用户而言,URL 可能不够直观易懂。
    • 用法:通过修改 URL 的 hash 片段来实现页面切换,通常是监听浏览器的 “hashchange” 事件,再根据新的 hash 值来处理相应的逻辑。

选择使用哪种模式,取决于具体需求和场景。如果需要兼容性好且无需额外服务器配置,则可以选择 hash 模式。而如果希望 URL 更加直观可读,并愿意进行服务器配置来支持,那么可以选择 history 模式。

28、h5页面从A页面到B页面再到C页面 在不刷新页面的情况下返回A页面用上面的哪一种路由模式实现?

要在不刷新页面的情况下从 C 页面返回 A 页面,可以使用 history 模式来实现。 History 模式允许通过修改 URL 的路径来切换页面,同时不会触发页面的完全刷新。

具体步骤如下:

  1. 在 A 页面中设置路由链接或按钮,点击时导航到 B 页面。
  2. 在 B 页面中同样设置路由链接或按钮,点击时导航到 C 页面。
  3. 在 C 页面中设置路由链接或按钮,点击时执行前端代码,调用浏览器的 history.back() 方法,即可返回到 A 页面,而无需刷新页面。

需要注意的是,使用 history 模式进行页面切换时,需要确保服务器已配置了相关路由规则,以确保直接访问特定路径时能正确加载对应的页面。

29、vue3中生命周期函数

在 Vue 3 中,由于引入了 Composition API,生命周期函数的概念发生了改变。Vue 3 中不再使用以前的选项式 API 中的生命周期钩子函数(如 beforeCreatemounted 等),而是使用了一组新的函数。

以下是 Vue 3 中常用的生命周期函数:

  1. setup:在组件实例创建之前执行,用于设置组件的初始状态和逻辑。可以返回一个对象,供组件中其他部分使用,也可以返回一个清理函数。
  2. onBeforeMount:在组件挂载之前执行,类似于 Vue 2.x 中的 beforeMount
  3. onMounted:在组件挂载后执行,类似于 Vue 2.x 中的 mounted
  4. onBeforeUpdate:在组件更新之前执行,类似于 Vue 2.x 中的 beforeUpdate
  5. onUpdated:在组件更新之后执行,类似于 Vue 2.x 中的 updated
  6. onBeforeUnmount:在组件卸载之前执行,类似于 Vue 2.x 中的 beforeDestroy
  7. onUnmounted:在组件卸载后执行,类似于 Vue 2.x 中的 destroyed

此外,还有其他一些生命周期函数,如 onActivatedonDeactivated(用于 keep-alive 组件)、onRenderTrackedonRenderTriggered(用于调试)等。

需要注意的是,Vue 3 中的生命周期函数采用了更加灵活的 Composition API 及函数式编程的方式,与 Vue 2.x 中的选项式 API 不完全一样。

30、Electron 如何与主进程进行通信?代码示例

在 Electron 中,可以使用 IPC(Inter-Process Communication,进程间通信)来实现主进程和渲染进程之间的通信。IPC 分为两种类型:主进程向渲染进程发送消息和渲染进程向主进程发送消息。

以下是 Electron 中主进程与渲染进程的通信示例代码:

// 主进程 main.js
const { app, BrowserWindow, ipcMain } = require(‘electron’);
let mainWindow;

function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true // 允许渲染进程使用 Node.js API
}
});

mainWindow.loadFile(‘index.html’);

ipcMain.on(‘messageToMain’, (event, arg) => {
console.log(arg); // 打印从渲染进程传递的消息
event.reply(‘messageToRenderer’, ‘Message received in the main process.’); // 回复消息给渲染进程
});
}

app.whenReady().then(() => {
createWindow();
});

// 渲染进程 index.html

Electron IPC Example Send Message to Main Process

在示例代码中,主进程通过 ipcMain 模块监听名为 ‘messageToMain’ 的事件,并在接收到消息时输出到控制台。之后使用 event.reply 方法向渲染进程回复消息。

渲染进程通过 ipcRenderer 模块向主进程发送名为 ‘messageToMain’ 的事件,并通过 ipcRenderer.on 监听名为 ‘messageToRenderer’ 的事件接收主进程的回复消息,并将消息显示在网页中。

注意,在 Electron 中,默认情况下是禁止在渲染进程中使用 Node.js 的 API 的,所以需要在 BrowserWindow 的配置中将 nodeIntegration 设置为 true,以便渲染进程可以使用 Node.js 的功能。

这是一个简单的示例,你可以根据自己的需求进行扩展。

31、用const 定义一个对象 这个对象能再增加属性吗

使用 const 关键字定义的对象是具有不可变性的常量,一旦定义后就不能再改变其绑定的值。这意味着无法直接添加新属性到已经定义的对象上。如果尝试添加属性,会导致报错。

然而,可以通过一些其他方式来实现对已定义对象的扩展,例如使用 Object.assign() 方法或扩展运算符 ...

以下是两种方法的示例:

  1. 使用 Object.assign() 方法:
const obj = {  
  prop1: 'value1',
  prop2: 'value2'
};

const extendedObj = Object.assign({}, obj, { prop3: 'value3' });

console.log(extendedObj);
// 输出: { prop1: 'value1', prop2: 'value2', prop3: 'value3' }

在上述示例中,使用 Object.assign() 将新的属性 { prop3: 'value3' } 合并到了一个新的空对象 {} 和原始对象 obj 中,从而创建了一个包含所有属性的新对象 extendedObj

  1. 使用扩展运算符 ...
const obj = {  
  prop1: 'value1',
  prop2: 'value2'
};

const extendedObj = {
  ...obj,
  prop3: 'value3'
};

console.log(extendedObj);
// 输出: { prop1: 'value1', prop2: 'value2', prop3: 'value3' }

在上面的示例中,使用扩展运算符 ... 将原始对象 obj 的属性展开,然后添加新的属性 { prop3: 'value3' },最终创建了一个包含所有属性的新对象 extendedObj

无论是使用 Object.assign() 方法还是扩展运算符 ...,都会创建一个新的对象,并将原始对象的属性以及新属性合并到其中,从而实现对已定义对象的扩展。

32、下列代码执行结果

const a = {b:1,c:2}:
a.d=3;

抱歉,我给出了错误的回答。对于用 const 定义的对象,不能直接添加新属性。无论是使用点符号还是赋值语句,都无法向常量对象添加新的属性。如果尝试这样做,会导致语法错误。例如,在你的例子中,尝试给常量对象 a 添加属性 d 会导致错误。

正确的做法是在定义对象时就将所有需要的属性初始化完整,或者使用其他方式来实现属性的动态增加,如使用 let 关键字定义可变对象。下面是一个示例:

let a = { b: 1, c: 2 };
a.d = 3;

console.log(a);
// 输出: { b: 1, c: 2, d: 3 }

在上述示例中,使用 let 关键字定义了可变对象 a,然后可以通过赋值语句添加新属性 d。输出结果为 { b: 1, c: 2, d: 3 }
对于想要扩展现有常量对象的情况,无法直接通过给常量对象添加新的属性来实现。而是需要参考之前提到的使用 Object.assign() 方法或扩展运算符 ... 的方式来扩展对象。

33、vue 中样式隔离

在 Vue 中,可以使用不同的方式来实现组件样式的隔离和封装。以下是几种常见的方法:

  1. Scoped CSS(作用域样式):
    Vue 提供了一种称为 “scoped” 的样式隔离方式。通过在单个组件的 <style> 标签上添加 scoped 属性,可以将样式限制在当前组件的作用域内,避免样式冲突。
<template>
  <div class="example">
    <!-- 组件内容 -->
  </div>
</template>

<style scoped>
.example {
  /* 组件样式 */
}
</style>

上述示例中,.example 类名只会应用于当前组件内部,不会影响其他组件中相同类名的元素。

  1. CSS Modules:
    CSS Modules 是一种基于文件命名约定的样式模块化方案。在 Vue 项目中启用 CSS Modules 后,每个样式文件都被视为独立的模块,并且在组件中通过导入和使用模块化的方式引入样式。
<template>
  <div :class="$style.example">
    <!-- 组件内容 -->
  </div>
</template>

<style module>
.example {
  /* 组件样式 */
}
</style>

使用 CSS Modules,组件的样式会自动与范围限定,并且可以在组件模板中使用 $style 对象来访问对应的样式类名。

  1. CSS-in-JS 库:
    除了上述的内建方式外,还可以使用第三方的 CSS-in-JS 库,如 styled-components、emotion 等。这些库允许在组件定义中直接编写样式,并将其与组件绑定在一起。
<template>
  <div class="example">
    <!-- 组件内容 -->
  </div>
</template>

<style lang="js">
import styled from 'styled-components';

const StyledExample = styled.div`
  /* 组件样式 */
`;

export default {
  name: 'MyComponent',
  components: {
    StyledExample
  }
};
</style>

通过 CSS-in-JS 库,在 Vue 组件中可以直接使用类似 CSS 的语法来定义、封装和使用样式。

以上是几种常见的在 Vue 中实现样式隔离的方法。每种方法都有其适用的场景和特点,请根据具体需求选择合适的方式。

34、vue 监听属性和计算属性的实现原理

在 Vue 中,属性的监听和计算属性都是利用了 Vue 的响应式系统来实现。

  1. 监听属性:
    Vue 提供了 $watch 方法,可以用于监听属性的变化。当被监听的属性发生变化时,Vue 会自动触发相应的回调函数。
this.$watch('propertyName', (newValue, oldValue) => {
  // 属性发生变化时执行的操作
});

通过 $watch 方法,Vue 在内部会创建一个“观察者(Watcher)”,它会订阅要监听的属性,并在属性发生改变时收到通知。然后触发对应的回调函数。

  1. 计算属性:
    计算属性是由数据属性计算而来的属性,它的值根据其他属性的变化而变化,并且会进行缓存以提高性能。在模板中使用计算属性的方式与普通属性一致。

计算属性的实现原理是通过使用 Vue 内部的“依赖追踪”机制。Vue 会在渲染组件时跟踪属性的读取,发现计算属性依赖的属性发生变化时,会重新计算并缓存计算属性的值。在下次访问该计算属性时,如果其依赖的属性没有发生变化,Vue 就直接返回缓存的计算结果,减少不必要的重复计算。

总结起来,Vue 的属性监听和计算属性实现原理都依赖于其内部的响应式系统。通过观察者模式和依赖追踪,Vue 能够自动跟踪属性的变化,并在需要的时候触发相应的回调函数或更新计算属性的值。这也是 Vue 在数据驱动视图的过程中,实现高效、灵活的响应式更新的关键机制之一。

35、vue 中数组用push()的方式为什么可以实现响应式

在 Vue 中,数组使用 push() 方法可以实现响应式更新,是因为 Vue 使用了特殊的技巧来拦截数组的变异方法(mutation methods)。

Vue 的响应式系统在创建响应式对象时会将数组的原型进行改写,并劫持了数组变异方法,比如 push()pop()shift()unshift()splice()sort()reverse() 等。当调用这些数组方法时,Vue 能够观察到数组的变化,并通知相关的依赖进行更新。

具体的实现原理如下:

  1. 当创建一个响应式数组时,Vue 会将数组的原型指向一个改写过的数组原型 Array.prototype
  2. 改写过的数组原型包含了对数组变异方法的重新定义,以使它们能够触发依赖更新。
  3. 当使用 push() 方法向数组中添加新元素时,Vue 能够捕捉到这个变化,并在内部执行相应的操作:
    • 添加新元素到数组中。
    • 将新元素设置为响应式,并进行依赖追踪。
    • 触发视图更新,重新渲染包含该数组的组件。

通过以上步骤,Vue 实现了对数组变异方法的拦截与处理,使得数组的变化能够被响应式系统检测到并进行相应的更新操作,从而实现了数组的响应式。

需要注意的是,Vue 的这种拦截和劫持机制只能处理使用变异方法改变的数组数据,对于直接通过索引修改数组元素或者改变数组长度的操作,Vue 是无法检测到的。为了解决这个问题,Vue 提供了一些特殊的方法来处理这类情况,比如使用 Vue.set 或者替换整个数组来触发更新等方式。

36、webpack 链式路由

Webpack本身并不提供链式路由(chained routing)的功能,因为Webpack是一个模块打包工具,主要负责处理模块之间的依赖关系和打包输出。

链式路由通常是指在前端应用中使用的一种路由方式,比如在Vue或React等框架中,通过路由器(router)来管理不同页面之间的跳转和展示。常见的前端路由库有Vue Router、React Router等。

如果你想在Webpack的配置中使用链式路由,可以结合相应的前端框架和路由库进行设置和配置。

例如,在使用Vue.js开发项目时,你可以借助Vue Router来实现链式路由。首先,安装Vue Router:

npm install vue-router

然后在Webpack的配置文件中,使用Vue Loader加载Vue组件,并在Vue实例中配置路由:

// webpack.config.js

module.exports = {
  // ...其他配置项
  module: {
    rules: [
      // Vue Loader的配置规则
      {
        test: /\.vue$/,
        use: 'vue-loader'
      },
      // ...其他加载器规则
    ]
  },
  // ...其他配置项
}

以及在Vue实例中定义路由和创建路由对象:

// main.js

import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import Home from './components/Home.vue';
import About from './components/About.vue';

Vue.use(VueRouter);

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
];

const router = new VueRouter({
  routes
});

new Vue({
  router,
  render: h => h(App)
}).$mount('#app');

在这个例子中,我们配置了两个路由:’/‘和’/about’,分别对应Home组件和About组件。当用户访问不同的路由时,相应的组件会被加载并显示。

需要注意的是,在使用链式路由时,你可能还需要通过Webpack的配置项,将路由相关的文件进行打包和输出,以确保在浏览器端能够正确加载和使用。

总结起来,Webpack本身并不直接提供链式路由功能,但你可以使用它配合前端框架和路由库来实现链式路由。对于Vue.js项目,可以使用Vue Router来配置和管理路由。

37、websocket 主题

WebSocket是一种网络通信协议,它允许在客户端和服务器之间建立双向通信的连接。它的主要特点是实时性和低延迟。使用WebSocket,可以实现实时数据传输、推送通知、即时聊天等功能。

至于"主题",在WebSocket中没有直接称为"主题"的概念。但是,可以通过在消息中添加特定的标识符或自定义数据字段来实现类似主题的功能。这样,不同的消息可以根据标识符或数据字段进行区分和处理,以实现不同主题或频道的消息传递。

38、前端怎么做公共模块的抽离

在前端开发中,抽离公共模块是一种常见的优化方式,可以提高代码的可维护性和复用性。以下是几种常用的方法来进行公共模块的抽离:

  1. 函数封装:将一段常用的功能逻辑封装成函数,并以参数形式暴露出来,在不同的地方进行调用。这样可以避免代码重复,提高代码的复用性。

  2. 组件抽离:将一部分功能相似或通用的代码抽离成组件,通过引入和使用组件来实现公共模块的复用。例如,可以将页面中多处使用的头部、导航栏等抽离成组件。

  3. 工具类/插件封装:将一些常用的工具函数或第三方插件封装成库,供整个项目或多个项目共享使用。这样可以提高代码的复用性,并且便于在不同的项目中统一管理和更新。

  4. 模块化管理:使用模块化开发的方式,将公共的逻辑和功能封装成独立的模块,通过导入和使用模块来实现代码复用。可以使用像ES Modules、CommonJS等模块化规范,或者使用打包工具(如Webpack、Rollup)进行模块的打包和管理。

  5. 配置文件提取:将一些通用的配置项(如API接口地址、样式主题等)提取出来,放在单独的配置文件中进行管理。不同的模块可以共享和引用这些配置项,方便统一管理和修改。

以上是几种常见的方法来进行公共模块的抽离,根据具体需求和项目情况选择适合的方式进行抽离,以提高代码的可维护性和复用性。

39、什么是js 设计模式

以下是一些常见的 JavaScript 设计模式:

  1. 单例模式(Singleton Pattern):确保类只有一个实例,并提供全局访问点。

  2. 工厂模式(Factory Pattern):通过一个工厂方法创建对象,而无需直接使用 new 关键字。

  3. 观察者模式(Observer Pattern):定义一对多的依赖关系,当一个对象(主题)状态发生改变时,其所有依赖(观察者)将自动更新。

  4. 装饰者模式(Decorator Pattern):动态地给对象添加新功能,是继承关系的一种替代方案。

  5. 适配器模式(Adapter Pattern):将一个类的接口转换成客户端所期望的另一个接口,使得原本不兼容的类可以协同工作。

  6. 策略模式(Strategy Pattern):将可互换的行为封装起来,并使用委托来选择要执行的行为。

  7. 代理模式(Proxy Pattern):提供一个代理对象,控制对真实对象的访问,可以添加额外的逻辑。

这些设计模式有助于提高代码的可读性、可维护性和可测试性,同时提供了一种标准化的解决方案来解决常见的设计问题。在实际开发中,根据具体情况选择合适的设计模式可以提升代码质量和开发效率。

当然!以下是一些常见设计模式的 JavaScript 代码示例:

1. 单例模式(Singleton Pattern):

const Singleton = (function() {
  let instance;

  function createInstance() {
    // 创建单例对象的代码
    return {/* 单例对象的属性和方法 */};
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

// 使用单例对象
const singletonInstance1 = Singleton.getInstance();
const singletonInstance2 = Singleton.getInstance();

console.log(singletonInstance1 === singletonInstance2); // 输出: true

2. 工厂模式(Factory Pattern):

function createProduct(type) {
  if (type === "A") {
    return new ProductA();
  } else if (type === "B") {
    return new ProductB();
  } else {
    throw new Error("Invalid product type.");
  }
}

// 具体产品类 A
function ProductA() {
  this.name = "Product A";
  // ...
}

// 具体产品类 B
function ProductB() {
  this.name = "Product B";
  // ...
}

// 使用工厂创建产品
const productA = createProduct("A");
const productB = createProduct("B");

console.log(productA.name); // 输出: "Product A"
console.log(productB.name); // 输出: "Product B"

3. 观察者模式(Observer Pattern):

class Subject {
  constructor() {
    this.observers = [];
  }
  
  subscribe(observer) {
    this.observers.push(observer);
  }
  
  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }
  
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    // 处理更新通知的逻辑
    console.log("Received data:", data);
  }
}

// 创建主题和观察者
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

// 订阅观察者
subject.subscribe(observer1);
subject.subscribe(observer2);

// 发送通知
subject.notify("Hello observers!");

这些是一些简单的代码示例来说明各

40、这三种的区别和优点缺点是什么

对于单例模式、工厂模式和观察者模式,它们有不同的应用场景、特点和优缺点:

  1. 单例模式(Singleton Pattern):

    • 特点:确保一个类只有一个实例,并提供全局访问点。
    • 优点:
      • 提供了全局访问点,方便在程序中的任何地方访问该实例。
      • 节省了系统资源,因为只有一个实例存在。
    • 缺点:
      • 单例模式使用全局变量增加了程序的耦合度。
      • 可能会导致单例对象过于庞大,承担了太多的职责。
  2. 工厂模式(Factory Pattern):

    • 特点:通过一个工厂方法创建对象,而无需直接使用 new 关键字。
    • 优点:
      • 封装了对象的创建过程,客户端代码与具体的产品类解耦。
      • 可以根据需求灵活地创建不同类型的对象。
    • 缺点:
      • 增加了系统复杂性,需要额外编写工厂类和产品类。
      • 当需要创建多个相关产品时,工厂模式的代码会变得复杂。
  3. 观察者模式(Observer Pattern):

    • 特点:定义一对多的依赖关系,当一个对象(主题)状态发生改变时,其所有依赖(观察者)将自动更新。
    • 优点:
      • 解耦了主题和观察者之间的关系,使得它们可以独立变化。
      • 支持广播通信机制,一个主题可以同时通知多个观察者。
    • 缺点:
      • 如果有大量的观察者,可能会影响程序性能和内存消耗。
      • 观察者和主题之间可能存在循环引用。

选择适当的设计模式取决于具体的需求和上下文。单例模式适用于需要全局访问且实例唯一的情况,工厂模式适用于创建对象的场景,而观察者模式适用于对象间存在一对多的关系,当一个对象状态改变时需要通知其他相关对象。

41、后几种设计模式能用实例演示下吗?

当然可以!下面我将使用几个示例来演示工厂模式和观察者模式的实际应用。

  1. 工厂模式示例:
# 工厂接口
class AnimalFactory:
    def create_animal(self):
        pass

# 具体工厂:狗工厂
class DogFactory(AnimalFactory):
    def create_animal(self):
        return Dog()

# 具体工厂:猫工厂
class CatFactory(AnimalFactory):
    def create_animal(self):
        return Cat()

# 产品接口
class Animal:
    def sound(self):
        pass

# 具体产品:狗
class Dog(Animal):
    def sound(self):
        return "汪汪汪"

# 具体产品:猫
class Cat(Animal):
    def sound(self):
        return "喵喵喵"

# 客户端调用
dog_factory = DogFactory()
cat_factory = CatFactory()

dog = dog_factory.create_animal()
print(dog.sound())  # 输出:汪汪汪

cat = cat_factory.create_animal()
print(cat.sound())  # 输出:喵喵喵

在上述示例中,我们定义了一个动物工厂接口和两个具体的动物工厂(狗工厂和猫工厂),每个具体工厂可以创建相应的动物实例(狗和猫)。客户端可以通过具体工厂创建对应的动物对象,并调用其方法。

  1. 观察者模式示例:
# 主题接口
class Subject:
    def attach(self, observer):
        pass

    def detach(self, observer):
        pass

    def notify(self):
        pass

# 具体主题
class WeatherStation(Subject):
    def __init__(self):
        self.observers = []
        self.temperature = 0

    def attach(self, observer):
        self.observers.append(observer)

    def detach(self, observer):
        self.observers.remove(observer)

    def notify(self):
        for observer in self.observers:
            observer.update(self.temperature)

    def set_temperature(self, temperature):
        self.temperature = temperature
        self.notify()

# 观察者接口
class Observer:
    def update(self, temperature):
        pass

# 具体观察者
class User(Observer):
    def update(self, temperature):
        print(f"当前温度更新为:{temperature} 度")

# 客户端调用
weather_station = WeatherStation()

user1 = User()
user2 = User()

weather_station.attach(user1)
weather_station.attach(user2)

weather_station.set_temperature(25)
# 输出:
# 当前温度更新为:25 度
# 当前温度更新为:25 度

weather_station.detach(user2)

weather_station.set_temperature(30)
# 输出:
# 当前温度更新为:30 度

在上述示例中,我们创建了一个天气站主题,每当温度发生变化时,会通知所有的用户观察者。用户可以通过订阅主题来接收温度的更新信息。在示例中,我们创建了两个用户观察者,并通过主题订阅它们。当温度改变时,用户观察者会接收到相应的更新通知。

当然可以!下面我会为你分别介绍装饰者模式、适配器模式、策略模式和代理模式,并提供相应的示例。

  1. 装饰者模式(Decorator Pattern):
    装饰者模式允许向现有对象添加新功能,同时又不改变其结构。它通过将对象放入包装器中,从而为对象动态地添加更多的行为和责任。

示例:

# 抽象组件接口
class Component:
    def operation(self):
        pass

# 具体组件
class ConcreteComponent(Component):
    def operation(self):
        print("执行具体组件的操作")

# 抽象装饰者
class Decorator(Component):
    def __init__(self, component):
        self.component = component

    def operation(self):
        self.component.operation()

# 具体装饰者A
class ConcreteDecoratorA(Decorator):
    def operation(self):
        super().operation()
        self.add_functionality()

    def add_functionality(self):
        print("添加额外功能A")

# 具体装饰者B
class ConcreteDecoratorB(Decorator):
    def operation(self):
        super().operation()
        self.add_functionality()

    def add_functionality(self):
        print("添加额外功能B")

# 客户端调用
component = ConcreteComponent()
decoratorA = ConcreteDecoratorA(component)
decoratorB = ConcreteDecoratorB(decoratorA)

decoratorB.operation()

在上述示例中,我们定义了抽象组件接口(Component)和具体组件(ConcreteComponent)。然后,创建了抽象装饰者(Decorator)来包装组件,并定义了具体的装饰者A(ConcreteDecoratorA)和装饰者B(ConcreteDecoratorB)来添加额外功能。最后,在客户端调用时可以通过层层装饰来对组件进行功能扩展。

  1. 适配器模式(Adapter Pattern):
    适配器模式将一个类的接口转换成客户端所期待的另一种接口。通常用于让原本不兼容的类能够一起工作。

示例:

# 目标接口
class Target:
    def request(self):
        pass

# 需要适配的类
class Adaptee:
    def specific_request(self):
        print("执行特殊请求")

# 类适配器
class ClassAdapter(Target, Adaptee):
    def request(self):
        self.specific_request()

# 对象适配器
class ObjectAdapter(Target):
    def __init__(self, adaptee):
        self.adaptee = adaptee

    def request(self):
        self.adaptee.specific_request()

# 客户端调用
class_adapter = ClassAdapter()
class_adapter.request()

adaptee = Adaptee()
object_adapter = ObjectAdapter(adaptee)
object_adapter.request()

在上述示例中,我们有一个目标接口(Target),需要使用该接口的客户端无法直接与需要适配的类(Adaptee)进行交互。为了使其能够一起工作,我们创建了类适配器(ClassAdapter)和对象适配器(ObjectAdapter)。这两种

当然可以!下面我将使用几个示例来演示策略模式和代理模式的实际应用。

  1. 策略模式示例:
# 策略接口
class PaymentStrategy:
    def pay(self, amount):
        pass

# 具体策略:支付宝支付
class AlipayStrategy(PaymentStrategy):
    def pay(self, amount):
        return f"使用支付宝支付 {amount} 元"

# 具体策略:微信支付
class WechatPayStrategy(PaymentStrategy):
    def pay(self, amount):
        return f"使用微信支付 {amount} 元"

# 上下文
class PaymentContext:
    def __init__(self, strategy):
        self.strategy = strategy

    def set_strategy(self, strategy):
        self.strategy = strategy

    def perform_payment(self, amount):
        return self.strategy.pay(amount)

# 客户端调用
payment_context = PaymentContext(AlipayStrategy())
print(payment_context.perform_payment(100))  # 输出:使用支付宝支付 100 元

payment_context.set_strategy(WechatPayStrategy())
print(payment_context.perform_payment(200))  # 输出:使用微信支付 200 元

在上述示例中,我们定义了一个支付策略接口,并实现了两种具体的支付策略:支付宝支付和微信支付。上下文对象持有一个策略对象,并根据不同的策略进行支付操作。客户端可以根据需要选择不同的支付策略并执行支付。

  1. 代理模式示例:
# 主题接口
class Subject:
    def request(self):
        pass

# 具体主题
class RealSubject(Subject):
    def request(self):
        print("真实主题的请求")

# 代理
class Proxy(Subject):
    def __init__(self, real_subject):
        self.real_subject = real_subject

    def request(self):
        # 在调用真实主题的请求之前或之后,可以添加额外的逻辑
        print("代理处理请求")
        self.real_subject.request()
        print("代理请求处理结束")

# 客户端调用
real_subject = RealSubject()
proxy = Proxy(real_subject)

proxy.request()

在上述示例中,我们定义了一个主题接口和一个具体的真实主题。代理对象持有真实主题的引用,并在执行真实主题的请求前后添加了额外的逻辑。客户端通过代理对象来间接地调用真实主题的请求,代理在调用前后提供了额外的功能。

注意:以上示例只是简化的代码片段,没有展示完整的类结构和方法实现,仅用于演示概念和原理。在实际应用中,需要根据具体需求进行适当的设计和实现。

41、vuex 数据 刷新页面白屏的问题原因

刷新页面后出现白屏的问题通常与 Vuex 数据的初始化和恢复有关。下面是可能导致这种问题的几个原因:

  1. 数据未进行持久化:Vuex 存储的数据默认情况下是存储在内存中的,当页面刷新时,数据会丢失。如果你希望在刷新页面后仍然保留 Vuex 中的数据,需要将数据进行持久化,例如使用浏览器的 LocalStorage 或类似的技术将数据保存到本地,并在页面加载时恢复数据。

  2. 缺少数据的恢复逻辑:即使数据已经进行了持久化处理,也需要在页面加载时恢复这些数据到 Vuex。可以通过在应用初始化阶段检查本地存储中是否存在数据,并将其重新写入 Vuex 来实现数据的恢复。

  3. 异步操作的延迟加载:如果在页面加载时,存在一些异步操作(例如获取远程数据或进行异步计算),并且这些操作在完成之前会访问 Vuex 数据,可能会导致白屏。为了解决这个问题,你可以在异步操作完成后再渲染页面内容或在等待期间显示加载状态。

请注意,以上仅是一些可能导致白屏问题的常见原因,具体情况还需要根据你的应用程序架构和代码进行具体分析和调试。

42、webpack 链式列表

关于Webpack和链式列表的组合,我不太清楚你具体指的是什么。Webpack是一个现代化的静态模块打包工具,用于构建JavaScript应用程序。而链式列表是一种数据结构,与Webpack的工作方式有一定的区别。

如果你想了解在Webpack中如何使用链式列表,这可能并不直接相关。Webpack主要用于处理模块依赖关系、代码转换、资源优化等工作,而链式列表是一种数据结构,用于存储和组织数据元素。

然而,在Webpack的配置文件中,你可以使用链式风格的方法来指定各种加载器(loader)和插件(plugins)的处理顺序和配置选项。这是因为Webpack的配置文件采用了一种基于链式调用的API风格,你可以按照链式的顺序添加各个配置项。

例如,你可以通过使用.use()方法和.loader()方法来指定加载器的处理顺序,并且每个加载器可以使用链式语法来进行配置。类似地,你也可以使用.plugin()方法来添加各种插件,并且可以根据需要进行链式调用和配置。

需要注意的是,这里提到的链式调用和链式列表并非完全相同的概念。Webpack中的链式调用是指一种串联多个配置选项的方式,而链式列表则是一种数据结构。

43、webpack 和 vite 的区别 和优缺点 和实现原理

Webpack和Vite都是用于构建现代化JavaScript应用程序的工具,它们在一些方面有相似之处,但也存在一些区别。

  1. 区别:

    • 构建方式:Webpack是一个传统的静态模块打包器,通过分析模块间的依赖关系,将多个模块打包成一个或多个输出文件。而Vite采用了现代化的开发服务器+原生ES模块的构建方式,不需要将所有代码打包合并,而是按需编译模块,提供更快的开发启动和热更新效果。

    • 开发环境:Webpack在开发环境中通常需要使用热模块替换(HMR)来实现快速的热更新,这会带来一定的性能损耗。而Vite利用浏览器原生支持的ES模块加载功能,在开发环境下直接使用原生ES模块加载,无需进行额外的转换和模块合并,因此具有更快的热更新速度。

    • 生产环境:在生产环境下,Webpack会将所有模块打包成一个或多个最终的输出文件,可以进行代码压缩、优化等操作。而Vite则会根据引入模块的情况进行按需编译,并生成适用于生产环境的代码,以提供更好的性能和加载速度。

    • 插件生态:Webpack具有非常强大的插件系统,能够通过各种插件扩展功能或优化构建过程。Vite也支持插件扩展,但由于其较新的发展阶段,插件生态相对较小。

  2. 优缺点:

    • Webpack 优点:

      • 成熟稳定,广泛应用于各类项目。
      • 强大的配置灵活性和丰富的插件生态。
      • 支持更多的文件类型和功能扩展。

      缺点:

      • 配置复杂,初学者上手难度较高。
      • 开发环境下热更新性能较差。
    • Vite 优点:

      • 快速的启动和热更新,因为它使用原生ES模块加载。
      • 更简单的配置,适合快速开发和原型验证。
      • 对现代JavaScript特性的原生支持。

      缺点:

      • 相对较新,插件生态相对较小。
      • 不适用于需要传统构建方式的复杂项目。
  3. 实现原理:

    • Webpack实现原理:Webpack基于构建流程和配置文件,通过解析模块间的依赖关系,生成依赖关系图,然后根据配置进行代码转换、文件分割、资源优化等操作,最终生成目标代码块。

    • Vite实现原理:Vite利用了浏览器原生支持的ES模

44、这两种工具 实现热更新的原理是什么

Webpack和Vite是两种常用的前端构建工具,它们在实现热更新(Hot Module Replacement)的原理上有所区别。

Webpack的热更新原理是通过Webpack Dev Server来实现的。当启动Webpack Dev Server时,它会在内存中构建并存储整个项目的资源。在开发过程中,当文件发生改变时,Webpack只会重新构建发生变化的模块,并将更新后的模块代码发送到浏览器端。浏览器接收到更新的模块代码后,使用HMR Runtime将新模块与旧模块进行比较,找出差异,并将差异应用到页面中,实现实时更新显示。

相比之下,Vite采用了一种基于原生ES模块的开发模式,利用了现代浏览器对ES模块的原生支持。当项目启动时,Vite会创建一个轻量级的本地服务器,同时会记录模块之间的依赖关系。在开发过程中,当文件发生改变时,Vite会重新解析受影响的模块,并向浏览器推送这些更新的模块,浏览器根据模块的导入关系请求新的模块,并在接收到新的模块后立即执行替换,从而实现快速的热更新。

从优点和缺点来看,Webpack的优点是功能强大且成熟稳定,支持各种高级特性和插件,但配置复杂,打包速度相对较慢。而Vite的优点是启动快、开发阶段的响应速度更快,适用于中小型项目,但对一些高级特性(如代码分割)的支持可能不够完善。

总结来说,Webpack在构建过程中将所有资源打包处理,而Vite借助现代浏览器原生ES模块的支持,在开发过程中能够实现更快的模块更新。选择使用哪种工具可以根据项目需求和个人偏好来决定。

45、vite 和webpack 热更新原理 哪里不同导致了vite 更快?

Vite和Webpack在热更新原理上有一些不同之处,这也是导致Vite更快的主要原因之一。

Webpack的热更新机制是通过使用webpack-dev-server或webpack-hot-middleware来实现的。当文件发生变化时,Webpack会重新编译整个模块,并将新的模块通过WebSocket推送到浏览器端,然后浏览器进行页面的刷新。这意味着每次修改都需要重新编译整个模块,即使只有一个文件发生了变化。这对于大型项目来说可能会导致性能下降,因为重新编译整个模块需要花费较长的时间。

而Vite采用了一种全新的热更新方式。它利用ES模块的特性,在开发服务器启动时,会先解析所有的模块依赖关系,然后根据依赖关系创建一个虚拟的依赖图。当文件发生变化时,Vite只需要重新编译被修改的文件以及与之相关的依赖模块,而不需要重新构建整个应用。这样就能够极大地提高热更新的速度,因为只有少量的文件需要重新编译和刷新,而不是整个项目。

此外,Vite还利用了浏览器的原生ES模块加载功能,通过在开发过程中直接在浏览器中加载原始的ES模块,而不是将它们打包成一个或多个文件。这消除了打包和构建过程中的繁琐步骤,使得开发过程更加轻量级和高效。

综上所述,Vite相比Webpack在热更新方面更快速,主要是因为它只重新编译被修改的文件和相关依赖模块,而不需要重新构建整个项目,并且利用浏览器的原生ES模块加载功能,提供了更高效的开发体验。

46、这两种工具 实现热更新的原理是什么

Webpack和Vite是两种常用的前端构建工具,它们在实现热更新(Hot Module Replacement)的原理上有所区别。

Webpack的热更新原理是通过Webpack Dev Server来实现的。当启动Webpack Dev Server时,它会在内存中构建并存储整个项目的资源。在开发过程中,当文件发生改变时,Webpack只会重新构建发生变化的模块,并将更新后的模块代码发送到浏览器端。浏览器接收到更新的模块代码后,使用HMR Runtime将新模块与旧模块进行比较,找出差异,并将差异应用到页面中,实现实时更新显示。

相比之下,Vite采用了一种基于原生ES模块的开发模式,利用了现代浏览器对ES模块的原生支持。当项目启动时,Vite会创建一个轻量级的本地服务器,同时会记录模块之间的依赖关系。在开发过程中,当文件发生改变时,Vite会重新解析受影响的模块,并向浏览器推送这些更新的模块,浏览器根据模块的导入关系请求新的模块,并在接收到新的模块后立即执行替换,从而实现快速的热更新。

从优点和缺点来看,Webpack的优点是功能强大且成熟稳定,支持各种高级特性和插件,但配置复杂,打包速度相对较慢。而Vite的优点是启动快、开发阶段的响应速度更快,适用于中小型项目,但对一些高级特性(如代码分割)的支持可能不够完善。

总结来说,Webpack在构建过程中将所有资源打包处理,而Vite借助现代浏览器原生ES模块的支持,在开发过程中能够实现更快的模块更新。选择使用哪种工具可以根据项目需求和个人偏好来决定。

47、JavaScript中对象调用自身的原型方法

//创建构造函数

function Foo(){ }

//添加公有方法,即添加原型方法

Foo.prototype.bar = function(){

alert('hello');

}

创建Foo的实例后,实例可以直接调用原型方法 .bar(),但是对象Foo不能直接调用原型方法.bar()。

var f = new Foo('hello');

f.bar();// hello

Foo.bar(); //undefined

让Foo对象调用.bar 方法,在中间加上prototype 即可。

Foo.prototype.bar();  // hello

例子:

var a = {};
var obj = {
    add1(){
        this.aa = true;
    },
    add2() {
        this.bb = true;
    }
};

console.log(obj.aa) // undefined
console.log(obj.bb) // undefined
console.log(a.aa) // undefined
console.log(a.bb) // undefined

a._proto_ = obj;
a._proto_.add1();
a._proto_.add2();

console.log(obj.aa) // true
console.log(obj.bb) // true
console.log(a.aa) // undefined
console.log(a.bb) // undefined



48、有一个数组,数组里面存放着n个promise函数,要求这些promise函数顺序执行。

调试数据arr:

function a(){
    return new Promise((resolve,reject)=>{
        resolve(1);
    })
}
function b(){
    return new Promise((resolve,reject)=>{
        resolve(2);
    })
}
function c(){
    return new Promise((resolve,reject)=>{
        resolve(3);
    })
}
function d(){
    return new Promise((resolve,reject)=>{
        resolve(4);
    })
}
const arr = [a,b,c,d];

1、async+await 方法
async function doPromise(arr) {
  for (let i = 0; i < arr.length; i++) {
    let res = await arr[i]();
    console.log(res);
  }
}
doPromise(arr);

2、递归方法
function doPromise(arr) {
    function doing(i){
        if(i==arr.length) return;
        arr[i]().then(res=>{
            console.log(res);
            doing(++i);
        })
    }
    doing(0);

49、浏览器回流与重绘
1、浏览器的渲染过程

  1. 解析HTML,生成DOM树,解析CSS,生成CSSOM树
  2. 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
  3. Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
  4. Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
  5. Display:将像素发送给GPU,展示在页面上。
1.1 生成渲染树

为了构建渲染树,浏览器主要完成了以下工作:

1、从DOM树的根节点开始遍历每个可见节点。
2、对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。
3、根据每个可见节点以及其对应的样式,组合生成渲染树。
不可见的节点包括:

  • 一些不会渲染输出的节点,比如script、meta、link等。
  • 一些通过css进行隐藏的节点。比如display:none。
注意:
  • 利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。
  • 渲染树只包含可见的节点
1.2 回流

当渲染树(render Tree)中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这个过程就称为回流(reflow),也就是重新布局(relayout)。

1.3 重绘

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如 background-color 。则就叫称为重绘。

2、何时发生回流重绘

回流阶段需要计算节点的位置和几何信息,当页面布局和几何信息发生变化的时候,就需要回流。

  • 添加或删除可见的DOM元素

  • 元素的位置发生变化

  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)

  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。

  • 页面一开始渲染的时候(这肯定避免不了)

  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

回流必定会发生重绘,重绘不一定会引发回流。重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。根据改变的范围和程度,渲染树中或多或少的部分节点需要重新计算,有些改变还会触发整个页面的重排。

3、浏览器的优化机制

由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:

offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle()
getBoundingClientRect
以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。

4、减少回流和重绘

4.1 最小化重绘和重排
避免频繁操作样式,合并多次对DOM和样式的修改。

一次性重写style属性
使用cssText取代多次修改样式
将样式定义为class,一次性修改class属性

4.2 脱离文档流批量修改DOM
当我们需要对DOM对一系列修改的时候,可以通过以下步骤减少回流重绘次数:

使元素脱离文档流
对其进行多次修改
将元素带回到文档中。
该过程的第一步和第三步可能会引起回流,但是经过第一步之后,对DOM的所有修改都不会引起回流,因为它已经不在渲染树了。

有三种方式可以让DOM脱离文档流:

隐藏元素,应用修改,重新显示,这会在展示和隐藏节点的时候,产生两次重绘。
使用文档片段(document fragment)在当前DOM之外构建一个子树,应用修改,再把它拷贝回文档。
将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。

4.3 避免频繁读取会引发回流/重绘的属性
当我们访问元素的一些属性的时候,会导致浏览器强制清空队列,进行强制同步布局。需要多次使用的属性,使用变量缓存起来,避免直接读取。

4.4 对于复杂动画元素使用绝对定位让其脱离文档流
对于复杂动画效果,由于会经常的引起回流重绘,因此,我们可以使用绝对定位,让它脱离文档流。否则会引起父元素以及后续元素频繁的回流。

4.5 CSS3硬件加速(GPU加速)
使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。

常见的触发硬件加速的css属性:

transform
opacity
filter
Will-change
css3硬件加速的缺点

如果你为太多元素使用css3硬件加速,会导致内存占用较大,会有性能问题。
在GPU渲染字体会导致抗锯齿无效。这是因为GPU和CPU的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊。

4.6 其他细节
使用CSS动画取代直接修改元素的位置或大小等。
使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
避免在循环中批量修改元素样式。
避免使用table布局,可能很小的一个小改动会造成整个 table 的重新布局。
JS动画中使用requestAnimationFrame取代setTimeout和setInterval。
CSS 选择符从右往左匹配查找,避免节点层级过多
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如 video、canvas 标签,浏览器会自动将该节点变为图层
尽可能在DOM树的最末端改变class。
避免设置多层内联样式。
将动画效果应用到position属性为absolute或fixed的元素上。
避免使用CSS表达式(例如:calc())。
requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点:

requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
停止requestAnimationFrame

是否有类似clearTimeout和clearInterval这样的类似方法?有。cancelAnimationFrame()接收一个参数 requestAnimationFrame默认返回一个id,cancelAnimationFrame只需要传入这个id就可以停止了。

50、vue中,ref属性使用详情介绍

在Vue中,ref是一个特殊的属性,用于获取组件实例或DOM元素的引用。通过在标签上添加ref属性,可以在Vue组件内部使用该元素或组件。

1、获取DOM元素的引用:

在普通HTML元素上使用ref可以获取该元素的引用。比如,在以下代码中,我们给一个input元素添加了ref属性,并在Vue组件内部使用它:

<template>
  <div>
    <input type="text" ref="myInput">
  </div>
</template>
<script>
export default {
  mounted() {
    // 使用this.$refs获取DOM元素的引用
    this.$refs.myInput.focus();
  }
}
</script>

通过this.$refs.myInput获取该元素的引用,并调用focus方法使其聚焦。
2、获取子组件的引用:

在Vue组件中使用ref可以获取子组件的引用,从而可以在父组件内部调用子组件的方法或访问其数据。

<template>
  <div>
    <child ref="myChild"></child>
  </div>
</template>

<script>
import Child from './Child.vue';
export default {
  components: { Child },
  mounted() {
    // 使用this.$refs获取子组件的引用
    this.$refs.myChild.sayHello();
  }
}
</script>


在父组件中将子组件引入,并在子组件标签上添加ref属性,然后就可以通过this.$refs.myChild获取子组件的引用,在父组件内部调用子组件的sayHello方法。

3、动态添加/移除ref:

在Vue中,ref也可以动态添加/移除。在以下代码中,我们使用v-for指令循环渲染一个input元素数组,并为每个元素添加一个ref,然后点击按钮时,移除第二个input元素的ref。

<template>
  <div>
    <div v-for="(item, index) in items" :key="index">
      <input type="text" :ref="'myInput' + index">
    </div>
    <button @click="removeRef">移除Ref</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: ['A', 'B', 'C']
    }
  },
  methods: {
    removeRef() {
      // 移除第二个input元素的ref
      this.$refs['myInput1'].forEach(elem => elem.removeAttribute('ref'));
    }
  }
}
</script>



通过$refs[‘myInput’ + index]动态添加ref,再通过removeAttribute方法移除第二个input元素的ref。
51、js ||与|、&&与&的区别介绍
1.情景展示

在js当中,|,||,&,&&分别代表什么意思,有什么区别?

2.&&,||

&&和||这个两个运算符叫做:逻辑运算符

&&:左右两侧为true(条件都成立)时,返回true,否则,返回false,也就是:两真则真,一假则假。

||:左右两侧有一个为true(条件一个成立即可)时,返回true;

只有,左右两侧都为false时,才返回false,也就是:一真则真,两假则假。

在js中,还有一种特殊的存在,那就是:undefined。

我们在js当中,从页面取值或者调用js对象的时候,经常会遇到undefined,有这样的需求:

当第一个值为undefined时,我们让它取第二个值;当第一个有值时,我们只取第一个值,如何实现?

通过逻辑运算符:||可以实现。

性能(速度)方面的特点:

逻辑运算符&& || 中,如果&&的第一个运算数是false,就不再考虑第二个运算数,直接返回false;

如果||的第一个运算数是true,也不再考虑第二个运算数,直接返回true。

3.&,|

&和|这个两个运算符叫做:(逐)位运算符,也就是用来进行位运算的,即:一位一位地进行比较(平常根本用不到,顶多也就是加减乘除这种普通的运算操作)

特点:对两个二进制操作数逐位进行比较(如果不是二进制,会将其转换成二进制,再进行逐计算)

由于,js是弱类型语言,所以,它俩也可以进行“逻辑运算”,当用&和|进行"逻辑运算"时,实际上true被转换成1,false被转换成0,再进行逐位运算;

即逻辑逐位运算的本质是:逐位运算。

逻辑逐位运算逐位运算运算结果
逻辑逐位运算符:’&’
true&true1&11
true&false1&00
false&true0&10
false&false0&00
逻辑逐位运算符:’|’
true|true1|11
true|false1|01
false|true0|11
false|false0|00

&运算示例

|运算示例

性能(速度)方面的特点:

&和|运算符,它们总是要比较两个运算数才得出结果,因而性能上&&和||会比&和|好。

总而言之就是:可以使用&&和||的地方,不要用&和|。

另外,’|'位运算符,还可以对小数进行取整操作,具体格式为:

小数|0

详细用法介绍:

当小数为正数时,向下取整,相当于:Math.floor();

当小数为负数时,向上取整,相当于:Math.ceil();

window.onload = function(){
    console.log(99.9999|0);
    console.log(Math.floor(99.9999));
    console.log(-99.9999|0);
    console.log(Math.ceil(-99.9999));
}
52、js面向对象怎么理解
<一>、认识对象。

首先要认识对象:在编程中,对象(object)是具体的某一个实例,唯一的某一个个体。如:电脑就是一个统称,而你面前的这一台电脑就是对象。而电脑的统称在编程中叫做类,概念就是:具有一类相同特征事物的抽象概念。在js中只有对象的概念。没有类的概念,在ECMA6中必须加回来。

对象的语法:(创建对象)

var obj1 = new Object();

var obj2 = Object();

var obj3 = {};
<二> 、在js中面向对象的编程思想是什么呢?

直接将生活逻辑映射到我们的编程中。

1.分析应用题里的实体。2.分析实体的属性和性能。3.实体之间相互作用得出结果。

<三>、对象:既可以存储数据,又可以存储函数的结构。
<四>、用面向对象创建一个人的方法:

var person = new Object(); person.name = "blue"; person.sex = "男"; person.showName = function(){ alert("我的名字叫:" + this.name); } person.showSex = function(){ alert("我的性别是" + this.sex + "的"); }

一下是封装函数创造一个人,也叫作使用new构造函数(本质上是构造对象),在JS中的构造函数充当的角色是类的存在。

function createPerson(name, sex){
    var obj = new Object();
    obj.name = name;
    obj.sex = sex;
    obj.showName = function(){
       alert("我的名字叫" + this.name);
    }
    obj.showSex = function(){
        alert("我的性别是" + this.sex + "的");
    }
    return obj;
}
    var p1 = createPerson("blue", "男");
<五>、继承和多态

在JS中一切皆对象。

继承:从父一级继承到的属性和方法。

   1. 原型链继承 继承方法
   for(funcName in Person.prototype){
        Worker.prototype[funcName] = Person.prototype[funcName];
   }
	在任何函数上都有一个叫做prototype原型的属性,他是一个对象,如果我们将函数添加给某一个可以创建对象的函数的原型prototype,该函数共享。
    
   2.Object.create  根据原对象,生成一个新对象
  Worker.prototype = Object.create(Person.prototype);

    3、直接通过调用构造函数继承
    worker.prototype = new Person();
多态:我自己拓展和重写的属性和方法。
function Person(name, sex){
    this.name = name;
    this.sex = sex;
}

Person.prototype.showName = function(){
    alert(`我的名字是:` + this.name);
}
Person.prototype.showSex = function(){
    alert(`我的性别是:` + this.sex);
}

function Worker(name, sex, job){
     Person.call(this, name, sex);
     this.job = job;
}
Worker.prototype.showJob = function(){
     alert(`我的工作是` + this.job);
}
var w1 = new Worker("red", "女", "画家");
w1.showName();
w1.showSex();
w1.showJob();
<六>、满足继承、封装、多态的特点叫做面向对象。

理解起来就是:面向对象是一种编程思想,而我们要进行面向对象编程,就必须用到类(ECMA6之前,没有类,用构造函数充当类的存在)和对象语法实现,最终实现的面向对象的代码,拥有继承、封装、多态的特点。

53、vue父子组件传值之 p a r e n t 、 parent、 parentchildren、$refs

使用场景

  • $refs:父组件访问子组件
    • 如果在普通的DOM元素上使用,引用指向的是DOM元素;
    • 如果用在子组件上,引用的是组件实例
  • $parent:子组件访问父组件
  • $children:父组件访问子组件
示例一:$parent 子组件获取父组件的数据、调用父组件的方法
父组件
<template>
  <div class="about">
    <MessageBox ref = 'mes' class="MessageBox"/>
    <h2>父组件-表格</h2>
 
    <el-table
      :data="tableData"
      style="width: 100%">
      <el-table-column
        prop="name"
        label="姓名"
        >
      </el-table-column>
      <el-table-column
        prop="desc"
        label="内容"
        >
      </el-table-column>
      <el-table-column
        prop="time"
        label="时间">
      </el-table-column>
    </el-table>
 
  </div>
</template>
 
<script>
import MessageBox from '../components/message.vue';
export default {
  data() {
    return {
      tableData: [],
    };
  },
  methods: {
    fun() {
      console.log('父组件的方法!!!')
    }
  },
  components: {
    MessageBox
  },
};
</script>
<style scoped lang="scss">
.about{
  width: 50vw;
  margin: auto;
}
</style>
 
子组件
<template>
    <div class="message-container">
        <h2>子组件-留言板信息输入</h2>
        <el-form ref="form" :model="form" label-width="80px">
            <el-form-item label="姓名">
                <el-input v-model="form.name" clearable ></el-input>
            </el-form-item>
              <el-form-item label="内容">
                <el-input type="textarea" v-model="form.desc"></el-input>
            </el-form-item>
              <el-button type="primary" @click="addMes()">提 交</el-button>
        </el-form>
    </div>
</template>
<script>
export default {
    name:'message',
    data() {
        return {
            form:{
                name:'',
                desc:'',
            }
        };
    },
    methods: {
        addMes(){
            const obj = Object.assign(this.form,{time:this.getNowTime()});
            this.$parent.tableData.push(obj);
 
            // 获取父组件的数据
            // this.$parent.tableData;
            // 调用父组件的方法
            // this.$parent.fun();
        },
        // 获取当前时间
        getNowTime() {
            let dtime = new Date();
            let nian = dtime.getFullYear(),
                yue = dtime.getMonth() + 1,
                day = dtime.getDate(),
                hours = dtime.getHours(),
                fen = dtime.getMinutes(),
                miao = dtime.getSeconds();
            return nian + "-" + yue + "-" + day + " " + hours + ":" + fen + ":" + miao;
        }
    },
    components: {
        
    }
}
</script>
示例二:$children 父组件获取子组件的数据、调用子组件的方法
父组件
<template>
  <div class="about">
    <MessageBox ref = 'mes' class="MessageBox"/>
    <h2>父组件-表格</h2>
 
    <el-button type="primary" @click="addMes()">提 交</el-button>
 
    <el-table
      :data="tableData"
      style="width: 100%">
      <el-table-column
        prop="name"
        label="姓名"
        >
      </el-table-column>
      <el-table-column
        prop="desc"
        label="内容"
        >
      </el-table-column>
      <el-table-column
        prop="time"
        label="时间">
      </el-table-column>
    </el-table>
 
  </div>
</template>
 
<script>
import MessageBox from '../components/message.vue';
export default {
  data() {
    return {    
      tableData: [],
    };
  },
  methods: {
    addMes() {
      // 获取子组件的数据
      const child_val = this.$children[0].form;
      const obj = Object.assign(child_val,{time:this.getNowTime()});
      this.tableData.push(obj);
      // 调用子组件的方法
      this.$children[0].fun();
    },
    // 获取当前时间
    getNowTime() {
        let dtime = new Date();
        let nian = dtime.getFullYear(),
            yue = dtime.getMonth() + 1,
            day = dtime.getDate(),
            hours = dtime.getHours(),
            fen = dtime.getMinutes(),
            miao = dtime.getSeconds();
        return nian + "-" + yue + "-" + day + " " + hours + ":" + fen + ":" + miao;
    },
  },
  components: {
    MessageBox
  },
};
</script>
<style scoped lang="scss">
.about{
  width: 50vw;
  margin: auto;
}
</style>
 
子组件
<template>
    <div class="message-container">
        <h2>子组件-留言板信息输入</h2>
        <el-form ref="form" :model="form" label-width="80px">
            <el-form-item label="姓名">
                <el-input v-model="form.name" clearable ></el-input>
            </el-form-item>
                <el-form-item label="内容">
                <el-input type="textarea" v-model="form.desc"></el-input>
            </el-form-item>
        </el-form>
    </div>
</template>
<script>
export default {
    name:'message',
    data() {
        return {
            form:{
                name:'',
                desc:'',
            }, 
        };
    },
    methods: {
        fun() {
            console.log('子组件的方法!!!')
        }
    },
    components: {
        
    }
}
</script>
示例三:$refs 父组件获取子组件的数据、调用子组件的方法
<MessageBox ref = 'mes' class="MessageBox"/>
 
 
// 获取子组件的数据
const child_val = this.$refs.mes.form;
 
// 调用子组件的方法
this.$refs.mes.fun();

r e f 的 用 法 与 ref的用法与 refchildren的方法相同,只需要将 children[0] 换成 $refs.mes 即可。

54、前端发请求阶段如何进行优化:

前端发起请求的优化可以从以下几个方面考虑:

  • 减少请求次数:可以将多个小的请求合并为一个大的请求,减少网络开销。可以使用 HTTP/2 的多路复用特性,或者使用资源打包工具如Webpack进行合并。
  • 压缩文件大小:使用压缩算法对传输的文件进行压缩,减少文件大小。可以使用Gzip或者Brotli压缩算法进行压缩。
  • 缓存策略:合理设置缓存策略,避免重复请求相同的资源。可以通过设置Cache-Control、Expires等HTTP头来控制缓存。
  • 使用CDN加速:将静态资源部署到CDN上,利用就近访问提高请求速度。
  • 异步加载:将页面中不需要立即显示的内容通过异步加载的方式延迟加载,提高页面的首屏加载速度。
  • 懒加载:对于图片、视频等资源,可以使用懒加载技术,延迟加载,当用户需要访问时再进行加载。
  • 预加载:对于即将访问的页面或资源,可以使用预加载技术,在空闲时间提前加载。
  • 使用HTTP/2:HTTP/2引入了多路复用、头部压缩等技术,提高了请求的效率。
跨域的解决方法
(1)jsonp的认识
(2)cores的认识

跨域是指在浏览器中,当前网页所在的域与请求的资源所在的域不一致。由于浏览器的同源策略限制,跨域请求通常会被浏览器拦截,导致请求失败。

解决跨域问题的常见方法有:

  1. JSONP(JSON with Padding):JSONP是一种利用
55、跨域请求被拦截后,怎样判断请求是否到达后端,

跨域请求被拦截后,前端可以通过查看响应头中的Access-Control-Allow-Origin字段来判断请求是否成功到达后端。如果该字段的值是前端请求的域名或者通配符*,则说明请求成功到达后端。如果该字段的值是其他域名,则说明请求被拦截。另外,前端还可以通过查看响应状态码来判断请求是否成功到达后端,常见的成功状态码是200。

预检请求options

预检请求是指浏览器在发送跨域请求之前,会先发送一个OPTIONS请求,用于获取服务器对跨域请求的支持情况。预检请求的目的是为了保护服务器,避免非法跨域请求对服务器造成安全风险。

在预检请求中,浏览器会发送一个OPTIONS请求,并携带一些预检请求头,如Access-Control-Request-MethodAccess-Control-Request-Headers。服务器收到预检请求后,会根据这些头部信息来判断是否允许跨域请求。

如果服务器允许跨域请求,会返回一个带有Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers等响应头的响应。浏览器收到响应后,才会继续发送真正的跨域请求。

如果服务器不允许跨域请求,或者没有正确配置预检请求的响应头,浏览器就会拦截跨域请求,并抛出一个错误。

总结起来,预检请求是为了确认服务器是否允许跨域请求,并且提供了一种机制来防止非法跨域请求对服务器造成风险

56、ajax中如何实现isonp跨域 说说原生js实现方法

在原生的AJAX中实现JSONP跨域可以通过以下几个步骤:

  1. 创建一个script标签,并设置其src属性为目标接口的URL,同时传入一个回调函数名作为参数,例如:<script src="http://example.com/api?callback=callbackFunc"></script>。回调函数名是前端定义的一个函数,用于处理从服务器返回的数据。
  2. 在前端代码中定义一个与回调函数名相同的函数,该函数作为回调函数将在服务器返回数据时被调用。例如:function callbackFunc(data) { console.log(data); }
  3. 服务器接收到请求后,会将数据作为参数传入回调函数,并将回调函数作为响应内容返回给前端。例如:callbackFunc({"name": "John", "age": 25})
  4. 前端在接收到响应后,会自动执行回调函数,并可以在其中处理返回的数据。

这种方法利用了script标签不受同源策略限制的特性,通过动态创建script标签来实现跨域请求,并利用回调函数来处理返回的数据。但需要注意的是,JSONP只支持GET请求,且返回的数据必须是经过特定格式包装的JavaScript代码,需要服务器端的支持。

57、iframe跨域操作怎么实现的

在前端中,使用iframe进行跨域操作可以通过以下几种方式实现:

  1. window.postMessage方法:可以在父窗口和子窗口之间进行跨文档通信。通过在父窗口中使用postMessage方法向子窗口发送消息,子窗口中监听message事件来接收消息,并做相应的处理。
  2. location.hash方法:可以通过改变子窗口的hash值来传递数据。父窗口通过设置子窗口的src属性,将要传递的数据添加到hash值中,然后子窗口通过监听hashchange事件来获取传递的数据。
  3. document.domain方法:只适用于主域相同,子域不同的情况。通过将父窗口和子窗口的document.domain属性设置为相同的主域,可以实现跨域操作。注意,该方法只适用于主域相同的情况,不能解决不同主域之间的跨域问题。
  4. window.name方法:可以通过将数据存储在window.name属性中来进行跨域传递。父窗口将要传递的数据存储在window.name属性中,然后子窗口再次加载时可以获取到该数据。

这些方法都是基于浏览器的同源策略下的漏洞或者特殊机制来实现跨域操作的,需要注意使用时的安全性和兼容性问题。

58、考察的是一个表单提交,如何防止用户多次提交,减少服务器端的压力

可以通过以下几种方法来防止用户多次提交表单,减少服务器端的压力:

  1. 前端禁用提交按钮:在用户点击提交按钮后,立即禁用按钮,防止用户多次点击。可以通过设置按钮的disabled属性为true来实现禁用。
  2. 前端显示加载状态:在用户点击提交按钮后,可以在页面上显示一个加载状态,告知用户正在提交并等待响应。可以通过添加一个loading图标或者文本提示来实现。
  3. 后端生成唯一标识:在用户第一次提交表单时,后端生成一个唯一标识,并将其存储在session或者cookie中。然后将该唯一标识返回给前端,并在表单中添加一个隐藏字段,将唯一标识作为值。当用户再次提交表单时,前端会将隐藏字段的值传递给后端,后端根据唯一标识判断是否已经处理过该请求。
  4. 前端倒计时:在用户点击提交按钮后,可以设置一个倒计时,在倒计时结束之前禁止用户再次点击。可以使用JavaScript的定时器来实现倒计时,并在倒计时结束后重新启用提交按钮。
  5. 后端幂等性处理:在后端接收到表单提交请求时,可以进行幂等性处理。即判断是否已经处理过该请求,如果已经处理过,则直接返回之前的处理结果,不再重复处理。

以上方法可以结合使用,根据具体的场景和需求选择合适的防止用户多次提交表单的方法。

59、创建10个a标签,点击弹出它是第几个

您可以使用JavaScript来创建10个a标签,并为每个a标签添加点击事件。在点击事件中,可以通过event.target获取当前点击的a标签元素,并通过父元素的childNodes属性获取其在父元素中的索引位置,从而得到它是第几个a标签。

以下是示例代码:

// 创建父元素
var parentElement = document.createElement('div');

// 创建10个a标签,并添加点击事件
for (var i = 0; i < 10; i++) {
  var a = document.createElement('a');
  a.innerHTML = '点击我';
  a.addEventListener('click', function(event) {
    var index = Array.prototype.indexOf.call(event.target.parentNode.childNodes, event.target);
    alert('我是第' + (index + 1) + '个a标签');
  });
  parentElement.appendChild(a);
}

// 将父元素添加到页面中
document.body.appendChild(parentElement);

以上代码会创建一个包含10个a标签的父元素,并为每个a标签添加点击事件。当点击某个a标签时,会弹出它是第几个a标签的提示信息。

60、左固定布局,右侧自适应布局

左固定布局,右侧自适应布局可以通过以下几种方式实现:

  1. 使用CSS的float属性:将左侧固定的元素设置为float:left,右侧自适应的元素设置为width:100%。这样左侧元素会占据左侧的空间,右侧元素会自动填充剩余的空间。
.left {
  float: left;
  width: 200px;
}

.right {
  width: 100%;
}
<div class="left">
  左侧固定内容
</div>
<div class="right">
  右侧自适应内容
</div>
  1. 使用CSS的flexbox布局:将父容器设置为display:flex,并设置flex-direction为row。左侧固定的元素设置为固定宽度,右侧自适应的元素设置为flex:1。
.container {
  display: flex;
  flex-direction: row;
}

.left {
  width: 200px;
}

.right {
  flex: 1;
}
<div class="container">
  <div class="left">
  </div>
  <div class="right">
    右侧自适应内容
  </div>
</div>
  1. 使用CSS的grid布局:将父容器设置为display:grid,并使用grid-template-columns定义两列的宽度。左侧固定的元素设置为固定宽度,右侧自适应的元素设置为auto。
.container {
  display: grid;
  grid-template-columns: 200px auto;
}

.left {
  width: 200px;
}
<div class="container">
  <div class="left">
    左侧固定内容
  </div>
  <div class="right">
    右侧自适应内容
  </div>
</div>

以上是几种常见的实现左固定布局,右侧自适应布局的方法,根据

61、题目:给一个字符串,循环输出每一个字符,当输出到最后一个字符时再从头开始

思路:根据打印情况,猜想需要返回一个函数,考点也就在闭包上(当时还思考了迭代器、函数柯里化两个知识点)

function loopString(s) {
  let i = 0

  return function () {
    if (i === s.length) i = 0
    let res = s[i]
    i++
    return res
  }
}
const bar = loopString('bar');
console.log(bar(), bar(), bar(), bar()); // 'b' 'a' 'r' 'b'


62、题目:给一个数,判断该数是否是斐波那契数列里的一项,若是则返回第几项,若不是则返回-1

思路:考算法,递归可能是最先被想到,其实动态规划也简单,我习惯使用,只要维护两个数,两数之和即是下一位数,一直反复推出后面的数字即可



function findFibonacciIndex(n) {
  let a = 1, b = 1, sum = 0, index = 1
  while (sum < n) {
    sum = a + b
    a = b
    b = sum
    index++
  }
  return sum === n ? index : -1
}
console.log(findFibonacciIndex(5)); // 4
63、js 实现一个斐波那契数列 要求小于 给定的数字n
var arr = [];
function getFbArry(n) {
    var a=1,b=1,sum=0;
    while(sum<=n) {
        sum = a+b;
        a=b;
        b=sum
       arr.push(a) 
    }
    var arrStr = ('1'+'|'+arr.join('|')).split('|')
    return arrStr
    
}
什么是斐波那契数列

斐波那契数列是一个数列,其中每个数字都是前两个数字之和。数列的前两个数字通常是0和1。从第三个数字开始,每个数字都是前两个数字之和。例如,斐波那契数列的前几个数字是0、1、1、2、3、5、8、13等。这个数列在数学和计算机科学中都有广泛的应用。

返回斐波那契数列的第几位的,代码如下

在JavaScript中,可以使用递归或迭代的方式实现斐波那契数列。

递归方式:
function fibonacciRecursive(n) {
  if (n <= 1) {
    return n;
  }
  return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}

console.log(fibonacciRecursive(10)); // 输出:55
迭代方式:
function fibonacciIterative(n) {
 if (n <= 1) {
    return n;
  }
  let prev = 0;
  let curr = 1;
  for (let i = 2; i <= n; i++) {
    const temp = curr;
    curr = prev + curr;
    prev = temp;
  }
  return curr;
}

console.log(fibonacciIterative(10)); // 输出:55

以上代码分别使用递归和迭代的方式实现了斐波那契数列。在递归方式中,通过递归调用函数本身来计算斐波那契数列的值。在迭代方式中,通过循环来计算斐波那契数列的值。两种方式都可以得到相同的结果,但迭代方式通常比递归方式更高效。

64、题目:实现链式的加减乘除(myCalculator(1).add(12).minus(3).multi(10).div(5).getValue() === 25)

您可以定义一个名为myCalculator的函数,该函数接受一个初始值作为参数。然后,在函数中返回一个对象,该对象具有add、minus、multi和div方法,以及一个getValue方法。

在每个方法中,您可以将当前值与传入的参数执行相应的操作,并将结果保存在当前值中。最后,在getValue方法中,返回当前值。

以下是示例代码:

function myCalculator(initialValue) {
  let value = initialValue;

  return {
    add: function(num) {
      value += num;
      return this;
    },
    minus: function(num) {
      value -= num;
      return this;
    },
    multi: function(num) {
      value *= num;
      return this;
    },
    div: function(num) {
      value /= num;
      return this;
    },
    getValue: function() {
      return value;
    }
  };
}

const result = myCalculator(1).add(12).minus(3).multi(10).div(5).getValue();
console.log(result); // 输出:25

在上述代码中,我们首先调用myCalculator函数,并传入初始值1。然后,使用链式调用的方式依次调用add、minus、multi和div方法,并传入相应的参数。最后,调用getValue方法获取最终的结果,并将结果打印出来。运行代码后,输出的结果为25,表示链式的加减乘除操作得到了正确的结果。

思路:链式,按.调用方法,联想到类的知识点,因为实例调用类中的方法就是按.调用的,调用完后再返回this,也就相当于计算完后,又把实例返回了,可以继续调用方法

function myCalculator(val) {
  // todo
  class Cal {
    constructor(val) {
      this.value = val
    }
    getValue() {
      return this.value
    }
    // 加法
    add(newVal) {
      this.value = this.value + newVal
      return this
    }
    // 减法
    minus(newVal) {
      this.value = this.value - newVal
      return this
    }
    // 乘法
    multi(newVal) {
      this.value = this.value * newVal
      return this
    }
    // 除法
    div(newVal) {
      this.value = this.value / newVal
      return this
    }
  }
  const obj = new Cal(num)
  return obj
}
console.log(myCalculator(121).add(1).minus(2)); // 120
64、题目:搜索多个文件夹路径(字符串数组,例如[’/usr/bin’, ‘/etc/config’]),查找是否有公共路径,若有则返回公共路径(字符串),否则返回 null

思路:类似于搜索字符串的公共前缀,那么双循环就可以搞定,但是得对数组处理一下,因为路径里包含了 ‘/’ 需要除去,所以返回答案时还要还原回来

function findParentDirectory(paths) {
  let arr = []
  // 把 paths 处理为二维数组赋值给 arr
  paths.forEach(ele => {
    let a = ele.split('/')
    a.shift()
    arr.push(a)
  });
  let res = []
  for (let i = 0; i < arr[0].length; i++) {
    let isTrue = true
    for (let j = 0; j < arr.length - 1; j++) {
      if (arr[j][i] !== arr[j + 1][i]) {
        isTrue = false
        break
      }
    }
    isTrue && res.push(arr[0][i])
  }
  if (res.length) {
    return '/' + res.join('/')
  } else {
    return null
  }
}
console.log(findParentDirectory(['/home/usr/vue', '/home/usr/react']));
// '/home/usr'

解法二:

可以使用字符串的公共前缀来查找是否有公共路径。首先,将第一个文件夹路径作为初始的公共路径。然后,依次遍历剩余的文件夹路径,将当前公共路径与当前文件夹路径进行比较,找出它们的公共前缀。如果公共前缀为空字符串,则表示没有公共路径,直接返回null。如果公共前缀不为空,则将公共前缀更新为新的公共路径。最后,返回最终的公共路径。

以下是示例代码:

function findCommonPath(paths) {
  if (paths.length === 0) {
    return null;
  }

  let commonPath = paths[0];

  for (let i = 1; i < paths.length; i++) {
    let j = 0;

    while (j < commonPath.length && j < paths[i].length && commonPath[j] === paths[i][j]) {
      j++;
    }

    if (j === 0) {
      return null;
    }

    commonPath = commonPath.substring(0, j);
  }

  return commonPath;
}

const paths = ['/usr/bin', '/etc/config'];
const result = findCommonPath(paths);
console.log(result); // 输出:"/"
在上述代码中,我们将文件夹路径数组设置为['/usr/bin', '/etc/config'],然后调用findCommonPath函数来查找是否有公共路径。最后,打印结果即可。运行代码后,输出的结果为"/",表示存在公共路径"/"
65、题目:若a * a + b * b === c * c,则称’a,b,c’为勾股数,找出比 n 小的所有勾股数

思路:三循环暴力求解,暂时没想到更好的方法,先感谢各位观众给出更好的方法了(评论区已有大佬给出一种双指针的解法了)

function gougu(n) {
  let arr = []
  // 最小的勾股数从 3 开始
  for (let a = 3; a <= n; a++) {
    for (let b = a + 1; b <= n; b++) {
      for (let c = b + 1; c <= n; c++) {
        if (a * a + b * b === c * c) {
          arr.push(`${a},${b},${c}`)
        }
      }
    }
  }
  return arr
}
console.log(gougu(10)); // 

可以使用嵌套循环来找出比 n 小的所有勾股数。首先,定义一个空数组来存储符合条件的勾股数。然后,使用两个循环遍历所有可能的 a 和 b 的取值,范围从 1 到 n-1。在每次循环中,计算 a * a + b * b 的值,并判断是否等于 c * c。如果相等,则将 a、b 和 c 加入到数组中。最后,返回数组即可。

以下是示例代码:

def find_pythagorean_numbers(n):
    pythagorean_numbers = []
    for a in range(1, n):
        for b in range(1, n):
            c = (a * a + b * b) ** 0.5
           if c.is_integer() and c < n:
               pythagorean_numbers.append((a, b, int(c)))
    return pythagorean_numbers

n = 10
result = find_pythagorean_numbers(n)
print(result)

在上述代码中,我们将 n 设置为 10,然后调用 find_pythagorean_numbers 函数来找出比 10 小的所有勾股数。最后,打印结果即可。运行代码后,输出的结果为 [(3, 4, 5), (4, 3, 5), (6, 8, 10), (8, 6, 10)],这些都是比 10 小的勾股数。

66、将一串字符串驼峰化,第一个‘-’后的字母无需转小写为大写,其他‘-’后的字母需转小写为大写

例1:font-size转化为fontSize
例2:-weront-size转化为werontSize

function ss(str) {
  if (str[0] == "-") str = str.slice(1);// 加上这个可以保证第二个案例通过,否则会将空字符当作为第一个单词
  let res = str.split("-");
  var fist = res[0];
  if (fist[0] == "-") fist = fist.slice(1);
  let result = fist;

  for (let i = 1; i < res.length; i++) {
    let temp = res[i][0].toUpperCase();
    let temp2 = res[i].replace(res[i][0], temp);
    result += temp2;
  }
  return result;
}

console.log(ss("font-size"));
console.log(ss("-weront-size"));

解法二

您可以使用正则表达式和replace方法来将一串字符串驼峰化。首先,使用replace方法将所有的连字符(-)替换为空格(或其他字符)。然后,使用replace方法和正则表达式来将每个单词的首字母转换为大写,并将其余字母转换为小写。最后,使用replace方法将所有的空格(或其他字符)替换为空字符串。

以下是示例代码:

function toCamelCase(str) {
  return str.replace(/-/g, ' ')
            .replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) {
              return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
            })
            .replace(/\s+/g, '');
}

const result = toCamelCase('hello-world-example');
console.log(result); // 输出:helloWorldExample

在上述代码中,我们将字符串设置为’hello-world-example’,然后调用toCamelCase函数来将其驼峰化。最后,打印结果即可。运行代码后,输出的结果为’helloWorldExample’,表示字符串已成功驼峰化。

67、一串字符串,当字符[a-zA-Z]重复,输出true,否则输出false。

解法1

function ss(str) {
  str = str.match(/[a-zA-Z]/g); //[ 'h', 't', 'h' ]
  console.log(str);
  for (let i = 0; i < str.length - 1; i++) {
    if (str[i] == str[i + 1]) return true;
  }
  return false;
}
console.log(ss("hth000"));

解法2:

function ss(str) {
  return /([a-zA-Z])\1/g.test(str);
}

console.log(ss("hth000"));

解法3:

可以使用正则表达式和Set数据结构来判断字符串中的字符是否重复。首先,使用正则表达式将字符串中的非字母字符替换为空字符串。然后,将字符串中的每个字符添加到Set中,如果Set的大小与字符串的长度不相等,则表示字符串中有重复字符,返回true;否则,返回false。

以下是示例代码:

function isDuplicate(str) {
  const cleanStr = str.replace(/[^a-zA-Z]/g, '');
  const charSet = new Set(cleanStr);

  return charSet.size !== cleanStr.length;
}

console.log(isDuplicate('abc')); // 输出:false
console.log(isDuplicate('abca')); // 输出:true

在上述代码中,我们分别测试了字符串’abc’和’abca’。第一个字符串中没有重复字符,所以返回false;第二个字符串中有重复字符’a’,所以返回true。运行代码后,可以得到相应的结果

js 时间复杂度 和空间复杂度

在JavaScript中,时间复杂度和空间复杂度是衡量算法性能的两个重要指标。

时间复杂度(Time Complexity)表示算法执行所需要的时间与问题规模之间的关系。常见的时间复杂度有:

  • 常数时间复杂度 O(1):无论问题规模的大小,算法的执行时间都是固定的。
  • 线性时间复杂度 O(n):算法的执行时间与问题规模呈线性关系。
  • 对数时间复杂度 O(log n):算法的执行时间与问题规模的对数值呈线性关系。
  • 平方时间复杂度 O(n^2):算法的执行时间与问题规模的平方呈线性关系。
  • 指数时间复杂度 O(2^n):算法的执行时间与问题规模的指数值呈线性关系。

空间复杂度(Space Complexity)表示算法执行所需要的额外空间与问题规模之间的关系。常见的空间复杂度有:

  • 常数空间复杂度 O(1):算法执行所需要的额外空间是固定的,与问题规模无关。
  • 线性空间复杂度 O(n):算法执行所需要的额外空间与问题规模呈线性关系。
  • 平方空间复杂度 O(n^2):算法执行所需要的额外空间与问题规模的平方呈线性关系。

要分析算法的时间复杂度和空间复杂度,可以根据算法中的循环、递归、函数调用等操作来确定执行次数,并根据问题规模的变化情况来确定复杂度的增长趋势。

需要注意的是,时间复杂度和空间复杂度是描述算法性能的理论指标,只是对算法的估计,并不能精确地表示

v-for 和v-if为什么不能一块儿用

v-for和v-if不能一起使用的原因是由于它们的编译顺序不同。v-for是在v-if之前被解析和渲染的。

在Vue的编译过程中,v-for的优先级比v-if要高。这意味着v-for会先被解析和渲染,然后才会去判断v-if的条件。如果v-for中的列表为空或条件不满足,那么v-if将无法生效,因为v-for已经在此时已经渲染了元素。

为了避免这种问题,可以将v-if放置在包裹v-for的父元素上,或者使用计算属性来处理过滤后的列表。

以下是示例代码:

<template>
  <div>
    <div v-if="shouldShowList">
      <div v-for="item in filteredList" :key="item.id">
        <!-- 渲染列表项 -->
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: [...], // 列表数据
      shouldShowList: true // 是否显示列表
    };
  },
  computed: {
    filteredList() {
      // 根据条件过滤列表数据
      return this.list.filter(item => item.condition);
    }
  }
};
</script>

在上述代码中,我们将v-if放置在包裹v-for的父元素上,并使用计算属性filteredList来处理过滤后的列表数据。通过这样的方式,可以确保v-if在v-for之前进行判断,从而避免了两者冲突的问题。

什么时候使用闭包

闭包可以在以下情况下使用:

  1. 保护变量:闭包可以创建一个私有作用域,将变量封装在内部函数中,从而保护变量不受外部访问和修改。这在一些需要隐藏数据的场景下非常有用。
  2. 记忆函数:闭包可以记住函数的上下文和状态,使得函数可以记住之前的操作和结果。这在一些需要缓存计算结果的场景下非常有用,可以提高函数的性能。
  3. 延长作用域链:闭包可以在函数内部访问外部函数的变量,从而延长作用域链。这在一些需要访问外部函数的变量的场景下非常有用。
  4. 模块化开发:通过使用闭包,可以将代码封装在模块中,避免全局变量的污染,实现模块化开发。

需要注意的是,使用闭包也会带来一些问题,例如内存泄漏和性能问题。因此,在使用闭包时需要注意内存管理和性能优化
// 输出:55


##### 迭代方式:

function fibonacciIterative(n) {
if (n <= 1) {
return n;
}
let prev = 0;
let curr = 1;
for (let i = 2; i <= n; i++) {
const temp = curr;
curr = prev + curr;
prev = temp;
}
return curr;
}

console.log(fibonacciIterative(10)); // 输出:55


以上代码分别使用递归和迭代的方式实现了斐波那契数列。在递归方式中,通过递归调用函数本身来计算斐波那契数列的值。在迭代方式中,通过循环来计算斐波那契数列的值。两种方式都可以得到相同的结果,但迭代方式通常比递归方式更高效。

#### 64、题目:实现链式的加减乘除(myCalculator(1).add(12).minus(3).multi(10).div(5).getValue() === 25)

您可以定义一个名为myCalculator的函数,该函数接受一个初始值作为参数。然后,在函数中返回一个对象,该对象具有add、minus、multi和div方法,以及一个getValue方法。

在每个方法中,您可以将当前值与传入的参数执行相应的操作,并将结果保存在当前值中。最后,在getValue方法中,返回当前值。

以下是示例代码:

function myCalculator(initialValue) {
let value = initialValue;

return {
add: function(num) {
value += num;
return this;
},
minus: function(num) {
value -= num;
return this;
},
multi: function(num) {
value *= num;
return this;
},
div: function(num) {
value /= num;
return this;
},
getValue: function() {
return value;
}
};
}

const result = myCalculator(1).add(12).minus(3).multi(10).div(5).getValue();
console.log(result); // 输出:25


在上述代码中,我们首先调用myCalculator函数,并传入初始值1。然后,使用链式调用的方式依次调用add、minus、multi和div方法,并传入相应的参数。最后,调用getValue方法获取最终的结果,并将结果打印出来。运行代码后,输出的结果为25,表示链式的加减乘除操作得到了正确的结果。



思路:链式,按.调用方法,联想到类的知识点,因为实例调用类中的方法就是按.调用的,调用完后再返回this,也就相当于计算完后,又把实例返回了,可以继续调用方法
```js
function myCalculator(val) {
  // todo
  class Cal {
    constructor(val) {
      this.value = val
    }
    getValue() {
      return this.value
    }
    // 加法
    add(newVal) {
      this.value = this.value + newVal
      return this
    }
    // 减法
    minus(newVal) {
      this.value = this.value - newVal
      return this
    }
    // 乘法
    multi(newVal) {
      this.value = this.value * newVal
      return this
    }
    // 除法
    div(newVal) {
      this.value = this.value / newVal
      return this
    }
  }
  const obj = new Cal(num)
  return obj
}
console.log(myCalculator(121).add(1).minus(2)); // 120
64、题目:搜索多个文件夹路径(字符串数组,例如[’/usr/bin’, ‘/etc/config’]),查找是否有公共路径,若有则返回公共路径(字符串),否则返回 null

思路:类似于搜索字符串的公共前缀,那么双循环就可以搞定,但是得对数组处理一下,因为路径里包含了 ‘/’ 需要除去,所以返回答案时还要还原回来

function findParentDirectory(paths) {
  let arr = []
  // 把 paths 处理为二维数组赋值给 arr
  paths.forEach(ele => {
    let a = ele.split('/')
    a.shift()
    arr.push(a)
  });
  let res = []
  for (let i = 0; i < arr[0].length; i++) {
    let isTrue = true
    for (let j = 0; j < arr.length - 1; j++) {
      if (arr[j][i] !== arr[j + 1][i]) {
        isTrue = false
        break
      }
    }
    isTrue && res.push(arr[0][i])
  }
  if (res.length) {
    return '/' + res.join('/')
  } else {
    return null
  }
}
console.log(findParentDirectory(['/home/usr/vue', '/home/usr/react']));
// '/home/usr'

解法二:

可以使用字符串的公共前缀来查找是否有公共路径。首先,将第一个文件夹路径作为初始的公共路径。然后,依次遍历剩余的文件夹路径,将当前公共路径与当前文件夹路径进行比较,找出它们的公共前缀。如果公共前缀为空字符串,则表示没有公共路径,直接返回null。如果公共前缀不为空,则将公共前缀更新为新的公共路径。最后,返回最终的公共路径。

以下是示例代码:

function findCommonPath(paths) {
  if (paths.length === 0) {
    return null;
  }

  let commonPath = paths[0];

  for (let i = 1; i < paths.length; i++) {
    let j = 0;

    while (j < commonPath.length && j < paths[i].length && commonPath[j] === paths[i][j]) {
      j++;
    }

    if (j === 0) {
      return null;
    }

    commonPath = commonPath.substring(0, j);
  }

  return commonPath;
}

const paths = ['/usr/bin', '/etc/config'];
const result = findCommonPath(paths);
console.log(result); // 输出:"/"
在上述代码中,我们将文件夹路径数组设置为['/usr/bin', '/etc/config'],然后调用findCommonPath函数来查找是否有公共路径。最后,打印结果即可。运行代码后,输出的结果为"/",表示存在公共路径"/"
65、题目:若a * a + b * b === c * c,则称’a,b,c’为勾股数,找出比 n 小的所有勾股数

思路:三循环暴力求解,暂时没想到更好的方法,先感谢各位观众给出更好的方法了(评论区已有大佬给出一种双指针的解法了)

function gougu(n) {
  let arr = []
  // 最小的勾股数从 3 开始
  for (let a = 3; a <= n; a++) {
    for (let b = a + 1; b <= n; b++) {
      for (let c = b + 1; c <= n; c++) {
        if (a * a + b * b === c * c) {
          arr.push(`${a},${b},${c}`)
        }
      }
    }
  }
  return arr
}
console.log(gougu(10)); // 

可以使用嵌套循环来找出比 n 小的所有勾股数。首先,定义一个空数组来存储符合条件的勾股数。然后,使用两个循环遍历所有可能的 a 和 b 的取值,范围从 1 到 n-1。在每次循环中,计算 a * a + b * b 的值,并判断是否等于 c * c。如果相等,则将 a、b 和 c 加入到数组中。最后,返回数组即可。

以下是示例代码:

def find_pythagorean_numbers(n):
    pythagorean_numbers = []
    for a in range(1, n):
        for b in range(1, n):
            c = (a * a + b * b) ** 0.5
           if c.is_integer() and c < n:
               pythagorean_numbers.append((a, b, int(c)))
    return pythagorean_numbers

n = 10
result = find_pythagorean_numbers(n)
print(result)

在上述代码中,我们将 n 设置为 10,然后调用 find_pythagorean_numbers 函数来找出比 10 小的所有勾股数。最后,打印结果即可。运行代码后,输出的结果为 [(3, 4, 5), (4, 3, 5), (6, 8, 10), (8, 6, 10)],这些都是比 10 小的勾股数。

66、将一串字符串驼峰化,第一个‘-’后的字母无需转小写为大写,其他‘-’后的字母需转小写为大写

例1:font-size转化为fontSize
例2:-weront-size转化为werontSize

function ss(str) {
  if (str[0] == "-") str = str.slice(1);// 加上这个可以保证第二个案例通过,否则会将空字符当作为第一个单词
  let res = str.split("-");
  var fist = res[0];
  if (fist[0] == "-") fist = fist.slice(1);
  let result = fist;

  for (let i = 1; i < res.length; i++) {
    let temp = res[i][0].toUpperCase();
    let temp2 = res[i].replace(res[i][0], temp);
    result += temp2;
  }
  return result;
}

console.log(ss("font-size"));
console.log(ss("-weront-size"));

解法二

您可以使用正则表达式和replace方法来将一串字符串驼峰化。首先,使用replace方法将所有的连字符(-)替换为空格(或其他字符)。然后,使用replace方法和正则表达式来将每个单词的首字母转换为大写,并将其余字母转换为小写。最后,使用replace方法将所有的空格(或其他字符)替换为空字符串。

以下是示例代码:

function toCamelCase(str) {
  return str.replace(/-/g, ' ')
            .replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) {
              return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
            })
            .replace(/\s+/g, '');
}

const result = toCamelCase('hello-world-example');
console.log(result); // 输出:helloWorldExample

在上述代码中,我们将字符串设置为’hello-world-example’,然后调用toCamelCase函数来将其驼峰化。最后,打印结果即可。运行代码后,输出的结果为’helloWorldExample’,表示字符串已成功驼峰化。

67、一串字符串,当字符[a-zA-Z]重复,输出true,否则输出false。

解法1

function ss(str) {
  str = str.match(/[a-zA-Z]/g); //[ 'h', 't', 'h' ]
  console.log(str);
  for (let i = 0; i < str.length - 1; i++) {
    if (str[i] == str[i + 1]) return true;
  }
  return false;
}
console.log(ss("hth000"));

解法2:

function ss(str) {
  return /([a-zA-Z])\1/g.test(str);
}

console.log(ss("hth000"));

解法3:

可以使用正则表达式和Set数据结构来判断字符串中的字符是否重复。首先,使用正则表达式将字符串中的非字母字符替换为空字符串。然后,将字符串中的每个字符添加到Set中,如果Set的大小与字符串的长度不相等,则表示字符串中有重复字符,返回true;否则,返回false。

以下是示例代码:

function isDuplicate(str) {
  const cleanStr = str.replace(/[^a-zA-Z]/g, '');
  const charSet = new Set(cleanStr);

  return charSet.size !== cleanStr.length;
}

console.log(isDuplicate('abc')); // 输出:false
console.log(isDuplicate('abca')); // 输出:true

在上述代码中,我们分别测试了字符串’abc’和’abca’。第一个字符串中没有重复字符,所以返回false;第二个字符串中有重复字符’a’,所以返回true。运行代码后,可以得到相应的结果

js 时间复杂度 和空间复杂度

在JavaScript中,时间复杂度和空间复杂度是衡量算法性能的两个重要指标。

时间复杂度(Time Complexity)表示算法执行所需要的时间与问题规模之间的关系。常见的时间复杂度有:

  • 常数时间复杂度 O(1):无论问题规模的大小,算法的执行时间都是固定的。
  • 线性时间复杂度 O(n):算法的执行时间与问题规模呈线性关系。
  • 对数时间复杂度 O(log n):算法的执行时间与问题规模的对数值呈线性关系。
  • 平方时间复杂度 O(n^2):算法的执行时间与问题规模的平方呈线性关系。
  • 指数时间复杂度 O(2^n):算法的执行时间与问题规模的指数值呈线性关系。

空间复杂度(Space Complexity)表示算法执行所需要的额外空间与问题规模之间的关系。常见的空间复杂度有:

  • 常数空间复杂度 O(1):算法执行所需要的额外空间是固定的,与问题规模无关。
  • 线性空间复杂度 O(n):算法执行所需要的额外空间与问题规模呈线性关系。
  • 平方空间复杂度 O(n^2):算法执行所需要的额外空间与问题规模的平方呈线性关系。

要分析算法的时间复杂度和空间复杂度,可以根据算法中的循环、递归、函数调用等操作来确定执行次数,并根据问题规模的变化情况来确定复杂度的增长趋势。

需要注意的是,时间复杂度和空间复杂度是描述算法性能的理论指标,只是对算法的估计,并不能精确地表示

v-for 和v-if为什么不能一块儿用

v-for和v-if不能一起使用的原因是由于它们的编译顺序不同。v-for是在v-if之前被解析和渲染的。

在Vue的编译过程中,v-for的优先级比v-if要高。这意味着v-for会先被解析和渲染,然后才会去判断v-if的条件。如果v-for中的列表为空或条件不满足,那么v-if将无法生效,因为v-for已经在此时已经渲染了元素。

为了避免这种问题,可以将v-if放置在包裹v-for的父元素上,或者使用计算属性来处理过滤后的列表。

以下是示例代码:

<template>
  <div>
    <div v-if="shouldShowList">
      <div v-for="item in filteredList" :key="item.id">
        <!-- 渲染列表项 -->
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: [...], // 列表数据
      shouldShowList: true // 是否显示列表
    };
  },
  computed: {
    filteredList() {
      // 根据条件过滤列表数据
      return this.list.filter(item => item.condition);
    }
  }
};
</script>

在上述代码中,我们将v-if放置在包裹v-for的父元素上,并使用计算属性filteredList来处理过滤后的列表数据。通过这样的方式,可以确保v-if在v-for之前进行判断,从而避免了两者冲突的问题。

什么时候使用闭包

闭包可以在以下情况下使用:

  1. 保护变量:闭包可以创建一个私有作用域,将变量封装在内部函数中,从而保护变量不受外部访问和修改。这在一些需要隐藏数据的场景下非常有用。
  2. 记忆函数:闭包可以记住函数的上下文和状态,使得函数可以记住之前的操作和结果。这在一些需要缓存计算结果的场景下非常有用,可以提高函数的性能。
  3. 延长作用域链:闭包可以在函数内部访问外部函数的变量,从而延长作用域链。这在一些需要访问外部函数的变量的场景下非常有用。
  4. 模块化开发:通过使用闭包,可以将代码封装在模块中,避免全局变量的污染,实现模块化开发。

需要注意的是,使用闭包也会带来一些问题,例如内存泄漏和性能问题。因此,在使用闭包时需要注意内存管理和性能优化

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值