牛客面试前端1

HTML语义化

  • 是什么
    前端语义化是指在构建网页时多使用html语义化标签布局,多使用带有语义的标签如header,aside,footer等标签
  • 为什么
    结构清晰利于开发者开发与维护 有利于seo搜索引擎优化 有利于在网络卡顿时,正常显示页面结构(虽然没有样式),提高用户体验

盒模型

  • 是什么:
    页面布局的元素可以看做一个个盒子,盒子包括content,padding,border,margin
    一般分为两种盒子模型 :
    标准盒子模型与怪异盒子模型
    两者区别是,标准盒子设置盒子宽高时默认是content的宽高,而怪异盒子设置时是除了margin都包括
  • 怎么办:
    为了布局方便,采取css属性,box-sizing:border-box设置为怪异盒子

浮动

  • 是什么
    一种网页布局方式,顾名思义,布局的元素会浮在标准布局之上
  • 为什么
    最初是为了实现图文环绕效果,目前一般用于将块级元素排列在一行
  • 怎么做
    元素设置flot:left/right 存在问题:由于脱标,若父元素未设置高度会造成父盒子塌陷现象,然后影响之后其他元素布局 解决方案:常用的给父元素设置overflow:hidden 添加伪元素,或直接设置高度等

样式优先级的规则

CSS样式的优先级应该分成四大类

  • 第一类!important,无论引入方式是什么,选择器是什么,它的优先级都是最高的。
  • 第二类引入方式,行内样式的优先级要高于嵌入和外链,嵌入和外链如果使用的选择器相同就看他们在页面中插入的顺序,在后面插入的会覆盖前面的。
  • 第三类选择器,选择器优先级:id选择器>(类选择器 | 伪类选择器 | 属性选择器 )> (后代选择器 | 伪元素选择器 )> (子选择器 | 相邻选择器) > 通配符选择器 。
  • 第四类继承样式,是所有样式中优先级比较低的。
  • -第五类 浏览器默认样式优先级最低。

css尺寸设置的单位

  • px 绝对长度,由屏幕分辨率决定
  • em 相对长度,相对自身font大小,自身fontsize未设置继承父元素
  • rem 相对长度,相对页面根元素大小
  • vw/vh 相对长度,相对视窗的宽/高 1/100

BFC

  • 是什么
    块级格式化上下文,使自己成为一个独立环境不影响上下文其他元素的布局
  • 为什么
    主要用于清除浮动影响,与避免外边距合并等问题
  • 怎么做
    overflow:hidden display:flow-root等

未知宽高元素水平垂直居中

1.flex
父元素设置display:flex,justify-content:center,align-items:center
2.position
子绝父相,
子元素注意left:50℅,top:50%,transform:translate(-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 (用于引用数据类型)

  1. typeof:常用于判断基本数据类型,对于引用数据类型除了function返回’function‘,其余全部返回’object’。
  2. instanceof:主要用于区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true,
let arr = []; console.log(arr instanceof Array); // true
function MyClass() {} let obj = new MyClass(); console.log(obj instanceof MyClass); // true
  1. constructor:用于检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。
let num = 123; console.log(num.constructor === Number); // true 
let str = 'hello'; console.log(str.constructor === String); // true
//但这种方法对于 `null` 和 `undefined` 不起作用,因为它们是原始值,没有 `constructor` 属性。
  1. 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]"
  1. 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()方法指向不同的数据类型上面,返回不同的结果

