有多种方法可以实现子元素在父元素中垂直居中的方式,以下是其中几种常用的方法:
父元素设置为 flex 容器,并使用 align-items 属性将子元素垂直居中。
.parent {
display: flex;
align-items: center; /* 子元素垂直居中 */
}
将父元素设置为 table,子元素设置为 table-cell,并使用 vertical-align 属性将子元素垂直居中。
.parent {
display: table;
}
.child {
display: table-cell;
vertical-align: middle; /* 子元素垂直居中 */
}
使用绝对定位将子元素相对于父元素居中,结合 transform 属性进行位移变换。
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 子元素垂直居中 */
}
父元素设置为 grid 容器,并使用 place-items 属性将子元素垂直居中。
.parent {
display: grid;
place-items: center; /* 子元素垂直居中 */
}
以上是常用的几种方法,选择适合你项目需求和浏览器兼容性的方式来实现子元素在父元素中垂直居中。
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; /* 子元素垂直居中 */
}
KeepAlive 组件是 Vue.js 中的一个内置组件,用于缓存和复用组件实例。它可以将动态组件进行缓存,在组件切换时保留其状态,提高应用性能。
作用:
常用的钩子函数:
控制组件级存储:
可以通过
的特殊属性 include
和 exclude
控制具体哪些组件需要被缓存,以及哪些组件不需要被缓存。
include
:指定要缓存的组件名称数组,只有包含在该数组中的组件才会被缓存。exclude
:指定不需要缓存的组件名称数组,这些组件将不会被缓存。示例如下:
<keep-alive :include="['ComponentA', 'ComponentB']" :exclude="['ComponentC']">
<router-view>router-view>
keep-alive>
以上代码中,只有名为 “ComponentA” 和 “ComponentB” 的组件会被缓存,而名为 “ComponentC” 的组件不会被缓存。其他组件则按需创建和销毁。
使用 include
和 exclude
可以灵活地控制组件级别的缓存策略,根据具体需求来决定哪些组件需要保持状态并进行复用。
判断对象是否为数组的三个常用方法如下:
Array.isArray()
方法:if (Array.isArray(obj)) {
// obj 是一个数组
}
instanceof
运算符:if (obj instanceof Array) {
// obj 是一个数组
}
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的环境中也能正常工作。
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` 时,要确保参数和参数列表的正确配对,并根据具体需求选择合适的方法来改变函数的执行上下文。
在 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
方法将其保留两位小数并返回;如果不是数字,则直接返回原值。
在使用过滤器时,只需调用过滤器函数并传入要格式化的数据即可。在上面的示例中,我们分别对 number1
、number2
和 string
进行过滤,并输出结果。
请注意,在实际开发中,可能会使用框架或库来提供更便捷和灵活的过滤器机制,比如在 Vue.js 中使用过滤器。以上示例仅演示了一个简单的自定义过滤器的实现方式。
使用 Vue 的过滤器可以更方便地对数据进行格式化和处理。下面是一个使用 Vue 过滤器的示例,实现保留两位小数的数字过滤器:
html复制代码
{{ number1 | twoDecimal }}
{{ number2 | twoDecimal }}
{{ string | twoDecimal }}
在上述代码中,我们创建了一个 Vue 实例,并在该实例中定义了一个过滤器 twoDecimal
。在模板中,通过使用管道操作符 |
将要过滤的数据与过滤器名称相连,在输出时会自动应用过滤器。
在这个示例中,number1
、number2
和 string
是 Vue 实例的数据属性,分别代表要过滤的数字和字符串。通过 {{ number | twoDecimal }}
的形式,将数值绑定到对应的模板插值处,同时应用名为 twoDecimal
的过滤器,从而实现保留两位小数的效果。
运行以上代码,你将看到页面上输出的数值已按照过滤器的设定进行了格式化。
在 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/[email protected]">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
的全局自定义指令。该指令包含了几个生命周期钩子函数,例如 bind
、inserted
、update
和 unbind
,你可以根据需要实现相应的逻辑。
在示例代码中,bind
钩子函数用来在指令绑定时设置元素的背景颜色为黄色,并添加一个焦点事件监听器,当聚焦到输入框时,将文字颜色设置为红色。
在 Vue 实例的模板中,我们通过 v-my-directive
指令将这个自定义指令应用到了一个 元素上。当 Vue 渲染该模板时,
my-directive
指令就会被触发,执行对应的逻辑。
请注意,以上示例代码是注册一个全局自定义指令的基本用法,你可以根据实际需求进行扩展和修改。
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}
可以实现对象的浅拷贝,并且复制后的对象与原始对象的属性值不共享。
浅拷贝和深拷贝是两种常用的对象或数组复制方法,它们在复制过程中处理引用类型数据的方式不同。
[...arr]
或{...obj}
、Object.assign()
等。深拷贝是指完全复制一个对象或数组及其所有嵌套的对象或数组,得到一个全新且独立的副本。这样两个对象/数组互不干扰,修改其中一个不会影响另一个。
深拷贝复制了所有层级的数据,包括基本类型和引用类型数据,递归地遍历复制每个对象或数组的属性或元素。
常见的实现深拷贝的方式包括循环递归、JSON序列化与反序列化、使用第三方库(如Lodash或深拷贝工具函数)等。
实现深拷贝的方式有多种。以下是几种常见的方法:
手动递归遍历:
这是一种基本的深拷贝方法,通过递归遍历对象或数组的每个属性,并创建完全独立的副本。
JSON 序列化和反序列化:
这种方法利用 JavaScript 中的 JSON 对象提供的序列化(JSON.stringify)和反序列化(JSON.parse)功能,将对象转换为字符串再解析为新对象,实现深拷贝。但需要注意该方法无法复制函数、正则表达式等特殊对象。
使用第三方库:
需要注意的是,虽然这些方法都可以实现深拷贝,但在处理特定类型的数据或大型对象时可能会存在性能问题。在选择使用哪种方法时,请考虑到数据的复杂性、大小和需要拷贝的深度,并根据实际情况选择最适合的深拷贝方式。
在选择使用浅拷贝还是深拷贝时,需要根据具体需求和数据结构来决定。如果对象/数组的属性值不包含引用类型数据、或者对共享值的修改无影响,那么可以使用浅拷贝;如果需要完全独立且不受影响地操作复制后的对象/数组,那么应该使用深拷贝。
WebSocket 是一种基于 HTTP 协议进行双向通信的协议,通信的两端可以同时发送和接收数据。在 WebSocket 连接中,心跳包用于维持连接的存活状态,可以通过定时发送特定格式的数据包来检测连接是否处于正常工作状态。
具体地,可以通过在服务器端设置一个定时器,在一定时间间隔内发送心跳包。客户端也可以实现类似的逻辑,在一定时间内没有收到服务端的消息,则认为连接出现了问题需要重新连接或重连。WebSocket 还支持 PING 和 PONG 控制帧,可以使用这些帧来检测连接是否存活。当客户端发送 PING 帧时,如果服务端成功接收到,则会回复一个 PONG 帧,客户端可以通过检查是否收到 PONG 帧来判断连接状态。在大部分情况下,开启心跳机制是很重要的,因为它可以避免因长时间没有通信而导致的连接中断,保持 WebSocket 的稳定性和可靠性。
前端在监听 WebSocket 心跳时,可以通过在客户端实现定时器来检测连接是否存活。具体地,可以在连接建立成功后,设置一个定时器,定期发送心跳包到服务器,并在服务器端回复的限定时间内未收到相应的数据包,则认为连接异常,需要重新连接或重连。
同时,WebSocket 还提供了 onclose 事件和 onerror 事件,用于监听连接的关闭和错误。当 WebSocket 断开连接或发生错误时,这两个事件都会触发,可以在事件回调中实现自定义逻辑,比如进行重新连接或提示用户等。需要注意的是,在监听 WebSocket 事件时,也要考虑到接口文档中相关的限制条件,避免违反相关法律法规
在前端应用中,可以通过以下步骤将 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 化。但无论使用何种方法,都应该遵循安全性最佳实践并谨慎处理您的用户数据。
前端同步代码是会阻塞 UI 线程的,如果在同步 JavaScript 代码中调用异步函数,由于异步函数通常需要等待返回结果,那么可能会使页面长时间无响应。因此,在同步 JavaScript 代码中调用异步函数会导致页面卡死甚至崩溃,不可取。
同时,在异步 JavaScript 代码中调用异步函数也需要小心。虽然异步函数通常会使用 Promise 或回调方式来处理异步操作,并且在执行时不会阻塞 UI 线程,但是在多层嵌套异步调用时,可能会出现回调地狱问题,通过 async / await 方式可以有效解决这个问题。
为了避免上述问题,建议在编写JavaScript 代码时,谨慎考虑同步和异步调用的方式,尽量避免长时间阻塞UI线程,并通过适当的方式来处理异步回调嵌套和错误捕获等相关问题。
视频在安卓可以正常播放而在iOS上无法正常播放,这可能是由于使用了一种不受 iOS 设备支持的视频编解码格式所致。
iOS 上支持播放的视频编解码格式包括 H.264、HEVC(H.265)、MPEG-4 Part 2 和 Motion JPEG 等。而在 Android 上,通常支持更多的视频编解码格式,包括 VP9、AV1 等开源编解码格式。因此,如果使用了 iOS 不支持的视频编解码格式,将导致视频无法在 iOS 设备上正常播放。
为了确保视频能够在所有设备上正常播放,建议尽可能使用受广泛支持的视频编解码格式。如果需要使用某些特定编解码格式,则需要进行额外处理,例如,在视频上传时,将其转换为不同的格式以适应不同的设备。
同时,也可以考虑使用跨平台的视频播放库,例如 Video.js 或 Jwplayer 等,它们通常会自动检测设备并使用相应的编解码格式进行播放,从而最大限度地提高视频的兼容性。
在前端进行后台接口封装可以提高代码的可读性、稳定性和重用性,同时也能更好地维护后台接口,降低与后端的耦合度。以下是一些推荐的封装方式:
1。封装 Ajax 请求函数:将常见的 Ajax 请求封装成函数,在函数内部处理请求、响应错误和数据转换等逻辑,以减少冗余代码和复杂度。
2。封装 API 接口对象:将后端 API 接口封装到一个对象中,每个接口用一个方法表示,并统一处理请求和响应。这种方式方便集中管理所有 API 接口,易于调用和测试。
3.封装数据模型类:将后端的接口数据结构封装为一个前端数据模型类,提供相应的方法进行数据操作和业务处理。这种方式可以有效抽象出基础业务逻辑和处理过程,并实现数据缓存、数据加工等功能。
4.中间件封装:使用中间件框架如 axios-interceptors 等,对 HTTP 请求和响应进行拦截和处理,实现许多共同的需求,例如鉴权、数据缓存和统一错误处理等。
5.Hook 封装:使用 React 的 Hook 监听组件生命周期或者自定义 Hook,对接口进行处理,使 Hooks 像普通函数一样调用接口,并且 Hooks 也可以处理接口并返回值。
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 函数接收前面抛出的错误,并进行处理。
以上方式都能有效封装前端与后端之间的接口,提升开发效率与维护便利性,根据实际项目需求进行选择即可。
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 的菜单栏中选择 「发行」-「云端打包」,登录后按照页面的提示操作即可。
在微信小程序中,每个小程序必须拥有自己的 AppID 和 AppSecret,这两个参数被称为小程序的凭证,用于进行认证和接口调用。
*AppID:是一个字符串,是小程序的唯一标识符,与开发者帐号相关联。
*AppSecret:是一个字符串,是小程序的加密密钥,与App ID相关联。App Secret用来进行小程序后端的身份验证,如果泄露此密码,可能会导致恶意用户获取您小程序的数据或者搞乱各种API等操作。
AppID 是小程序的唯一标识符,可通过微信公众平台申请获得。而 AppSecret 则是用于对接口进行签名的密钥,需要开发者在开发者中心中手动设置。
在使用微信小程序的开放接口时,需要使用小程序的凭证进行接口调用,同时还需要针对不同的接口填写相应的参数。具体接口调用方法可以参考微信小程序开发文档,文档中提供了详细的接口说明和调用示例。
要在公众号中实现微信支付,需要进行以下步骤:
1.在微信商户平台注册账号并进行实名认证,获取商户号和密钥等信息。
2.使用商户号和密钥申请微信支付接口的api密钥和证书。
3.在公众号开发者后台中,设置支付授权目录和JSAPI支付安全域名,并开通JSAPI支付功能。
4.实现支付功能:使用微信支付接口的统一下单 API,向微信支付服务器发起请求获取预支付交易会话标识(prepay_id)。
5.客户端调用JSAPI方法,拉起微信支付界面,用户确认支付后完成支付过程。
具体的步骤和操作细节可以参考微信官方文档中提供的支付接口开发指南,以及相关的示例代码和接口文档。在实现支付功能时还需要注意数据加密和处理异常情况等问题。另外建议开发者严格按照微信支付平台的标准接入流程进行开发和测试,并在审核前仔细检查相关功能是否符合规范要求。
在前端实现支付宝支付,需要进行以下步骤:
1.在支付宝开发者平台创建应用并进行认证,获取应用ID和密钥等信息。
2.采用支付宝提供的SDK或接口,将应用与支付宝账号关联。
3.实现支付功能,使用支付宝提供的统一收单下单并支付页面接口,向支付宝服务器发送下单请求,并获得一个唯一的订单号和支付宝跳转链接。
4.将跳转链接进行二维码生成或者页面渲染,展示给用户以完成支付过程。
具体的步骤和操作细节可以参考支付宝官方文档中提供的支付接口开发指南,以及相关的示例代码、接口文档。在实现支付功能时还需要注意数据加密和处理异常情况等问题。另外建议开发者严格按照支付宝平台的标准流程进行开发和测试,并在审核前仔细检查相关功能是否符合规范要求。
利用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%保证一定能够展示出来,请开发者在使用时进行细致的测试和异常处理。
检查网络连接是否正常,确保 CDN 或静态资源能够正常加载。
检查代码中是否有语法错误、逻辑错误或其他代码问题,可以使用开发者工具进行调试,并检查控制台报错信息。
确认浏览器版本是否支持所使用的技术,如是否支持 ES6、CSS Grid等。如果不支持,建议降级或用 polyfill 处理。
预加载相关资源,例如预加载 HTML、CSS、JS 文件。
将关键 CSS 和 JS 文件放在 head 标签中,以便在文档加载时就能够渲染。
合理、简化页面结构和样式,尽量减少 HTTP 请求次数和响应容量。
使用浏览器工具对页面加载过程进行分析,找到具体瓶颈并进行优化。
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 盒模型提供了灵活的布局控制,使用不同的属性和值可以实现各种布局效果。可以通过设置盒模型的属性来调整元素的尺寸、间距和边框样式,以满足设计需求。
前端页面渲染机制主要涉及的是浏览器如何对网页进行解析、构建和渲染的过程。具体来说,它通常包括以下几个阶段:
构建 DOM 树:当浏览器接收到 HTML 页面时,会按照标签的层次关系依次解析文件内容,并根据标签生成对应的 DOM 元素节点。
解析 CSS 样式:在 CSS 阶段中,浏览器会处理样式表信息,解析出每个元素所对应的应用样式,并将其合并组成一个规则列表。
生成 Render 树:Render 树是一个包含每个节点和其对应样式属性信息的树形结构,与 DOM 树类似。它的每个节点都代表了文档中相应的可视化元素。
布局(Layout):在 Render 树构建完成后,浏览器会进入布局阶段,计算出每个节点的位置和尺寸信息,并确定它们在屏幕上的呈现方式。
绘制(Painting):布局完成后,浏览器就可以进入绘制阶段,将页面内容绘制到屏幕上。
总的来说,浏览器在渲染页面时,会从 DOM 树、CSS 样式、JavaScript 脚本等多个方面对页面进行处理。在实际应用中,为了提高页面的性能,还需要注意避免网络请求、资源重复加载等问题。
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 协议。
无论是 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 方式来传递参数。
Vuex 是 Vue.js 的一个状态管理库,主要用于 Vue 应用中的数据共享问题。Vuex 存放在集中式存储中的状态是响应式的,能够做到在组件间传递和共享数据。
关于Vuex 数据传参导致白屏的问题,可能存在以下原因:
Vuex 数据传递出现了循环依赖,导致系统进入死循环,最终导致白屏。可以通过检查数据流向来判断是否有这种情况。
在使用 Vuex 进行数据传递时,没有及时调用异步操作需要的命令,导致白屏。这种情况下需要检查代码,确保异步操作的正确执行。
由于异步操作或其他原因,Vuex 中的数据未成功加载,但系统却尝试渲染视图,导致出现白屏。此时需要确保数据正常加载以后再进行视图渲染。
针对以上问题,可以针对具体的情况分别处理。例如,在处理循环依赖的问题时,可以修改代码结构、减少数据依赖关系等方式来解决;在处理异步操作相关问题时,需要仔细考虑应该何时调用异步操作,并且要编写良好的错误处理机制,避免出现无法处理的异常情况;对于数据加载问题,可以使用 Vue.js 提供的生命周期函数来确保数据加载完成后再进行视图渲染。
调整 WebSocket 同时连接的数量限制:我们可以调整 WebSocket 的同时连接数。例如,如果服务器只支持 1000 个连接,并且正在处理第 1001 个连接,则新的连接会被拒绝。通过调整同步连接数的限制,可以降低线程并发量和资源消耗。
根据消息的价值和重要性进行数据分类和筛选:服务器可以根据不同类型的消息内容,将其分配到不同的线程中进行处理,以避免单一线程处理太多数据导致负载过大。这可以通过使用消息队列等技术实现。
水平扩展服务器:如果以上两种方法无法解决问题,则可以使用水平扩展服务器的方法来增加系统的集群节点。这种方法可以通过添加更多服务器同时处理客户端请求来提高服务器的吞吐量和并发性能。
总之,处理 WebSocket 过多线程的方法有很多,具体取决于您的应用场景和实际需求。
下面是展示如何使用 Web Audio API 在 Canvas 上处理音频的基本步骤:
加载音频文件:使用 JavaScript 的 Audio 对象来加载音频文件。
创建音频上下文对象:创建一个 AudioContext 对象。该对象将被用于从音频文件中读取数据并传递到 Web Audio API。
解码音频数据:调用 AudioContext 对象的 decodeAudioData() 方法解码音频数据。
创建音频节点:根据需要创建各种类型的音频节点,例如 SourceNode、GainNode、DestinationNode 等。节点将构成一个音频处理图谱,每个节点都会执行一些特定的音频操作。
将音频节点连接起来:使用 connect() 方法将音频节点与其他节点连接起来形成所需的音频处理图谱。由此形成的处理图可以通过简单的组合来实现从音频文件读取数据并对其进行处理。
开始播放音频:调用 start() 方法启动音频轨道中的播放或停止音频播放 。
在 Canvas 中处理音频的能力不仅限于上述步骤。实际上, Canvas 有一些内置功能可用于处理和操作音频,例如使用图形来控制音频播放、操纵声音的可视化效果和创建有趣的交互式体验。具体取决于您的需求和想要实现的功能。
想要在 Webpack 中配置 rem,可以采用以下步骤:
在终端中输入以下命令安装相关插件:
npm install postcss-loader postcss-pxtorem -D
在 webpack.config.js 文件中的 module.rules 数组中添加 postcss-loader,如下所示:
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
}
};
在项目根目录创建 postcss.config.js 文件,并配置 postcss-pxtorem 插件,如下所示:
module.exports = {
plugins: [
require('postcss-pxtorem')({
rootValue: 75, // 根字体大小
propList: ['*'], // 全部选择器都需要转换
mediaQuery: false // 是否开启媒体查询模式
})
]
};
在 index.html 的 head 标签内设置 HTML 文档的 font-size,如下所示:
Webpack Project
最后,重新运行 Webpack 即可实现在 CSS 中使用 rem。
为了解决样式兼容问题,可以考虑在 Webpack 中配置 PostCSS 插件 Autoprefixer,它可以自动添加浏览器前缀,从而简化前端编写 CSS 样式时的兼容性处理工作。
使用 Autoprefixer 需要以下步骤:
执行以下命令来安装所需依赖:
npm install postcss-loader autoprefixer -D
在 module.rules 数组中添加处理 CSS 文件的 loader,连同 Autoprefixer 插件一起使用。下面是示例代码:
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}
]
}
]
}
};
建议将 Autoprefixer 和 Babel 一起配合使用来兼容更多 JavaScript 特性和一些旧版浏览器。将目标浏览器放在项目根目录的 package.json 文件的 browserslist 配置项中。例如:
"browserslist": [
"> 1%",
"last 2 versions"
]
这条指令的意思是我们需要支持全球占有率 1% 及以上的浏览器,并且支持最新的两个主要浏览器版本。
经过上述步骤,就完成了 Autoprefixer 的配置。在接下来开发和打包过程中,Autoprefixer 将自动解析 CSS,并根据你的 browserslist 配置添加相应的前缀,从而帮助解决跨浏览器兼容性问题。
Vite 是 Vue.js 创始人尤雨溪开发的一款面向现代浏览器的轻量级前端开发服务器和构建工具。它提供了快速的冷启动,内置开箱即用支持 Vue 单文件组件 (SFC) 的 devServer,并能够在开发期间基于 ES Module 进行非常快速的模块热更新 (HMR),从而解决了 Webpack 打包重启时间过长的问题。
下面是 Vite 的主要特点:
快速冷启动:通过 ES Module 的特性,在服务启动时只需要编译当前需要的文件,而不需要像 Webpack 那样一次性编译整个项目,从而提升了冷启动速度。
实时模块热替换:基于 Vue 3 的 Reactivity API 实现,可以更精准地定位需要替换的模块,同时也提供了对 CSS 和自定义模块类型的支持,可以加快前端开发的速度。
内置优化插件:通过内置的预处理插件以及针对生产环境的打包优化,可以帮助用户轻松地实现优化产品代码的目的。
零配置:默认配置下就可以直接使用,无需进行额外的配置。
可扩展性:Vite 提供了 API 和插件体系,方便用户根据需求扩展、优化相关功能。
使用 Vite 可以轻松实现项目开发和构建,其兼容多种框架 (Vue / React / Preact / LitElement),支持 TypeScript、SCSS、LESS 等多个前端开发技术栈,并且提供了快速开发、更佳的开发体验和更快的构建效率。
使用 Vite 是非常简单的,下面介绍如何使用 Vite 来搭建 Vue.js 应用程序。
首先需要全局安装 @vite/cli:
npm install -g vite 或者 yarn global add vite
使用以下命令创建一个新的 Vue 项目:
npm init vite-app my-project 或者 yarn create vite-app my-project
进入项目目录并安装依赖:
cd my-project
npm install 或者 yarn
执行以下命令运行项目:
npm run dev 或者 yarn dev
然后打开浏览器访问 http://localhost:3000 即可查看应用程序。
使用以下命令构建项目:
npm run build 或者 yarn build
构建完成后,所有打包好的文件都会生成在 dist 目录下。
以上就是使用 Vite 搭建 Vue.js 应用程序的基本流程。同时 Vite 也支持其他前端框架,只需按照相应文档进行相应设置即可。
Vue 在实现数据双向绑定时,使用了 Object.defineProperty() 方法来为对象上的属性添加 getter 和 setter,从而实现数据响应式。
但是,Object.defineProperty() 方法只能劫持对象的属性,而不能劫持整个对象或数组。这就意味着当你改变数组中的某一个元素时,它是不会触发响应式更新的,因为 Vue 并没有劫持数组元素本身的 setter。
由于数组是一种比较特殊的对象类型,在 JavaScript 中,最好使用 Array 类型提供的方法对其进行操作。这些数组的方法可以触发响应式更新,例如 push、pop、splice 等等。所以推荐在 Vue 中,我们尽量使用这些数组方法来修改数组中的元素,从而保证数据响应式。
总结一下,Vue 监听不到对象里面的数组变化,是因为 Object.defineProperty() 方法只能劫持对象的属性,而不能劫持整个对象或数组,但是 Vue 提供了一些数组的方法,可以触发响应式更新。因此,我们在Vue使用中,建议使用这些数组方法来修改数组中的元素。
在 Vue 中,要监听数组内部元素的变化,可以使用一个名为 Vue.set 或者 this.$set 的方法来实现。这个方法会将新增的元素转换为响应式数据,并触发界面的重新渲染。
Vue.set(obj, propertyName/index, value) 方法接收 3 个参数:
例如:
// 数组增加元素
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 和深度监控可以实现数组(对象)的监听,但是开销比较大。建议尽量使用数组提供的方法来代替直接对数组进行操作,以获得更好的性能和用户体验。
Vue 的响应式系统在创建响应式对象时会将数组的原型进行改写,并劫持了数组变异方法,比如 push()
、pop()
、shift()
、unshift()
、splice()
、sort()
和 reverse()
等。当调用这些数组方法时,Vue 能够观察到数组的变化,并通知相关的依赖进行更新。
具体的实现原理如下:
Array.prototype
。push()
方法向数组中添加新元素时,Vue 能够捕捉到这个变化,并在内部执行相应的操作:
通过以上步骤,Vue 实现了对数组变异方法的拦截与处理,使得数组的变化能够被响应式系统检测到并进行相应的更新操作,从而实现了数组的响应式。
需要注意的是,Vue 的这种拦截和劫持机制只能处理使用变异方法改变的数组数据,对于直接通过索引修改数组元素或者改变数组长度的操作,Vue 是无法检测到的。为了解决这个问题,Vue 提供了一些特殊的方法来处理这类情况,比如使用 Vue.set
或者替换整个数组来触发更新等方式。
路由 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。
配置方式分为以下两种:
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)
location / {
try_files $uri /index.html;
}
以上示例是在 Nginx 配置文件中添加了一个转发规则,将所有请求重定向到 index.html 文件并让 Vue Router 接管路由。
总之,路由 history 模式通过 HTML5 的 History API 实现,它可以更改 URL 的 pathname 和 search字段。服务端需要根据实际情况进行配置,比如使用服务器框架或 Nginx 等服务器,以捕获到所有的路由并渲染 index.html 文件,并让 Vue Router 接管路由。
安装和引入 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 的详细教程:
首先,在 HTML 文件中引入 Tree.js 库
如果您的项目使用 npm 包管理器管理依赖,也可以通过以下命令安装 Three.js:
npm install three
然后在你的项目中,以如下方式引用:
import * as THREE from 'three';
在开始创建 3D 场景之前,需要先初始化一个 Scene
对象,指定背景颜色等属性。
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf4f6f9);
相机是观察者看到 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);
渲染器负责通过 WebGL 技术将场景呈现到作为 HTML 元素的画布中。
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement); // 将 canvas 元素添加到 document.body 中
在 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);
当场景变得复杂
.nummber 自动将用户的输入值转为数值类型
.trim 自动过滤用户输入的首民空白字料
.Lazy 只有在输入数据结束才会调用,不会在输入时调用
pages 用来存放所有小程序的页面9
utils 用来存放工具性质的模块 (例如:格式化时间的自定义模块)
app.js 小程序项目的入口文件
app.json 小程序项目的全局配置文件
app.wxss 小程序项目的全局样式文件
project.config.json 项目的配置文件
sitemap.json 用来配置小程序及其页面是否允许被微信索引
小程序官方建议把所有小程序的页面,都存放在 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()事件
注册小程序账号:在微信公众平台注册一个小程序账号,注册完成后,可以获取到 AppID 和 AppSecret,这是后续开发的必备信息。
创建小程序项目:使用微信开发者工具创建一个新的小程序项目,填写小程序的名称、AppID 等基本信息,并选择对应的开发语言(小程序支持多种语言开发)。
开发页面:在微信开发者工具中开发小程序页面,包括页面的布局、样式、交互逻辑等。
开发接口:在服务器端开发小程序需要的接口,比如获取数据、提交表单等功能。
测试和调试:开发完成后需要进行测试和调试,确保功能正常。
提交审核:当开发完成后,需要提交审核,经过审核后即可发布到小程序平台供用户使用。
公众号网页开发流程:
注册公众号账号:在微信公众平台注册一个公众号账号,注册完成后,可以获取到 AppID 和 AppSecret,这是后续开发的必备信息。
开发网页:在本地开发网页,包括页面的布局、样式、交互逻辑等。
接入微信JS-SDK:通过引入微信JS-SDK,可以在网页中调用微信的API,比如获取用户信息、分享等。
进行网页授权:通过网页授权,可以获取用户的微信身份信息,实现个性化的功能。
部署网页:将网页部署到服务器上,并配置服务器地址、令牌(Token)、消息加密密钥(EncodingAESKey)等信息。
验证服务器:配置完成后,微信公众平台会向服务器发送一个验证请求,需要在服务器中进行验证,并返回验证信息。
测试和调试:部署完成后需要进行测试和调试,确保功能正常。
提交审核:当开发完成后,需要提交审核,经过审核后即可发布到公众号中供用户使用。
总体来说,微信小程序开发和公众号网页开发都需要涉及多个方面,包括前端、后端、数据库等,需要开发者具备一定的技术能力和经验。同时,还需要对微信公众平台的规则和限制有一定的了解,确保开发的功能符合相关要求。
每一个?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、甚至是嵌套子模块——从上至下进行同样方式的分割
四大核心属性,以及一个子模块管理属性,state存放数据,Mutations同步修改state里面的数据,actions异步修改数据,但需要调用Mutations里面的方法修改,getters数据过滤器,对数据进行整理,还有一个modules,当数据过多和复杂时,将数据用模块化分开,在住文件中用modules引用
Leon:
简单回答
store,存储变量 mutation,发起变量更改,记录更改 action,发起异步变量更改,记录更改 getter,计算属性 module,模块划分
答案:Event Loop是浏览器提供的一种机制,用于执行JavaScript代码。它主要作用在于监听任务队列,将其中挂起的任务分配到可用的线程上去执行。
答案:nav标签用于定义导航栏,通常包含一个或多个链接,用户通过点击这些链接可以跳转到不同的页面或功能。
答案:computed属性会在其依赖数据发生改变时自动更新,而watch则需要手动监听数据的变化。computed适用于处理复杂的计算,并且数据源发生变化时需要经过一些额外的操作;而watch则适用于监听某个特定数据的变化,并且需要执行一些副作用操作(如发送网络请求)。
答案:Virtual DOM是React中的一种机制,它是一个轻量级的JavaScript对象,用于表示真实DOM节点的结构。由于Virtual DOM比真实DOM更容易操作和修改,所以React会首先对Virtual DOM进行操作并计算出最小变化集合,然后再将这些变化同步到真实DOM节点上,从而减少不必要的页面重绘。
答案:可以使用Array.isArray()方法来判断一个变量是否是数组类型:
复制代码
var arr = [];
if (Array.isArray(arr)) {
console.log("arr is an array");
}
答案:let和const都是块级作用域的声明方式,用于替代var关键字。它们的主要区别在于const声明的变量必须被初始化,并且在后续的操作中不能被重新赋值;而let声明的变量则可以被重新赋值。
答案:可以使用for-in循环来遍历一个对象的所有可枚举属性:
复制代码
var obj = {a: 1, b: 2, c: 3};
for (var prop in obj) {
console.log(prop + ": " + obj[prop]);
}
答案:props和state都是React组件中用于存储组件数据的属性,但它们的作用略有不同。props通常是由父组件向子组件传递的静态数据,子组件无法修改它们,只能读取。而state则是组件内部维护的动态数据,只能通过setState()方法来修改,但这些修改会触发组件的重新渲染。
答案:常用于在React应用中进行Ajax请求的第三方库包括Axios、jQuery和Fetch等。
答案:通过Error对象可以轻松地创建自定义异常,可以像下面这样定义并抛出一个名为"CustomError"的异常:
复制代码
function CustomError(message) {
this.message = message;
this.name = "CustomError";
}
throw new CustomError("Something went wrong");
答案:需要使用“==null”运算符来检测一个变量是否为null或undefined,代码如下:
复制代码
var a;
if (a == null) {
console.log("a is null or undefined");
}
答案:可以使用addEventListener()方法来在DOM元素上注册事件监听器:
复制代码
var btn = document.querySelector("#myBtn");
btn.addEventListener("click", function() {
// do something
});
答案:shouldComponentUpdate()方法用于控制组件是否需要重新渲染。如果该方法返回false,则React将不会重新渲染该组件,从而提高页面的性能。
答案:可以使用isNaN()函数来判断一个变量是否为NaN:
复制代码
var num = NaN;
if (isNaN(num)) {
console.log("num is NaN");
}
答案:CSS中所有元素都被视为一个箱子,包含content、padding、border和margin四个部分。盒模型通过这四个部分来定义一个元素的大小、边距和边框等属性。
答案:React中的组件生命周期包括以下几个阶段:
Mounting(挂载):组件被创建并插入到DOM中;
Updating(更新):组件的state或props发生改变,需要重新渲染;
Unmounting(卸载):组件从DOM中移除;
Error Handling(错误处理):组件在渲染期间发生错误,需要进行错误处理。
答案:可以使用Date对象来获取当前时间,代码如下:
复制代码
var now = new Date();
console.log(now.toDateString());
答案:Refs是用于在React组件中获取DOM节点或组件实例的方法。它可以通过React.createRef()方法或回调函数的方式来创建,并且可以在组件中
答:闭包是指函数与其相关引用环境组合的一种特殊情况。简单来说,就是一个函数能够访问在函数定义时不在其作用域内的变量。例如:
javascript
复制代码
function outerFunction() {
let number = 10;
function innerFunction() {
console.log(number);
}
return innerFunction;
}
let closure = outerFunction();
closure(); // 输出 10
答:使用Array.isArray()方法。
答:常见的HTTP状态码有以下几个:
200 OK 表示请求成功。
201 Created 表示请求成功并创建了新资源。
204 No Content 表示请求成功但无返回内容。
400 Bad Request 表示客户端发送的请求有误。
401 Unauthorized 表示客户端需要身份验证才能访问该资源。
403 Forbidden 表示服务器拒绝提供该资源。
404 Not Found 表示请求的资源不存在。
500 Internal Server Error 表示服务器发生错误。
答:使用in运算符或者Object.hasOwnProperty()方法。
答:事件循环指的是JavaScript在执行代码时处理异步操作的一种机制。当有异步操作时,它们会被放到事件队列中,等待JavaScript引擎空闲时去处理。事件循环由三个部分组成:调用栈、事件队列和微任务队列。当调用栈为空时,JavaScript开始处理事件队列中的事件。处理完每一个事件后,JavaScript还会执行微任务队列中所有的任务。
答:let和const都用于声明变量,但是它们之间有几个重要的区别:
let声明的变量可以修改,const声明的变量不能修改。
let声明的变量在同一个作用域内不能重复定义,const声明的变量也不能重复定义,而且一旦赋值就不能改变。
使用let声明的变量没有定义提升,而使用const声明的变量也没有定义提升,并且必须在声明的同时就进行赋值。
const声明的变量必须在初始化时赋值,否则会抛出异常。
答:bind()方法是jQuery早期的事件绑定方法,而on()方法是新版jQuery引入的更强大的事件绑定方法。相比之下,on()方法具有以下优势:
on()方法支持多个事件同时绑定到同一个元素上,而bind()方法只能绑定一个。
on()方法可以为后添加的元素绑定事件,而bind()方法不支持这种方式。
on()方法还可以使用命名空间对事件进行管理。
答:JavaScript中的typeof操作符可以返回以下几种值:
“undefined” 表示该变量未定义。
“boolean” 表示该变量是布尔类型。
“number” 表示该变量是数值类型。
“string” 表示该变量是字符串类型。
“object” 表示该变量是对象类型或null类型(注意,null被认为是一个空对象)。
“function” 表示该变量是函数类型。
答:浏览器的同源策略指的是一个网页的脚本只能读取来自同一来源的数据。同源是指协议、域名和端口号都相同。如果两个页面的URL有任何一个部分不同,就会被视为不同源。同源策略的目的是保护用户隐私和安全。
答:在JavaScript中,this关键字引用的是当前执行代码所处的上下文对象。具体来说,this指向的是函数调用时的环境,取决于函数的调用方式。如果在全局作用域中使用this,则它会指向全局对象window;如果在独立的函数中使用this,则它会指向全局对象或undefined;如果在对象的方法中使用this,则它会指向该对象;如果使用call、apply或bind方法,可以手动设置this的值。
答:JavaScript中的原型继承是通过原型链实现的一种继承机制。每个JavaScript对象都有一个指向另一个对象的内部链接,这个链接就是该对象的原型。如果在对象上访问一个属性或方法时,如果该对象本身不存在该属性或方法,JavaScript会沿着原型链查找,直到找到具有该属性或方法的对象为止。可以使用Object.create()方法来创建一个新对象并将其原型设为某个现有对象。
答:CSS中的盒模型指的是元素在文档流中所占据的空间,包括元素的内容区域、内边距、边框和外边距等。根据标准盒模型和IE盒模型的区别,盒模型可以分为两种不同的模式,即标准(content-box)和IE(border-box)盒模型。标准盒模型计算元素尺寸时不包含内边距和边框的宽度,而IE盒模型将内边距和边框的宽度纳入了元素的尺寸计算。
答:小程序是一种轻量级的应用程序,可以在移动设备上直接使用,无需下载安装。它结合了Web开发的灵活性和原生应用的性能,提供了一种更好的用户体验。
答:小程序具有以下优势:
答:小程序和传统网页开发相比有几个区别:
答:小程序的页面生命周期包括onLoad、onShow、onReady、onHide、onUnload等。执行顺序如下:
答:小程序之间的通信可以通过以下方式实现:
答:uni-app是一个基于Vue.js的开发框架,可以同时构建多个平台的应用程序,包括iOS、Android、Web、微信小程序等。通过uni-app,开发者可以使用一套代码编写跨平台的应用,并具备原生应用的性能和体验。
答:uni-app具有以下主要特点:
一套代码,多端运行:开发者只需编写一次代码,即可同时发布到多个平台。
原生级性能:通过底层的渲染优化和性能调优,uni-app实现了原生应用的性能表现。
开发门槛低:基于Vue.js进行开发,对于熟悉Vue.js的开发者来说上手较快。
生态丰富:uni-app拥有庞大的插件市场和活跃的社区支持,提供了丰富的插件和组件库。
跨平台支持:可以使用同一份代码开发多个平台的应用,提高开发效率。
组件化开发:采用Vue.js的组件化开发思想,可以快速构建复杂的UI界面。
内置原生能力:具备访问设备功能、调用原生API等能力。
良好的性能表现:通过编译生成原生代码,提升应用的运行效率。
大量插件和扩展:拥有丰富的插件和扩展库,方便开发各种功能和特效。
答:uni-app的工作原理可以简述为:使用前端技术栈(Vue.js + Webpack)开发页面,通过编译器将代码转换为各个平台所需的代码,再通过各个平台的运行时环境将代码渲染为可执行的应用。
答:在uni-app中,可以使用navigateTo、redirectTo、switchTab等API进行页面跳转。同时也可以通过标签结合Vue Router实现页面间的导航。
答:uni-app支持大量的第三方插件和组件库,包括但不限于以下:
答:uni-app的页面生命周期包括onLoad、onShow、onReady、onHide、onUnload等。与小程序的页面生命周期类似。
答:uni-app之间的页面通信可以采用以下方式:
答:uni-app通过封装了一套API来实现原生功能的调用。可以使用uni-app提供的API,如拍照、获取地理位置、调用相应等设备能力。也可以使用插件或原生扩展来实现对原生功能的调用。
需要使用 npm 安装 Vuex ,在终端输入如下命令:
npm install vuex --save
在项目中引入 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 四种选项来管理应用的状态。
在 src 目录下找到 main.js 文件,将以下代码添加到文件开头:
import Vue from 'vue'
import App from './App.vue'
import store from './vuex'
new Vue({
el: '#app',
store,
components: { App },
template: ' '
})
这段代码将 store 引入了全局中,并在 Vue 实例中注册该 store,这样就使整个应用程序都可以访问到 store 中的状态和其他特性。
在组件中使用 Vuex 需要做如下两步操作:
下面是一个基本的组件示例,演示如何在组件中使用 state 和 mutations。
{{ count }}
在模板中,我们使用 {{ count }}
显示 state 中存储的数据,使用 @click="increment"
触发 mutations 中的 increment
方法来更新状态。在 scripts 中,我们使用 mapState
将 count 映射到 computed 中,使用 mapMutations
将 increment 映射到 methods 中。
在组件中使用 actions 和 getters 的方法与上述使用 state 和 mutations 的方法类似,在此不再赘述。以下是示例代码:
在模板中,我们使用 {{ getCount }}
显示 getters 中计算的数据,使用 @click="incrementAsync"
触发 actions 中的 incrementAsync
方法来更新状态。在 scripts 中,我们使用 mapGetters
将 getCount 映射到 computed 中,使用 mapActions
将 incrementAsync 映射到 methods 中。
Vuex 是一个用于 Vue.js 应用程序的状态管理库,它允许我们集中式地处理应用程序的所有状态。通过创建和修改 store 实例中的 state、mutations、actions、getters 等选项,我们可以方便地管理应用程序的状态,并在组件中轻松使用这些状态。
Vue的双向绑定原理是通过数据劫持和发布-订阅模式实现的。关键点在于通过Object.defineProperty()
方法来劫持对象的属性访问,当数据发生变化时,触发相应的监听器,并更新视图。
实现水平垂直居中的方式有多种。其中一种常用的方式是使用Flex布局,设置容器为display: flex;justify-content: center;align-items: center;
。
常用的伪元素有::before
和::after
,它们在元素的内容前后添加虚拟元素,可以通过CSS样式进行修饰。
移动端适配可以采用响应式布局或者使用媒体查询等方式进行适配。常见的做法是使用百分比、rem单位或者CSS框架(如Bootstrap)来实现灵活布局和缩放效果。
常见的本地存储方式有Cookie、LocalStorage和SessionStorage。它们的区别在于作用域、存储容量和数据有效期等方面。Cookie存储在客户端浏览器中,有大小限制且过期时间可以设置;LocalStorage和SessionStorage则是HTML5提供的Web Storage API,存储在浏览器中,LocalStorage没有过期时间限制且容量较大,而SessionStorage在会话结束后自动清除。
JS的数据类型包括基本数据类型(Number、String、Boolean、Null、Undefined、Symbol)和引用数据类型(Object)。可以使用typeof
操作符判断基本数据类型,使用instanceof
操作符或Object.prototype.toString.call()
方法判断引用数据类型。
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表示指数。例如,2的3次方可以表示为2 ** 3,结果为8。
需要注意的是,幂运算符的优先级高于加减乘除运算符,因此需要使用括号来控制运算顺序。例如,(2 + 3) ** 2的结果为25,而2 + 3 ** 2的结果为17。
三者都用于声明变量,但区别在于作用域和可修改性。var
声明的变量具有函数作用域,而let
和const
声明的变量具有块级作用域。另外,let
和var
声明的变量可以被修改,而const
声明的变量是常量,不可被修改。
除了ES6新增的构造函数Set具有天然去重功能和数组迭代外,常见的数组去重方法还包括使用indexOf()
方法、利用对象属性唯一性等方式进行去重。
深拷贝是指在内存中完全复制一个对象,包括其所有的属性和嵌套对象,而浅拷贝只复制对象的引用。实现深拷贝可以通过递归遍历对象的属性,并创建新的对象来实现。
Vue的生命周期包括:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy和destroyed。在beforeCreate阶段,Vue实例被初始化,数据观测和事件配置之前;在created阶段,Vue实例完成数据观测、属性和方法的运算;在beforeMount阶段,模板编译成虚拟DOM之前;在mounted阶段,虚拟DOM挂载到实际DOM后;在beforeUpdate阶段,数据更新之前;在updated阶段,数据更新完成;在beforeDestroy阶段,开始销毁Vue实例之前;在destroyed阶段,Vue实例销毁完成。
组件通讯方式包括父子组件通讯(props和$emit)、兄弟组件通讯(通过共同的父组件传递数据或使用事件总线)和跨层级通讯(使用Vuex或provide/inject)。
Vuex有state、getters、mutations、actions和modules等属性。state是存储共享状态的地方,getters用于获取state中的数据,mutations用于修改state的数据(同步操作),actions用于处理异步操作或提交mutations,而modules可以将store分割成模块化的部分。
Vue的监听属性通过watch选项来实现,可监听数据的变化并执行相应的回调函数;而计算属性通过computed选项来定义,它根据依赖的数据自动计算得出结果,计算属性具有缓存机制,在依赖数据未改变时直接返回缓存结果。
防抖和节流是解决频繁触发事件时的性能优化手段。防抖是在一定时间内只执行最后一次操作,常用的实现方式是使用setTimeout延迟执行;节流是在一定时间间隔内只执行一次操作,常用的实现方式是使用定时器控制执行频率。
beforeEach
:在路由切换开始之前调用,可以用来进行全局的前置守卫验证。
beforeResolve
:在导航被确认之前调用,可以用来进行延迟加载的路由组件的初始化。
afterEach
:在每次路由切换完成之后调用,可以用来进行全局的后置处理操作。
路由独享的守卫:
beforeEnter
:在单个路由配置中定义,只对该路由有效。组件内的守卫:
beforeRouteEnter
:在进入路由对应的组件前调用,可以访问不到组件实例,需要使用 next 方法来传递组件实例给回调函数。
beforeRouteUpdate
:在路由参数发生变化时调用,例如从 /user/1 导航到 /user/2。
beforeRouteLeave
:在离开当前路由时调用,可以用来确认是否离开或取消导航。
这些导航守卫可以让你控制路由的跳转行为,在路由切换前进行验证、权限控制或其他必要的操作。
用正确的标签做正确的事情。
html 语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析;即使在没有样式 CSS 情况下也以一种文档格式显示,并且是容易阅读的;
搜索引擎的爬虫也依赖于 HTML 标记来确定上下文和各个关键字的权重,利于 SEO;
使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。
alt 是给搜索引擎识别,在图像无法显示时的替代文本;
title 是关于元素的注释信息,主要是给用户解读。
当鼠标放到文字或是图片上时有 title 文字显示。(因为 IE 不标准)在 IE 浏览器中 alt 起到了 title 的作用,变成文字提示。
在定义 img 对象时,将 alt 和 title 属性写全,可以保证在各种浏览器中都能正常使用。
解决加载缓慢的第三方内容如图标和广告等的加载问题
Security sandbox
并行加载脚本
6、即时内容为空,加载也需要时间
没有语意
href (Hypertext Reference)指定网络资源的位置,从而在当前元素或者当前文档和由当前属性定义的需要的锚点或资源之间定义一个链接或者关系。(目的不是为了引用资源,而是为了建立联系,让当前标签能够链接到目标地址。)
src source(缩写),指向外部资源的位置,指向的内容将会应用到文档中当前标签所在位置。
href与src的区别
请求资源类型不同:href 指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的联系。在请求 src 资源时会将其指向的资源下载并应用到文档中,比如 JavaScript 脚本,img 图片;
作用结果不同:href 用于在当前文档和引用资源之间确立联系;src 用于替换当前内容;
浏览器解析方式不同:当浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。
有两种, IE 盒子模型、W3C 盒子模型;
盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);
区 别: IE 的 content 部分把 border 和 padding 计算了进去;
!important > 行内样式(比重1000)> ID 选择器(比重100) > 类选择器(比重10) > 标签(比重1) > 通配符 > 继承 > 浏览器默认属性
单行文本: line-height = height
图片: vertical-align: middle;
absolute 定位: top: 50%;left: 50%;transform: translate(-50%, -50%);
flex: display:flex;margin:auto
link 是 XHTML 标签,除了加载CSS外,还可以定义 RSS 等其他事务;@import 属于 CSS 范畴,只能加载 CSS。
link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载。
link 是 XHTML 标签,无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不支持。
link 支持使用 Javascript 控制 DOM 去改变样式;而@import不支持。
opacity 会继承父元素的 opacity 属性,而 RGBA 设置的元素的后代元素不会继承不透明属性。
display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。
visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。
display:none 会导致页面回流和重绘,visibility:hidden 只会导致重绘
relative:相对定位,相对于自己本身在正常文档流中的位置进行定位。
absolute:生成绝对定位,相对于最近一级定位不为static的父元素进行定位。
fixed: (老版本IE不支持)生成绝对定位,相对于浏览器窗口或者frame进行定位。
static:默认值,没有定位,元素出现在正常的文档流中。
sticky:生成粘性定位的元素,容器的位置根据正常文档流计算得出。
考查的是css3的transform
height: 1px;
transform: scale(0.5);
1、@support 主要是用于检测浏览器是否支持CSS的某个属性,其实就是条件判断,如果支持某个属性,你可以写一套样式,如果不支持某个属性,你也可以提供另外一套样式作为替补。
2、calc() 函数用于动态计算长度值。 calc()函数支持 “+”, “-”, “*”, “/” 运算;
3、@media 查询,你可以针对不同的媒体类型定义不同的样式。
rem是全部的长度都相对于根元素元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。
子元素字体大小的em是相对于父元素字体大小
元素的width/height/padding/margin用em的话是相对于该元素的font-size
全称是 Viewport Width 和 Viewport Height,视窗的宽度和高度,相当于 屏幕宽度和高度的 1%,不过,处理宽度的时候%单位更合适,处理高度的 话 vh 单位更好。
px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。
一般电脑的分辨率有{19201024}等不同的分辨率
19201024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素
这属于简单的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°*/
}
新的语义标签
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
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
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)
浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一。
Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,
可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决.
数据类型主要包括两部分:
基本数据类型: Undefined、Null、Boolean、Number 和 String
引用数据类型: Object (包括 Object 、Array 、Function)
ECMAScript 2015 新增:Symbol(创建后独一无二且不可变的数据类型 )
typeof 运算符
instanceof 运算符
Object.prototype.toString 方法
null 表示一个对象被定义了,值为“空值”;
undefined 表示不存在这个值。
(1)变量被声明了,但没有赋值时,就等于undefined。 (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。 (3)对象没有赋值的属性,该属性的值为undefined。 (4)函数没有返回值时,默认返回undefined。
arr instanceof Array
arr.constructor == Array
Object.prototype.toString.call(arr) == ‘[Object Array]’
==,当且仅当两个运算数相等时,它返回 true,即不检查数据类型
===,只有在无需类型转换运算数就相等的情况下,才返回 true,需要检查数据类型
它的功能是把对应的字符串解析成 JS 代码并运行;
应该避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 语句,一次执行)。
不需要function关键字来创建函数
省略return关键字
改变this指向
var 存在变量提升。
let 只能在块级作用域内访问。
const 用来定义常量,必须初始化,不能修改(对象特殊)
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小
{‘age’:‘12’, ‘name’:‘back’}
document.write 只能重绘整个页面
innerHTML 可以重绘页面的一部分
(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.
(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
(3)设置响应HTTP请求状态变化的函数.
(4)发送HTTP请求.
(5)获取异步调用返回的数据.
(6)使用JavaScript和DOM实现局部刷新.
概念:同源策略是客户端脚本(尤其是Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。
这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。
指一段脚本只能读取来自同一来源的窗口和文档的属性。
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包常见方式,就是在一个函数的内部创建另一个函数
使用闭包主要为了设计私有的方法和变量,闭包的优点是可以避免变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念。
闭包有三个特性:
函数嵌套函数
函数内部可以引用外部的参数和变量
参数和变量不会被垃圾回收机制回收
应用场景,设置私有变量的方法
不适用场景:返回闭包的函数是个非常大的函数
闭包的缺点就是常驻内存,会增大内存使用量,使用不当会造成内存泄漏
垃圾回收器会每隔一段时间找出那些不再使用的内存,然后为其释放内存
一般使用标记清除方法(mark and sweep), 当变量进入环境标记为进入环境,离开环境标记为离开环境
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
还有引用计数方法(reference counting), 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。
const obj = {}
obj.proto === Object.prototype // true
(通过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
// 原型链最终点是 null
Object.prototype.proto === null // true
obj.proto.proto === null // true
每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,
如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,
于是就这样一直找下去,也就是我们平时所说的原型链的概念。
关系:instance.constructor.prototype = instance.proto
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
function add(num1, num2) {
const num = num1 + num2;
if(num2 === 100) {
return num;
} else {
return add(num, num2 + 1)
}
}
var sum = add(1, 2);
可以分为微任务(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
async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。是Generator函数的语法糖,并对Generator函数进行了改进。
*内置执行器,无需手动执行 next() 方法。
*更好的语义
*更广的适用性:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
*返回值是 Promise,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用。
*async 隐式返回 Promise 作为结果的函数,那么可以简单理解为,await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。
*每打开一个新网页就会创建一个渲染进程
*渲染进程是多线程的
*负责页面渲染的 GUI 渲染线程
*负责JavaScript的执行的 JavaScript 引擎线程,
*负责浏览器事件循环的事件触发线程,注意这不归 JavaScript 引擎线程管
*负责定时器的定时触发器线程,setTimeout 中低于 4ms 的时间间隔算为4ms
*负责XMLHttpRequest的异步 http 请求线程
*GUI 渲染线程与 JavaScript 引擎线程是互斥的
*单线程JavaScript是因为避免 DOM 渲染的冲突,web worker 支持多线程,但是 web worker 不能访问 window 对象,document 对象等。
*MVVM分为Model、View、ViewModel三者。
*Model 代表数据模型,数据和业务逻辑都在Model层中定义;
*View 代表UI视图,负责数据的展示;
*ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
*Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。
v-if 是真正的条件渲染,会控制这个 DOM 节点的存在与否。因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页> 面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 > HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
基于上面一点,SPA 相对对服务器压力小;
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一> 加载,部分页面按需加载;
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所> 有的页面切换需要自己建立堆栈管理;
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
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。
单一状态树,定义应用状态的默认初始值,页面显示所需的数据从该对象中进行读取。
2、Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。它便作为一个“唯一数据源”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。
单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
不可直接对 state 进行更改,需要通过 Mutation 方法来更改。
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
1、可以认为是 store 的计算属性,对 state 的加工,是派生出来的数据。
2、就像 computed 计算属性一样,getter 返回的值会根据它的依赖被缓存起来,且只有当它的依 赖值发生改变才会被重新计算。
3、可以在多组件中共享 getter 函数,这样做还可以提高运行效率。
4、在 store 上注册 getter,getter 方法接受以下参数: state, 如果在模块中定义则为模块的局部状态
5、getters, 等同于 store.getters
1、Vuex中store数据改变的唯一方法就是mutation
2、通俗的理解,mutations 里面装着改变数据的方法集合,处理数据逻辑的方法全部放在 mutations 里,使数据和视图分离。
1、action 提交的是 mutation,通过 mutation 来改变 state ,而不是直接变更状态。
2、action 可以包含任意异步操作。
1、由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
2、为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
四大核心属性,以及一个子模块管理属性,state存放数据,Mutations同步修改state里面的数据,actions异步修改数据,但需要调用Mutations里面的方法修改,getters数据过滤器,对数据进行整理,还有一个modules,当数据过多和复杂时,将数据用模块化分开,在住文件中用modules引用
<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'
}
<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'
}
}
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。
这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
computed
:是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;watch
: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
由于 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)
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
生命周期 描述
beforeCreate 组件实例被创建之初,组件的属性生效之前
created 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用
mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdate 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
updated 组件数据更新之后
activited keep-alive 专属,组件被激活时调用
deadctivated keep-alive 专属,组件被销毁时调用
beforeDestory 组件销毁前调用
destoryed 组件销毁后调用
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父 beforeUpdate -> 父 updated
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
比如有父组件 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 等都可以监听。
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?
因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,
如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
我们在 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='something'>
//相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
父组件:
<ModelChild v-model="message"></ModelChild>
子组件:
<div>{{value}}</div>
props:{
value: String
},
methods: {
test1(){
this.$emit('input', '小红')
},
},
Vue 组件间通信只要指以下 3 类通信:****父子组件通信、隔代组件通信、兄弟组件通信****,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。
这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
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所识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v−bind="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修饰器的)v−on事件监听器。它可以通过v−on="listeners" 传入内部组件
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。
服务端渲染 SSR 的优缺点如下:
更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
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,路由会自动强制进入这个模式.
早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 ‘#search’:
https://www.word.com#search
URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:
history.pushState() 和 history.repalceState()。
这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。
唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据。
即:
输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。
Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。
其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View
实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
如果被问到 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() 也能对对象和数组(部分方法的操作)进行监听。
受现代 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 的功能所调用的方法)
保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
虚拟 DOM 的实现原理主要包括以下 3 部分:
用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
diff 算法 — 比较两棵虚拟 DOM 树的差异;
pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
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
}
v-if 和 v-show 区分使用场景
computed 和 watch 区分使用场景
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
长列表性能优化
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染 SSR or 预渲染
Webpack 对图片进行压缩
减少 ES6 转为 ES5 的冗余代码
提取公共代码
模板预编译
提取组件的 CSS
优化 SourceMap
构建结果输出分析
Vue 项目的编译优化
开启 gzip 压缩
浏览器缓存
CDN 的使用
使用 Chrome Performance 查找性能瓶颈
Vue 3.0 的目标是让 Vue 核心变得更小、更快、更强大,因此 Vue 3.0 增加以下这些新特性:
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.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。
同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。
vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。
3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。
此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。
现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。
vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其他的更改:
支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。
基于 treeshaking 优化,提供了更多的内置功能。
使用发布订阅模式将数据劫持和模板编译结合,实现双向绑定
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。
Object.defineProperty
是 JavaScript 中用来定义对象属性的方法。它接受三个参数:
obj
):要定义属性的对象。prop
):要定义或修改的属性名。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
),因此任何对该属性的赋值操作都会被忽略。
vue2 用的是 Object.defindProperty 但是vue3用的是Proxy
一次只能对一个属性进行监听,需要遍历来对所有属性监听
对于对象的新增属性,需要手动监听
对于数组通过push、unshift方法增加的元素,也无法监听
patch将新老VNode节点进行比对,然后将根据两者的比较结果进行最小单位地修改视图,而不是将整个视图根据新的VNode重绘。patch的核心在于diff算法,这套算法可以高效地比较virtual DOM的变更,得出变化以修改视图。
diff算法核心是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。
同层级比较(只比较同一层级,不跨级比较)
tag 不相同,则直接删除重建,不在深度比较
tag 和 key,两个都相同,则认为是相同节点,会进行深度比较
Vue 的模板编译原理可以概括为以下几个步骤:
解析:Vue 将模板字符串转换为抽象语法树 (AST)。它使用 HTML 解析器将模板解析为 DOM 树,并使用指令解析器将指令和表达式解析为对应的 AST 节点。
优化:Vue 对生成的 AST 进行静态节点标记和静态根节点提升的优化过程。静态节点是在编译阶段确定不需要改变的节点,可以被直接复用,从而提高渲染性能。静态根节点提升将多次出现的静态子树提升到 render 函数外部,避免每次重新创建静态子树。
代码生成:Vue 将优化后的 AST 通过代码生成器转换为渲染函数。渲染函数是一个 JavaScript 函数,它会在组件实例发生变化时被调用,生成最终的虚拟 DOM 并更新真实 DOM。
渲染:每当组件的状态发生变化时,Vue 会调用渲染函数来重新生成虚拟 DOM,并计算出与上一次渲染结果的差异 (Diff)。然后,Vue 会将差异应用到真实 DOM 上,完成视图的更新。
通过模板编译的过程,Vue 实现了将模板转换为可复用的渲染函数的能力。这种方式使得开发者可以使用类似于传统模板引擎的语法来编写组件的模板,并在运行时将其转换为高效的渲染函数,实现数据驱动视图的更新。
1、【模板编译】将template模板,经过编译系统后生成VNode,(模板字符串→AST→Render函数)
2、【渲染】然后再通过渲染系统来将VNode生成真实DOM(document.createElement && Mount挂载到真实DOM节点上)
3、【响应式】通过响应式系统对数据进行监听,当数据发生改变时,触发依赖项(组件)
4、【diff & Patch】组件内收到通知后,会通过diff算法对比VNode的变化,尽可能复用代码,找出最小差异,保证性能消耗最小。
5、【渲染】拿到需要新增/删除/修改的VNode后,逐一去操作真实DOM进行修改(通过选择器选择到对应真实DOM节点进行修改)
Webpack 是一个 静态模块打包器,可以分析各个模块的依赖关系,项目中的所有资源皆为模块,通过分析模块间的依赖关系,在其内部递归构建出一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle。最终编绎输出模块为 HTML、JavaScript、CSS 以及各种静态文件(图片、字体等)。
webpack 就像一条生产线,要经过一系列处理流程(loader)后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。
插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。
模块打包 可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。
编译兼容 在前端的“上古时期”,手写一堆浏览器兼容代码一直是令前端工程师头皮发麻的事情,而在今天这个问题被大大的弱化了,通过webpack的Loader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less,.vue,.jsx这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。
能力扩展 通过webpack的Plugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。
Webpack 的打包过程可以大致分为以下几个步骤:
解析配置:Webpack 会读取并解析项目根目录下的配置文件(例如:webpack.config.js),获取打包相关的配置信息。
入口分析:根据配置中指定的入口文件(entry),Webpack 会从入口开始递归地解析模块依赖关系。Webpack 会根据入口文件及其所依赖的文件,构建起一个完整的模块依赖图(Dependency Graph)。
模块转换:Webpack 在解析每个模块时,会根据配置中的规则(loaders)对模块进行相应的转换操作。Loaders 可以将模块源代码转换成 Webpack 可以处理的有效模块代码,例如将 ES6 语法转换为 ES5、将 Sass 编译为 CSS 等。
模块组装:经过模块转换后,Webpack 会得到一组已转换的模块。这些模块通过加载器产生的中间代码(通常是 CommonJS 或 ES Module 格式),被组装成一个或多个 Chunk。Chunk 是由模块之间的依赖关系构成的一个代码块。
输出生成:在组装完成后,Webpack 将根据配置中的出口设置(output)来确定输出的文件名和路径,并将 Chunk 内容写入指定的输出文件中。同时,根据需要,可以对输出文件进行压缩、混淆、代码分割等优化处理。
构建完成:Webpack 在整个构建过程中,会生成一系列的构建信息、警告和错误提示,以便开发者进行问题排查和优化。最终,Webpack 将输出构建结果的统计信息,包括文件大小、模块数量等。
总的来说,Webpack 的打包原理是通过解析配置文件、分析入口文件依赖、应用加载器转换模块、组装模块成 Chunk,并将结果进行输出。在这个过程中,Webpack 提供了丰富的插件和工具,使得开发者能够根据需求自定义构建流程,并实现各种功能,例如代码拆分、按需加载、缓存优化等。
webpack中的loader是一个函数,主要为了实现源码的转换,所以loader函数会以源码作为参数,比如,将ES6转换为ES5,将less转换为css,然后再将css转换为js,以便能嵌入到html文件中。
默认情况下,webpack只支持对js和json文件进行打包,但是像css、html、png等其他类型的文件,webpack则无能为力。因此,就需要配置相应的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 文件,以方便断点调试。
plugin是一个类,类中有一个apply()方法,主要用于Plugin的安装,可以在其中监听一些来自编译器发出的事件,在合适的时机做一些事情。
webpack中的plugin赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在webpack的不同阶段(钩子 / 生命周期),贯穿了webpack整个编译周期。目的在于「解决 loader 无法实现的其他事」。
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:定义环境变量
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 的实例,参数都通过构造函数传入。
热更新的核心就是客户端从服务端拉去更新后的文件,准确的说是 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 操作,也就是进行浏览器刷新来获取最新打包代码。
Webpack 中将 require 替换为 webpack_require,会根据 moduleId 到 installedModules 找是否加载过,加载过则直接返回之前的 export,不会重复加载。
webpack 4.0默认在生产环境的时候是支持代码压缩的,即mode=production模式下。实际上webpack 4.0默认是使用terser-webpack-plugin这个压缩插件,在此之前是使用 uglifyjs-webpack-plugin,两者的区别是后者对 ES6 的压缩不是很好,同时我们可以开启 parallel参数,使用多进程压缩,加快压缩。
CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等。可以使用另外一个插件:css-minimizer-webpack-plugin。
使用HtmlWebpackPlugin插件来生成 HTML 的模板时候,通过配置属性minify进行 html 优化。
module.exports = {
plugin:[
new HtmlwebpackPlugin({
minify:{
minifyCSS: false, // 是否压缩css
collapseWhitespace: false, // 是否折叠空格
removeComments: true // 是否移除注释
}
})
]
}
配置image-webpack-loader
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文件
]
排除webpack不需要解析的模块,即在使用loader的时候,在尽量少的模块中去使用。可以借助 include和exclude这两个参数,规定loader只在那些模块应用和在哪些模块不应用。
使用bable-plugin-transform-runtime插件
通过配置CommonsChunkPlugin插件,将多个页面的公共代码抽离成单独的文件
组件懒加载、路由懒加载、开启gzip、公共的第三方包上cdn、配置cache缓存Loader对文件的编译副本、配置resolve提高文件的搜索速度(@: src)
为了让浏览器缓存发挥最大作用,该策略尽量遵循以下五点就能发挥浏览器缓存最大作用。
「考虑拒绝一切缓存策略」: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文件引入文件名发生改变才会下载最新文件
**「渲染层面」**的性能优化,无疑是如何让代码解析更好执行更快。因此笔者从以下五方面做出建议。
「CSS策略」:基于CSS规则
「DOM策略」:基于DOM操作
「阻塞策略」:基于脚本加载
「回流重绘策略」:基于回流重绘
「异步更新策略」:基于异步更新
上述五方面都是编写代码时完成,充满在整个项目流程的开发阶段里。因此在开发阶段需时刻注意以下涉及到的每一点,养成良好的开发习惯,性能优化也自然而然被使用上了。
渲染层面的性能优化更多表现在编码细节上,而并非实体代码。简单来说就是遵循某些编码规则,才能将渲染层面的性能优化发挥到最大作用。
**「回流重绘策略」**在渲染层面的性能优化里占比较重,也是最常规的性能优化之一。上年笔者发布的掘金小册《玩转CSS的艺术之美》使用一整章讲解回流重绘,本章已开通试读,更多细节请戳这里。
避免出现超过三层的嵌套规则
避免为ID选择器添加多余选择器
避免使用标签选择器代替类选择器
避免使用通配选择器,只对目标节点声明规则
避免重复匹配重复定义,关注可继承属性
缓存DOM计算属性
避免过多DOM操作
使用DOMFragment缓存批量化DOM操作
阻塞策略
脚本与DOM/其它脚本的依赖关系很强:对
六大指标基本囊括大部分性能优化细节,可作为九大策略的补充。笔者根据每条性能优化建议的特征将指标划分为以下六方面。
「加载优化」:资源在加载时可做的性能优化
「执行优化」:资源在执行时可做的性能优化
「渲染优化」:资源在渲染时可做的性能优化
「样式优化」:样式在编码时可做的性能优化
「脚本优化」:脚本在编码时可做的性能优化
「V8引擎优化」:针对V8引擎特征可做的性能优化
主要分成两部分:渲染引擎(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 的分支)]
1.压缩 css, js, 图片
2.减少 http 请求次数, 合并 css、js 、合并图片(雪碧图)
3.使用 CDN
4.减少 dom 元素数量
5.图片懒加载
6.静态资源另外用无 cookie 的域名
7.减少 dom 的访问(缓存 dom)
8.巧用事件委托
9.样式表置顶、脚本置低
大致可以分为如下7步:
输入网址;
发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;
与web服务器建立TCP连接;
浏览器向web服务器发送http请求;
web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);
浏览器下载web服务器返回的数据及解析html源文件;
生成DOM树,解析css和js,渲染页面,直至显示完成;
一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
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 服务器端暂时无法处理请求(可能是过载或维护)。
当页面滚动的时间被触发 -> 执行加载图片操作 -> 判断图片是否在可视区域内 -> 在,则动态将data-src的值赋予该图片
尽量使用css3动画,开启硬件加速
适当使用touch时间代替click时间
避免使用css3渐变阴影效果
可以用transform: translateZ(0) 来开启硬件加速
不滥用float。float在渲染时计算量比较大,尽量减少使用
不滥用web字体。web字体需要下载,解析,重绘当前页面
合理使用requestAnimationFrame动画代替setTimeout
css中的属性(css3 transitions、css3 3D transforms、opacity、webGL、video)会触发GUP渲染,耗电
为了准确无误地吧数据送达目标处,TCP协议采用了三次握手策略。用TCP协议把数据包送出去后,TCP不会对传送后的情况置之不理,他一定会向对方确认是否送达,握手过程中使用TCP的标志:SYN和ACK
发送端首先发送一个带SYN的标志的数据包给对方
接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息
最后,发送端再回传一个带ACK的标志的数据包,代表“握手”结束
如在握手过程中某个阶段莫明中断,TCP协议会再次以相同的顺序发送相同的数据包
第一次挥手:主动关闭方发送一个FIN,用来关注主动方到被动关闭方的数据传送,也即是主动关闭方告诫被动关闭方:我已经不会再给你发数据了(在FIN包之前发送的数据,如果没有收到对应的ACK确认报文,主动关闭方依然会重发这些数据)。但是,此时主动关闭方还可以接受数据
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号收到序号 +1(与SYN相同,一个 FIN占用一个序号)
第三次挥手:被动关闭方发送一个 FIN。用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会给你发送数据了
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手
HTTP协议通常承载与 TCP协议之上,在HTTP和TCP之间添加一个安全协议层(SSL或TSL),这个时候,就成了我们常说的HTTPS
默认HTTP的端口号为80,HTTPS的端口号为443
因为网络请求需要中间有很多的服务器路由的转发,中间的节点都可能篡改信息,而如果使用HTTPS,密钥在你和终点站才有,https之所有说比http安全,是因为他利用ssl/tls协议传输。包含证书,流量转发,负载均衡,页面适配,浏览器适配,refer传递等,保障了传输过程的安全性
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,它本身具有以下特征
从浏览器中创建 XMLHttpRequest
支持 Promise API
客户端支持防止CSRF
提供了一些并发请求的接口(重要,方便了很多的操作)
从 node.js 创建 http 请求
拦截请求和响应
转换请求和响应数据
取消请求
自动转换JSON数据
语法简洁,更加语义化
基于标准 Promise 实现,支持 async/await
同构方便,使用 isomorphic-fetch
更加底层,提供的API丰富(request, response)
脱离了XHR,是ES规范里新的实现方式
fetch是一个低层次的API,你可以把它考虑成原生的XHR,所以使用起来并不是那么舒服,需要进行封装。
fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})
fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
fetch没有办法原生监测请求的进度,而XHR可以
注意:用心找自己做的项目中自己感觉最拿出来手的(复杂度最高,用的技术最多的项目),描述的时候尽可能往里面添加一些技术名词
布局我们用html5+css3
我们会用reset.css重置浏览器的默认样式
JS框架的话我们选用的是jQuery(也可能是Zepto)
我们用版本控制工具git来协同开发
我们会基于gulp搭建的前端自动化工程来开发(里面包含有我们的项目结构、我们需要引用的第三方库等一些信息,我们还实现了sass编译、CSS3加前缀等的自动化)
我们的项目中还用到了表单验证validate插件、图片懒加载Lazyload插件
前端是最贴近用户的程序员,比后端、数据库、产品经理、运营、安全都近。
1、实现界****面交互****
2、提升用户体验
3、有了Node.js,前端可以实现服务端的一些事情
前端是最贴近用户的程序员,前端的能力就是能让产品从 90分进化到 100 分,甚至更好,
参与项目,快速高质量完成实现效果图,精确到1px;
与团队成员,UI设计,产品经理的沟通;
做好的页面结构,页面重构和用户体验;
处理hack,兼容、写出优美的代码格式;
针对服务器的优化、拥抱最新前端技术。
在flex布局中,有几个重要的周边属性可以调整弹性容器和弹性项目之间的间距和对齐方式。以下是这些属性的介绍:
justify-content
:用于设置弹性项目在主轴上的对齐方式。它可以接受以下值:flex-start
:将弹性项目靠近主轴起始端对齐。flex-end
:将弹性项目靠近主轴结束端对齐。center
:将弹性项目居中对齐。space-between
:将弹性项目平均分布在主轴上,首个项目靠近起始端,最后一个项目靠近结束端,项目之间的间距相等。space-around
:将弹性项目平均分布在主轴上,包括首尾项目两侧,项目之间和项目与容器之间的间距相等。align-items
:用于设置弹性项目在交叉轴上的对齐方式。它可以接受以下值:flex-start
:将弹性项目靠近交叉轴起始端对齐。flex-end
:将弹性项目靠近交叉轴结束端对齐。center
:将弹性项目在交叉轴上居中对齐。stretch
:将弹性项目拉伸以填充交叉轴。baseline
:将弹性项目的基线对齐。align-content
:用于设置多行弹性容器在交叉轴上的对齐方式。它可以接受以下值:flex-start
:将多行弹性容器靠近交叉轴起始端对齐。
flex-end
:将多行弹性容器靠近交叉轴结束端对齐。
center
:将多行弹性容器在交叉轴上居中对齐。
space-between
:将多行弹性容器平均分布在交叉轴上,首行靠近起始端,末行靠近结束端,行与行之间间距相等。
space-around
:将多行弹性容器平均分布在交叉轴上,包括首尾两侧,行与行之间和容器与首尾行之间的间距相等。
stretch
:如果有剩余空间,将行高拉伸以填充交叉轴。
flex-direction
flex-direction 设置主轴 的方向。
flex-wrap
设置是否换行展示
以上就是flex布局中常用的周边属性,它们可以帮助您控制弹性容器和弹性项目的对齐和间距。
flex-flow
属性是以下属性的简写属性:在CSS预处理器(如Sass、Less)中,"mixins"和"extends"是两个用于代码复用的关键概念。
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"选择器内部,从而实现了样式的复用。
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代码的效率和可维护性。
无感登录(Seamless login)是一种用户在使用应用程序或网站时无需手动输入用户名和密码进行认证的登录方式,通常通过利用持久性凭证、单点登录或社交媒体授权等方式来实现。以下是实现无感登录的一些常见方法:
持久性凭证(Persistent Tokens):该方法通过在用户设备上存储持久性凭证(如长期有效的令牌或 Cookie),使得用户下次访问应用程序时可以自动识别并进行登录。这种凭证可以使用加密技术保证安全性。
单点登录(Single Sign-On, SSO):SSO 是一种身份验证机制,允许用户通过一次登录即可访问多个关联应用程序或网站。用户在通过认证后会获得一个令牌,该令牌可以在不同的应用程序之间共享,避免了重复登录的繁琐过程。
社交媒体授权(Social Media Authorization):通过允许用户使用其社交媒体账户(如Facebook、Google等)进行登录,应用程序可以获取到用户授权的信息,并将其作为有效的身份验证凭据使用。
在实现无感登录时,需要考虑以下几点安全性问题:
综上所述,实现无感登录可以结合持久性凭证、单点登录和社交媒体授权等技术手段,同时务必关注用户隐私和系统安全,确保用户登录过程的便利性和安全性。
在Vue中,自定义指令可以通过钩子函数来扩展元素和组件的行为。以下是Vue自定义指令的常见钩子函数和属性:
除了钩子函数中的属性外,还可以在自定义指令中使用一些全局属性和方法,如Vue.directive
用于注册全局自定义指令,以及el
、componentInstance
等属性。
需要注意的是,不同的钩子函数中可用的参数可能会有所不同,请根据实际需要选择合适的钩子函数和属性来实现自定义指令的功能。
在Vue的
组件中,可以使用max
属性来限制缓存的组件实例数量。当缓存的组件实例数量超过max
设置的数值10时,
组件会按照LRU(Least Recently Used)的方式移除最不活跃的组件实例,以保持缓存数量不超过设定值。
max
设置的数值10。max
,新的组件实例将被添加到缓存中,不会触发删除操作。max
,
组件会根据最近访问时间和使用情况,移除最不活跃的组件实例,释放资源空间,并将新的组件实例添加到缓存中。这样做的目的是控制缓存组件的数量,避免因缓存过多导致内存占用过高,同时保持较常用的组件实例在缓存中,提高系统性能和用户体验。
需要注意的是,超出max
设置的数值后的删除行为是自动触发的,并且无法通过配置进行更改。如果需要更细粒度地控制组件实例的缓存和销毁行为,可以考虑自定义缓存策略或动态调整max
的值。
在Vue的生命周期中,computed
属性的计算是在beforeMount
和mounted
勾子函数之间进行的。
beforeCreate
:实例刚在内存中创建,数据观测 (data observer) 和 event/watcher 事件配置之前。created
:实例已经完成了数据观测 (data observer),属性和方法的运算,watch/event 事件回调。但是还未挂载到DOM上。beforeMount
:在将实例挂载到DOM之前,此时computed属性的计算尚未开始。mounted
:实例已挂载到DOM上。这个阶段可以访问到DOM元素,进行一些初始化操作,例如使用外部插件、操作DOM等。beforeUpdate
:在数据更新之前,发生在虚拟DOM重新渲染和打补丁之前。这里可以在更新之前访问现有的DOM。updated
:重新渲染和打补丁之后,组件已更新。在这个钩子函数中,可以操作DOM,但要避免无限循环更新。beforeUnmount
(Vue 3中为beforeUnmount
):实例销毁之前调用。在这里可以进行必要的清理工作。unmounted
(Vue 3中为unmounted
):组件实例被销毁后调用。在这个阶段,组件的数据绑定和监听器都会被移除,可以进行最后的清理工作。根据上述顺序,在beforeMount
和mounted
之间打印computed
属性的值,可以在组件挂载到DOM之前获取计算属性的结果。
for...in
和for...of
都是用于遍历数据的循环语句,但它们有以下几个区别:
for...in
:用于遍历对象的可枚举属性。它会遍历对象自身及其原型链上的所有可枚举属性(包括继承而来的属性)。for...of
:用于遍历可迭代对象(例如数组、字符串、Map、Set等)。它可以遍历对象内部的元素或值。for...in
:返回的是属性名或键名(字符串类型)。在遍历对象时,可以通过获取属性值,进一步操作属性对应的值。for...of
:返回的是集合中的元素或值。在遍历可迭代对象时,直接获取对象的元素或值,而不需要访问键名。for...in
:遍历对象属性时,不保证顺序,因为它是根据原始插入顺序遍历属性,但在一些情况下可能会以其他顺序遍历。(例如数字属性会按照升序排列)for...of
:遍历可迭代对象时,可以按照元素在集合中的顺序进行迭代。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
用于遍历可迭代对象的元素或值。
nextTick
是Vue中的一个异步方法,用于在DOM更新后执行回调函数。它的原理如下:nextTick
方法的调用时机通常是在Vue的更新队列被添加后的微任务中执行。更新队列会在数据变化后触发,并对组件进行重新渲染。
当数据发生变化时,Vue会将需要更新的组件添加到一个异步的更新队列中,而不是立即进行DOM更新。
在下一次事件循环的微任务阶段,Vue会开始遍历更新队列并执行相应的更新操作。
在每个组件更新完成之后,nextTick
方法注册的回调函数会被调用。这确保了回调函数在DOM更新后执行。
由于使用微任务的方式,在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更新完成后执行回调函数。
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.');
复制代码
浏览器运行结果与方式一一致,此处不再赘述。
部署时使用哈希(hash)作为文件名的方式,可以有效解决浏览器缓存问题,确保用户获取到最新的文件版本。然而,使用哈希刷新后可能会导致白屏问题,这主要是因为以下原因:
缓存问题:
当文件名中的哈希发生变化时,浏览器将视为一个新的文件,并下载最新的文件。但在某些情况下,浏览器可能仍然会从缓存中加载旧文件,导致页面出现错误或白屏。
引用路径问题:
如果在 HTML 或 CSS 文件中引用了被哈希的资源文件,而没有及时更新对应的路径,浏览器将无法正确地加载新文件,从而导致白屏问题。
强制刷新页面:
可以通过按下 Ctrl + F5(Windows)或 Command + Shift + R(Mac)的组合键来强制刷新页面,忽略缓存并重新加载所有文件。
更新引用路径:
确保在 HTML 或 CSS 文件中对哈希文件的引用路径是正确的。可以修改对应文件引用的路径,或者使用相对路径或绝对路径来确保能够正确加载最新的文件。
清除浏览器缓存:
如果问题仍然存在,可以尝试清除浏览器缓存。具体的步骤取决于所使用的浏览器,可以通过浏览器设置或按照特定浏览器的教程进行操作。
使用版本号代替哈希:
考虑在文件名中使用带有版本号的方式来解决缓存问题,而不是使用哈希。每次更新文件时,修改文件名中的版本号,确保浏览器能够正确加载最新的文件。
请根据具体情况尝试这些方法,并确保在部署前进行充分测试,以避免出现白屏问题。
在前端开发中,可以使用路由库或框架来实现tab页的路由模式。以下是一般的实现步骤:
配置路由:
首先,您需要配置路由以定义每个tab对应的URL路径和组件。这可以通过路由库或框架的配置文件完成。
创建tab组件:
创建一个tab组件,用于显示和切换不同的标签页。该组件可以包含tab标题和一个用于渲染对应标签页内容的容器。
处理tab切换事件:
在tab组件中,为每个tab标题添加点击事件处理程序,以便在用户点击tab时触发路由切换操作。您可以使用路由库或框架提供的方法来进行导航操作,将当前URL更新为对应的tab路由。
渲染对应标签页内容:
当路由发生变化时,根据当前URL路径获取到对应的标签页组件,并将其渲染到tab组件的内容容器中。这通常由路由库或框架自动完成。
请注意,在实际开发中,具体的实现方式可能会因所使用的前端框架或库而有所差异。您可以参考相关框架或库的文档,查找适合您项目的具体实现方法。
同时,以下是几个常用的前端路由库或框架供您参考:
在 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
状态。
在路由跳转中要实现从 C 页面返回到 A 页面,可以使用路由导航的方法进行跳转和返回操作。以下是一种可能的解决方案:
从页面 A 跳转到页面 B:
router.push
或
组件来实现从 A 跳转到 B。从页面 B 跳转到页面 C:
在页面 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 分别与对应的路由关联。
希望对你有所帮助!如果还有其他问题,请随时提问。
前端部署和配置依赖于你使用的前端框架和工具。下面是一般性的步骤来帮助你进行前端部署和配置:
打包静态资源:在部署前,首先需要将前端代码打包成静态资源。大多数前端框架和构建工具(如Webpack、Parcel、Rollup等)都提供了打包功能。运行相应的打包命令,它们会将你的源代码编译、压缩和打包成最终的静态文件。
选择合适的服务器:选择适合你的项目的服务器。常用的选择包括:
配置服务器:根据你选择的服务器类型,对服务器进行相应的配置:
上传静态资源:将打包生成的静态资源上传到服务器。具体的上传方法和工具取决于你使用的服务器和部署方式:
配置域名和端口:根据你的需求,为部署的前端应用配置域名和端口。这涉及到DNS解析和服务器端口配置,具体操作可以参考相关文档或咨询服务提供商。
以上是一个简单的前端部署和配置流程的概述。实际的部署过程可能因具体项目和环境而有所不同。确切的步骤和指令取决于你所使用的前端框架、工具和服务器类型。
希望对你有所帮助!如有其他问题,请随时提问。
Sass和Less是两种常用的CSS预处理器,它们为CSS提供了更强大的功能和易用性。下面是它们的一些主要属性和配置:
$
符号定义变量,并可以在样式中多次引用。@extend
关键字实现样式的继承,减少重复代码。@import
导入外部Sass文件,可以模块化管理样式。@
符号定义变量,并可以在样式中多次引用。&:extend
关键字实现样式的继承,减少重复代码。@import
导入外部Less文件,可以模块化管理样式。@import
导入其他Sass或Less文件时,要注意导入顺序,确保样式按正确顺序加载。需要注意的是,具体的属性和配置细节可能会因为不同版本和工具的使用而有所变化。建议查阅相关文档或官方指南以获取最新和准确的信息。
答案: Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,可以让JavaScript代码在服务器端运行。与传统的前端开发相比,Node.js使得开发者可以使用JavaScript开发后端应用程序。
答案: Node.js具有以下几个特点:
Node.js适用于构建高并发、实时性要求高的网络应用,如聊天室、实时通讯应用、推送服务、大规模数据处理等
答案和解析:
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
方法指定监听的端口,并在回调函数中输出服务器运行信息。
答案: 在Node.js中,模块是可以重用的代码块,用于组织和封装相关功能。每个文件就是一个模块,模块里面定义的变量、函数或类默认只在当前模块内可见,不同模块之间可以通过导入和导出的方式进行交互。
在Node.js中,可以使用require
函数来导入其他模块的导出部分,并将其赋值给一个变量,然后可以通过该变量访问被导入模块的内容。例如:
// 导入名为myModule的模块
const myModule = require('./myModule');
// 使用myModule模块中导出的函数
myModule.myFunction();
答案: require()
函数用于在Node.js中加载模块,并返回模块的导出部分(exports)。它接收一个模块标识符作为参数,可以是相对路径或模块名。
在加载模块时,Node.js会根据传入的标识符解析模块的位置。如果是相对路径,则根据当前模块的位置进行解析;如果是模块名,则按照一定的规则搜索模块并加载。
模块可以通过exports
对象来导出其功能。在模块内部,可以将需要导出的变量、函数或类赋值给exports
对象的属性。例如:
// myModule.js
exports.myFunction = () => {
console.log('Hello from myModule!');
};
其他模块在使用该模块时,可以通过require()
函数导入,并访问其中导出的内容。
http
:用于创建基于HTTP协议的服务器和客户端。fs
:提供文件系统相关功能,例如读取和写入文件。path
:用于处理文件路径的工具函数。os
:提供操作系统相关的信息和操作。events
:用于处理事件的模块。then()
方法处理成功的结果,使用catch()
方法捕获错误。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.');
});
在Express框架中,可以使用中间件(如body-parser
)来解析请求体,获取表单提交的数据。通过Express提供的路由功能,可以根据不同的HTTP请求方法和URL路径,编写对应的处理函数来处理表单提交的数据。处理函数可以访问请求对象(req)中的参数和数据,并进行相应的处理逻辑,最后返回响应给客户端。
Express框架是一个基于Node.js的快速、灵活的Web应用程序框架,它简化了Web应用程序的开发过程。它的主要用途包括路由管理、中间件支持、静态文件服务、模板引擎等。相比原生的Node.js开发,Express提供了更简洁、高效的API和丰富的插件生态系统,使得开发Web应用变得更加容易。
在Express框架中,可以使用app.get()
、app.post()
等方法定义路由来处理不同的HTTP请求。根据请求的URL路径和请求方法,配置对应的路由处理函数,完成具体的业务逻辑。以RESTful API为例,其设计原则包括:
事件驱动编程是一种编程范式,基于事件和事件处理函数之间的关系来组织代码逻辑。在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事件"。
中间件是一种在请求和响应的处理过程中,位于应用程序和服务器之间的函数或功能模块。在Node.js中,Express框架广泛使用中间件机制来实现各种功能,如路由处理、错误处理、身份验证等。
中间件在请求到达路由处理函数之前或响应发送给客户端之前,对请求和响应进行预处理、转换或执行其他操作。它可以修改请求和响应对象、调用下一个中间件函数,或终止请求-响应周期。
通过使用中间件可以实现以下功能:
安装相关数据库驱动:首先根据需要选择合适的数据库,然后使用npm(Node.js包管理工具)安装相应的数据库驱动模块,例如MongoDB可以使用npm install mongodb
命令安装。
引入数据库驱动模块:在代码中引入所需的数据库驱动模块,例如MongoDB可以使用require('mongodb')
进行引入。
建立数据库连接:创建一个到数据库的连接,提供数据库的连接配置信息,例如服务器地址、端口号、认证凭证等。
执行数据库操作:使用数据库驱动模块提供的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();
});
WebSocket是一种在单个TCP连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时数据传输,而不需要在每个请求-响应周期中建立新的连接。
与HTTP相比,WebSocket具有以下区别:
连接方式:HTTP是无状态的请求-响应协议,在每个请求中都需要重新建立连接。而WebSocket在初始化阶段通过HTTP协议进行握手,完成后建立了一条持久的连接,可以在任意时刻双向传输数据。
传输效率:由于WebSocket建立了持久连接,避免了多次创建和关闭连接的开销,因此在频繁通信和大量数据传输场景下更高效。
支持实时性:WebSocket支持服务器主动推送数据给客户端,使得实时性要求较高的应用(如聊天应用、股票行情等)能够及时更新数据,而不需要客户端不断轮询或发送请求。
需要注意的是,WebSocket协议作为一个独立的协议存在,与HTTP协议并没有本质的关系,但WebSocket的握手过程使用了HTTP协议来完成初始连接的建立。
前端常见的跨域处理方式包括以下几种:
JSONP(JSON with Padding):JSONP是一种利用。回调函数名是前端定义的一个函数,用于处理从服务器返回的数据。
function callbackFunc(data) { console.log(data); }
。callbackFunc({"name": "John", "age": 25})
。这种方法利用了script标签不受同源策略限制的特性,通过动态创建script标签来实现跨域请求,并利用回调函数来处理返回的数据。但需要注意的是,JSONP只支持GET请求,且返回的数据必须是经过特定格式包装的JavaScript代码,需要服务器端的支持。
在前端中,使用iframe进行跨域操作可以通过以下几种方式实现:
这些方法都是基于浏览器的同源策略下的漏洞或者特殊机制来实现跨域操作的,需要注意使用时的安全性和兼容性问题。
可以通过以下几种方法来防止用户多次提交表单,减少服务器端的压力:
以上方法可以结合使用,根据具体的场景和需求选择合适的防止用户多次提交表单的方法。
您可以使用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标签的提示信息。
左固定布局,右侧自适应布局可以通过以下几种方式实现:
.left {
float: left;
width: 200px;
}
.right {
width: 100%;
}
左侧固定内容
右侧自适应内容
.container {
display: flex;
flex-direction: row;
}
.left {
width: 200px;
}
.right {
flex: 1;
}
右侧自适应内容
.container {
display: grid;
grid-template-columns: 200px auto;
}
.left {
width: 200px;
}
左侧固定内容
右侧自适应内容
以上是几种常见的实现左固定布局,右侧自适应布局的方法,根据
思路:根据打印情况,猜想需要返回一个函数,考点也就在闭包上(当时还思考了迭代器、函数柯里化两个知识点)
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'
思路:考算法,递归可能是最先被想到,其实动态规划也简单,我习惯使用,只要维护两个数,两数之和即是下一位数,一直反复推出后面的数字即可
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
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
以上代码分别使用递归和迭代的方式实现了斐波那契数列。在递归方式中,通过递归调用函数本身来计算斐波那契数列的值。在迭代方式中,通过循环来计算斐波那契数列的值。两种方式都可以得到相同的结果,但迭代方式通常比递归方式更高效。
您可以定义一个名为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
思路:类似于搜索字符串的公共前缀,那么双循环就可以搞定,但是得对数组处理一下,因为路径里包含了 ‘/’ 需要除去,所以返回答案时还要还原回来
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函数来查找是否有公共路径。最后,打印结果即可。运行代码后,输出的结果为"/",表示存在公共路径"/"。
思路:三循环暴力求解,暂时没想到更好的方法,先感谢各位观众给出更好的方法了(评论区已有大佬给出一种双指针的解法了)
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 小的勾股数。
例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’,表示字符串已成功驼峰化。
解法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。运行代码后,可以得到相应的结果
在JavaScript中,时间复杂度和空间复杂度是衡量算法性能的两个重要指标。
时间复杂度(Time Complexity)表示算法执行所需要的时间与问题规模之间的关系。常见的时间复杂度有:
空间复杂度(Space Complexity)表示算法执行所需要的额外空间与问题规模之间的关系。常见的空间复杂度有:
要分析算法的时间复杂度和空间复杂度,可以根据算法中的循环、递归、函数调用等操作来确定执行次数,并根据问题规模的变化情况来确定复杂度的增长趋势。
需要注意的是,时间复杂度和空间复杂度是描述算法性能的理论指标,只是对算法的估计,并不能精确地表示
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的父元素上,或者使用计算属性来处理过滤后的列表。
以下是示例代码:
在上述代码中,我们将v-if放置在包裹v-for的父元素上,并使用计算属性filteredList来处理过滤后的列表数据。通过这样的方式,可以确保v-if在v-for之前进行判断,从而避免了两者冲突的问题。
闭包可以在以下情况下使用:
需要注意的是,使用闭包也会带来一些问题,例如内存泄漏和性能问题。因此,在使用闭包时需要注意内存管理和性能优化
// 输出: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
思路:类似于搜索字符串的公共前缀,那么双循环就可以搞定,但是得对数组处理一下,因为路径里包含了 ‘/’ 需要除去,所以返回答案时还要还原回来
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函数来查找是否有公共路径。最后,打印结果即可。运行代码后,输出的结果为"/",表示存在公共路径"/"。
思路:三循环暴力求解,暂时没想到更好的方法,先感谢各位观众给出更好的方法了(评论区已有大佬给出一种双指针的解法了)
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 小的勾股数。
例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’,表示字符串已成功驼峰化。
解法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。运行代码后,可以得到相应的结果
在JavaScript中,时间复杂度和空间复杂度是衡量算法性能的两个重要指标。
时间复杂度(Time Complexity)表示算法执行所需要的时间与问题规模之间的关系。常见的时间复杂度有:
空间复杂度(Space Complexity)表示算法执行所需要的额外空间与问题规模之间的关系。常见的空间复杂度有:
要分析算法的时间复杂度和空间复杂度,可以根据算法中的循环、递归、函数调用等操作来确定执行次数,并根据问题规模的变化情况来确定复杂度的增长趋势。
需要注意的是,时间复杂度和空间复杂度是描述算法性能的理论指标,只是对算法的估计,并不能精确地表示
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的父元素上,或者使用计算属性来处理过滤后的列表。
以下是示例代码:
在上述代码中,我们将v-if放置在包裹v-for的父元素上,并使用计算属性filteredList来处理过滤后的列表数据。通过这样的方式,可以确保v-if在v-for之前进行判断,从而避免了两者冲突的问题。
闭包可以在以下情况下使用:
需要注意的是,使用闭包也会带来一些问题,例如内存泄漏和性能问题。因此,在使用闭包时需要注意内存管理和性能优化