做网站需要哪些素材,网站弹出一张图怎么做代码,项目开发流程8个步骤流程图,网站页面怎么做导航HTML语义化
是什么 前端语义化是指在构建网页时多使用html语义化标签布局#xff0c;多使用带有语义的标签如header#xff0c;aside#xff0c;footer等标签为什么 结构清晰利于开发者开发与维护 有利于seo搜索引擎优化 有利于在网络卡顿时#xff0c;正常显示页面结构多使用带有语义的标签如headerasidefooter等标签为什么 结构清晰利于开发者开发与维护 有利于seo搜索引擎优化 有利于在网络卡顿时正常显示页面结构虽然没有样式提高用户体验
盒模型
是什么 页面布局的元素可以看做一个个盒子盒子包括contentpaddingbordermargin 一般分为两种盒子模型 : 标准盒子模型与怪异盒子模型 两者区别是标准盒子设置盒子宽高时默认是content的宽高而怪异盒子设置时是除了margin都包括怎么办 为了布局方便采取css属性box-sizingborder-box设置为怪异盒子
浮动
是什么 一种网页布局方式顾名思义布局的元素会浮在标准布局之上为什么 最初是为了实现图文环绕效果目前一般用于将块级元素排列在一行怎么做 元素设置flotleft/right 存在问题由于脱标若父元素未设置高度会造成父盒子塌陷现象然后影响之后其他元素布局 解决方案常用的给父元素设置overflowhidden 添加伪元素或直接设置高度等
样式优先级的规则
CSS样式的优先级应该分成四大类
第一类!important无论引入方式是什么选择器是什么它的优先级都是最高的。第二类引入方式行内样式的优先级要高于嵌入和外链嵌入和外链如果使用的选择器相同就看他们在页面中插入的顺序在后面插入的会覆盖前面的。第三类选择器选择器优先级id选择器类选择器 | 伪类选择器 | 属性选择器 后代选择器 | 伪元素选择器 子选择器 | 相邻选择器 通配符选择器 。第四类继承样式是所有样式中优先级比较低的。-第五类 浏览器默认样式优先级最低。
css尺寸设置的单位
px 绝对长度由屏幕分辨率决定em 相对长度相对自身font大小自身fontsize未设置继承父元素rem 相对长度相对页面根元素大小vw/vh 相对长度相对视窗的宽/高 1/100
BFC
是什么 块级格式化上下文使自己成为一个独立环境不影响上下文其他元素的布局为什么 主要用于清除浮动影响与避免外边距合并等问题怎么做 overflowhidden displayflow-root等
未知宽高元素水平垂直居中
1.flex 父元素设置displayflexjustify-contentcenteralign-itemscenter 2.position 子绝父相 子元素注意left50℅top50%transformtranslate-50%-50%
三栏布局实现方案
三栏布局要求左右两边盒子宽度固定中间盒子宽度自适应盒子的高度都是随内容撑高的
js数据类型
JS数据类型分为两类
基本数据类型(简单数据类型) 分别是Number 、String、Boolean、BigInt(表示任意大的整数)、Symbol、Null、Undefined。引用数据类型(复杂数据类型) 通常用Object代表普通对象数组正则日期Math数学函数都属于Object。
数据分成两大类的本质区别 基本数据类型和引用数据类型它们在内存中的存储方式不同。 基本数据类型是直接存储在栈中的简单数据段占据空间小属于被频繁使用的数据。 引用数据类型是存储在堆内存中占据空间大。 引用数据类型在栈中存储了指针该指针指向堆中该实体的起始地址当解释器寻找引用值时会检索其在栈中的地址取得地址后从堆中获得实体。
null 和 undefined 的区别如何让一个属性变为null
undefind 是全局对象的一个属性当一个变量没有被赋值或者一个函数没有返回值或者某个对象不存在某个属性却去访问或者函数定义了形参但没有传递实参这时候都是undefined。 undefined通过typeof判断类型是’undefined’。undefined undefined undefined undefined 。null 代表对象的值未设置相当于一个对象没有设置指针地址就是null。null通过typeof判断类型是’object’。null null null null null undefined null ! undefined undefined 表示一个变量初始状态值而 null 则表示一个变量被人为的设置为空对象而不是原始状态。
在实际使用过程中不需要对一个变量显式的赋值 undefined当需要释放一个对象时直接赋值为 null 即可。 加分回答 null 其实属于自己的类型 Null而不属于Object类型typeof 之所以会判定为 Object 类型是因为JavaScript 数据类型在底层都是以二进制的形式表示的二进制的前三位为 0 会被 typeof 判断为对象类型而 null 的二进制位恰好都是 0 因此null 被误判断为 Object 类型。 对象被赋值了null 以后对象对应的堆内存中的值就是游离状态了GC 会择机回收该值并释放内存。因此需要释放某个对象就将变量设置为 null即表示该对象已经被清空目前无效状态。
JavaScript有几种方法判断变量的类型
4种方法判断变量的类型分别是typeof、instanceof、Object.prototype.toString.call()对象原型链判断方法、 constructor (用于引用数据类型)
typeof常用于判断基本数据类型对于引用数据类型除了function返回’function‘其余全部返回’object’。instanceof主要用于区分引用数据类型检测方法是检测的类型在当前实例的原型链上用其检测出来的结果都是true
let arr []; console.log(arr instanceof Array); // true
function MyClass() {} let obj new MyClass(); console.log(obj instanceof MyClass); // trueconstructor用于检测引用数据类型检测方法是获取实例的构造函数判断和某个类是否相同如果相同就说明该数据是符合那个数据类型的这种方法不会把原型链上的其他类也加入进来避免了原型链的干扰。
let num 123; console.log(num.constructor Number); // true
let str hello; console.log(str.constructor String); // true
//但这种方法对于 null 和 undefined 不起作用因为它们是原始值没有 constructor 属性。Object.prototype.toString.call()适用于所有类型的判断检测检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。 这四种判断数据类型的方法中各种数据类型都能检测且检测精准的就是Object.prototype.toString.call()这种方法。
let num 123;
let str hello;
let bool true;
let obj {};
let arr [];
let func function() {};
let nullVar null;
let undefVar; console.log(Object.prototype.toString.call(num)); // [object Number]
console.log(Object.prototype.toString.call(str)); // [object String]
console.log(Object.prototype.toString.call(bool)); // [object Boolean]
console.log(Object.prototype.toString.call(obj)); // [object Object]
console.log(Object.prototype.toString.call(arr)); // [object Array]
console.log(Object.prototype.toString.call(func)); // [object Function]
console.log(Object.prototype.toString.call(nullVar)); // [object Null]
console.log(Object.prototype.toString.call(undefVar)); // [object Undefined]Array.isArray()
let arr []; console.log(Array.isArray(arr)); // true选择哪种方法取决于你的具体需求和你想要检测的变量类型。对于原始类型typeof 和 Object.prototype.toString.call() 是最常用的方法。对于对象类型instanceof 和 constructor 属性可能更合适。而 Array.isArray() 则专门用于检测数组。
加分回答 instanceof的实现原理 验证当前类的原型prototype是否会出现在实例的原型链__proto__上只要在它的原型链上则结果都为true。因此instanceof 在查找的过程中会遍历左边变量的原型链直到找到右边变量的 prototype找到返回true未找到返回false。 Object.prototype.toString.call()原理Object.prototype.toString 表示一个返回对象类型的字符串call()方法可以改变this的指向那么把Object.prototype.toString()方法指向不同的数据类型上面返回不同的结果
数组去重都有哪些方法
map
function uniqueArray(arr) {
return Array.from(new Map(arr.map(item [item, 1])).keys()); } let array [1, 2, 2, 3, 4, 4, 5, 5]; console.log(uniqueArray(array)); // [1, 2, 3, 4, 5]使用Set数据结构 利用Set类型数据无重复项new 一个 Set参数为需要去重的数组Set 会自动删除重复的元素再将 Set 转为数组返回。这个方法的优点是效率更高代码简单思路清晰缺点是可能会有兼容性问题
function uniqueArray(arr) { return Array.from(new Set(arr));
} let array [1, 2, 2, 3, 4, 4, 5, 5];
console.log(uniqueArray(array)); // [1, 2, 3, 4, 5]
filterindexof filterindexof 去重这个方法和第一种方法类似利用 Array 自带的 filter 方法返回 arr.indexOf(num) 等于 index 的num。原理就是 indexOf 会返回最先找到的数字的索引假设数组是 [1, 1]在对第二个1使用 indexOf 方法时返回的是第一个1的索引0。这个方法的优点是可以在去重的时候插入对元素的操作可拓展性强。
function uniqueArray(arr) { return arr.filter((item, index) arr.indexOf(item) index); } let array [1, 2, 2, 3, 4, 4, 5, 5]; console.log(uniqueArray(array)); // [1, 2, 3, 4, 5]使用filter和includes
function uniqueArray(arr) { return arr.filter((item, index) !arr.slice(0, index).includes(item)); } let array [1, 2, 2, 3, 4, 4, 5, 5]; console.log(uniqueArray(array)); // [1, 2, 3, 4, 5]使用reduce
function uniqueArray(arr) {
return arr.reduce((accumulator, currentValue) {
if (!accumulator.includes(currentValue)) { accumulator.push(currentValue); } return accumulator; }, []); } let array [1, 2, 2, 3, 4, 4, 5, 5]; console.log(uniqueArray(array)); // [1, 2, 3, 4, 5]
使用for循环
function uniqueArray(arr) {
let obj {};
let result [];for (let i 0; i arr.length; i) {if (!obj[arr[i]]) { obj[arr[i]] true; result.push(arr[i]); } } return result; } let array [1, 2, 2, 3, 4, 4, 5, 5]; console.log(uniqueArray(array)); // [1, 2, 3, 4, 5]伪数组和数组的区别
伪数组也称为类数组和数组在JavaScript中存在一些关键的区别。以下是它们之间的主要差异 数据类型 数组是JavaScript中的一种特殊对象专门用于存储和操作数据的有序集合。数组的元素通过索引从0开始进行访问且所有元素都是同一类型尽管在实际应用中JavaScript的数组可以包含不同类型的元素。伪数组本质上是一个对象具有类似数组的某些特性如索引和length属性但并非真正的数组。常见的伪数组包括函数中的arguments对象、某些DOM方法返回的元素集合如document.getElementsByClassName()以及字符串尽管字符串的索引和length属性与数组相似但它们的行为并不完全相同。 原型链 数组继承自Array.prototype因此具有数组特有的方法如push()、pop()、forEach()等。伪数组通常继承自Object.prototype不具有数组特有的方法。尽管它们可能具有length属性但这个属性通常是静态的不会随着成员的变化而改变。 访问方法 数组可以直接使用数组的方法如push()、pop()、slice()等。伪数组由于它们不是真正的数组因此不能直接使用数组的方法。但是可以通过其他方式如使用Array.prototype的方法或循环来处理伪数组的元素。 长度 数组长度是动态的可以随着元素的添加或删除而改变。伪数组长度通常是静态的不会随着成员的变化而改变。 用途 数组用于存储和操作有序的数据集合是JavaScript中常用的数据结构之一。伪数组通常用于表示具有类似数组特性的对象集合但由于其不是真正的数组因此在使用时需要特别注意。
伪数组 它的类型不是Array而是Object 而数组类型是Array。 可以使用的length属性查看长度也可以使用[index]获取某个元素但是不能使用数组的其他方法 也不能改变长度遍历使用for in方法。 伪数组的常见场景
函数的参数arguments原生js获取DOMdocument.querySelector(‘div’) 等jquery获取DOM$(“div”)等
加分回答 伪数组转换成真数组方法 Array.prototype.slice.call(伪数组)
[].slice.call(伪数组)Array.from(伪数组) 转换后的数组长度由 length 属性决定。索引不连续时转换结果是连续的会自动补位。
map 和 forEach 的区别
map 和 forEach 的区别 map 有返回值返回新数组可以开辟新空间return出来一个length和原数组一致的数组即便数组元素是undefined或者是null。 forEach 默认无返回值返回结果为undefined可以通过在函数体内部使用索引修改数组元素。
加分回答 map的处理速度比forEach快而且返回一个新的数组方便链式调用其他数组新方法比如filter、reduce let arr [1, 2, 3, 4, 5]; let arr2 arr.map(value value * value).filter(value value 10); // arr2 [16, 25]
es6中箭头函数
what 箭头函数是es6新增的一种函数定义方式 how
当函数体是单条语句的时候可以省略{}和return。另一种是包含多条语句不可以省略{}和return。 箭头函数最大的特点就是没有this所以this是从外部获取就是继承外部的执行上下文中的this由于没有this关键字所以箭头函数也不能作为构造函数 这使得在回调函数中使用 this 变得更加容易因为它不会意外地改变。 同时通过 call() 或 apply() 方法调用一个函数时只能传递参数不能绑定this第一个参数会被忽略。箭头函数也没有原型和super。不能使用yield关键字因此箭头函数不能用作 Generator 函数。不能返回直接对象字面量。 没有 arguments 对象 箭头函数没有 arguments 绑定。如果需要访问函数参数列表可以使用剩余参数rest parameters。 没有原型prototype 由于箭头函数没有 this 绑定它们也不能用作构造函数因此没有 prototype 属性。 不支持 new 关键字 由于箭头函数没有构造函数功能使用 new 关键字调用箭头函数会抛出一个错误。 没有 super 或 new.target 箭头函数同样不支持这两个值因为箭头函数不是由 new 创建的它们没有自己的 this 绑定也不指向任何父类或构造函数。 加分回答 箭头函数的不适用场景 定义对象上的方法 当调用 dog.jumps 时lives 并没有递减。因为 this 没有绑定值而继承父级作用域。
var dog { lives: 20, jumps: () { this.lives--; } } 不适合做事件处理程序 此时触发点击事件this不是button无法进行class切换
var button document.querySelector(button); button.addEventListener(click, () { this.classList.toggle(on); });箭头函数函数适用场景 简单的函数表达式内部没有this引用没有递归、事件绑定、解绑定适用于map、filter等方法中写法简洁 var arr [1,2,3]; var newArr arr.map((num)num*num) //-内层函数表达式需要调用this且this应与外层函数一致时 let group { title: Our Group, students: [John, Pete, Alice], showList() { this.students.forEach( student alert(this.title : student) ); } }; group.showList();事件扩展符用过吗(…)什么场景下
展开语法(Spread syntax),es6 中通过…语法形式来表示
函数调用 等价于apply的方式用于将一个数组或类数组对象展开为函数调用的参数。示例function myFunction(v, w, x, y, z) { ... }var args [1, 2, 3]; myFunction(...args, 4, 5); 数组构造 将数组展开为另一个数组的元素或者与其他数组组合。示例var arr1 [0, 1]; var arr2 [...arr1, 2, 3]; // arr2 为 [0, 1, 2, 3]还可以用于复制数组避免修改原数组。示例var arr3 [1, 2, 3]; var arr4 [...arr3]; // arr4 是 arr3 的浅拷贝 构造字面量对象 将对象表达式按key-value的方式展开用于对象的合并或浅拷贝。示例var obj1 { a: 1, b: 2 }; var obj2 { ...obj1, c: 3 }; // obj2 为 { a: 1, b: 2, c: 3 } 数组字符串连接 可以与字符串连接将数组中的元素转化为字符串并用逗号分隔然后与其他字符串连接。示例var arr [1, 2, 3]; var str The numbers are: ...arr; // 注意这里直接与字符串相加是不支持的但可以结合其他方法如join 浅拷贝 正如前面提到的可以使用展开语法进行数组的浅拷贝或对象的浅合并。 迭代器的使用 展开语法内部也基于迭代器Iterator。与for...of类似它通过数组的迭代器访问数组中的每个成员。 剩余参数与展开语法的结合 在函数定义时可以使用剩余参数Rest Parameters收集多余的参数到一个数组中在函数调用时可以使用展开语法将数组的元素作为单独的参数传入。
请注意展开语法只能用于可迭代对象如数组、字符串、Set、Map等或者具有迭代协议的对象。对于非可迭代对象如普通对象除非转换为可迭代对象如使用Object.entries()或Object.values()使用展开语法会导致错误。 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开 还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。常见的场景等价于apply的方式、将数组展开为构造函数的参数、字面量数组或字符串连接不需要使用concat等方法了、构造字面量对象时,进行浅克隆或者属性拷贝 加分回答 只能用于可迭代对象 在数组或函数参数中使用展开语法时, 该语法只能用于 可迭代对象 var obj {‘key1’: ‘value1’}; var array […obj];
// TypeError: obj is not iterable 剩余语法剩余参数 剩余语法(Rest syntax) 看起来和展开语法完全相同不同点在于, 剩余参数用于解构数组和对象。从某种意义上说剩余语法与展开语法是相反的| 展开语法将数组展开为其中的各个元素而剩余语法则是将多个元素收集起来并“凝聚”为单个元素。
function f(...[a, b, c]) { return a b c; }
f(1) // NaN (b and c are undefined)
f(1, 2, 3) // 6
f(1, 2, 3, 4) // 6 (the fourth parameter is not destructured)闭包理解
what: 闭包 外层变量 内层函数 一个典型的闭包示例是一个函数内部返回另一个函数。内部函数可以访问外部函数的变量即使外部函数已经执行完毕。这是因为内部函数形成了一个闭包它包含了外部函数的词法环境
一个函数和词法环境的引用捆绑在一起这样的组合就是闭包closure。 一般就是一个函数Areturn其内部的函数B被return出去的B函数能够在外部访问A函数内部的变量这时候就形成了一个B函数的变量背包A函数执行结束后这个变量背包也不会被销毁并且这个变量背包在A函数外部只能通过B函数访问。 闭包形成的原理 作用域链当前作用域可以访问上级作用域中的变量 闭包解决的问题能够让函数作用域中的变量在函数执行结束之后不被销毁同时也能在函数外部可以访问函数内部的局部变量。
why:
闭包在编程中有许多实际应用场景如封装、函数式编程、定时器和事件处理、模块模式、回调函数、循环中的异步操作和缓存等。例如在JavaScript中闭包可以用于创建私有变量和函数实现信息隐藏和封装也可以用于处理定时器和事件帮助保存局部状态。 闭包带来的问题 由于垃圾回收器不会将闭包中变量销毁于是就造成了内存泄露内存泄露积累多了就容易导致内存溢出。 加分回答 : 闭包的应用能够模仿块级作用域能够实现柯里化在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。
JS变量提升
what: 变量提升是指JS的变量和函数声明会在代码编译期提升到代码的最前面。 how: 变量提升成立的前提是使用Var关键字进行声明的变量并且变量提升的时候只有声明被提升赋值并不会被提升同时函数的声明提升会比变量的提升优先。 变量提升的结果可以在变量初始化之前访问该变量返回的是undefined。在函数声明前可以调用该函数。
只有声明会被提升而赋值操作不会。所以即使变量声明被提升了但在声明之前的任何尝试去访问或修改该变量的值都会导致undefined如果变量未被初始化或ReferenceError如果变量在声明之前被当作函数或对象属性来访问。
加分回答 ES6使用let和const声明的变量是不会创建提升 在初始化之前访问let和const创建的变量会报错。
this指向普通函数、箭头函数
this关键字由来 在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点基于这个需求JavaScript 又搞出来另外一套 this 机制。
this存在的场景有三种 全局执行上下文和函数执行上下文和eval执行上下文eval这种不讨论。
在全局执行环境中无论是否在严格模式下在任何函数体外部this 都指向全局对象。在函数执行上下文中访问this函数的调用方式决定了 this 的值。谁调用,指向谁 在全局环境中调用一个函数函数内部的 this 指向的是全局变量 window 通过一个对象来调用其内部的一个方法该方法的执行上下文中的 this 指向对象本身。 普通函数this指向 当函数被正常调用时在严格模式下this 值是 undefined非严格模式下 this 指向的是全局对象 window 通过一个对象来调用其内部的一个方法该方法的执行上下文中的 this 指向对象本身。 new 关键字构建好了一个新对象并且构造函数中的 this 其实就是新对象本身。 嵌套函数中的 this 不会继承外层函数的 this 值。 箭头函数this指向 箭头函数并不会创建其自身的执行上下文所以箭头函数中的 this 取决于它的外部函数。
加分回答
箭头函数因为没有this所以也不能作为构造函数但是需要继承函数外部this的时候使用箭头函数比较方便
var myObj {
name : 闷倒驴,
showThis:function(){ console.log(this); // myObj var bar (){this.name 王美丽; console.log(this) // myObj } bar(); } }; myObj.showThis(); console.log(myObj.name); // 王美丽 console.log(window.name); // call apply bind的作用和区别
what:
call、apply、bind的作用都是改变函数运行时的this指向。 bind和call、apply在使用上有所不同
how:
bind在改变this指向的时候返回一个改变执行上下文的函数不会立即执行函数而是需要调用该函数的时候再调用即可但是call和apply在改变this指向的同时执行了该函数。 bind只接收一个参数就是this指向的执行上文。call、apply接收多个参数第一个参数都是this指向的执行上文后面的参数都是作为改变this指向的函数的参数。但是call和apply参数的格式不同call是一个参数对应一个原函数的参数但是apply第二个参数是数组数组中每个元素代表函数接收的参数数组有几个元素函数就接收几个元素。
call、apply和bind都可以用于改变函数运行时的this上下文。
区别 执行方式 call和apply会立即执行函数并修改函数内部的this指向。bind不会立即执行函数而是返回一个新的函数这个新函数的this被永久地绑定到了bind的第一个参数上。 参数传递 call和bind在传递参数时都是将参数逐一传入。 function.call(thisArg, arg1, arg2, ...)function.bind(thisArg, arg1, arg2, ...) apply在传递参数时需要使用一个数组或类数组对象。 function.apply(thisArg, [argsArray]) 返回值 call和apply都会直接执行函数并返回函数的执行结果如果有的话。bind返回一个新的函数这个新函数在被调用时会按照bind指定的this和参数来执行原函数。
示例
call
javascript复制代码function greet(name) { console.log(Hello, ${this.greeting} ${name}!); } const obj { greeting: World }; greet.call(obj, Alice); // 输出: Hello, World Alice!apply
javascript复制代码function greet(names) { for (let i 0; i names.length; i) { console.log(Hello, ${this.greeting} ${names[i]}!); } } const obj { greeting: World }; greet.apply(obj, [Alice, Bob, Charlie]); // 输出: // Hello, World Alice! // Hello, World Bob! // Hello, World Charlie!bind
javascript复制代码function greet(name) { console.log(Hello, ${this.greeting} ${name}!); } const obj { greeting: World }; const boundGreet greet.bind(obj); boundGreet(Alice); // 输出: Hello, World Alice!在这个例子中boundGreet是一个新的函数它的this已经被永久地绑定到了obj上。因此无论我们如何调用boundGreet它的this都会指向obj。
加分回答 call的应用场景 对象的继承在子构造函数这种调用父构造函数但是改变this指向就可以继承父的属性
function superClass () {this.a 1; this.print function () { console.log(this.a); }
}
function subClass () { superClass.call(this); // 执行superClass并将superClass方法中的this指向subClassthis.print();
}
subClass(); 借用Array原型链上的slice方法把伪数组转换成真数组
let domNodes Array.prototype.slice.call(document.getElementsByTagName(div)); apply的应用场景 Math.max获取数组中最大、最小的一项 let max Math.max.apply(null, array); let min Math.min.apply(null, array);
// 实现两个数组合并let arr1 [1, 2, 3]; let arr2 [4, 5, 6];Array.prototype.push.apply(arr1, arr2); console.log(arr1); // [1, 2, 3, 4, 5, 6]
bind的应用场景 在vue或者react框架中 使用bind将定义的方法中的this指向当前类
js继承的方法和优缺点 原型链继承 优点
是JavaScript中最基本的继承方式。父级的方法或属性更改会反映到所有子实例上。
缺点
引用类型的属性会被所有实例共享修改一个实例的属性会影响到其他实例。在创建子类型实例时不能向父类型构造函数中传递参数。在父类型构造函数中定义的引用类型值的实例属性会在子类型原型上变成原型属性被所有子类型实例所共享。同时在创建子类型的实例时不能向超类型的构造函数中传递参数。 借用构造函数继承 在子类型构造函数的内部调用父类型构造函数使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。
优点
解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。可以在子类型构造函数中向父类型构造函数传递参数。可以在子类型中向父类型添加属性和方法。
缺点
方法都在构造函数中定义因此函数复用就无从谈起。每个方法都要在每个实例上重新创建一遍。在超类型的原型中定义的方法对子类型而言也是不可见的结果所有类型都只能使用构造函数模式。借用构造函数的缺点是方法都在构造函数中定义因此无法实现函数复用。在父类型的原型中定义的方法对子类型而言也是不可见的结果所有类型都只能使用构造函数模式。 组合继承 将原型链和借用构造函数的组合到一块。 使用原型链实现对原型属性和方法的继承而通过借用构造函数来实现对实例属性的继承。 这样既通过在原型上定义方法实现了函数复用又能够保证每个实例都有自己的属性。
优点
融合了原型链继承和借用构造函数继承的优点是JavaScript中最常用的继承模式。既可以在子类型构造函数中向父类型构造函数传递参数也可以实现函数复用。
缺点
父类构造函数被调用了两次一次在创建子类型原型的时候另一次在子类型构造函数内部。是无论在什么情况下都会调用两次超类型构造函数一次是在创建子类型原型的时候另一次是在子类型构造函数内部 原型式继承 通过复制现有对象来创建新对象同时保持对原型的链接。 在一个函数A内部创建一个临时性的构造函数然后将传入的对象作为这个构造函数的原型最后返回这个临时类型的一个新实例。 本质上函数A是对传入的对象执行了一次浅复制。ECMAScript 5通过增加Object.create()方法将原型式继承的概念规范化了。这个方法接收两个参数作为新对象原型的对象以及给新对象定义额外属性的对象第二个可选。在只有一个参数时Object.create()与这里的函数A方法效果相同。
优点
可以在不必预先定义构造函数的情况下实现继承。
缺点
包含引用类型的属性会被所有实例共享。 寄生式继承 创建一个仅用于封装继承过程的函数该函数在内部以某种方式增强对象最后返回对象。 寄生式继承背后的思路类似于寄生构造函数和工厂模式创建一个实现继承的函数以某种方式增强对象然后返回这个对象。
优点 可以在不改变对象构造函数的情况下给对象添加功能 缺点 使用场景有限主要用于为对象添加额外功能。 通过寄生式继承给对象添加函数会导致函数难以重用。 寄生组合式继承 结合了组合继承和寄生式继承的优点是真正意义上的高效且可维护的继承方式。
通过借用构造函数来继承属性通过原型链的混成形式来继承方法。本质上就是使用寄生式继承来继承超类型的原型然后再将结果指定给子类型的原型。
优点
只调用一次父类构造函数避免了在原型链和借用构造函数继承中的重复调用问题。高效率只调用一次父构造函数并且因此避免了在子原型上面创建不必要多余的属性。与此同时原型链还能保持不变实现了函数复用避免了在构造函数中定义方法的缺点。
缺点
实现起来稍微复杂一些需要理解原型链和构造函数继承的优缺点。 加分回答 ES6 Class实现继承。 原理原理ES5 的继承实质是先创造子类的实例对象this然后再将父类的方法添加到this上面Parent.apply(this)。 ES6 的继承机制完全不同实质是先将父类实例对象的属性和方法加到this上面所以必须先调用super方法然后再用子类的构造函数修改this。需要注意的是class关键字只是原型的语法糖JavaScript继承仍然是基于原型实现的。 优点语法简单易懂,操作更方便。 缺点并不是所有的浏览器都支持class关键字 class Person { //调用类的构造方法 constructor(name, age) { this.name name this.age age } //定义一般的方法 showName() {console.log(调用父类的方法) console.log(this.name, this.age); } } let p1 new Person(kobe, 39) console.log(p1) //定义一个子类class Student extends Person { constructor(name, age, salary) { super(name, age)//通过super调用父类的构造方法 this.salary salary } showName() {//在子类自身定义方法 console.log(调用子类的方法) console.log(this.name, this.age, this.salary); } } let s1 new Student(wade, 38, 1000000000) console.log(s1) s1.showName()
// 定义一个父类超类 class Animal { constructor(name) { this.name name; } speak() { console.log(${this.name} makes a noise.); } } // 定义一个子类继承自Animal类 class Dog extends Animal { constructor(name, breed) { // 调用父类的构造函数 super(name); // 定义子类特有的属性 this.breed breed; } bark() { console.log(${this.name} barks.); } // 覆盖父类的方法 speak() { console.log(${this.name} says woof!); } } // 创建一个Dog实例 const myDog new Dog(Buddy, Golden Retriever); // 调用继承自父类的方法 myDog.speak(); // 输出: Buddy says woof! // 调用子类特有的方法 myDog.bark(); // 输出: Buddy barks. // 访问继承自父类的属性 console.log(myDog.name); // 输出: Buddy在上面的例子中
Animal 是一个父类也称为超类或基类。Dog 是一个子类通过 extends 关键字继承自 Animal 类。在 Dog 类的构造函数中使用 super(name) 调用父类的构造函数以确保父类中的 name 属性被正确初始化。Dog 类还定义了一个特有的方法 bark() 和一个覆盖了父类 speak() 方法的同名方法。创建 Dog 类的实例 myDog并调用其继承自父类的方法 speak() 和子类特有的方法 bark()。
new会发生什么
new 关键字会进行如下的操作
创建一个空的简单JavaScript对象即{}为步骤1新创建的对象添加属性__proto__将该属性链接至构造函数的原型对象 将步骤1新创建的对象作为this的上下文 如果该函数没有返回对象则返回this。
加分回答 new关键字后面的构造函数不能是箭头函数。
创建一个空对象首先JavaScript会创建一个新的空对象。设置原型将这个空对象的__proto__内部属性在ES6中通常使用Object.getPrototypeOf()和Object.setPrototypeOf()来访问和修改设置为构造函数的prototype对象。这样新创建的对象就可以继承构造函数原型上的属性和方法。改变this指向在构造函数中this关键字引用的是新创建的对象。执行构造函数中的代码接着执行构造函数中的代码为新创建的对象添加属性和方法。返回新对象如果构造函数中没有显式地返回一个对象即返回的不是一个对象类型的值如原始值undefined、null、数字、字符串、布尔值等那么new表达式将自动返回新创建的对象。如果构造函数返回了一个对象那么这个对象将作为new表达式的返回值。
function Person(name, age) { this.name name; this.age age; // 如果这里返回一个对象那么new Person()将返回这个对象而不是上面创建的新对象 // return { job: developer }; } Person.prototype.greet function() { console.log(Hello, my name is ${this.name} and I am ${this.age} years old.); }; const john new Person(John Doe, 30); john.greet(); // 输出: Hello, my name is John Doe and I am 30 years old.在这个例子中new Person(John Doe, 30)创建了一个新的Person对象并将name和age属性设置为传递给构造函数的参数值。然后由于Person.prototype上有一个greet方法所以新创建的john对象可以调用这个方法。
defer和async区别
在JavaScript中defer和async都是用于控制脚本异步执行的关键字但它们之间存在一些重要的区别。
defer
当使用defer属性时浏览器会异步地下载脚本但不会立即执行它。脚本的下载不会阻塞页面的渲染但是脚本的执行会等到整个文档都解析完成后才会开始。如果有多个带有defer属性的脚本它们会按照在HTML文档中出现的顺序来执行。defer适用于那些不需要立即执行但需要在文档完全加载后再执行的脚本。例如你可能想在页面加载完成后设置一些事件监听器或初始化某些功能。使用defer时不要在脚本中调用document.write()因为此时文档已经完成解析document.write()会覆盖整个页面。
async
当使用async属性时浏览器也会在后台异步地下载脚本但与defer不同的是一旦脚本下载完成它就会立即执行而不管文档是否已经完全解析。由于脚本的执行可能会发生在文档解析过程中所以async脚本可能会改变DOM的内容从而可能导致页面渲染的突然变化。如果有多个带有async属性的脚本它们不会按照在HTML文档中出现的顺序来执行。浏览器会尽可能快地并行下载和执行这些脚本所以它们的执行顺序是不确定的。使用async时需要确保脚本不会依赖于尚未加载的DOM元素否则可能会出现错误。
总结
defer和async都用于异步加载脚本但它们的执行时机不同。defer脚本会在整个文档解析完成后执行而async脚本会在下载完成后立即执行。defer脚本会按照顺序执行而async脚本的执行顺序是不确定的。在选择使用defer还是async时需要根据你的具体需求来决定。如果你需要确保脚本在文档解析完成后执行并且不关心脚本的下载时间那么可以使用defer。如果你希望脚本尽快执行但可以接受页面渲染的突然变化那么可以使用async。
浏览器会立即加载JS文件并执行指定的脚本 “立即”指的是在渲染该 script 标签之下的文档元素之前也就是说不等待后续载入的文档元素读到就加载并执行 加上async属性加载JS文档和渲染文档可以同时进行异步当JS加载完成JS代码立即执行会阻塞HTML渲染。 加上defer加载后续文档元素的过程将和 script.js 的加载并行进行异步当HTML渲染完成才会执行JS代码。 加分回答 渲染阻塞的原因 由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面即 JavaScript 线程和 UI 线程同时运行,那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JavaScript 引擎为互斥的关系。当浏览器在执行 JavaScript 程序的时候,GUI 渲染线程会被保存在一个队列中,直到 JS 程序执行完成,才会接着执行。如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉
promise是什么与使用方法
Promise是JavaScript中用于处理异步操作的对象它代表了一个最终可能完成也可能被拒绝的异步操作及其结果值。Promise对象使得异步操作更加容易管理和组合避免了回调地狱Callback Hell的问题。
Promise的基本使用方法如下
创建Promise对象使用new Promise()构造函数来创建一个新的Promise对象。构造函数接受一个函数作为参数这个函数有两个参数resolve和reject。这两个函数是Promise内部的回调分别表示异步操作成功和失败时的回调函数。
javascript复制代码const promise new Promise((resolve, reject) { // 异步操作 // 如果异步操作成功则调用resolve(value) // 如果异步操作失败则调用reject(error) });处理Promise的结果使用.then()和.catch()方法来处理Promise的结果。.then()方法用于处理Promise成功的情况.catch()方法用于处理Promise失败的情况。这两个方法都接受一个回调函数作为参数该回调函数将在Promise的状态改变时被调用。
javascript复制代码promise.then((result) { // 处理异步操作成功的结果 }).catch((error) { // 处理异步操作失败的结果 });链式调用.then()和.catch()方法都可以返回一个新的Promise对象因此可以链式调用它们来处理多个异步操作。在.then()方法的回调函数中可以返回一个新的Promise对象来表示下一个异步操作。
javascript复制代码promise.then((result1) { // 处理第一个异步操作的结果 return new Promise((resolve, reject) { // 第二个异步操作 // ... }); }).then((result2) { // 处理第二个异步操作的结果 }).catch((error) { // 处理任何一个异步操作失败的结果 });Promise的状态Promise对象有三种状态pending等待中、fulfilled已成功和rejected已失败。一旦Promise的状态从pending变为fulfilled或rejected它将永远不会改变。这意味着Promise的结果只能被处理一次。如果多次调用.then()或.catch()只有第一次调用时会被处理。
以上就是Promise的基本概念和使用方法。通过使用Promise可以更优雅地处理JavaScript中的异步操作。 Promise的作用 Promise是异步微任务解决了异步多层嵌套回调的问题让代码的可读性更高更容易维护 Promise使用 Promise是ES6提供的一个构造函数可以使用Promise构造函数new一个实例Promise构造函数接收一个函数作为参数这个函数有两个参数分别是两个函数 resolve和rejectresolve将Promise的状态由等待变为成功将异步操作的结果作为参数传递过去reject则将状态由等待转变为失败在异步操作失败时调用将异步操作报出的错误作为参数传递过去。实例创建完成后可以使用then方法分别指定成功或失败的回调函数也可以使用catch捕获失败then和catch最终返回的也是一个Promise所以可以链式调用。 Promise的特点 对象的状态不受外界影响Promise对象代表一个异步操作有三种状态。 - pending执行中 - Resolved成功又称Fulfilled - rejected拒绝 其中pending为初始状态fulfilled和rejected为结束状态结束状态表示promise的生命周期已结束。一旦状态改变就不会再变任何时候都可以得到这个结果。 Promise对象的状态改变只有两种可能状态凝固了就不会再变了会一直保持这个结果 - 从Pending变为Resolved - 从Pending变为Rejectedresolve 方法的参数是then中回调函数的参数reject 方法中的参数是catch中的参数then 方法和 catch方法 只要不报错返回的都是一个fullfilled状态的promise 加分回答 Promise的其他方法 Promise.resolve() :返回的Promise对象状态为fulfilled并且将该value传递给对应的then方法。 Promise.reject()返回一个状态为失败的Promise对象并将给定的失败信息传递给对应的处理方法 Promise.all()返回一个新的promise对象该promise对象在参数对象里所有的promise对象都成功的时候才会触发成功一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。 Promise.any()接收一个Promise对象的集合当其中的一个 promise 成功就返回那个成功的promise的值。 Promise.race()当参数里的任意一个子promise被成功或失败后父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄并返回该promise对象。
JS实现异步的方法
在JavaScript中有多种方式可以实现异步操作。以下是一些常见的方法
回调函数Callbacks
这是早期处理异步操作的主要方式。当一个函数需要异步执行某些操作时它接受一个回调函数作为参数并在异步操作完成时调用这个回调函数。
javascript复制代码function fetchData(callback) { setTimeout(() { const data Some data; callback(null, data); // 第一个参数通常为错误对象 }, 1000); } fetchData((err, data) { if (err) { console.error(err); } else { console.log(data); } });Promises
Promises 是一种更现代、更强大的处理异步操作的方式。它们代表了一个最终可能完成也可能被拒绝的异步操作及其结果值。
javascript复制代码function fetchData() { return new Promise((resolve, reject) { setTimeout(() { const data Some data; resolve(data); }, 1000); }); } fetchData().then(data { console.log(data); }).catch(err { console.error(err); });async/await
async/await 是基于Promises的语法糖使异步代码看起来像同步代码一样。async函数返回一个Promiseawait表达式会暂停async函数的执行并等待Promise解决然后恢复async函数的执行并返回解决的值。
javascript复制代码async function fetchData() { return new Promise((resolve, reject) { setTimeout(() { const data Some data; resolve(data); }, 1000); }); } async function processData() { try { const data await fetchData(); console.log(data); } catch (err) { console.error(err); } } processData();事件监听器Event Listeners
对于DOM事件、网络请求等可以使用事件监听器来处理异步事件。当事件发生时会调用相应的事件处理函数。
javascript复制代码fetch(https://api.example.com/data) .then(response response.json()) .then(data { // 处理数据 }) .catch(error { console.error(Error:, error); });注意虽然fetch API在上述示例中看起来像是一个回调函数但实际上它返回的是一个Promise这使得它可以与.then()和.catch()方法一起使用或者使用async/await语法。 5. Generators
虽然Generators本身并不直接用于处理异步操作但它们可以与Promises和特定的库如co结合使用以创建基于生成器的异步流程控制。然而这种方法现在已经被更现代的async/await语法所取代。 6. Web Workers
Web Workers 允许在Web应用程序中运行后台线程。这些线程可以执行复杂的计算或处理任务而不会阻塞主线程UI线程。它们主要用于在Web浏览器中执行计算密集型任务。然而请注意Web Workers 不能访问主线程的DOM并且它们之间的通信只能通过消息传递进行。 所有异步任务都是在同步任务执行结束之后从任务队列中依次取出执行。 回调函数是异步操作最基本的方法比如AJAX回调 回调函数 优点是简单、容易理解和实现 缺点是不利于代码的阅读和维护各个部分之间高度耦合使得程序结构混乱、流程难以追踪尤其是多个回调函数嵌套的情况而且每个任务只能指定一个回调函数。 此外它不能使用 try catch 捕获错误不能直接 return
Promise包装了一个异步调用并生成一个Promise实例当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve 和 reject方法then接收到对应的数据做出相应的处理。 Promise不仅能够捕获错误而且也很好地解决了回调地狱的问题缺点是无法取消 Promise错误需要通过回调函数捕获。 Generator 函数是 ES6 提供的一种异步编程解决方案Generator 函数是一个状态机封装了多个内部状态可暂停函数, yield可暂停next方法可启动每次返回的是yield后的表达式结果。优点是异步语义清晰缺点是手动迭代Generator 函数很麻烦实现逻辑有点绕 async/awt是基于Promise实现的async/awt使得异步代码看起来像同步代码所以优点是使用方法清晰明了缺点是awt 将异步代码改造成了同步代码如果多个异步代码没有依赖性却使用了 awt 会导致性能上的降低代码没有依赖性的话完全可以使用 Promise.all 的方式。 加分回答 JS 异步编程进化史callback - promise - generator/yield - async/awt。 async/awt函数对 Generator 函数的改进 体现在以下三点
内置执行器。 Generator 函数的执行必须靠执行器而 async 函数自带执行器。也就是说async 函数的执行与普通函数一模一样只要一行。更广的适用性。 yield 命令后面只能是 Thunk 函数或 Promise 对象而 async 函数的 awt 命令后面可以跟 Promise 对象和原始类型的值数值、字符串和布尔值但这时等同于同步操作。更好的语义。 async 和 awt比起星号和 yield语义更清楚了。async 表示函数里有异步操作awt 表示紧跟在后面的表达式需要等待结果。 目前使用很广泛的就是promise和async/awt
cookie sessionStorage localStorage区别
Cookie、SessionStorage、 LocalStorage都是浏览器的本地存储。 它们的共同点 都是存储在浏览器本地的 它们的区别 cookie是由服务器端写入的 而SessionStorage、 LocalStorage都是由前端写入的cookie的生命周期是由服务器端在写入的时候就设置好的 LocalStorage是写入就一直存在除非手动清除SessionStorage是页面关闭的时候就会自动清除。 cookie的存储空间比较小大概4KB SessionStorage、 LocalStorage存储空间比较大大概5M。 Cookie、SessionStorage、 LocalStorage数据共享都遵循同源原则SessionStorage还限制必须是同一个页面。
在前端给后端发送请求的时候会自动携带Cookie中的数据但是SessionStorage、 LocalStorage不会 加分回答 由于它们的以上区别所以它们的应用场景也不同Cookie一般用于存储登录验证信息SessionID或者tokenLocalStorage常用于存储不易变动的数据减轻服务器的压力SessionStorage可以用来检测用户是否是刷新进入页面如音乐播放器恢复播放进度条的功能。
cookie、sessionStorage和localStorage在Web开发中都是用于在客户端存储数据的方式但它们之间存在一些重要的区别。
存储位置
cookie是由服务器端写入的保存在用户计算机上的小型文本文件。这个文件可以被服务器用来识别用户身份、跟踪用户活动、保存用户设置等。sessionStorage和localStorage都是由前端写入的存储在浏览器的本地存储空间中。
存储大小
cookie存储空间比较小通常只有4KB左右。sessionStorage和localStorage存储空间相对较大通常可以达到5MB或更多。
生命周期
cookie生命周期是由服务器端在写入的时候就设置好的可以设置一个过期时间当这个时间到达后cookie就会失效。另外用户也可以通过浏览器设置来手动删除cookie。sessionStorage是页面关闭的时候就会自动清除的即只在当前会话期间有效。当用户关闭浏览器标签页或者浏览器窗口时sessionStorage中的数据会被清除。localStorage数据是永久性的除非手动删除否则会一直存在。
数据共享
三者都遵循同源原则即只能存储和访问与当前页面同源的数据。sessionStorage还限制必须是同一个页面即在同一浏览器窗口或标签页中打开的多个页面可以通过sessionStorage来共享数据但页面关闭后数据就会被清空。localStorage则可以在多个页面之间共享数据只要这些页面是同源的。
使用场景
cookie主要用于会话管理、个性化设置等。例如当用户登录一个网站时服务器会生成一个包含会话ID的cookie并发送给浏览器浏览器将这个cookie保存在本地。此后每次用户发送请求时浏览器都会自动将这个cookie发送给服务器服务器通过会话ID识别用户身份从而保持用户的登录状态。sessionStorage主要用于临时数据存储、状态管理、数据共享、缓存管理等。例如在用户登录后可以将用户的登录状态存储在sessionStorage中以便在不同页面之间共享用户的登录状态。localStorage主要用于在浏览器端存储和获取数据如本地缓存、用户偏好设置、表单数据的自动填充等。例如可以使用localStorage来缓存一些静态资源如图片、CSS文件、JavaScript文件等以便提高应用程序的性能和加载速度。
综上所述cookie、sessionStorage和localStorage在存储位置、存储大小、生命周期、数据共享和使用场景等方面都存在明显的区别。在选择使用哪种存储方式时需要根据具体的需求和场景来进行选择。
说一说如何实现可过期的localstorage数据
实现可过期的localStorage数据通常意味着你需要为存储在localStorage中的每个项目设置一个额外的过期时间戳。然后你可以编写一个机制来定期检查这些项目的过期时间并在需要时删除它们。然而由于localStorage本身并没有提供自动过期的功能你需要通过JavaScript来实现这一点。
以下是一个简单的实现步骤 定义数据格式 当你存储数据到localStorage时除了实际的数据值之外还要存储一个过期时间戳。这可以是一个对象其中包含一个data属性和一个expires属性。 javascript复制代码const dataWithExpiry { data: Some data to store, expires: Date.now() (1000 * 60 * 60 * 24) // 1 day from now in milliseconds }; localStorage.setItem(myKey, JSON.stringify(dataWithExpiry));检查并删除过期数据 你需要编写一个函数来遍历localStorage中的所有项目并检查它们的过期时间戳。如果某个项目的过期时间戳已经小于当前时间戳那么删除这个项目。 javascript复制代码function cleanExpiredData() { for (let i 0; i localStorage.length; i) { const key localStorage.key(i); const item JSON.parse(localStorage.getItem(key)); if (item item.expires item.expires Date.now()) { localStorage.removeItem(key); } } } // 你可以定期调用这个函数比如每次页面加载时 window.onload cleanExpiredData; // 或者使用setInterval来定期清理 // setInterval(cleanExpiredData, 60000); // 每分钟执行一次获取数据时检查过期 当你从localStorage中获取数据时也应该检查它是否已过期。如果已过期你可以返回null或undefined来表示数据不可用。 javascript复制代码function getDataWithExpiry(key) { const item JSON.parse(localStorage.getItem(key)); if (item item.expires item.expires Date.now()) { // 数据已过期可以删除它可选 localStorage.removeItem(key); return null; } return item ? item.data : null; } const myData getDataWithExpiry(myKey);设置合理的过期时间 确保你设置的过期时间戳是合理的并且符合你的应用程序的需求。你可能需要根据数据的类型和使用频率来调整过期时间。 注意事项 由于localStorage的容量限制你需要确保不要存储过多的数据或过多的过期数据。定期清理过期数据可能会影响性能特别是在有大量数据的情况下。因此你可能需要权衡清理频率和性能之间的关系。如果你的应用程序需要在多个标签页或窗口之间共享数据并且这些标签页或窗口可能同时打开那么你需要确保你的过期清理机制不会在这些标签页或窗口之间造成不一致的状态。
localStorage只能用于长久保存整个网站的数据保存的数据没有过期时间直到手动去删除。 所以要实现可过期的localStorage缓存的中重点就是如何清理过期的缓存。 目前有两种方法 一种是惰性删除另一种是定时删除。 惰性删除是指某个键值过期后该键值不会被马上删除而是等到下次被使用的时候才会被检查到过期此时才能得到删除。 实现方法是存储的数据类型是个对象该对象有两个key一个是要存储的value值另一个是当前时间。获取数据的时候拿到存储的时间和当前时间做对比如果超过过期时间就清除Cookie。 定时删除是指每隔一段时间执行一次删除操作并通过限制删除操作执行的次数和频率来减少删除操作对CPU的长期占用。另一方面定时删除也有效的减少了因惰性删除带来的对localStorage空间的浪费。 实现过程获取所有设置过期时间的key判断是否过期过期就存储到数组中遍历数组每隔1S固定时间删除5个固定个数直到把数组中的key从localstorage中全部删除。 加分回答 LocalStorage清空应用场景token存储在LocalStorage中要清空
说一下token 能放在cookie中吗
能。 token一般是用来判断用户是否登录的它内部包含的信息有 uid(用户唯一的身份标识)、 time(当前时间的时间戳)、 sign签名token 的前几位以哈希算法压缩成的一定长度的十六进制字符串 token可以存放在Cookie中token 是否过期应该由后端来判断不该前端来判断所以token存储在cookie中只要不设置cookie的过期时间就ok了如果 token 失效就让后端在接口中返回固定的状态表示token 失效需要重新登录再重新登录的时候重新设置 cookie 中的 token 就行。 加分回答 token认证流程 客户端使用用户名跟密码请求登录服务端收到请求去验证用户名与密码验证成功后服务端签发一个 token 并把它发送给客户端客户端接收 token 以后会把它存储起来比如放在 cookie 里或者 localStorage 里客户端每次发送请求时都需要带着服务端签发的 token把 token 放到 HTTP 的 Header 里服务端收到请求后需要验证请求里带有的 token 如验证成功则返回对应的数据
是的token可以放在cookie中。在Web开发中将token如JWT、session token等存储在cookie中是一种常见的做法特别是用于身份验证和授权。
将token存储在cookie中有几个优点
HTTP自动发送一旦cookie被浏览器接收并存储浏览器会在后续的请求中自动将cookie发送到服务器无需在客户端代码中显式添加。这使得在客户端和服务器之间传递token变得简单和透明。跨页面保持登录状态通过将token存储在cookie中用户可以在整个应用程序的不同页面之间保持登录状态而无需在每个页面重新验证。安全性虽然cookie本身不是最安全的数据存储方式但可以通过设置cookie的属性来提高安全性。例如可以设置Secure标志以确保cookie仅通过HTTPS传输设置HttpOnly标志以防止JavaScript访问cookie从而防止跨站脚本攻击以及设置SameSite属性来限制cookie的发送条件。
然而将token存储在cookie中也存在一些考虑因素
大小限制cookie的大小有限制通常为4KB因此如果token很大可能不适合存储在cookie中。XSS攻击虽然可以通过设置HttpOnly标志来降低跨站脚本攻击的风险但如果应用程序中存在其他安全漏洞攻击者仍然可能能够窃取cookie中的token。CSRF攻击由于cookie是自动发送的因此存在跨站请求伪造CSRF的风险。然而通过结合其他安全措施如使用不可预测的token、验证请求来源等可以降低这种风险。
总之将token存储在cookie中是一种可行的做法但需要根据应用程序的具体需求和安全要求来权衡利弊。在决定使用cookie存储token之前请确保了解相关的安全风险并采取相应的安全措施。
axios的拦截器原理及应用
axios的拦截器的应用场景 请求拦截器用于在接口请求之前做的处理比如为每个请求带上相应的参数token时间戳等。 返回拦截器用于在接口返回之后做的处理比如对返回的状态进行判断token是否过期。 axios为开发者提供了这样一个API拦截器。拦截器分为 请求request拦截器和 响应response拦截器。 拦截器原理创建一个chn数组数组中保存了拦截器相应方法以及dispatchRequestdispatchRequest这个函数调用才会真正的开始下发请求把请求拦截器的方法放到chn数组中dispatchRequest的前面把响应拦截器的方法放到chn数组中dispatchRequest的后面把请求拦截器和相应拦截器forEach将它们分unshift,push到chn数组中为了保证它们的执行顺序需要使用promise以出队列的方式对chn数组中的方法挨个执行。
加分回答
Axios 是一个基于 promise 的 HTTP 库可以用在浏览器和 node.js 中。从浏览器中创建 XMLHttpRequests,从 node.js 创建 http 请求,支持 Promise API,可拦截请求和响应可转换请求数据和响应数据可取消请求可自动转换 JSON 数据客户端支持防御 XSRF
Axios的拦截器原理及应用如下
原理
Axios的拦截器允许你在请求被发送到服务器之前或响应从服务器返回之后对其进行一些操作。拦截器实质上是函数这些函数在请求被处理前或响应被处理前被调用。它们提供了修改请求和响应的机会。
Axios拦截器主要有两种类型
请求拦截器Request Interceptors在请求发送到服务器之前对其进行修改。例如你可以在这里添加认证信息、设置请求头等。响应拦截器Response Interceptors在服务器响应返回后对其进行处理。例如你可以在这里对响应数据进行转换、错误处理等。
拦截器的实现原理是通过链式调用来实现的。当你使用Axios发送请求时Axios会将请求配置对象传递给第一个请求拦截器函数。这个函数可以对请求配置对象进行修改然后返回修改后的配置对象或者返回一个Promise对象用于异步处理。然后这个配置对象或Promise会被传递给下一个请求拦截器直到所有请求拦截器都被处理完毕。最后请求会被发送到服务器。对于响应拦截器流程是类似的但是操作的对象是服务器返回的响应数据。
应用
Axios的拦截器在实际应用中有很多用途以下是一些常见的应用场景
认证和授权你可以使用拦截器来添加认证信息如JWT token到每个请求的头部或者在响应返回时进行授权检查。错误处理拦截器可以用于捕获请求和响应的错误并进行统一处理。例如你可以在拦截器中捕获特定的错误状态码并在发生错误时执行相应的操作如显示错误提示。请求转换拦截器可以用于在发送请求之前对请求数据进行修改。这可以包括添加、删除或修改请求的参数、标头或URL等。响应转换拦截器还可以在接收到响应后对其进行修改。例如你可以使用拦截器来处理常见的响应格式如JSON将其转换为更适合前端使用的结构。缓存策略拦截器可用于实现缓存策略以减少对服务器的重复请求。你可以在拦截器中检查缓存是否存在并根据需要返回缓存数据或继续发出请求。请求超时和重试你可以使用拦截器来设置请求的超时时间并在请求超时时取消请求或执行相应的处理。同时你还可以实现请求重试的逻辑在网络错误或服务器错误时自动重试请求。
总之Axios的拦截器提供了在发送请求和处理响应之前进行干预的能力可以用于实现许多与网络请求相关的功能和需求。
创建ajax过程
创建ajax过程
创建XHR对象new XMLHttpRequest()设置请求参数request.open(Method, 服务器接口地址);发送请求: request.send()如果是get请求不需要参数post请求需要参数request.send(data)监听请求成功后的状态变化根据状态码进行相应的处理。
XHR.onreadystatechange function () {
if (XHR.readyState 4 XHR.status 200) { console.log(XHR.responseText); // 主动释放,JS本身也会回收的 XHR null; } }; 加分回答 POST请求需要设置请求头 readyState值说明 0初始化,XHR对象已经创建,还未执行open 1载入,已经调用open方法,但是还没发送请求 2载入完成,请求已经发送完成 3交互,可以接收到部分数据 4数据全部返回 status值说明 200成功 404没有发现文件、查询或URl 500服务器产生内部错误
创建 AJAXAsynchronous JavaScript and XML过程通常涉及以下几个步骤。以下是一个基本的 AJAX 请求的创建过程
创建 XMLHttpRequest 对象
在浏览器中你可以使用 XMLHttpRequest 对象来发送 AJAX 请求。但现代浏览器还支持 fetch API它是一个更现代、更简洁的替代方案。不过为了解释 AJAX 的基本原理我们仍然使用 XMLHttpRequest。
javascript复制代码var xhr new XMLHttpRequest();设置请求方法和 URL
使用 open() 方法设置请求的方法和要请求的 URL。第一个参数是请求类型如 “GET”、“POST” 等第二个参数是请求的 URL第三个参数是可选的表示是否异步处理请求默认为 true。
javascript复制代码xhr.open(GET, https://example.com/api/data, true);设置响应处理函数
使用 onreadystatechange 属性来设置响应处理函数。这个函数将在请求的状态改变时被调用。通常我们会在请求完成即 readyState 等于 4且成功即 status 等于 200时处理响应。
javascript复制代码xhr.onreadystatechange function() { if (xhr.readyState 4 xhr.status 200) { // 处理响应数据 var responseData xhr.responseText; // 对于文本响应 // 或者使用 xhr.responseJSON如果服务器设置了正确的响应头 console.log(responseData); } };发送请求
使用 send() 方法发送请求。对于 GET 请求你通常不需要传递任何参数给 send()。但对于 POST 请求你需要传递一个参数这个参数是要发送到服务器的数据可以是 DOMString 或 Document。
javascript复制代码xhr.send(); // 对于 GET 请求 // 对于 POST 请求可能需要像下面这样 // xhr.send(JSON.stringify(someData));处理错误
你还可以添加一个错误处理函数来捕获网络错误或请求失败的情况。这可以通过监听 onerror 事件来实现。
javascript复制代码xhr.onerror function() { console.error(An error occurred while fetching the data.); };设置请求头可选
如果你需要设置自定义的请求头可以在调用 send() 之前使用 setRequestHeader() 方法。
javascript复制代码xhr.setRequestHeader(Content-Type, application/json);处理其他状态码可选
除了检查 status 是否等于 200 外你还可以处理其他 HTTP 状态码如 404、500 等。 8. 使用 fetch API可选
如前所述现代浏览器支持 fetch API这是一个更现代、更简洁的 AJAX 替代方案。使用 fetch 可以更简洁地发送请求和处理响应。
javascript复制代码fetch(https://example.com/api/data) .then(response response.json()) // 解析 JSON 响应 .then(data console.log(data)) // 处理数据 .catch(error console.error(Error:, error)); // 捕获错误