数组去重都有哪些方法?

  1. 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]
  1. 使用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]

  1. filter+indexof
    filter+indexof 去重:这个方法和第一种方法类似,利用 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]
  1. 使用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]
  1. 使用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]

  1. 使用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中存在一些关键的区别。以下是它们之间的主要差异:

  1. 数据类型

    • 数组:是JavaScript中的一种特殊对象,专门用于存储和操作数据的有序集合。数组的元素通过索引(从0开始)进行访问,且所有元素都是同一类型(尽管在实际应用中,JavaScript的数组可以包含不同类型的元素)。
    • 伪数组:本质上是一个对象,具有类似数组的某些特性,如索引和length属性,但并非真正的数组。常见的伪数组包括函数中的arguments对象、某些DOM方法返回的元素集合(如document.getElementsByClassName())以及字符串(尽管字符串的索引和length属性与数组相似,但它们的行为并不完全相同)。
  2. 原型链

    • 数组:继承自Array.prototype,因此具有数组特有的方法,如push()pop()forEach()等。
    • 伪数组:通常继承自Object.prototype,不具有数组特有的方法。尽管它们可能具有length属性,但这个属性通常是静态的,不会随着成员的变化而改变。
  3. 访问方法

    • 数组:可以直接使用数组的方法,如push()pop()slice()等。
    • 伪数组:由于它们不是真正的数组,因此不能直接使用数组的方法。但是,可以通过其他方式(如使用Array.prototype的方法或循环)来处理伪数组的元素。
  4. 长度

    • 数组:长度是动态的,可以随着元素的添加或删除而改变。
    • 伪数组:长度通常是静态的,不会随着成员的变化而改变。
  5. 用途

    • 数组:用于存储和操作有序的数据集合,是JavaScript中常用的数据结构之一。
    • 伪数组:通常用于表示具有类似数组特性的对象集合,但由于其不是真正的数组,因此在使用时需要特别注意。

伪数组
它的类型不是Array,而是Object
而数组类型是Array。
可以使用的length属性查看长度,也可以使用[index]获取某个元素,但是不能使用数组的其他方法
也不能改变长度,遍历使用for in方法。
伪数组的常见场景:

  • 函数的参数arguments
  • 原生js获取DOM:document.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

  1. 当函数体是单条语句的时候可以省略{}和return。
  2. 另一种是包含多条语句,不可以省略{}和return。 箭头函数最大的特点就是没有this,所以this是从外部获取,就是继承外部的执行上下文中的this,由于没有this关键字所以箭头函数也不能作为构造函数,

这使得在回调函数中使用 this 变得更加容易,因为它不会意外地改变。

