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 是一个数组
}

这些方法都可以判断给定的对象是否为数组。对于这三种方法,推荐使用 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或深拷贝工具函数)等。

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

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树的加载机制涉及的一系列流程和步骤。

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,可以采用以下步骤:

  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 中配置 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 是 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 和深度监控可以实现数组(对象)的监听,但是开销比较大。建议尽量使用数组提供的方法来代替直接对数组进行操作,以获得更好的性能和用户体验。

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、类与继承、模块化等。

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. 垂直居中几种方式?*

单行文本: line-height = height

图片: vertical-align: middle;

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

flex: display:flex;margin:auto

*4. 简明说一下 CSS link 与 @import 的区别和用法?*

link 是 XHTML 标签,除了加载CSS外,还可以定义 RSS 等其他事务;@import 属于 CSS 范畴,只能加载 CSS。

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

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

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

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

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

*6. 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

.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、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 可以通过对象语法和数组语法进行动态绑定:

*对象语法:*

data: {

​ isActive: true,

hasError: false

}

*数组语法:*

data: {

activeClass: ‘active’,

errorClass: ‘text-danger’

}

*Style 也可以通过对象语法和数组语法进行动态绑定:*

*对象语法:*

data: {

activeColor: ‘red’,

fontSize: 30

}

*数组语法:*

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” >

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 表单元素为例:*

*相当于*

*如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:*

父组件:

子组件:

{{value}}

props:{

value: String

},

methods: {

test1(){

this.$emit(‘input’, ‘小红’)

},

},

*15. Vue 组件间通信有哪几种方式?*

Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 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=" attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(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修饰器的)von事件监听器。它可以通过von="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,路由会自动强制进入这个模式.

*18. 能说下 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 路由模式的实现主要基于存在下面几个特性:

pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;

我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);

history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

*19. Vue 框架怎么实现对象和数组的监听?*

Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据。

即:

输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。

Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。

其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View。

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。

实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

*20. Vue 是如何实现数据双向绑定的?*

如果被问到 Vue 怎么实现数据双向绑定,大家肯定都会回答 通过 Object.defineProperty() 对数据进行劫持,但是 Object.defineProperty() 只能对属性进行数据劫持,不能对整个对象进行劫持。

同理无法对数组进行劫持,但是我们在使用 Vue 框架中都知道,Vue 能检测到对象和数组(部分方法的操作)的变化,那它是怎么实现的呢?我们查看相关代码如下:

/**

* Observe a list of Array items.

*/

observeArray (items: Array) {

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 | 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 无法进行针对性的极致优化。

  1. 虚拟 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 还提供了以下特性:

用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。

默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。

更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。

不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。

更好的调试功能:我们可以使用新的 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() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

1、obj:需要定义属性的对象

2、prop:需要定义的属性

3、{}:要定义或修改的属性描述符。

​ value: “18”, // 设置默认值 (与 get() 互斥)

​ enumerable: true, //这一句控制属性可以枚举 enumerable 改为true 就可以参与遍历了 默认值false

​ writable: true, // 该属性是否可写 默认值false (与 set() 互斥)

​ configurable: true, // 该属性是否可被删除 默认值false

​ get // 当有人读取 prop 的时候 get函数就会调用,并且返回就是 sss 的值

​ set // 当有人修改 prop 的时候 set函数就会调用, 有个参数这个参数就是修 改后的值

*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 模板编译原理*

模板字符串 转换成 element AST(解析器)

Vue-loader 切割解析 .vue 文件(parseHTML按标签以出栈入栈形式切割(自闭合不入栈直接处理),出栈时维护父子关系)生成 AST(抽象语法树)

使用大量正则匹配开始结束标签,while指针定位解析位置,

对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)

在dom更新时不需 diff 静态节点。

使用 element AST 生成 render 函数代码字符串(代码生成器)

Vue-template-compiler再解析成render(可执行函数字符串-with(this)=>{return _c(‘div’)}),new Function 生成函数,传递给组件的 render

在组件渲染的时候直接调用 render 即可

*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的打包过程/打包原理/构建流程?*

初始化:启动构建,读取与合并配置参数,加载plugin,实例化Compiler

编译:从Entry出发,针对每个Module串行调用对应的Loader去翻译文件中的内容,再找到该Module依赖的Module,递归的进行编译处理

输出:将编译后的Module组合成Chunk,将Chunk转换成文件,输出到文件系统中

细节:

Webpack CLI 通过 yargs模块解析 CLI 参数,并转化为配置对象option(单入口:Object,多入口:Array),调用 webpack(option) 创建 compiler 对象。

如果有 option.plugin,则遍历调用plugin.apply()来注册 plugin,

判断是否开启了 watch,如果开启则调用 compiler.watch,否则调用 compiler.run,开始构建。

创建 Compilation 对象来收集全部资源和信息,然后触发 make 钩子。

make阶段从入口开始递归所有依赖,

每次遍历时调用对应Loader翻译文件中内容,然后生成AST,遍历AST找到下个依赖继续递归,

根据入口和模块之间关系组装chunk,输出到dist中的一个文件内。

在以上过程中,webpack会在特定的时间点(使用tapable模块)广播特定的事件,插件监听事件并执行相应的逻辑,并且插件可以调用webpack提供的api改变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 // 是否移除注释

}

})

]

}

  1. 图片压缩

配置image-webpack-loader

  1. 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文件

]

  1. 缩小打包域

排除webpack不需要解析的模块,即在使用loader的时候,在尽量少的模块中去使用。可以借助 include和exclude这两个参数,规定loader只在那些模块应用和在哪些模块不应用。

  1. 减少 ES6 转为 ES5 的冗余代码

使用bable-plugin-transform-runtime插件

  1. 提取公共代码

通过配置CommonsChunkPlugin插件,将多个页面的公共代码抽离成单独的文件

  1. 其他

组件懒加载、路由懒加载、开启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/其它脚本的依赖关系很强:对

脚本与DOM/其它脚本的依赖关系不强:对

回流重绘策略

缓存DOM计算属性

使用类合并样式,避免逐条改变样式

使用display控制DOM显隐,将DOM离线化

异步更新策略

在异步任务中修改DOM时把其包装成微任务

  1. 性能优化六大指标

六大指标基本囊括大部分性能优化细节,可作为九大策略的补充。笔者根据每条性能优化建议的特征将指标划分为以下六方面。

「加载优化」:资源在加载时可做的性能优化

「执行优化」:资源在执行时可做的性能优化

「渲染优化」:资源在渲染时可做的性能优化

「样式优化」:样式在编码时可做的性能优化

「脚本优化」:脚本在编码时可做的性能优化

「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步:

输入网址;

发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;

与web服务器建立TCP连接;

浏览器向web服务器发送http请求;

web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);

浏览器下载web服务器返回的数据及解析html源文件;

生成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. 移动端性能优化?*

尽量使用css3动画,开启硬件加速

适当使用touch时间代替click时间

避免使用css3渐变阴影效果

可以用transform: translateZ(0) 来开启硬件加速

不滥用float。float在渲染时计算量比较大,尽量减少使用

不滥用web字体。web字体需要下载,解析,重绘当前页面

合理使用requestAnimationFrame动画代替setTimeout

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. 你的优点是什么?缺点是什么?*