八股文
# 1.cookie-session-local
共同点:都是浏览器存储
不同点: 1. cookie由服务器写入, sessionStorage以及localStorage都是由前端写入
2. cookie的生命周期由服务器端写入时就设置好的,localStorage是写入就一直存在,除非手动清除,sessionStorage是由页面关闭时自动清除
3. cookie存储空间大小约4kb, sessionStorage及localStorage空间比较大,大约5M
4. 3者的数据共享都遵循同源原则,sessionStorage还限制必须是同一个页面
5. 前端给后端发送请求时,自动携带cookie, session 及 local都不携带
6. cookie一般存储登录验证信息或者token,localStorage常用于存储不易变动的数据,减轻服务器压力,sessionStorage可以用来监测用户是否是刷新进入页面,如音乐播放器恢复进度条功能
# 2.Js数据类型
# 基本数据类型和引用数据类型
基本数据类型:number,string,boolean,bigint,undefined,null,symbol
引用数据类型:object(普通对象,数组,正则,日期,Math数学函数)
# 3.闭包
1.内层函数引用外层函数中变量,这些变量的集合就是闭包
2.通过作用域链,当前作用域可以访问上级作用域中的变量
3.解决的问题:保存变量,保护变量
4.带来的问题:内存泄露
5.块级作用域
# 4.call、apply、bind
首先,call apply bind三个方法都可以用来改变函数的this指向,具体区别如下:
1、fn.call (newThis,params) call函数的第一个参数是this的新指向,后面依次传入函数fn要用到的参数。会立即执行fn函数。
2、fn.apply (newThis,paramsArr) apply函数的第一个参数是this的新指向,第二个参数是fn要用到的参数数组,会立即执行fn函数。
3、fn.bind (newThis,params) bind函数的第一个参数是this的新指向,后面的参数可以直接传递,也可以按数组的形式传入。 不会立即执行fn函数,且只能改变一次fn函数的指向,后续再用bind更改无效。返回的是已经更改this指向的新fn
手撕:
//call
Function.prototype.mycall = function(target,...args){
target = target || window;
const targetKey = symbol();
target[targetkey] = this;
const res = target[targetkey].(...args);
delete target[targetkey]
return res
}
//apply
Function.prototype.mycall = function(target,args){
target = target || window;
const targetKey = symbol();
target[targetkey] = this;
const res = target[targetkey].(...args);
delete target[targetkey]
return res
}
//bind
Function.prototype.mycall = function(target,...args){
target = target || window;
const targetKey = symbol();
target[targetkey] = this;
const res = target[targetkey].(...args);
delete target[targetkey]
return res
}
# 5.水平垂直居中
注意:父元素需要高度
父元素{
display:flex;
}
子元素{
margin:auto;
}
# 6.margin和padding
margin:外边距,作用于外部对象
padding:内边距,作用于内部对象
# 7.vw和百分比
vw,视图窗口的宽度
百分比,继承于父元素的宽度
# 8.浏览器小字体
{
transform:scale(0.5)//缩放
-webkit-transform:scale(0.5)
}
# 9.es6新特性
# 1.let/const/var
三者都是定义变量
const声明是常量,不可以更改,有块级作用域,没有变量提升,可以重复声明
let声明的是变量,可以更改,有作用域,没有变量提升,不可以重复声明
var声明的变量会提升到作用域的最顶部,可以更改,没有作用域,可以重复声明
# 2.箭头函数
1.箭头函数是匿名函数,没有自己的this,this也不可以改变,call、apply、bind也不可以
2.没有原型prototype,_ _ proto _ _,arguments
3.不能作为构造函数(不能使用new创建对象)
# 3.新增的数组API和对象API
新增数组API:
- arr.flat 数组扁平化
- Array.from() 将数组、类数组转为数组
- Array.of 将数值转为数组
- find() 找到符合要求的第一个元素值
- findIndex() 找到符合要求的第一个元素的下标
- fill() 填充一个数组
- includes() 数组是否包含某特定值
- Array.isArray() 判断是否是数组
新增对象API
- Object.assign() 将可枚举属性从一个对象复制到另一个对象,浅拷贝,redux中使用
- Object.freeze(obj) 冻结对象
- Object.keys 返回自身可枚举属性,不包括原型链上的
- Object.entries() 返回对象可枚举键值对
# 4.新增新特性
- let const
- 块级作用域
- 箭头函数
- class
- 模板字符串
- 展开运算符
- 结构赋值
- 新增的数组API和对象API
- promise
- proxy
- Symbol、bigInt
- Set、Map/WeapMap
# 10.防抖节流
# 1.防抖
由于触发事件过于频繁,需要最后一次事件触发之后才执行相关的操作,防止多次触发造成性能的损耗。
let inp = document.querySelector("input")
//调用
inp.input = debunce(function(){
console.log(this.value)
},500)
//封装防抖函数
function debunce(fn,t){
let timer = null; //设置定时器
return function(){
if(timer !== null){
//判断是否为最后一次定时器
clearTimeout(timer);
}
timer = setTimeout(()=>{
fn.call(this) //改变this指向为inp
},t)
}
}
# 2.节流
由于触发事件过于频繁,需要减少触发次数,防止多次触发造成性能的损耗。
window.onscoll = throttle(function(){
console.log('处理事件')
},500)
function throttle(fn,t){
return function(){
let flag = false
if(flag){
setTimeout(()=>{
fn.call(this)
},t)
flag = true
}
flag = false;
}
}
# 11.防止button多次点击
- css设置
pointer-events为none - 增加变量控制,当变量满足条件时才执行点击事件的后续代码
- 如果按钮使用 button 标签实现,可以使用
disabled属性 - 加遮罩层,比如一个全屏的loading,避免触发按钮的点击事件
# 12.判断设备
navigator.userAgent
if (/Mobi|Android|iPhone/i.test(navigator.userAgent)) { // 当前设备是移动设备 } // 另一种写法 if ( navigator.userAgent.match(/Mobi/i) || navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/iPhone/i) ) { // 当前设备是移动设备 }window.screen,window.innerWidth屏幕宽度
window.orientation侦测屏幕方向
touch触控事件CSS 通过 media query(媒介查询)为网页指定响应式样式
# 13.map、forEach、filter
1.map和filter函数的参数,是完全相同的
array.map(function(currentValue,index,arr), thisValue)
array.filter(function(currentValue,index,arr), thisValue)
- currentValue:数组元素;
- index:索引
- arr:原数组;
- thisValue:作为该执行回调时使用,传递给函数,用作 "this" 的值
2.forEach的参数
array.forEach(function(currentValue,index,arr))
- currentValue:数组元素
- index:索引
- arr:原数组
3.用途
- map方法返回的新数组是原数组的映射,何为映射?就是和原数组的长度相同,数值做相应处理。
- filter方法返回的值是过滤原数组后的新数组,和原数组长度不同,数值不变。不改变原数组
- forEach()常用于遍历数组,用于调用数组的每一个元素,并将其传递给回调函数。传输函数不需要返回值。
# 14.深拷贝、浅拷贝
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝,如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就 是内存地址。
深拷贝,开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
实现:
- 浅拷贝:
Object.assignArray.prototype.slice(),Array.prototype.concat()即slice()和concat()- 使用拓展运算符实现的复制
- 深拷贝:
- _.cloneDeep()
- jQuery.extend()
- JSON.stringify()
- 手写循环递归
# 15.小程序组件生命周期
- created --被创建时
- attached --进入页面节点树时
- ready-- 视图层布局完成后
- moved --被移动到节点树另一个位置时执行
- detached --被从页面节点树移除时执行
- error-- 组件方法抛出错误时
# 16.this
- 在函数体中,非显示或隐式地简单调用函数时,在严格模式下,函数内的this会被绑定到undefined上,在非严格模式下会被绑定到window/global上。
- 一般使用new方法调用函数时,构造函数内的this会被绑定到新创建的对象上。
- 一般通过call、bind、apply方法显式调用函数时,函数体的this会被绑定到指定参数的对象上。
- 一般通过上下文对象调用函数时,函数体内的this会被绑定到该对象上。
- 在箭头函数中,this的指向是由外层作用域(函数或者全局)来决定的。
# 17.Ajax和Axios的区别?
ajax特点::
1.异步的XML (opens new window)和JavaScript 2.可以运行浏览器 不可以运行在node (opens new window) 3.发送给后台数据需要手动转换 请求头手动设置 4.后端响应的数据需要自己转换/json格式
*axios (opens new window)特点: 1.基于promise (opens new window)的http库 2.可以调用promise的api (opens new window) 3.axios默认发送就是get请求 发送数据默认格式json 4.axios请求头的数据格式会自动转换
# 18.判断数据类型
1.typeof
typeof null -- object typeof undefined -- undefined typeof [] -- object typeof Symbol('symbol') -- Symbol2.instanceof:a instanceof b判断的是, a是否为 b实例,即a 原型链上是否存在 b的构造函数。
//递归实现 function instanceof(L,f){ if(typeof L !== 'object'){ return false } while(true){ if(L === null){ //遍历到顶端 return false } if(L.__proto__ === R.prototype){ return true } L = L.__proto__ } }3.Object.Prototype.toString().call('xxx')
4.constructor
对于 undefine 、null, 如果尝试读取其 constructor 属性,则会报错,并且 constructor 返回的是构造函数本身,一般使用它来判断数据类型的情况并不多见
# 19.函数参数传递
• 函数参数为基本数据类型时,函数内复制了一份参数值,任何操作都不会影响原参数的实际值
• 函数参数是引用类型时,当在函数体内修改这个值的某个属性值时,将会对原来的参数进行修改
• 函数参数是引用类型时, 如果直接修改这个值的引用地址,则相当与在函数体内新创建了一个引用, 何操作都不会影响原参数实际值
# 20.Promise
# 1.含义
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。- 所谓
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
# 2.特点
对象的状态不受外界影响。
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从
pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
如果改变已经发生了,你再对
Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
# 3.缺点
- 无法取消
Promise,一旦新建它就会立即执行,无法中途取消。 - 如果不设置回调函数,
Promise内部抛出的错误,不会反应到外部。 - 当处于
pending状态时,无法得知目前进展到哪一个阶段。
# 4.手写
class Promise {
constructor(executor) {
// 定义 Promise 的初始状态为 pending
this.state = "pending";
// 定义 Promise 完成后的值和拒绝的原因
this.value = undefined;
this.reason = undefined;
// 定义两个回调函数列表,分别用于处理 onFulfilled 和 onRejected 回调函数
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 定义 resolve 和 reject 方法
const resolve = (value) => {
// 只有当 Promise 状态为 pending 时才可以转为 fulfilled 状态
if (this.state === "pending") {
this.state = "fulfilled";
this.value = value;
// 执行 onFulfilled 回调函数列表中的所有回调函数
this.onFulfilledCallbacks.forEach((callback) => callback(value));
}
};
const reject = (reason) => {
// 只有当 Promise 状态为 pending 时才可以转为 rejected 状态
if (this.state === "pending") {
this.state = "rejected";
this.reason = reason;
// 执行 onRejected 回调函数列表中的所有回调函数
this.onRejectedCallbacks.forEach((callback) => callback(reason));
}
};
try {
// 在 Promise 构造函数中执行 executor 函数,并将 resolve 和 reject 方法作为参数传递给它
executor(resolve, reject);
} catch (error) {
// 如果执行 executor 函数时发生错误,则将 Promise 状态转为 rejected 状态,并将错误作为原因
reject(error);
}
}
// 定义 then 方法
then(onFulfilled, onRejected) {
// 创建一个新的 Promise 对象
const newPromise = new Promise((resolve, reject) => {
// 定义 onFulfilledCallback 回调函数
const onFulfilledCallback = (value) => {
try {
// 如果 onFulfilled 回调函数存在,则执行它
const result = onFulfilled(value);
// 如果 onFulfilled 回调函数返回的是 Promise 对象,则等待该 Promise 对象完成后再继续执行
if (result instanceof Promise) {
result.then(resolve, reject);
} else {
// 否则,将该值作为新的 Promise 对象的值,并将新的 Promise 对象状态转为 fulfilled
resolve(result);
}
} catch (error) {
// 如果执行 onFulfilled 回调函数时发生错误,则将新的 Promise 对象状态转为 rejected,并将错误作为原因
reject(error);
}
};
// 定义 onRejectedCallback 回调函数
const onRejectedCallback = (reason) => {
try {
// 如果 onRejected 回调函数存在,则执行它
const result = onRejected(reason);
// 如果 onRejected 回调函数返回的是 Promise 对象,则等待该 Promise 对象完成后再继续执行
if (result instanceof Promise) {
result.then(resolve, reject);
} else {
// 否则,将该值作为新的 Promise 对象的值,并将新的 Promise 对象状态转为 fulfilled
reject(result);
}
}
# 21.数组方法
# 1.reduce简介
语法:
arr.reduce(callback(accumulator, currentValue[, currentIndex[, array]])[, initialValue]);
其中callback可以接受四个参数:
accumulator:累加器,它存储了上一次回调函数的返回值,或者是initialValue的值(如果指定了的话)。currentValue:当前数组元素的值。currentIndex:当前数组元素的索引array:原数组initialValue:指定的初始值,如果不指定initialValue,那么累加器的初始值将会是数组中的第一个元素。
# 2.reduce技巧
//1.计算元素出现的次数
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const fruitsCount = fruits.reduce((accumulator,currentValue)=>{
accumulator[currentValue] = (accumulator[currentValue] || 0) +1;
//通过 accumulator[currentValue] 来获取对象中指定键名对应的值。
//如果这个键名不存在,则使用逻辑或运算符 || 来设置默认值为 0。
//然后将这个值加 1,表示出现次数加 1。
//最后将更新后的值重新赋值给对象的属性 accumulator[currentValue],实现累加统计的效果。
return accumulator;
},{})
//2.数组扁平化
const array = [[1,2],[2,3],[4,5]];
const flatArray = array.reduce((accumulator,currentValue)=>{
accumulator = [...accumulator,...currentValue]
return accumulator;
},[])
//3.条件分组(根据age)
const people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 },
{ name: 'David', age: 25 },
{ name: 'Emily', age: 30 }
];
const groupedPeople = people.reduce((accumulator,currentValue)=>{
const key = currentValue.age
if(!accumulator[key]){
accumulator[key] = []
}
accumulator[key].push(currentValue)
return accumulator
},{})
//4.数组合并
const keys = ['name', 'age', 'gender'];
const values = ['Alice', 25, 'female'];
const person = keys.reduce((accumulator, currentValue, index) => {
accumulator[currentValue] = values[index];
return accumulator;
}, {});
# 3.reduce手写
function myReduce(array, callback, initialValue) {
// 如果提供了初始值,则将它赋值给累加器;否则,将第一个元素赋值给累加器
let accumulator = initialValue === undefined ? undefined : initialValue;
// 遍历数组中的每个元素
for (let i = 0; i < array.length; i++) {
// 如果累加器有值,则将它与当前元素一起传递给回调函数计算,并将结果赋值给累加器
if (accumulator !== undefined) {
accumulator = callback(accumulator, array[i], i, array);
} else {
// 如果累加器没有值,则将当前元素赋值给累加器
accumulator = array[i];
}
}
// 返回最终的累加器值
return accumulator;
}