同时通过 call()apply() 方法调用一个函数时,只能传递参数(不能绑定this),第一个参数会被忽略。箭头函数也没有原型和super。不能使用yield关键字,因此箭头函数不能用作 Generator 函数。不能返回直接对象字面量。

  1. 没有 arguments 对象

    箭头函数没有 arguments 绑定。如果需要访问函数参数列表,可以使用剩余参数(rest parameters)。

  2. 没有原型(prototype

    由于箭头函数没有 this 绑定,它们也不能用作构造函数,因此没有 prototype 属性。

  3. 不支持 new 关键字

    由于箭头函数没有构造函数功能,使用 new 关键字调用箭头函数会抛出一个错误。

  4. 没有 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 中通过…语法形式来表示

  1. 函数调用
    • 等价于apply的方式,用于将一个数组或类数组对象展开为函数调用的参数。
    • 示例:function myFunction(v, w, x, y, z) { ... }var args = [1, 2, 3]; myFunction(...args, 4, 5);
  2. 数组构造
    • 将数组展开为另一个数组的元素,或者与其他数组组合。
    • 示例:var arr1 = [0, 1]; var arr2 = [...arr1, 2, 3]; // arr2 为 [0, 1, 2, 3]
    • 还可以用于复制数组,避免修改原数组。
    • 示例:var arr3 = [1, 2, 3]; var arr4 = [...arr3]; // arr4 是 arr3 的浅拷贝
  3. 构造字面量对象
    • 将对象表达式按key-value的方式展开,用于对象的合并或浅拷贝。
    • 示例:var obj1 = { a: 1, b: 2 }; var obj2 = { ...obj1, c: 3 }; // obj2 为 { a: 1, b: 2, c: 3 }
  4. 数组字符串连接
    • 可以与字符串连接,将数组中的元素转化为字符串并用逗号分隔,然后与其他字符串连接。
    • 示例:var arr = [1, 2, 3]; var str = 'The numbers are: ' + ...arr; // 注意:这里直接与字符串相加是不支持的,但可以结合其他方法如join
  5. 浅拷贝
    • 正如前面提到的,可以使用展开语法进行数组的浅拷贝或对象的浅合并。
  6. 迭代器的使用
    • 展开语法内部也基于迭代器(Iterator)。与for...of类似,它通过数组的迭代器访问数组中的每个成员。
  7. 剩余参数与展开语法的结合
    • 在函数定义时,可以使用剩余参数(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)。
一般就是一个函数A,return其内部的函数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这种不讨论。

  1. 在全局执行环境中无论是否在严格模式下,(在任何函数体外部)this 都指向全局对象。
  2. 在函数执行上下文中访问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:

  1. bind在改变this指向的时候,返回一个改变执行上下文的函数,不会立即执行函数,而是需要调用该函数的时候再调用即可,
  2. 但是call和apply在改变this指向的同时执行了该函数。 bind只接收一个参数,就是this指向的执行上文。
  3. call、apply接收多个参数,第一个参数都是this指向的执行上文,后面的参数都是作为改变this指向的函数的参数。
  4. 但是call和apply参数的格式不同,call是一个参数对应一个原函数的参数,但是apply第二个参数是数组,数组中每个元素代表函数接收的参数,数组有几个元素函数就接收几个元素。
  • callapplybind都可以用于改变函数运行时的this上下文。

区别

  1. 执行方式

    • callapply会立即执行函数,并修改函数内部的this指向。
    • bind不会立即执行函数,而是返回一个新的函数,这个新函数的this被永久地绑定到了bind的第一个参数上。
  2. 参数传递

    • callbind在传递参数时都是将参数逐一传入。
      • function.call(thisArg, arg1, arg2, ...)
      • function.bind(thisArg, arg1, arg2, ...)
    • apply在传递参数时需要使用一个数组或类数组对象。
      • function.apply(thisArg, [argsArray])
  3. 返回值

    • callapply都会直接执行函数并返回函数的执行结果(如果有的话)。
    • bind返回一个新的函数,这个新函数在被调用时,会按照bind指定的this和参数来执行原函数。

示例

  1. call
javascript复制代码function greet(name) {    console.log(`Hello, ${this.greeting} ${name}!`);  }    const obj = { greeting: 'World' };  greet.call(obj, 'Alice');  // 输出: Hello, World Alice!
  1. 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!
  1. 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指向subClass
	this.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 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this

加分回答
new关键字后面的构造函数不能是箭头函数。

  1. 创建一个空对象:首先,JavaScript会创建一个新的空对象。
  2. 设置原型:将这个空对象的__proto__(内部属性,在ES6中通常使用Object.getPrototypeOf()Object.setPrototypeOf()来访问和修改)设置为构造函数的prototype对象。这样,新创建的对象就可以继承构造函数原型上的属性和方法。
  3. 改变this指向:在构造函数中,this关键字引用的是新创建的对象。
  4. 执行构造函数中的代码:接着,执行构造函数中的代码,为新创建的对象添加属性和方法。
  5. 返回新对象:如果构造函数中没有显式地返回一个对象(即返回的不是一个对象类型的值,如原始值undefinednull、数字、字符串、布尔值等),那么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对象,并将nameage属性设置为传递给构造函数的参数值。然后,由于Person.prototype上有一个greet方法,所以新创建的john对象可以调用这个方法。

defer和async区别?

在JavaScript中,deferasync都是用于控制脚本异步执行的关键字,但它们之间存在一些重要的区别。

defer

  • 当使用defer属性时,浏览器会异步地下载脚本,但不会立即执行它。脚本的下载不会阻塞页面的渲染,但是脚本的执行会等到整个文档都解析完成后才会开始。
  • 如果有多个带有defer属性的脚本,它们会按照在HTML文档中出现的顺序来执行。
  • defer适用于那些不需要立即执行,但需要在文档完全加载后再执行的脚本。例如,你可能想在页面加载完成后设置一些事件监听器或初始化某些功能。
  • 使用defer时,不要在脚本中调用document.write(),因为此时文档已经完成解析,document.write()会覆盖整个页面。

async

  • 当使用async属性时,浏览器也会在后台异步地下载脚本,但与defer不同的是,一旦脚本下载完成,它就会立即执行,而不管文档是否已经完全解析。
  • 由于脚本的执行可能会发生在文档解析过程中,所以async脚本可能会改变DOM的内容,从而可能导致页面渲染的突然变化。
  • 如果有多个带有async属性的脚本,它们不会按照在HTML文档中出现的顺序来执行。浏览器会尽可能快地并行下载和执行这些脚本,所以它们的执行顺序是不确定的。
  • 使用async时,需要确保脚本不会依赖于尚未加载的DOM元素,否则可能会出现错误。

总结

  • deferasync都用于异步加载脚本,但它们的执行时机不同。
  • 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的基本使用方法如下:

  1. 创建Promise对象:使用new Promise()构造函数来创建一个新的Promise对象。构造函数接受一个函数作为参数,这个函数有两个参数:resolvereject。这两个函数是Promise内部的回调,分别表示异步操作成功和失败时的回调函数。
javascript复制代码const promise = new Promise((resolve, reject) => {    // 异步操作    // 如果异步操作成功,则调用resolve(value)    // 如果异步操作失败,则调用reject(error)  });
  1. 处理Promise的结果:使用.then().catch()方法来处理Promise的结果。.then()方法用于处理Promise成功的情况,.catch()方法用于处理Promise失败的情况。这两个方法都接受一个回调函数作为参数,该回调函数将在Promise的状态改变时被调用。
javascript复制代码promise.then((result) => {    // 处理异步操作成功的结果  }).catch((error) => {    // 处理异步操作失败的结果  });
  1. 链式调用.then().catch()方法都可以返回一个新的Promise对象,因此可以链式调用它们来处理多个异步操作。在.then()方法的回调函数中,可以返回一个新的Promise对象来表示下一个异步操作。
javascript复制代码promise.then((result1) => {    // 处理第一个异步操作的结果    return new Promise((resolve, reject) => {      // 第二个异步操作      // ...    });  }).then((result2) => {    // 处理第二个异步操作的结果  }).catch((error) => {    // 处理任何一个异步操作失败的结果  });
  1. Promise的状态:Promise对象有三种状态:pending(等待中)、fulfilled(已成功)和rejected(已失败)。一旦Promise的状态从pending变为fulfilled或rejected,它将永远不会改变。这意味着Promise的结果只能被处理一次。如果多次调用.then().catch(),只有第一次调用时会被处理。

以上就是Promise的基本概念和使用方法。通过使用Promise,可以更优雅地处理JavaScript中的异步操作。

Promise的作用:

Promise是异步微任务,解决了异步多层嵌套回调的问题,让代码的可读性更高,更容易维护

Promise使用:

Promise是ES6提供的一个构造函数,可以使用Promise构造函数new一个实例,Promise构造函数接收一个函数作为参数,这个函数有两个参数,分别是两个函数 resolverejectresolve将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;reject则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例创建完成后,可以使用then方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,then和catch最终返回的也是一个Promise,所以可以链式调用。

Promise的特点:

  1. 对象的状态不受外界影响(Promise对象代表一个异步操作,有三种状态)。 - pending(执行中) - Resolved(成功,又称Fulfilled) - rejected(拒绝) 其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise对象的状态改变,只有两种可能(状态凝固了,就不会再变了,会一直保持这个结果): - 从Pending变为Resolved - 从Pending变为Rejected
  3. resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数
  4. 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中,有多种方式可以实现异步操作。以下是一些常见的方法:

  1. 回调函数(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);      }  });
  1. 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);  });
  1. async/await

async/await 是基于Promises的语法糖,使异步代码看起来像同步代码一样。async函数返回一个Promise,await表达式会暂停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();
  1. 事件监听器(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或者token,LocalStorage常用于存储不易变动的数据,减轻服务器的压力,SessionStorage可以用来检测用户是否是刷新进入页面,如音乐播放器恢复播放进度条的功能。

cookie、sessionStorage和localStorage在Web开发中都是用于在客户端存储数据的方式,但它们之间存在一些重要的区别。

  1. 存储位置:
  • cookie:是由服务器端写入的,保存在用户计算机上的小型文本文件。这个文件可以被服务器用来识别用户身份、跟踪用户活动、保存用户设置等。
  • sessionStorage和localStorage:都是由前端写入的,存储在浏览器的本地存储空间中。
  1. 存储大小:
  • cookie:存储空间比较小,通常只有4KB左右。
  • sessionStorage和localStorage:存储空间相对较大,通常可以达到5MB或更多。
  1. 生命周期:
  • cookie:生命周期是由服务器端在写入的时候就设置好的,可以设置一个过期时间,当这个时间到达后,cookie就会失效。另外,用户也可以通过浏览器设置来手动删除cookie。
  • sessionStorage:是页面关闭的时候就会自动清除的,即只在当前会话期间有效。当用户关闭浏览器标签页或者浏览器窗口时,sessionStorage中的数据会被清除。
  • localStorage:数据是永久性的,除非手动删除,否则会一直存在。
  1. 数据共享:
  • 三者都遵循同源原则,即只能存储和访问与当前页面同源的数据。
  • sessionStorage还限制必须是同一个页面,即在同一浏览器窗口或标签页中打开的多个页面可以通过sessionStorage来共享数据,但页面关闭后数据就会被清空。
  • localStorage则可以在多个页面之间共享数据,只要这些页面是同源的。
  1. 使用场景:
  • cookie:主要用于会话管理、个性化设置等。例如,当用户登录一个网站时,服务器会生成一个包含会话ID的cookie并发送给浏览器,浏览器将这个cookie保存在本地。此后,每次用户发送请求时,浏览器都会自动将这个cookie发送给服务器,服务器通过会话ID识别用户身份,从而保持用户的登录状态。
  • sessionStorage:主要用于临时数据存储、状态管理、数据共享、缓存管理等。例如,在用户登录后,可以将用户的登录状态存储在sessionStorage中,以便在不同页面之间共享用户的登录状态。
  • localStorage:主要用于在浏览器端存储和获取数据,如本地缓存、用户偏好设置、表单数据的自动填充等。例如,可以使用localStorage来缓存一些静态资源(如图片、CSS文件、JavaScript文件等),以便提高应用程序的性能和加载速度。

综上所述,cookie、sessionStorage和localStorage在存储位置、存储大小、生命周期、数据共享和使用场景等方面都存在明显的区别。在选择使用哪种存储方式时,需要根据具体的需求和场景来进行选择。

说一说如何实现可过期的localstorage数据?

实现可过期的localStorage数据通常意味着你需要为存储在localStorage中的每个项目设置一个额外的过期时间戳。然后,你可以编写一个机制来定期检查这些项目的过期时间,并在需要时删除它们。然而,由于localStorage本身并没有提供自动过期的功能,你需要通过JavaScript来实现这一点。

以下是一个简单的实现步骤:

  1. 定义数据格式
    当你存储数据到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));
    
  2. 检查并删除过期数据
    你需要编写一个函数来遍历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); // 每分钟执行一次
    
  3. 获取数据时检查过期
    当你从localStorage中获取数据时,也应该检查它是否已过期。如果已过期,你可以返回nullundefined来表示数据不可用。

    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');
    
  4. 设置合理的过期时间
    确保你设置的过期时间戳是合理的,并且符合你的应用程序的需求。你可能需要根据数据的类型和使用频率来调整过期时间。

  5. 注意事项

    • 由于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认证流程

  1. 客户端使用用户名跟密码请求登录
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端签发一个 token ,并把它发送给客户端
  4. 客户端接收 token 以后会把它存储起来,比如放在 cookie 里或者 localStorage 里
  5. 客户端每次发送请求时都需要带着服务端签发的 token(把 token 放到 HTTP 的 Header 里)
  6. 服务端收到请求后,需要验证请求里带有的 token ,如验证成功则返回对应的数据

