# 面试题
# 自我介绍
我叫曾伟浩,2024年毕业于韩山师范学院,软件工程专业,本科。
(我在校多次参加编程竞赛活动,获得过国家奖学金)
我熟悉HTML、CSS、JavaScript,,并且熟悉掌握Vue、Uni-app等前端框架,以及Express、Koa等后端开发框架,了解并使用过React、Nuxt、Laravel等框架。
我在上一份工作从事 开发助理,从事微信公众号的开发与测试,用的技术栈主要是Vue、Element-UI、Vant。
在项目中,我经常使用版本控制工具Git来协同开发,同时我也非常注重代码的可读性和可维护性。
在工作中,我善于沟通和合作,乐于和团队成员一起讨论和解决问题,共同完成项目。
在平时,我也非常注重自我学习和提升,喜欢阅读前端相关的技术文章,以保持自己的技术竞争力。
总之,我热爱前端开发这个行业。谢谢!
# 反问
- 转正,比例
- 工作安排
- 津贴相关
- 实习期过后
# HTML/CSS
# Canvas
Canvas是HTML5新增的元素,是由HTML代码配合高度和宽度属性而定义出的一个绘制区域,我们可以使用JavaScript在Canvas内部进行绘制。
Canvas只有两个可选属性,一个是宽度width,一个高度height。如果你不设置宽高的话,默认的宽度width是300px,height是150px;
我们也可以使用css来设置canvas的宽高,但如果设置的宽高和初始宽高比例不一致的话,就会出现扭曲现象。
# 重排 reflow 重绘 repaint
重排也叫回流,当渲染数中的某部分或全部因为元素的规模尺寸,或者布局等改变了,而需要重新构建页面时,这就称为重排/回流。
重绘当某元素外观的改变 所触发的浏览器重绘行为,浏览器会根据元素的新属性重新绘制,重绘后元素呈现新的外观。
重排,重新排列,一般发生在布局变化或者元素大小发生变化时 重绘,重新绘制,一般发生在布局不变,视觉上变化的时候譬如阴影颜色啥的
# px,em,rem,vh,vw区别
px:相对于显示器分辨率而言。
em:其值基于其父元素的font-size。
rem:基于html的font-size。
1rem = 16px。
vm:视口宽度。
vh:视口高度。
# BFC
(Block Formatting Context) 块级格式化上下文
什么是BFC?
- BFC可以让元素成为隔离独立的容器。
- 且该隔离的容器内的子元素不会影响到外部的布局。
BFC可以干什么?
- 解决兄弟和父子之间的margin塌陷(垂直)
- 清除浮动带来的塌陷。
如何创建BFC?
- float:left/right
- position:absolute/fixed
- display:inline-block
- display:flex
- overflow:hidden/scroll/auto
# 清除浮动
- 给父元素定义height
- 结尾处加空div标签clear:both
- *父级div定义伪类:after和zoom*
- 父级div定义overflow:hidden
- 父级div也跟着浮动,需要定义宽度
- 结尾处加br标签 clear:both
# 标准盒模型和怪异盒模型
标准盒模型
box-sizing的默认值为:content-box;
即元素实际宽度尺寸 = width + padding + border
设置padding和border会影响元素的最终尺寸。
2
3
怪异盒模型(IE盒)
box-sizing: border-box;
即元素实际宽度尺寸 = width(宽度)
设置padding 和 border 不会改变元素的最终尺寸。
2
3
# 水平/垂直居中
水平居中
1. 行内元素
text-align: center;
2. 块级元素
margin: 0 auto;
3. 绝对定位50% + 自我位移50%
position: absolute;
left: 50%;
transform: translateX(-50%);
4. flex布局
display: flex;
justify-content: center;
2
3
4
5
6
7
8
9
10
11
12
13
14
垂直居中
5. 设置行高为本元素的高度
height: 100px;
line-height: 100px;
6. 绝对定位50% + 自我位移50%
position: absolute;
top: 50%;
transform: translateY(-50%);
7. flex布局
display: flex;
align-item: center;
8. table布局
父元素:
display: table;
子元素:
display: table-cell;
vertical-align: middle;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# CSS选择器优先级
1. !important
2. 内联样式
3. ID选择器
4. 类选择器
5. 元素选择器
6. 关系选择器
2
3
4
5
6
# JS
# 数据类型
值类型(基本类型)
字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol、BigInt。
引用数据类型(对象类型)
对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)。
基本类型与对象类型的区别
在 JavaScript 中,基本类型和引用类型具有以下区别:
存储方式:基本类型的值被直接存储在变量所分配的内存空间中,而引用类型的值是存储在堆内存中的对象,并且变量存储的只是对象的引用地址。
基本数据类型:基本类型值在内存中占据固定大小,直接存储在栈内存中的数据 引用数据类型:引用类型在栈中存储了指针,这个指针指向堆内存中的地址,真实的数据存放在堆内存里。
复制方式:当复制基本类型的值时,会创建一个新的值,并将其赋值给新的变量;而复制引用类型的值时,只会复制引用地址,指向同一个对象。
比较方式:基本类型的比较是按照值进行的,只要值相等,它们就被认为是相等的;引用类型的比较是比较它们的引用地址,即使内容相同,引用不同也会被认为是不相等的。
可变性:基本类型是不可变的,一旦创建就无法修改其值;而引用类型是可变的,可以通过修改对象的属性来改变对象的值。
传递方式:将基本类型作为参数传递给函数时,实际上是将值的副本传递给函数;将引用类型作为参数传递给函数时,传递的是引用地址,因此函数内部对对象的修改会影响到原始对象。
常见的基本类型有:undefined、null、boolean、number和string。引用类型包括:对象(Object)、数组(Array)、函数(Function)等。
了解这些区别对于正确理解 JavaScript 中的数据传递、赋值、比较等操作是很重要的。
实例解析JavaScript的可修改与不可修改数据类型
JavaScript基本数据类型,包括Number/String/Boolean/null/undefined类型的变量,其内存中的值是不可变的,对这个变量重新赋值实际上是在内存中新建了一个值,将这个变量名指向了新建的值的地址,这种变量类型被称为不可修改类型(immutable)。 其他的数据类型,例如数组和对象,其值是可变的,对其值的修改就是在原始内存地址上面做修改,这种变量类型被称为可修改类型(mutable)。下面是一个示例代码。
var s = "abcdefg";
var s2 = s;
var arr = [1, 2, 3, s];
var arr2 = arr;
s = "hijklmn";
console.log(s2);
// 输出:abcdefg
// 上面给s赋予新的值,s2指向的还是执行赋值语句时s的地址,所以s2的值不变
arr[0] = 5;
console.log(arr2);
// 输出:Array(4) [5, 2, 3, "abcdefg"]
// 对arr的修改就是在arr变量的内存上面直接修改,arr2指向了arr相同的内存路径,所以arr2的值和arr一样发生了变化
// arr当中保存的是赋值语句执行时s的地址,不因为s后面地址变化而变化
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 四则运算
运算中其中一方为字符串,那么就会把另一方也转换为字符串
如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
2
3
# 作用域
全局作用域
var a = 1; function bar(){ console.log(a); } bar();//1
1
2
3
4
5函数作用域
var a = 10; function bar(){ var a = 20; console.log(a); } console.log(a);//10,取的全局作用域中的a bar();//20,取的局部作用域中的a
1
2
3
4
5
6
7块级作用域
块级作用域指的就是使用 if () { }; while ( ) { } …这些语句所形成的语句块 , 并且其中变量必须使用let或const声明(否则就不是块级作用域了),保证了外部不可以访问语句块中的变量。
if(true) { let name='douqing'; console.log(name); // douqing } console.log(name); // ReferenceError: name is not defined
1
2
3
4
5
# 作用域链
当查找变量的时候,会先从当前作用域的变量对象中查找,如果没有找到,就会从父级作用域(上层环境)的变量对象中查找,一直找到全局作用域的变量对象,也就是全局对象。这样由多个作用域的变量对象构成的链表就叫做作用域链。它由当前环境与上层环境的一系列变量对象组成,保证了当前执行环境对符合访问权限的变量和函数的有序访问。 说直白点,作用域的集合就是作用域链。(子集可以访问父集,父集不能访问子集)
function fn(){
var a=10;
function fn1(){
var b=20;
alert(a) //10
function fn2(){
alert(b) //20
alert(a) //10 子集可以跨级访问父级
}
fn2()
}
fn1()
}
fn()
alert(b) // 报错
2
3
4
5
6
7
8
9
10
11
12
13
14
15
作用域链的结构
函数在执行过程中,先从自己内部查找变量,如果找不到,再从创建当前函数所在的作用域去找,从此往上,也就是向上一级找,直到找到全局作用域还是没找到。
总结
定义:作用域的集合就是作用域链 1、函数在执行的过程中,先从自己内部寻找变量 2、如果找不到,再从创建当前函数所在的作用域去找,从此往上,也就是向上一级找。 当在作用域内访问 变量/方法 的时候,会找离自己最近的那个 变量/方法 (就近原则)
# 函数作用域
# class
# 深拷贝、浅拷贝
深拷贝和浅拷贝是用于复制对象或数组的两种不同的方式。
浅拷贝(Shallow Copy): 浅拷贝创建一个新的对象或数组,但它只拷贝了原始对象或数组的引用,而不是实际的值。也就是说,如果原始对象或数组中包含引用类型的数据(如对象或数组),浅拷贝只会复制这些数据的引用,而不是复制实际的数据内容。因此,当修改浅拷贝后的对象或数组时,原始对象或数组中对应的引用数据也会受到影响。
深拷贝(Deep Copy): 深拷贝创建一个完全独立的新对象或数组,它将原始对象或数组的所有值和引用类型的数据都复制到了新对象或数组中。即使原始对象或数组中包含引用类型的数据,深拷贝也会递归地复制这些数据的值或引用,确保新对象或数组与原始对象或数组完全独立,互不影响。
示例:
// 浅拷贝示例
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj);
shallowCopy.a = 3;
console.log(obj.a); // 输出: 1,原始对象不受影响
shallowCopy.b.c = 4;
console.log(obj.b.c); // 输出: 4,浅拷贝对引用类型的数据只是复制了引用
// 深拷贝示例
const deepCopy = JSON.parse(JSON.stringify(obj));
deepCopy.a = 5;
console.log(obj.a); // 输出: 1,原始对象不受影响
deepCopy.b.c = 6;
console.log(obj.b.c); // 输出: 2,深拷贝创建了一个完全独立的对象
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
需要注意的是,深拷贝的实现方式(如上例中使用JSON.stringify()
和JSON.parse()
)对某些特殊的数据类型(如函数、正则表达式、循环引用等)可能会存在限制或无法处理。在实际使用中,可以根据需要选择合适的深拷贝方式或使用第三方库(如lodash的cloneDeep
方法)来实现深拷贝。
# == 和 ===
javascript会在==
中会进行类型转换,布尔值会转化为数字类型,例如:false -> 0,
数字字符串会转化为数字,例如:'123' -> 123,'012' -> 12。
例题:对象是引用类型,比较的是堆内存的地址
# NaN
null a number,表示不是数字。
# 事件捕获和事件冒泡
DOM事件流
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
事件捕获由外向内,事件冒泡由内到外;
默认为事件冒泡,可通过 addEventListener(事件,函数,true)
,设置为事件捕获。
事件捕获优先于事件冒泡,所以事件捕获的事件先响应。
阻止冒泡事件:e.stopPropagation()
例题:
# 原型链
# 原型对象
# 递归
# 闭包
# typeof 和 instanceof
# 解构赋值
# 函数柯里化
参数复用
# 变量提升
console.log(num) // undefinded
var num = 1
2
上述代码等同于
var num
console.log(num)
num = 1
2
3
函数提升
//函数声明式:
function foo () {}
//变量形式声明:
var fn = function () {}
2
3
4
fn()
var fn = function () {
console.log(1)
}
// 输出结果:Uncaught TypeError: fn is not a function
foo()
function foo () {
console.log(2)
}
// 输出结果:2
2
3
4
5
6
7
8
9
10
11
例题
- 例题1
var name = "JavaScript"
function showName(){
console.log(name);
if(0){
var name = "CSS"
}
}
showName()
2
3
4
5
6
7
8
这里输入undefined
禁用变量提升
通过使用 let
, const
声明变量。
使用 var
,块级作用域会失效,因为 var
的作用范围是整个函数。
function fn() {
var num = 1;
if (true) {
var num = 2;
console.log(num); // 2
}
console.log(num); // 2
}
fn()
2
3
4
5
6
7
8
9
下面使用 let
function fn() {
let num = 1;
if (true) {
let num = 2;
console.log(num); // 2
}
console.log(num); // 1
}
fn()
2
3
4
5
6
7
8
9
# call apply bind
三者用于改变函数内部的this指向
call
写法:function.call(context, arg1, arg2, ...)
const obj = {
name: 'Alice',
greeting: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
obj.greeting(); // 输出 "Hello, my name is Alice"
2
3
4
5
6
7
8
const person = {
name: 'Bob'
};
obj.greeting.call(person); // 输出 "Hello, my name is Bob"
2
3
4
5
apply
写法:function.apply(context, [argsArray])
const obj = {
name: 'Alice',
greeting: function(city, country) {
console.log(`Hello, my name is ${this.name} and I am from ${city}, ${country}`);
}
};
obj.greeting('New York', 'USA'); // 输出 "Hello, my name is Alice and I am from New York, USA
2
3
4
5
6
7
8
const person = {
name: 'Bob'
};
obj.greeting.apply(person, ['London', 'UK']); // 输出 "Hello, my name is Bob and I am from London, UK"
2
3
4
5
bind
写法:function.bind(thisArg, arg1, arg2, ...)
区别:返回一个新函数
const obj = {
name: 'Alice',
greeting: function(city, country) {
console.log(`Hello, my name is ${this.name} and I am from ${city}, ${country}`);
}
};
const person = {
name: 'Bob'
};
const boundGreeting = obj.greeting.bind(person, 'London');
boundGreeting('UK'); // 输出 "Hello, my name is Bob and I am from London, UK"
2
3
4
5
6
7
8
9
10
11
12
13
14
# 箭头函数与普通函数的区别
- 箭头函数没有自己的this,不能被new调用
- 箭头函数继承来的this指向永远不会改变
- call, apply,bind 等方法不能改变箭头函数中this的指向。
- 箭头函数不能作为构造函数使用
- 箭头函数没有原型 prototype
- 箭头函数没有自己的 arguments
# async 和 defer
async异步
defer延迟
# 事件委托
事件委托
也称为事件代理
。就是利用事件冒泡
,把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托就无法实现。
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。
阻止默认行为:event.preventDefault()
阻止冒泡行为:event.stopPropagation()
通过return false
的方式,既阻止了事件冒泡,也阻止了默认行为。
# Promise
Promise 是 JavaScript 中用于处理异步操作的对象。它代表着一个尚未完成但最终会完成的操作(或者一个尚未失败但最终会失败的操作)。使用 Promise 可以更好地管理和组织异步代码,并处理异步操作的结果。
Promise 对象有三种状态:
等待态(pending):初始状态,操作尚未完成或者失败。
已完成态(fulfilled):操作成功完成。
已拒绝态(rejected):操作失败。
一个 Promise 对象会有一个 then 方法,该方法接收两个回调函数作为参数:一个在 Promise 进入已完成态时被调用,另一个在 Promise 进入已拒绝态时被调用。
例子:
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const randomNumber = Math.random();
if (randomNumber < 0.5) {
resolve(randomNumber);
} else {
reject(randomNumber);
}
}, 1000);
});
myPromise.then(
(result) => {
console.log("操作成功", result);
},
(error) => {
console.log("操作失败", error);
}
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在上面的例子中,我们创建了一个 Promise 对象 myPromise
。在 Promise 的构造函数中,我们设置了一个 1 秒钟的延时,并生成一个随机数。如果随机数小于 0.5,我们调用 resolve
方法,表示操作成功;如果随机数大于等于 0.5,我们调用 reject
方法,表示操作失败。
然后,我们使用 then
方法分别指定了操作成功和操作失败时的回调函数。当 Promise 进入已完成态时,会执行成功回调函数,并将结果作为参数传递给回调函数;当 Promise 进入已拒绝态时,会执行失败回调函数,并将错误信息作为参数传递给回调函数。
这样,无论操作成功还是失败,我们都能够通过 then
方法来处理 Promise 的结果。
# ES6新特性
ES6(ECMAScript 2015)是JavaScript的第六个主要版本,引入了许多新特性和语法改进,以提升开发效率和代码质量。以下是一些ES6的新特性:
let和const关键字:用于声明块级作用域的变量和常量,替代了var关键字。
箭头函数:使用箭头(=>)定义函数,简化了函数的语法形式,并且自动绑定了函数内部的this关键字。
模板字符串:使用反引号(`)来创建字符串,可以在其中插入变量和表达式,更方便地拼接字符串。
解构赋值:可以从对象或数组中提取值,并赋给变量,简化了变量的声明和赋值过程。
默认参数:允许在函数定义时为参数指定默认值,简化了函数调用时参数的传递。
扩展运算符和剩余参数:扩展运算符(...)可以展开数组或对象,方便地进行数组合并、对象复制等操作;剩余参数(...)可以将多余的参数收集到一个数组中。
类和模块化:引入class关键字和模块化机制,使得面向对象编程更加方便和模块化开发更加可控。
Promise和异步编程:引入了Promise对象,用于处理异步操作和解决回调地狱问题。
迭代器和生成器:迭代器提供了一种统一的遍历接口,可以遍历各种数据结构;生成器是一种特殊的函数,可以暂停和恢复函数的执行,提供更简洁的异步编程方式。
模块化的导入和导出:允许通过import和export语句来导入和导出模块,提供了更好的模块化开发方式。
这只是ES6中的一些主要特性,还有其他一些小的改进和新增的语法糖,如对象字面量的增强、数组的扩展方法、模块化加载等。ES6的引入大大增强了JavaScript的功能和表达能力,使得开发者能够更高效地编写可维护、现代化的JavaScript代码。
# 数组方法
# ES6
forEach 遍历
var arr = [1, 2, 3, 4, 5] console.log('原始数组 : ', arr); var res = arr.forEach(function (item, index, arr) { console.log(item, "------", index, "-------", arr); })
1
2
3
4
5map 映射数组
var arr = [1, 2, 3, 4, 5] console.log('原始数组 : ', arr); var res = arr.map(function (item) { return item*1000 }) console.log(res);
1
2
3
4
5
6filter 过滤
var arr = [1, 2, 3, 4, 5] console.log('原始数组 : ', arr); var res = arr.filter(function (item) { return item > 2 }) console.log(res);
1
2
3
4
5
6every 判断数组是不是满足所有条件
// 只有所有的都满足条件返回的是true // 只要有一个不满足返回的就是false var arr = [1, 2, 3, 4, 5] console.log('原始数组 : ', arr); var res = arr.every(function (item) { return item > 0 }) console.log(res);//打印结果 true
1
2
3
4
5
6
7
8
9some
// 只有有一个满足条件返回的是true var arr = [1, 2, 3, 4, 5] console.log('原始数组 : ', arr); var res = arr.some(function (item) { return item > 3 }) console.log(res);//true
1
2
3
4
5
6
7
8find 用来获取数组中满足条件的第一个数据
// 返回的是item,不是索引!! var arr = [1, 2, 3, 4, 5] console.log('原始数组 : ', arr); var res = arr.find(function (item) { return item > 3 }) console.log(res)//4
1
2
3
4
5
6
7
8reduce 叠加后的效果
语法: 数组名.reduce(function (prev,item,index,arr) {},初始值)
prev :一开始就是初始值 当第一次有了结果以后;这个值就是第一次的结果
item :这个表示的是数组中的每一项
var arr = [1, 2, 3, 4, 5] var res = arr.reduce(function (prev, item) { return prev *= item }, 1) console.log(res);//120
1
2
3
4
5
# 改变原数组的方法
push
返回值: 就是这个数组的长度
var arr = [10, 20, 30, 40] res = arr.push(20) console.log(arr);//[10,20,30,40,20] console.log(res);//5
1
2
3
4pop 末尾删掉数据
返回值: 就是你删除的那个数据
var arr = [10, 20, 30, 40] res =arr.pop() console.log(arr);//[10,20,30] console.log(res);//40
1
2
3
4unshift 头部添加数据
返回值: 就是数组的长度
var arr = [10, 20, 30, 40] res=arr.unshift(99) console.log(arr);//[99,10,20,30,40] console.log(res);//5
1
2
3
4shift 头部删掉数据
返回值: 就是删除掉的那个数据
var arr = [10, 20, 30, 40] res=arr.shift() console.log(arr);[20,30,40] console.log(res);10
1
2
3
4reverse 反转数组
返回值: 就是翻转好的数组
var arr = [10, 20, 30, 40] res=arr.reverse() console.log(arr);//[40,30,20,10] console.log(res);//[40,30,20,10]
1
2
3
4sort 排序
var arr = [2, 63, 48, 5, 4, 75, 69, 11, 23] arr.sort() console.log(arr); // [11, 2, 23, 4, 48, 5, 63, 69, 75] arr.sort(function(a,b){return(a-b)}) console.log(arr); // [2, 4, 5, 11, 23, 48, 63, 69, 75] arr.sort(function(a,b){return(b-a)}) console.log(arr); // [75, 69, 63, 48, 23, 11, 5, 4, 2]
1
2
3
4
5
6
7splice 截取数组
语法一: 数组名.splice(开始索引,多少个)
作用: 就是用来截取数组的
返回值: 是一个新数组 里面就是你截取出来的数据
语法二: 数组名.splice(开始索引,多少个,你要插入的数据)
作用: 删除并插入数据
返回值: 是一个新数组 里面就是你截取出来的数据
var arr = [2, 63, 48, 5, 4, 75] res = arr.splice(1,2) console.log(arr); console.log(res); //****************************** //splice() 语法二 var arr = [2, 63, 48, 5, 4, 75] res = arr.splice(1,1,99999,88888) console.log(arr); console.log(res);
1
2
3
4
5
6
7
8
9
10
# 不改变原数组的方法
concat 合并数组
返回值:一个新数组
var arr = [10, 20, 10, 30, 40, 50, 60] res = arr.concat(20,"小敏",50) console.log(arr) console.log(res);
1
2
3
4join 把数组的所有元素放入一个字符串
var arr = [10, 20, 10, 30, 40, 50, 60] res = arr.join("+") console.log(arr) console.log(res); // 10+20+10+30+40+50+60
1
2
3
4slice 选取数组的一部分,并返回一个新数组
var arr = [10, 20, 10, 30, 40, 50, 60] res = arr.slice(1,4) console.log(arr) console.log(res);
1
2
3
4indexOf 搜索数组中的元素,并返回它所在的位置
includes 判断一个数组是否包含一个指定的值
# 浏览器
# 跨域
同源策略
同协议、同主机号(域名)、同端口
script标签允许跨域
JSONP ( JSON with Padding)
利用script标签可跨域的漏洞
CORS
服务器在响应头中添加 Access-Control-Allow-Origin:*
使用 PUT,PATCH,DELETE的时候,浏览器会自动发出一个预检请求(OPTIONS), 预检请求就是查看服务器是否支持当前跨域请求。
服务器代理
设置反向代理,可以利用 Nginx 。
# Cookie Storage Token
Cookie
cookie的出现是为弥补 HTTP的无状态
Storage
localStorage
sessionStorage
窗口关闭就会删除
区别
token
由 JWT 生成,JWT(json web token),有三部分组成:header,playload,signature
Header : 声明由什么算法来生成签名
Playload:有效期之类
signature
# http状态码
响应分为五类:信息响应(100–199),成功响应(200–299),重定向(300–399),客户端错误(400–499)和服务器错误 (500–599)
重点了解200-299
状态码 | 状态码英文名称 | 中文描述 |
---|---|---|
100 | Continue | 继续。客户端应继续其请求 |
101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
200 | OK | 请求成功。一般用于GET与POST请求 |
---|---|---|
201 | Created | 已创建。成功请求并创建了新的资源 |
202 | Accepted | 已接受。已经接受请求,但未处理完成 |
203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
306 | Unused | 已经被废弃的HTTP状态码 |
307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
---|---|---|
401 | Unauthorized | 请求要求用户的身份认证 |
402 | Payment Required | 保留,将来使用 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
405 | Method Not Allowed | 客户端请求中的方法被禁止 |
406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
409 | Conflict | 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 |
410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
412 | Precondition Failed | 客户端请求信息的先决条件错误 |
413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
416 | Requested range not satisfiable | 客户端请求的范围无效 |
417 | Expectation Failed | 服务器无法满足Expect的请求头信息 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
# get和post请求的区别
传输方式
get 会在将数据拼接在url后面传输;
post 通过报文传输。
服务端获取参数方式不同
传输大小
get 受限于url长度,2kb左右。
post 理论上不受限。
安全性
get 不安全,因为参数暴露在url上。
post 安全,参数隐藏在报文中。
功能上
get常用于请求数据,post常用于提交数据。
# 防抖和节流
防抖:连续多次触发事件,只执行最后一次。
节流:n秒内多次触发某事件,只执行一次。
使用场景
防抖:输入框下自动补全
节流:触底加载,防止重复点击
# 三次握手,四次挥手
三次握手和四次挥手都是在TCP/IP协议中用于建立和关闭网络连接的过程。
三次握手(Three-way Handshake)的流程如下:
- 客户端发送一个带有SYN(同步)标志的包给服务器,请求建立连接。
- 服务器收到请求后,回复一个带有SYN和ACK(确认)标志的包,表示收到了请求并同意建立连接。
- 客户端再回复一个带有ACK标志的包,表示确认收到了服务器的回复。
这个过程中,客户端先发送请求,服务器再回复确认,最后客户端再确认服务器的回复,三次握手完成后,连接建立成功。
四次挥手(Four-way Handshake)用于安全关闭连接,流程如下:
- 客户端发送一个带有FIN(结束)标志的包给服务器,表示要关闭连接。
- 服务器收到请求后,回复一个带有ACK标志的包,表示收到了关闭请求。
- 服务器继续传输可能还未发送完的数据,然后发送一个带有FIN标志的包给客户端,表示服务器也准备关闭连接。
- 客户端收到服务器的关闭请求后,回复一个带有ACK标志的包,表示确认收到了服务器的关闭请求。
这个过程中,客户端先发送关闭请求,服务器回复确认并继续关闭操作,最后客户端再确认服务器的关闭请求,四次挥手完成后,连接成功关闭。
三次握手和四次挥手的目的是为了确保连接的可靠性和顺利关闭,防止数据的丢失和重复发送。
# Vue
# Vue2和Vue3区别
区别 | Vue2 | Vue3 | |
---|---|---|---|
1 | 双向绑定原理 | Object.defineProperty | ProxyAPI |
2 | API方式 | 选项式API | 组合式API |
3 | 实例化 | new Vue() | ES module imports按需引入 |
4 | 生命周期 | 8个 | 6个(无created及之前) |
5 | 支持TS | ||
6 | 根节点个数 | 1个 | 可多个 |
Vue3性能更快的原因:
- Vue3的diff方法优化
- Vue2中的虚拟dom是全量多对比(每个节点都会比较),Vue3新增了静态标记(patch flag),只对比带有patch flag的节点。
# 生命周期
# Computed Watch
Computed属性和Watch监听器是Vue中两种不同的响应式数据处理方式,它们的区别主要体现在以下几个方面:
用途和场景:
- Computed:计算属性用于处理需要根据其他响应式数据计算得出的结果的情况。它们通常用于模板中需要多次使用同一个计算结果的场景,例如基于输入数据计算出一个衍生的数据。
- Watch:监听器用于监控特定的数据变化,并在数据变化时执行相应的回调函数。它们适用于需要在数据变化后执行一些异步操作、对数据进行深度处理或触发其他逻辑的场景。
语法和用法:
- Computed:计算属性是在Vue组件的计算选项中定义的。可以像访问普通属性一样在模板中使用计算属性,Vue会自动追踪依赖,并在依赖发生变化时重新计算。
- Watch:监听器是在Vue组件的监视选项中定义的。可以通过监听特定的数据或表达式来执行回调函数,在数据变化后执行相应的逻辑。
执行时机:
- Computed:计算属性是惰性求值的,只有在依赖的数据发生变化时,才会重新计算。当依赖的响应式数据没有发生变化时,会返回缓存的计算结果。
- Watch:监听器会在指定的数据发生变化后立即触发回调函数。
综上所述,Computed用于处理计算和衍生数据的情况,适用于模板中重复使用同一结果的场景,而Watch则用于监听特定数据变化,并执行相关的逻辑。根据实际需求和场景选择合适的方式来处理响应式数据。
# Vue3中ref和reactive
reactive
适用于复杂对象或数组,并且在模版中需要通过解构或访问属性来使用。
ref
适用于包装原始数据类型或单一的值,可以在模版中直接使用,且支持特定属性的监听和直接对象的替换。
# Composition API
- 更好地支持TypeScript,提供更好的类型推导和代码提示。
- 代码更直观,
- 通过使用Composition API,开发者可以将相关的逻辑代码聚集在一起,提高代码的可读性和维护性。
- 逻辑组合,Composition API的核心概念是将组件的逻辑按照功能进行组合,而不是按照生命周期钩子进行划分。
- 可重用性,使用Composition API,可以将逻辑代码封装成自定义函数或组合函数,使得逻辑可以在多个组件之间复用。
# 首屏加载优化
- 路由懒加载,首屏进入时直接加载和首屏相关的路由,其他路由实现懒加载。
- 非首屏的组件使用异步组件,首屏加载组件会将所有组件一起加载,所以非首屏显示的组件请使用异步引入的原则。
- 静态资源放在CDN链,俗称CDN加速,使得用户访问资源会找离自己物理距离最近的站点去获取资源。
- 减少首屏的js,css等资源文件大小。
- 图片使用 webpt格式。
- 使用服务端渲染。
- 降低DOM数量和层级。
- 图片宽高事先设定好,不然会导致图片宽高数据涌上来时,图片的高度变化带来重排重绘。
- 使用精灵图/雪碧图降低请求数量。
- 做Loading提示,添加提示/骨架屏/进度条。
- 图片懒加载。
- 降低首屏业务接口请求数。
# Vue3双向绑定原理
在Vue 3中,双向绑定的原理与Vue 2有些不同。Vue 3采用了基于Proxy的响应式系统来实现双向绑定。
响应式对象:Vue 3中,通过调用reactive()函数将对象转换为响应式对象。这个函数会使用Proxy来拦截对象的访问和修改。
Proxy拦截:当我们访问响应式对象的属性时,Proxy会拦截这个操作,并将其添加到依赖追踪的数据结构中。同时,如果属性的值也是一个对象,Proxy会对其进行递归的代理。
依赖追踪:Vue 3使用了一个叫做ReactiveEffect的数据结构来追踪属性的依赖关系。每当访问响应式对象的属性时,Proxy会触发一个ReactiveEffect,将其添加到当前活动的依赖列表中。
触发更新:当响应式对象的属性发生变化时,Proxy会触发依赖列表中所有的ReactiveEffect。这些ReactiveEffect执行后会通知相关组件进行重新渲染,从而实现了双向绑定。
当我们在模板中使用v-model指令绑定一个表单元素时,Vue 3会在编译阶段为该指令生成一个包含输入事件监听器的代码。这个监听器会在输入事件触发时更新绑定的属性值,从而触发Proxy的拦截和依赖追踪机制。
需要注意的是,Vue 3中的双向绑定是通过监听输入事件来实现的,而不是通过使用观察者模式实现的像Vue 2那样。这种改变使得Vue 3在性能上有所提升,并且简化了响应式系统的实现。
# Diff算法
Vue中的Virtual DOM diff算法是通过比较新旧虚拟DOM树的差异,然后只对有变化的部分进行更新,从而提高渲染效率。
Vue的diff算法主要分为以下几个步骤:
生成新旧虚拟DOM树:在每次数据变化时,Vue会生成一个新的虚拟DOM树,与之前的旧虚拟DOM树进行比较。
深度优先遍历:Vue使用深度优先遍历算法遍历新旧虚拟DOM树的节点,每个节点都会标记一个唯一的key值,用于更准确地找到对应的节点。
对比节点:对比新旧虚拟DOM树的节点,如果节点类型相同且key值相同,则认为是相同节点,继续对比其子节点。
更新节点:如果节点不同,Vue会通过真实DOM操作来更新视图。如果节点被替换,Vue会先创建新的DOM节点,然后移除旧的DOM节点,从而完成节点的更新。
列表渲染:在列表渲染时,Vue使用“key”来标识每个节点,从而跟踪它们的身份,这样能更准确地判断节点的移动、删除或添加。
通过以上步骤,Vue的diff算法能高效地找到变化的节点,并将这些变化应用到真实的DOM上,避免了全量更新。这样能减少对真实DOM的操作,提升了性能和渲染的效率。
需要注意的是,Vue的diff算法并非完全采用传统的diff算法,而是结合了特定的优化策略,以及针对虚拟DOM结构的特点进行了细微的调整和优化,从而提高了性能和效率。
# 算法
# 二分法寻找数组元素
# 冒泡排序
# 正则表达式
正则表达式是一种用于匹配、查找和替换字符串的强大工具。在JavaScript中,你可以使用内置的正则表达式对象和相关方法来操作和处理正则表达式。
以下是一些常见的正则表达式模式和用法:
直接量字符匹配:
/abc/
:匹配文本中的 "abc"。
字符类:
[abc]
:匹配字符"a"、"b"或"c"。[^abc]
:匹配除字符"a"、"b"和"c"以外的任意字符。[0-9]
:匹配任意一个数字字符。
特殊字符:
.
:匹配任意一个字符(除换行符以外)。\w
:匹配任意一个字母、数字或下划线。\d
:匹配任意一个数字字符。\s
:匹配任意一个空白字符(包括空格、制表符等)。
重复限定符:
*
:匹配前一个表达式零次或多次。+
:匹配前一个表达式一次或多次。?
:匹配前一个表达式零次或一次。{n}
:匹配前一个表达式恰好出现n次。{n,}
:匹配前一个表达式至少出现n次。
锚点:
^
:匹配字符串的开头。$
:匹配字符串的结尾。\b
:匹配单词的边界。
分组和捕获:
(abc)
:匹配"abc"字符串,并捕获到分组中。
在JavaScript中,你可以使用正则表达式的相关方法,如test()
、match()
、search()
、replace()
等来对字符串进行匹配、查找和替换操作。
例如,可以使用如下代码来检查一个字符串是否符合指定的正则表达式:
const pattern = /abc/;
const str = "abc123";
console.log(pattern.test(str)); // 输出:true
2
3
4
以上只是正则表达式的基础知识,正则表达式的语法和用法非常广泛和复杂。可以参考JavaScript官方文档或其他相关教程深入学习和掌握正则表达式的更多知识。