js才是真的难
概念
同步和异步
JS从一出生就是一门单线程语言,所有程序都需要排队执行。起初我认为这是极好的,直到有一天,当遇到定时器或者网络请求的时候经常被卡住。我还需要暂停下手头的工作,等待这些卡住的任务完成,以至于体验极差。为了解决这个问题,我将任务分成了同步和异步俩个类型。
同步任务
:处理那些不需要等待的任务,例如:let a = 1。
异步任务
:处理那些需要等待的任务,例如:setTimeout、网络请求等。
运行了一段时间感觉还可以,但是有一天却接到这样的投诉,promise发起的请求已经完成,但是很久不执行then,等得用户心急如焚
宏任务和微任务
JS的执行机制是自上而下,先去执行同步代码,然后是微任务,然后去渲染dom,最后去执行宏任务
微任务
:需要连贯执行:Promise、async、await
宏任务
:不需要连贯执行:setTimeout、setInterval、Ajax、DOM事件
Promice 加载图片
作用:解决地狱回调问题
当一个请求需要依赖上一个请求返回的结果的时候,俗称回调地狱。为了解决这种问题,Promise()就诞生了,他可以优雅的解决异步问题。Promice的状态一旦改变之后,就不可逆,并且不可以进行第二次改变。
Promice的三种状态 pending
<body>
<script type="text/javascript">
function one() {
return 'I am one'
}
function two() {
return new Promise ((resolve, reject) => {
setTimeout(() => {
resolve('I am two')
}, 3000);
})
}
function three() {
return 'I am three'
}
async function run() {
console.log(one())
console.log(await two())
console.log(three())
}
run()
</script>
</body>
async和await
前后端数据交互 HTTP 超文本协议
HTTP
协议,包括客户端(浏览器)、服务端(服务器)俩个实体。当我们想访问某篇博文时。客户端会发送请求(例如 https://www.bilibili.com)
给服务端,服务端收到API
会解析他们,并返回客户端相应的数据资源。这些数据资源,可以是html文档、图片、普通文本。
http和https到底有什么区别?
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
客户端发送HTTP
请求到服务端
HTTP 请求方式 | |
---|---|
GET[默认] | 获取列表 /list |
GET | 获取详情 /details?postId=1 |
POST | 增加 /tickets |
PUT | 更新替换 /tickets/12 |
PATCH | 修改 /tickets/12 |
DELETE | 删除 /tickets/12 |
cookie、session、jwt、token`记录管理状态
解释一下JS中的原型和原型链
new 实例对象、原型对象、构造函数、
不像java或者c++,js是动态的本身并不提供一个class的实现,即便是在es6中引入了class关键字,但那也只是语法糖,js仍然是基于原型的,当谈到继承时,js具有一种结构那就是对象。每一个实例对象都有一个私有属性称之为__proto__
指向它的构造函数的原形对象prototype
继承是指一个类的属性和方法,通过继承的方式让其他类用来使用。并且在使用的时候,还可以修改一定的方法和属性
原型和原型链可以解决javascript中实现继承的问题
class继承
原型
在JS中,每一个函数都自带一个 prototype 属性,我们就把 函数名.prototype 称为原型
。也叫“显式原型”。当我们打印 函数名.prototype
时,打印出来的这个对象,称作原型对象
/**
* 什么是原型?
* 在JS中,每一个函数都自带一个 prototype 属性,我们就把 函数名.prototype 称为原型。也叫“显式原型”
* 当我们打印 函数名.prototype 时,打印出来的这个对象,称作原型对象
*
* 原型对象 默认又有俩个属性:
* 1.constructor(构造器): 指向构造函数本身
* 2.__Proto__(隐式原型):指向其上一级的原型
*
* [[prototype]]其实就是__proto__ 因为各大浏览器厂家取名不同
*/
// 1.构造函数
function MM(name) {
this.name = name
}
// 2.实例化 - 创建对象
const mm = new MM('哈妮克孜')
// 3.打印
console.log(mm) // 实例化对象mm
console.log(MM.prototype) // 原型对象
console.log(MM.prototype.constructor === MM) // true => 指向函数本身
console.log(MM.prototype === mm.__proto__) // true =>
// __proto__和prototype不太一样,
// __proto__是对象拥有的隐式原型,prototype是函数拥有的显式原型
原型链
闭包
闭包是作用域的一种特殊应用。当我们写一些for循环或者a--,之类的函数时,通常都会在函数顶部声明一个全局变量。但有人说全局变量,这玩意儿占内存不说。任意函数都可以随便调用,会污染环境。于是就有一种新的机制,既能长期保存变量又不会污染全局,这就是闭包。
<script type="text/javascript">
function fa() {
var a = 10
function fb() {
a-- // 2.内部函数引用外部作用域的变量参数
console.log('aa', a)
}
return fb // 3.返回值是函数
}
var fm = fa() // 4.创建一个对象函数,让其长期驻留
// fm = null // 用完后,我们可以把它释放掉,不用担心内存泄漏
// 那什么是闭包? 满足以下 4个条件
/**
* 1.有函数嵌套
* 2.内部函数引用外部作用域的变量参数
* 3.返回值是函数
* 4.创建一个对象函数,让其长期驻留
*/
</script>
图片预加载和懒加载
当页面中有很多图片的时候,图片加载就需要一定的时间,不仅会影响渲染速度,还浪费服务器性能和带宽。而懒加载就是优先加载可视区的内容,其他内容等进入了可视区域再进行加载。我们要实现懒加载需要做到2个步骤,1.如何加载图片,2.如何判断进入可视区域。先说加载图片,我们知道图片都是根据图片标签上的src属性进行加载的,所以在图片进入可视区域前,我们先不给src属性赋值,或者给一个很小的loading图地址,等到图片进入可视区域后再给src附上真正的地址。再来看图片是否进入了可视区域。
方法一.IntersectionObserver
有个方法叫做交叉观察器,intersectionObserver。/ˌɪntəˈsekʃn//əbˈzɜːvə(r)/.音特塞克伸,额波蜇窝
,这个api可以自动观察一个元素是否可见或者两个元素是否相交,一般用于实现图片懒加载、内容无限滚动等功能
代码示例
// 自定义指令 v-lazy
export default {
mounted(el) {
console.log('el', el)
const imgSrc = el.src // 复制图片src地址
el.src = '' // 默认将src清空
// 交叉观察者 API
const lazyLoad = new IntersectionObserver((entries) => {
// 当元素出现在可视区域, 和离开可视区域被触发
console.log('entries', entries[0].isIntersecting)
if (entries[0].isIntersecting) {
// isIntersecting 为 true 说明进入可视区域
el.src = imgSrc // 进入可视区域 还原sec地址,实现懒加载
// 停止观察
lazyLoad.unobserve()
}
});
lazyLoad.observe(el)
}
}
方法二. vue-lazyload
安装vue-lazyload插件,在main.js 中全局引入,然后在图片元素src属性名称写成v-lazy即可
深拷贝 和 浅拷贝
假设B复制了A,当修改A时,B也跟着发生了变化,这说明只拷贝了指针,AB实际上还是共用的一份数据,这是浅拷贝。
假设B复制了A,当修改A时,A变,B没有变,那就是深拷贝,复制对象不受原对象的影响,因为不仅拷贝了指针,还拷贝了内存。他们自己有自己的内容,互相独立
浅拷贝
把变量A直接赋值给变量B就是浅拷贝
<script type="text/javascript">
const obj1 = {
name: '张三',
age: 18,
address: { city: '北京' },
nobby: [ '台球', '篮球']
}
const obj2 = obj1 // 浅拷贝
obj2.address.city = '上海'
console.log('obj1', obj1)
console.log('obj2', obj2)
</script>
深拷贝
方法一.JSON.parse(JSON.stringify(obj))
js内置的JSON序列化和反序列化方法
缺点: 不能存放函数,时间对象和正则
<script type="text/javascript">
const obj1 = {
name: '张三',
age: 18,
address: { city: '北京' },
fn: function name(params) {
},
nobby: [ '台球', '篮球'],
}
// const obj2 = obj1 // 浅拷贝
const obj2 = deepClone(obj1) // 深拷贝
obj2.address.city = '上海'
console.log('obj1', obj1)
console.log('obj2', obj2)
// 方法一:JSON序列化
function deepClone(params) {
return JSON.parse(JSON.stringify(params))
}
</script>
方法二.递归
缺点: 没有考虑循环引用
<script type="text/javascript">
const obj1 = {
name: '张三',
age: 18,
address: { city: '北京' },
fn: function name(obj) {
},
nobby: [ '台球', '篮球'],
}
// const obj2 = obj1 // 浅拷贝
const obj2 = deepClone(obj1) // 深拷贝
obj2.age = 20
obj2.address.city = '上海'
console.log('obj1', obj1)
console.log('obj2', obj2)
// 方法二:递归
function deepClone(obj) {
if (typeof(obj) !== 'object' || typeof(obj) === null) {
// 如果他不是一个对象,或者压根儿就是一个null。我们也没有必要处理它了
return obj
}
// instanceof 判断基本数据类型的方法
let res = obj instanceof Array ? [] : {}
// for in
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
res[key] = deepClone(obj[key])
}
}
return res
}
</script>
方法三.lodash.cloneDeep,推荐工作中使用
防抖和节流
区别 | 共同点 | 区别 | 应用场景 |
---|---|---|---|
防抖 debounce | 在事件频繁被触发时 | 只执行最后一次 | input搜索框 |
节流 throttle | 减少事件执行次数 | 有规律的执行 | 拖拽、scroll滚动条 |
防抖
// 输入框内容变化时的回调
function inputSearch_onChange(params) {
// 获取输入框 value
debounce(function () {
console.log('打印', params.target.value)
// ...执行 Axios 调取数据
})
}
// 防抖
let timer = null
function debounce(fn) {
if (timer) clearTimeout(timer) // 规定时间内若定时器存在则清除
timer = setTimeout(() => {
fn()
}, 1000);
}
节流
function inputSearch_onSearch(params) {
throttle(function () {
console.log('点击搜索按钮', params)
})
}
// 节流
let flag = true
function throttle(fn) {
if(flag) {
setTimeout(() => {
console.log('触发点击')
fn() // 调用接口
flag = true // 在定时器执行后 移除if阻断
}, 1000);
}
flag = false // 在执行一次后 if阻断定时器继续执行
}
请描述event loop的机制
堆和栈
for in 和 for of区别
区别 | ||
---|---|---|
for..in | 遍历得到key | 可枚举的数据:数组、字符串、对象 |
for..of | 遍历得到value | 可迭代的数据:数组、字符串、Set、Map |
代码示例
<script type="text/javascript">
/**
* 数组
*/
const arr = [10, 20, 30]
for (const key in arr) {
// 遍历得到 key
console.log('数组[for..in]', key) // 0,1,2
}
for (const val of arr) {
// 遍历得到 value
console.log('数组[for..of]', val) // 10,20,30
}
/**
* 对象
*/
const obj = {
name: '张三',
age: 18,
}
for (const key in obj) {
console.log('对象[for..in]', key) // name, age
}
for (const val of obj) {
console.log('对象[for..of]', val) // 报错
}
/**
* Set
*/
const set = new Set([10, 20, 30])
for (const key in set) {
console.log('Set[for..in]', key) // 无
}
for (const val of set) {
console.log('Set[for..of]', val) // 10, 20, 30
}
/**
* Map
*/
const map = new Map([
['a', 10],
['b', 10],
['c', 10]
]);
for (const key in map) {
console.log('Map[for..in]', key) // 无
}
for (const val of map) {
console.log('Map[for..of]', val) // ['a', 10], ['b', 10], ['c', 10]
}
</script>
map foreach
JS基础知识
- 前端算法入门一:刷算法题常用的JS基础扫盲(重点🏁)
- 前端算法入门二:时间空间复杂度&8大数据结构的JS实现
- 前端算法入门三:5大排序算法&2大搜索&4大算法思想
- 前端面试算法高频100题(附答案,分析思路,一题多解)
数据类型
基本数据类型:number、string、boolean、null、undefined
引用数据类型:数组(Array)、对象(Object)、函数(Function)
// 引用数据类型: 函数(Function)
let kiki = function name(params) {
alert('kk99')
}
kiki() // 弹窗 kk99
name()// 报错 TypeError: name is not a function
作用域
var a = 10
console.log('AA:', a) // AA: 10
console.log('BB:', b) // AA: undefined
var b = 10
/**
* 执行顺序,由于变量提升发生了改变
* var b
* console.log('BB:', b)
* b = 10 // b声明向上提升。b=10赋值留在远处
*/
console.log('CC:', c) // 报错 ReferenceError: c is not defined
/**
* 由于没有声明 c
* 报错,导致后续代码都不会在执行
*/
JS的三种声明方式,let、const、var的区别
(1)块级作用域: 块作用域由 { }
包括,let和const具有块级作用域,var不存在块级作用域。var声明的变量为全局变量。块级作用域解决了ES5中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
区别 | var | let | const |
---|---|---|---|
是否有块级作用域 | × | ✔️ | ✔️ |
能否重复声明变量 | ✔️ | × | × |
是否必须设置初始值 | × | × | ✔️ |
for 循环机制
for (let i = 0; i < 5; i++) {
// 循环体
}
/**
* 执行的顺序如下:
* 第一步 : i=0 初始化值
* 第二步 : i<5 进行条件判断,如果为真,则继续执行
* 第三步 : 执行循环体的内容
* 第四步 : i++ 变量i自增
* 第五步 : 回到第二步,条件判断为真,则执行循环体内容,再到i++一直循环,
* 直到第二步的判断条件为假,则退出该循环
*/
for(条件1;条件2;条件3){
循环体4
}
**执行的循环: 1243 243 243 ....直到条件2为假则退出循环**
JS数组方法
push()
push() 删除数组中的最后一个,在尾部追加,类似于压栈,原数组会变。
const arr = [1, 2, 3]
arr.push(8)
console.log(arr) // [1, 2, 3, 8]
JS字符串方法
replace()替换
replace() 替换字符串的某元素,并返回替换后的字符串
var str = "《帝国崛起:民族战线》"
str.replace(/民族战线/, '12') // '《帝国崛起:12》'
split()截取
把字符串分割为字符串数组
var time = "2023-02-28T11:25:00.075Z"
time.split('T')[0] // '2023-02-28'
time.split('T')[1] // '11:25:00.075Z'
toString() 和 split() 数组字符串转化
代码题
获取数组最大值
方法一:使用for遍历数组,设置一个变量存储最大值
let array = [1, 2 ,-3, 'kk', '67', true, null]
let list = []
array.forEach(element => {
// typeof() 判断数据类型
if (typeof(element) === "number") {
list.push(element)
}
});
console.log('list', list) // [1, 2, -3]
let mun = list[0]
list.forEach(item => {
if (mun < item) {
mun = item
}
});
console.log('mun', mun) // 2
方法二:使用Math.max(...arr)函数,其中...是拓展运算符
let array = [1, 2 ,-3, 'kk', '67', true, null]
let list = []
array.forEach(element => {
// typeof() 判断数据类型
if (typeof(element) === "number") {
list.push(element)
}
});
console.log('list', list) // [1, 2, -3]
console.log('max', Math.max(...list)) // 2
方法三:sort()将数组元素进行排序
数组名.sort(function(a,b){return a-b}); 从小到大排列 数组名.sort(function(a,b){return b-a}); 从大到小排列
let array = [1, 4, 2, 3]
array.sort((n1, n2) => {
return n2 - n1
})
console.log('array', array[0]) // 4
60秒倒计时,0停止
let count = 60
let timer = setInterval(() => {
count--
console.log(count)
if (count === 0) {
alert('停止')
clearInterval(timer)
// count = 60
}
}, 10);
for循环中含setTimeout打印
var a = 10
function A() {
console.log('1', a) // undefined
var a = 20
console.log('2', a) // 20
for (var a = 1; a < 5; a++) {
setTimeout(() => {
console.log('3', a) // 4个 5。setTimeout为异步最后执行
}, 10);
}
}
A()
console.log('4', a) // 10
答案解析
for (var i = 0; i < 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}
setTimeout是异步执行,10ms后往任务队列里面添加一个任务,只有主线上的全部执行完,才会执行任务队列里的任务,当主线执行完成后,i是10,所以此时再去执行任务队列里的任务时,i全部是10了。
对于打印10次是:每一次for循环的时候,settimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里面,等待执行,for循环了4次,就放了4次,当主线程执行完成后,才进入任务队列里面执行。
for (let i = 0; i < 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}
因为for循环头部的let不仅将i绑定到for循环快中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过 var 定义的变量是无法传入到这个函数执行域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function就能使用 i 这个变量了;这个匿名函数的参数作用域 和 for参数的作用域 不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的。
数组去重
1.new Set(数组)
Set是一系列无序、没有重复值的数据集合,传入一个需要去重的数组,Set会自动删除重复的元素
再将Set转数组返回。此方法效率高,代码清晰,缺点是存在兼容性问题
let arr = [1, 2, 3, 4, 1, 2, 3, 4]
console.log('数组去重', [...new Set(arr)]) // [1, 2, 3, 4]
2.对象属性(indexof)
利用对象属性key排除重复项
遍历数组,每次判断新数组中是否存在该属性,不存在就存储在新数组中
并把数组元素作为key,最后返回新数组
这个方法的优点是效率高,缺点是使用了额外空间
const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
var newArr = [];
arr.forEach((key,index)=>{
if(newArr.indexOf(key) === -1){
// 如果newArr 没有当前这项就会显示 -1.并且就把数丢进newArr数组里
newArr.push(key)
}
})
console.log(newArr) // [1, '1', 17, true, false, 'true', 'a', {}, {}]
去重数组中重复的对象
let arr = [
{ id: 0, name: "张三" },
{ id: 1, name: "李四" },
{ id: 2, name: "王五" },
{ id: 3, name: "赵六" },
{ id: 1, name: "孙七" },
{ id: 2, name: "周八" },
{ id: 2, name: "吴九" },
{ id: 3, name: "郑十" },
];
const removeDuplicateObj = (arr) => {
let newArr = []
let obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i].id]) {
newArr.push(arr[i]);
obj[arr[i].id] = true;
}
}
return newArr
};
console.log(removeDuplicateObj(arr));
ts。
vue TypeScript中引用js文件异常
修改tsconfig.json中"strict"的值为false,禁用严格模式。
前后端数据交互 HTTP 协议
HTTP
协议,包括客户端(浏览器)、服务端(服务器)俩个实体。当我们想访问某片博文时。客户端会发送请求(例如 https://www.bilibili.com)
给服务端,服务端收到API
会解析他们,并返回客户端相应的数据资源。这些数据资源,可以是html文档、图片、普通文本。
URL结构
URL由以下几部分组成:资源类型、存放资源的主机域名、资源文件名。
Protocol 协议 [资源类型]
[ˈprəʊtəkɒl] 坡儒特扣
Host 主机 [存放资源的主机域名]
[həust]
Path 路径 [资源文件名]
[pɑːθ]
Query 查询参数
[ˈkwɪəri]
客户端发送HTTP
请求到服务端
HTTP 请求方式 | |
---|---|
GET[默认] | 获取列表 /list |
GET | 获取详情 /details?postId=1 |
POST | 增加 /tickets |
PUT | 更新替换 /tickets/12 |
PATCH | 修改 /tickets/12 |
DELETE | 删除 /tickets/12 |
服务端返回响应到客户端

响应状态码含义描述 | |
---|---|
100-199 | 一般信息 100 Continue |
200-299 | 成功响应 201 Created |
300-399 | 重定向 301 Moved Permanently |
400-499 | 客户端错误 404 Not Found |
500-599 | 服务端错误 500 Internal Server Error |
http和https到底有什么区别?
# B站链接、# http和https到底有什么区别?
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
HTTP
无状态,需要用cookie、session、jwt、token
记录管理状态
HTTP
是无状态的,也就是说服务端是不认识客户端的。每次HTTP
发出的请求,都会认为是从全新的客户端发出来的。然而,在许多应用场景中,我们需要保持用户登录的状态或记录用户购物车中的商品。由于HTTP
是无状态协议,所以必须引入一些技术来记录管理状态,例如Cookie
和session
。
授权认证登录之 Cookie、Session、Token、JWT 详解
了解Cookie
- cookie 存储在客户端: cookie 是服务器发送到用户浏览器,并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时,被携带并发送到服务器上。因此,服务端脚本就可以读、写存储在客户端的cookie的值。
- cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名(绑定域名下的子域都是有效的),无法在别的域名下获取使用,同域名不同端口也是允许共享使用的。
什么是 Session(会话)
- session 是记录服务器与浏览器会话状态的过程。这个过程是连续的,也可以时断时续的。
- session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中
session 认证流程:
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 请求返回时将此 Session 的唯一标识 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动把此域名下的 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。
Cookie 和 Session 的区别
- 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
- 存取值的类型不同:Cookie 只支持存字符串数据,Session 可以存任意数据类型。
- 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
- 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
什么是 Token(令牌)
-
简单 token 的组成: Token是用户身份的验证方式。由uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
-
token 的身份验证流程:
- 客户端使用用户名跟密码,请求登录
- 服务端收到请求,验证成功后,服务端会生成一个 token 并把这个 token 发送给客户端
- 并将Token存到
Redis数据库
(数据结构服务器)中,设置过期时间30分钟 - 客户端每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
- 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
Token 和 Session 的区别
- Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。
- Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重复攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。
- 如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。
什么是 JWT
git命令行提交
// 查看当前分支
git branch
// 暂存
git stash
// 创建25分支,并切换到25分支
git checkout -b 25
// 拉去master 主分支 代码
git pull origin upstream
// 放开之前的暂存
git stash pop
// 选择自己要提交的文件
// 提交到本地
git commit -m"#25 标签管理"
// 提交到git
git push origin 25
/**
* 合并分支 (如把25分支合并到matser主分支,则先要进入master主分支)
*/
git checkout 25 // 切换分支
git pull origin 25 // 下拉全部代码
git checkout master // 切换回主分支
git merge 25 // git merge 分支名【将25分支合并到主分支】
hooks的用法
慕课网1块钱的教程
redux数据管理
ES6语法有哪些
JS的三种声明方式,let、const、var的区别
(1)块级作用域: 块作用域由 { }
包括,let和const具有块级作用域,var不存在块级作用域。var声明的变量为全局变量。块级作用域解决了ES5中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
区别 | var | let | const |
---|---|---|---|
是否有块级作用域 | × | ✔️ | ✔️ |
能否重复声明变量 | ✔️ | × | × |
是否必须设置初始值 | × | × | ✔️ |
ES6常用语法总结
JS基础入门
JS引用位置
JSON.parse()
JSON.parse()
把一个JSON字符串,转换为JSON对象
JSON.stringify()
把一个JSON对象,转换为JSON字符串
在接收服务器数据时一般是字符串
我们可以使用 JSON.parse() 方法
将数据转换为 Json对象。
JSON.parse('{"A": "27"}')
// {A: '27'}
JSON.stringify({"B": "18"})
// '{"B":"18"}'
js深拷贝和浅拷贝的区别
浅拷贝就像是单位大家写日报,每个人都可以修改同一个文档的内容,你修改了别人也能看到。
深拷贝就像别人给你复制了一份ex表格,你俩个各自改各自的。
并发、并行、异步、同步
异步、同步是俩种不同的编程模型。
同步
代表必须要等到前一个任务执行结束,才能触发下一个任务。同步中并没有并发和并行的概念
异步
代表不同的任务之间并不会相互等待,也就是说你在运行任务A时,也可以运行任务B。一个典型实现异步的方式就是多线程
异步编程指我们在执行一个长时间任务时,程序不需要等待,而是继续执行后续的代码,直到这些任务完成后在通知你。这种编程模式避免了程序的堵塞,大大得提高了cpu的效率。
async``await
语法糖
正则表达式
input 支持英文字母和数字,并且开头第一个只能是字母
<Form.Item {...layout} label="英文名称" name="ENname" rules={[{ required: true, validator: this.EName, message: '请填写英文字母(支持,0-9a-zA-Z_)' }]}>
<Input placeholder="请输入英文名称" onChange={e => this.inputValue_ENname(e)} />
</Form.Item>
// 自定义校验 英文表名
EName = (_, value) => {
const enPattern = /^[a-zA-Z][0-9a-zA-Z_]*$/ // 求只能输入英文字母的正则表达式
if (enPattern.test(value)) {
return Promise.resolve();
}
return Promise.reject('请填写以英文字母开头的表名(支持,0-9a-zA-Z_)');
}
驼峰命名法
let studentInfo = {};
// =>项目中常见的有特殊含义的端词组
add / insert / create 新增/插入/创建
del / remove / update 删除/移除/修改
select / query / get 选择/查询/获取
info 信息
JS中的数据类型
typeof 判断数据类型
-
基本数据类型(值类型 / 原始值)
- 数字 number
- 字符串 string
- 布尔 boolean
- 空对象指针 null
- 未定义 undefined
- ES6新增的唯一值类型 symbol
-
引用数据类型
-
对象数据类型 object
- 普通对象 {}
- 数组对象 []
- 正则对象 /^$/
- 日期对象 new Date
- 数学函数对象 Math
- ...
-
函数数据类型 function
-
// number数字类型
let n = 10;
n = 10.5;
n = -10;
n = 0;
n = NaN; //=>NaN:not a number 非有效数字
n = Infinity; //=>正/负无穷大 -Infinity [ɪnˈfɪnəti]
// string字符串:基于单引号、双引号、反引号(TAB上面的撇)包起来的都是字符串
let str = '';
str = '19';
str = "好好学习";
str = `我是ES6中新增的模板字符串,有助于字符串的拼接`;
str = '[object Object]';
// boolean布尔:true / false
let boo = true;
boo = false;
// 空
let nu = null;
nu = undefined;
let un; //=>默认值就是undefined
// Symbol:每一个Symbol()都是一个唯一值
let x = Symbol('珠峰');
let y = Symbol('珠峰');
console.log(x == y); //=>false
// object普通对象:大括号包起来,里面有零到多组属性名和属性值(键值对),这些属性名和属性值可以描述当前对象的特征(键:值,多组键值对用逗号分隔)
let obj = {
name: '好好学习',
age: 10,
teachers: 30
};
// Array数组对象:中括号包起来,逗号分隔数组中每一项的值(每一项的值可以是任意类型)
let arr = [10, '字符串', true, null];
// RegExp正则对象:两个斜杠包起来一大堆你看不懂的符号就是正则 O(∩_∩)O哈哈~
let reg = /$[+-]?(\d|([0-9]\d+))(\.\d+)?^/;
// function函数
function func(x, y) {
let total = x + y;
return total;
}
// ES6中的 Arrow Function 箭头函数
let fn = () => {
};
Math
称为数学函数[取整、随机数]
// 向上取整,有小数就加1
Math.ceil(5/2) // 3
// 向下取整,丢弃小数部分
Math.floor(5/2) // 2
// 四舍五入
Math.round(5/2) // 3
// 取余
5%2 // 1
// 随机数 (随机生成0 ~1之间的数)
Math.random()
// 获取随机生成 0 到 9之间的整数
Math.floor(Math.random()*10)
JS常用方法
if条件 和 三元表达式等同写法
for循环
map。filter
forEach
var arr = [1, 2, 3, 4, 5]
arr.forEach(function (item) {
if (item === 3) {
return;
}
console.log(item)
})
// 1
// 2
// 4
// 5
字面量,变量
6种方式给 JavaScript 数组添加元素
### slice()截取
截取数组中的部分元素
```js
// [公式] 从start位置开始,截取到end位置,end取不到
Arr.slice(start,end)
// [例子]
Arr = ['A', 'B', 'C', 'D', 'E']
Arr.slice(2, 4) // ["C", "D"]
filter()筛选
创建一个新的数组,筛选新数组中符合条件的所有元素
// 返回true的留下,false的舍弃
const Arr = [{ name: '曹操' },{ name: '刘备' },{ name: '宋江' }]
const kiki = Arr.filter(item => item.name === '宋江')
console.log('kiki', kiki[0]) // {name: "宋江"}
splice()
用于添加,删除,替换数组中的元素,改变原始数组。
// [公式] index起始位置,howmany删除元素的数量,item1添加到数组的新元素
array.splice(index, howmany, item1 ,....., itemX)
// [例子]-添加
const Hero = ["曹操", "曹昂", "曹丕"]
Hero.splice(2, 0, "孙权", "孙策")
console.log(Hero) // ["曹操", "曹昂", "孙权", "孙策", "曹丕"]
// [例子]-删除
const Hero = ["曹操", "曹昂", "曹丕"]
Hero.splice(1, 1)
console.log(Hero) // ["曹操", "曹丕"]
// [例子]-替换
const Hero = ["曹操", "曹昂", "曹丕"]
Hero.splice(0, 1, "曹阿瞒")
console.log(Hero) // ["曹阿瞒", "曹昂", "曹丕"]
forEach() 遍历
循环遍历里面的每一个元素
const heroDesc = [
{name: "曹操", age: 53},
{name: "刘备", age: 53},
{name: "孙权", age: 23}
]
heroDesc.forEach((item, index, arr) => {
const ele = arr[index];
if(ele.age > 30){
ele.boo = true
}
})
console.log(heroDesc)
// [{name: "曹操", age: 53, boo: true}, {name: "刘备", age: 53, boo: true}, {name: "孙权", age: 23}]
push()
map()、delete
map()用法1
const heroDesc = [
{name: "曹操", age: 53},
{name: "刘备", age: 53},
{name: "孙权", age: 23}
]
heroDesc.map(item => {
return item.name
})
// 打印结果 ['曹操', '刘备', '孙权']
map()用法2
split() 方法
"2:3:4:5".split(":") //将返回["2", "3", "4", "5"]
"|a|b|c".split("|") //将返回["", "a", "b", "c"]
var str="How are you doing today?"
document.write(str.split(" ",3))
// How,are,you
findIndex() 数组减去另一数组
www.jianshu.com/p/6ac483fa4…
介绍一下上面用到的数组实例中的find()
, findIndex()
, includes()
const arr1 = ['', '100', '120', '125', '125', '130', '130'];
const arr2 = ['', '120', '125', '125', '130'];
const arr3 = [];
arr1.forEach((a)=>{
let c = arr2.findIndex(b =>a === b);
if (c > -1) delete arr2[c];
else arr3.push(a);
});
console.log(arr3) //['100', '130']
遇到的问题
js禁用鼠标右键菜单
window.oncontextmenu = function(e){
//取消默认的浏览器自带右键 很重要!!
e.preventDefault();
}
// react 写法
<div onContextMenu={this.tableRight} />
// 禁止右键菜单事件
tableRight = (e) => {
e.preventDefault()
return false
}
js 获取URL地址附带参数 获得请求链接参数
function getParamString(name) {
var paramUrl = window.location.search.substr(1);
var paramStrs = paramUrl.split('&');
var params = {};
for(var index = 0; index < paramStrs.length; index++) {
params[paramStrs[index].split('=')[0]] = decodeURI(paramStrs[index].split('=')[1]);
}
return params[name];
}
// ————————————————
// 例如URL地址是这样的
http://127.0.0.1:8020/AjaxTester/test.html?age=3&key=xiaoming
// js使用如下
alert(getParamString("key")); //提示xiaoming
JS去掉字符串前后所有空格
const uiui = " ty787 97 99 lkjh . "
uiui.replace(/(^\s*)|(\s*$)/g, "")
// 打印结果:'ty787 97 99 lkjh .'
将字符串转成小写字母
var str = 'FeiNiaoMy.com';
console.log(str.toLowerCase());
// 打印结果:feiniaomy.com
js保留2位小数
toFixed函数来做四舍五入,函数参数里的2就是保留二位小数。
<el-progress :percentage="(item.num / DataSelectTop_TotalNum * 100).toFixed(2)" />