是的,token可以放在cookie中。在Web开发中,将token(如JWT、session token等)存储在cookie中是一种常见的做法,特别是用于身份验证和授权。

将token存储在cookie中有几个优点:

  1. HTTP自动发送:一旦cookie被浏览器接收并存储,浏览器会在后续的请求中自动将cookie发送到服务器,无需在客户端代码中显式添加。这使得在客户端和服务器之间传递token变得简单和透明。
  2. 跨页面保持登录状态:通过将token存储在cookie中,用户可以在整个应用程序的不同页面之间保持登录状态,而无需在每个页面重新验证。
  3. 安全性:虽然cookie本身不是最安全的数据存储方式,但可以通过设置cookie的属性来提高安全性。例如,可以设置Secure标志以确保cookie仅通过HTTPS传输,设置HttpOnly标志以防止JavaScript访问cookie(从而防止跨站脚本攻击),以及设置SameSite属性来限制cookie的发送条件。

然而,将token存储在cookie中也存在一些考虑因素:

  1. 大小限制:cookie的大小有限制(通常为4KB),因此如果token很大,可能不适合存储在cookie中。
  2. XSS攻击:虽然可以通过设置HttpOnly标志来降低跨站脚本攻击的风险,但如果应用程序中存在其他安全漏洞,攻击者仍然可能能够窃取cookie中的token。
  3. CSRF攻击:由于cookie是自动发送的,因此存在跨站请求伪造(CSRF)的风险。然而,通过结合其他安全措施(如使用不可预测的token、验证请求来源等),可以降低这种风险。

总之,将token存储在cookie中是一种可行的做法,但需要根据应用程序的具体需求和安全要求来权衡利弊。在决定使用cookie存储token之前,请确保了解相关的安全风险并采取相应的安全措施。

axios的拦截器原理及应用?

axios的拦截器的应用场景:
请求拦截器用于在接口请求之前做的处理,比如为每个请求带上相应的参数(token,时间戳等)。 返回拦截器用于在接口返回之后做的处理,比如对返回的状态进行判断(token是否过期)。
axios为开发者提供了这样一个API:拦截器。拦截器分为 请求(request)拦截器和 响应(response)拦截器。
拦截器原理:创建一个chn数组,数组中保存了拦截器相应方法以及dispatchRequest(dispatchRequest这个函数调用才会真正的开始下发请求),把请求拦截器的方法放到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拦截器主要有两种类型:

  1. 请求拦截器(Request Interceptors):在请求发送到服务器之前对其进行修改。例如,你可以在这里添加认证信息、设置请求头等。
  2. 响应拦截器(Response Interceptors):在服务器响应返回后对其进行处理。例如,你可以在这里对响应数据进行转换、错误处理等。

拦截器的实现原理是通过链式调用来实现的。当你使用Axios发送请求时,Axios会将请求配置对象传递给第一个请求拦截器函数。这个函数可以对请求配置对象进行修改,然后返回修改后的配置对象,或者返回一个Promise对象用于异步处理。然后,这个配置对象(或Promise)会被传递给下一个请求拦截器,直到所有请求拦截器都被处理完毕。最后,请求会被发送到服务器。对于响应拦截器,流程是类似的,但是操作的对象是服务器返回的响应数据。

应用

Axios的拦截器在实际应用中有很多用途,以下是一些常见的应用场景:

  1. 认证和授权:你可以使用拦截器来添加认证信息(如JWT token)到每个请求的头部,或者在响应返回时进行授权检查。
  2. 错误处理:拦截器可以用于捕获请求和响应的错误,并进行统一处理。例如,你可以在拦截器中捕获特定的错误状态码,并在发生错误时执行相应的操作(如显示错误提示)。
  3. 请求转换:拦截器可以用于在发送请求之前对请求数据进行修改。这可以包括添加、删除或修改请求的参数、标头或URL等。
  4. 响应转换:拦截器还可以在接收到响应后对其进行修改。例如,你可以使用拦截器来处理常见的响应格式(如JSON),将其转换为更适合前端使用的结构。
  5. 缓存策略:拦截器可用于实现缓存策略,以减少对服务器的重复请求。你可以在拦截器中检查缓存是否存在,并根据需要返回缓存数据或继续发出请求。
  6. 请求超时和重试:你可以使用拦截器来设置请求的超时时间,并在请求超时时取消请求或执行相应的处理。同时,你还可以实现请求重试的逻辑,在网络错误或服务器错误时自动重试请求。

总之,Axios的拦截器提供了在发送请求和处理响应之前进行干预的能力,可以用于实现许多与网络请求相关的功能和需求。

创建ajax过程?

创建ajax过程:

  1. 创建XHR对象:new XMLHttpRequest()
  2. 设置请求参数:request.open(Method, 服务器接口地址);
  3. 发送请求: request.send(),如果是get请求不需要参数,post请求需要参数request.send(data)
  4. 监听请求成功后的状态变化:根据状态码进行相应的处理。
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:服务器产生内部错误

创建 AJAX(Asynchronous JavaScript and XML)过程通常涉及以下几个步骤。以下是一个基本的 AJAX 请求的创建过程:

  1. 创建 XMLHttpRequest 对象

在浏览器中,你可以使用 XMLHttpRequest 对象来发送 AJAX 请求。但现代浏览器还支持 fetch API,它是一个更现代、更简洁的替代方案。不过,为了解释 AJAX 的基本原理,我们仍然使用 XMLHttpRequest

javascript复制代码var xhr = new XMLHttpRequest();
  1. 设置请求方法和 URL

使用 open() 方法设置请求的方法和要请求的 URL。第一个参数是请求类型(如 “GET”、“POST” 等),第二个参数是请求的 URL,第三个参数是可选的,表示是否异步处理请求(默认为 true)。

javascript复制代码xhr.open('GET', 'https://example.com/api/data', true);
  1. 设置响应处理函数

使用 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);      }  };
  1. 发送请求

使用 send() 方法发送请求。对于 GET 请求,你通常不需要传递任何参数给 send()。但对于 POST 请求,你需要传递一个参数,这个参数是要发送到服务器的数据(可以是 DOMString 或 Document)。

javascript复制代码xhr.send(); // 对于 GET 请求    // 对于 POST 请求,可能需要像下面这样:  // xhr.send(JSON.stringify(someData));
  1. 处理错误

你还可以添加一个错误处理函数来捕获网络错误或请求失败的情况。这可以通过监听 onerror 事件来实现。

javascript复制代码xhr.onerror = function() {      console.error('An error occurred while fetching the data.');  };
  1. 设置请求头(可选)

如果你需要设置自定义的请求头,可以在调用 send() 之前使用 setRequestHeader() 方法。

javascript复制代码xhr.setRequestHeader('Content-Type', 'application/json');
  1. 处理其他状态码(可选)

除了检查 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)); // 捕获错误

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/595367.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何快速找出文件夹里的全部带有英文纯英文的文件

参考此文章&#xff1a;如何快速找出文件夹里的全部带有中文&纯中文的文件 只需要根据自己的需求&#xff0c;把下面相关的设置调整好即可

【Hadoop】MapReduce (六)

MapReduce 组件 输入格式 - InputFormat InputFormat发生在Mapper之前&#xff0c;用于对数据进行切分和读取&#xff0c;会将读取到的数据传递给MapTask处理。所以InputFormat读取到的数据是什么格式&#xff0c;Mapper接收到的数据就是什么格式 作用 getSplits&#xff1a…

YOLO系列改进,自研模块助力涨点

目录 一、原理 二、代码 三、添加到YOLOv5中 一、原理 论文地址:

“先锋”西凤

执笔 | 文 清 编辑 | 古利特 制曲是酿酒的第一道工序&#xff0c;也是中国酿酒史上的一大创新&#xff0c;对白酒风味的影响至关重要。西凤酿酒人坚信“曲是酒之骨”&#xff0c;“曲”的品质决定酒的“骨气”&#xff0c;“酒曲”是酒体形成主题风味的基本定型元素和催化剂…

OpenNJet如何做到让用户永远在线

前言 最近看到了国内开源的一个名为OpenNJet的项目&#xff0c;有一个响亮的口号&#xff1a;“下一代云原生应用引擎”。 一下子就被吸引到了。 先看下官方对OpenNJet的介绍&#xff1a; OpenNJet 应用引擎是基于 NGINX 的面向互联网和云原生应用提供的运行时组态服务程序&…

如何根据配置动态生成Spring的Bean?

一、问题解析 在 Spring 应用中&#xff0c;根据运行时的配置&#xff08;比如数据库配置、配置文件、配置中心等&#xff09;动态生成 Spring Bean 是一种常见需求&#xff0c;特别是在面对多环境配置或者需要根据不同条件创建不同实例时。 Spring 提供了几种方式来实现这一需…

spice common模块

库分为三部分libspice-common.a&#xff0c;libspice-common-client.a,libspice-common-server.a。 1、libspice-common.a工程编译代码 # # libspice-common # spice_common_sources [ agent.c, agent.h, backtrace.c, backtrace.h, canvas_utils.c, canvas_utils.h, demarsha…

sql编写规范(word原件)

编写本文档的目的是保证在开发过程中产出高效、格式统一、易阅读、易维护的SQL代码。 1 编写目的 2 SQL书写规范 3 SQL编写原则 软件全套资料获取进主页或者本文末个人名片直接获取。

高德地图在vue3项目中使用:实现画矢量图、编辑矢量图

使用高德地图实现画多边形、矩形、圆&#xff0c;并进行编辑保存和回显。 1、准备工作 参考高德地图官网&#xff0c;进行项目key申请&#xff0c;链接: 准备 2、项目安装依赖 npm i amap/amap-jsapi-loader --save3、地图容器 html <template><!-- 绘制地图区域…

GNSS 地球自转改正算例分析

文章目录 Part.I IntroductionPart.II 由地球自转引起的误差的概念和改正方法Chap.I 误差概念Chap.II 改正方法 Part.II 算例分析Chap.I 基础数据Chap.II 计算过程 AppendixReference Part.I Introduction 为了更好地理解 地球自转改正&#xff0c;本文将介绍一个算例。 Part.…

手动交互式选点提取三维点云轮廓边界线 附python代码

一种新的三维点云轮廓边界提取方案: 1 手动选择一个边界或者其附近的点 2 自动搜索临近区域,并找到附近的平面和进行平面分割 3 提取平面的交点 4 该交点就是点云的轮廓边界点,把它往两边延展,就是完整的点云轮廓边界 import open3d as o3d import numpy as np import …

Java模块化系统:引领代码革命与性能飞跃

JDK工程结构的问题 在说Java模块化系统之前&#xff0c;先来说说Java9之前的JDK在工程结构上的问题&#xff0c;从JDK本身的问题说起&#xff0c;Java从1996年发布第一版到2017年发布Java9&#xff0c;中间经历了近20年的时间&#xff0c;在这期间发布了无数个大大小小的版本用…

RESTFul风格设计和实战

四、RESTFul风格设计和实战 4.1 RESTFul风格概述 4.1.1 RESTFul风格简介 RESTful&#xff08;Representational State Transfer&#xff09;是一种软件架构风格&#xff0c;用于设计网络应用程序和服务之间的通信。它是一种基于标准 HTTP 方法的简单和轻量级的通信协议&#x…

YAML如何操作Kubernetes核心对象

Pod Kubernetes 最核心对象Pod Pod 是对容器的“打包”&#xff0c;里面的容器&#xff08;多个容器&#xff09;是一个整体&#xff0c;总是能够一起调度、一起运行&#xff0c;绝不会出现分离的情况&#xff0c;而且 Pod 属于 Kubernetes&#xff0c;可以在不触碰下层容器的…

存储或读取时转换JSON数据

一、 数据库类型 二、使用Hutool工具 存储时将数据转换为JSON数据 获取时将JSON数据转换为对象 发现问题&#xff1a; 原本数据对象是Address 和 Firend但是转换完成后数据变成了JSONArray和JSONObject 三、自定义TypeHandler继承Mybatis的BaseTypeHandler处理器 package …

Feign 和 OpenFeign 的区别

Feign 和 OpenFeign 都是用来进行服务间调用的客户端库&#xff0c;它们旨在简化HTTP API客户端的编写过程&#xff0c;使得编写对外部服务的接口就像调用本地方法一样简单。尽管它们有相似之处&#xff0c;但也存在一些关键差异&#xff1a; 归属和演进&#xff1a; Feign 最初…

硬件设计计划与APQP

硬件设计的关键节点: 大的里程碑milestone分为: Kickoff->A Samples->做出第一批B样总成件->B Samples/OTS->C Samples->PPAP->SOP 具体到硬件,A/B/C sample阶段,又可细分为: 关键器件选型&硬件系统方案设计原理图绘制PCB LayoutA_BOM输出PCB制板…

3. 深度学习笔记--优化函数

深度学习——优化器算法Optimizer详解&#xff08;BGD、SGD、MBGD、Momentum、Adagrad、Adadelta、RMSprop、Adam、Nadam、AdaMax、AdamW &#xff09; 0. GD &#xff08;梯度下降&#xff09; Gradient Descent&#xff08;梯度下降&#xff09;是一种迭代优化算法&#xf…

FreeRTOS内存管理(1-20)

FreeRTOS内存管理简介 在使用FreeRTOS创建任务&#xff0c;队列&#xff0c;信号量等对象时&#xff0c;一般都提供两种方法 1&#xff1a;动态创建任务&#xff08;方法&#xff09;自动地从FreeRTOS管理的内存堆中申请创建对象所需要的内存&#xff0c;并且在删除对象后可以…

九州金榜|孩子沉迷于网络:家庭教育的挑战与对策

随着时代的进步&#xff0c;科技的发展&#xff0c;网络现在成为了我们日常生活不可分割的一部分。然而&#xff0c;随着网络的普及也出现了一些列的问题&#xff0c;其中孩子沉迷于网络就是当前家长最为关心的问题&#xff0c;对于这种情况的发生&#xff0c;家庭教育就显得尤…
最新文章