阿里SLS一面凉经
前面问了些经历相关的东西,直入主题吧
一、(浏览器)http缓存机制
我的回答:看过,没记起来,当时想的是Session Storage, Local Storage相关的东西
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存储在浏览器的缓存中
分为两个过程:强制缓存、协商缓存
强制缓存:
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。
控制强制缓存字段的分别是Expires和Cache Control,其中Cache Control比Expires优先级高(补充304会分析为什么
强制缓存主要分三种:
1、不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次请求一致
2、存在该缓存结果和缓存标识,但结果已经失效,强制缓存失效,则使用协商缓存
3、存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,则直接返回结果
协商缓存:
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回浏览器的。
控制协商缓存的字段分别有:
Last-Modified/If-Modified-Since, Etag/If-None-Match,其中Etag/If-None-Match的优先级比Last-Modified/If-Modified-Since高
协商缓存主要有以下两种情况:
1、协商缓存生效,返回304(缓存接着用?)
2、协商缓存失效,返回200和请求结果
补充点:304过程(待填)
二、Session Storage、Local Storage、Cookie的区别
我的回答:
- 大小方面
- 持久性方面
少答了第三条,呜呜呜
正解:
- cookie设置的过期时间之前一直有效;localStorage永久存储,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除
- cookie的数据会自动的传递到服务器;sessionStorage和localStorage数据保存在本地
三、如何减少函数组件的渲染次数
我的回答:当时想的是useMemo、useCallBack,没get到点呜呜呜
正解:
1、组件Render时,避免state,props没变的子组件render
组件Render会导致其子组件render,子组件用PureComponent(类组件)和React.memo(函数组件)可以避免该情况。
// 类组件 class class ClassComp extends React.PureComponent{} // 函数组件 function FnComp () {} React.memo(FnComp)
2、函数组件Render时,避免变化的函数属性值,导致子组件Render,会监听第二个数组参数中的值,其中的值没有变化则不会重新渲染
用useCallBack包裹该函数
const handleClick = useCallback(() => ..., []) return ( <ChildComp onClick={handleClick}> )
3、组件Render时,属性值避免使用箭头函数值,导致子组件Render,直接是有实例方法
如果子组件的属性是个箭头函数,父组件每次刷新,箭头函数都是新的,会导致子组件重新render,属性值用实例方法就可以避免该问题
例如
handleClick = () => {...}, render() { return ( <ChildComp onClick={handleClick}> ) }
改为
<Mouse> {this.renderChild} </Mouse>
4、避免prop Drilling导致的中间组件的render
Prop Drilling 指将外层组件的state通过props一层层传下去,传递到很深的子组件的过程。外层组件的State发生变化,中间组件都会Render
层级很深的子组件可以直接取到值,不需要中间属性的传递,使用Context APi或者Redux等状态管理工具可让子组件直接取到值
示例
// 父组件提供数据 <ThemeContext.Provider value={{ theme: this.state.theme }}> <Comp1> <Comp2> <Comp3> <ThemeContext.Consumer> {({theme}) => { // 子组件拿值 }} </ThemeContext.Consumer> </Comp3> </Comp2> </Comp1> </ThemeContext.Provider>
四、函数组件和类组件的区别
我的回答:
- 函数组件数据和逻辑操作是分离的,运行起来更加高效,想起啥答啥了,,,,
正解:
1、类组件是ES6使用class定义的组件,函数组件是接受单一props并返回一个React元素
2、钩子更简洁,数据的状态和操作方法分离
区别:
1、状态的有无:
hooks出现之前函数组件没有实例、生命周期、State、this,称函数组件为无状态组件
2、调用方式的不同:
函数组件重新渲染,将重新调用组件方法返回新的react元素
类组件重新渲染将new一个新的组件实例,然后调用render类方法返回组件实例
3、因为调用方式不同,在函数组件中使用会出现问题:
在操作中改变状态值,类会返回最新的值,而函数组件会按照顺序返回状态值
五、ES6的新特性
我的回答:
类、箭头函数、var、let、promise,当时就记得这些,主要是之前看到一篇博客就介绍了几个,面试完一看发现哇竟然这么多ES6新特性😅😅😅
正解:
- 箭头函数
- 省略小括号,只有一个参数
- 省略大括号,只有一个语句
- this指向声明时所在作用域
- 不能作为构造函数实例化
- 不能使用arguments
- rest参数
- args是个数组,用于获取函数实参
- 适合不定个数参数的场景
// 作用与 arguments 类似 function add(...args){ console.log(args); } add(1,2,3,4,5); // rest 参数必须是最后一个形参 function minus(a,b,...args){ console.log(a,b,args); } minus(100,1,2,3,4,5,19);
- spread运算符(展开运算符
- 对可迭代对象进行解包,将其转为字符串
- 作用:数组合并、数组克隆、伪数组转为真数组
// 展开数组 let ls = ['1','2','3']; function fn(){ console.log(arguments); } fn(...ls)
- 类
- class声明类
- constructor定义构造函数初始化
- extends继承父类
- super调用父级构造方法
- static定义静态方法和属性
- 父类方法可以重写
//父类 class Phone { //构造方法 constructor(brand, color, price) { this.brand = brand; this.color = color; this.price = price; } //对象方法 call() { console.log('我可以打电话!!!') } } //子类 class SmartPhone extends Phone { constructor(brand, color, price, screen, pixel) { super(brand, color, price); this.screen = screen; this.pixel = pixel; } //子类方法 photo(){ console.log('我可以拍照!!'); } playGame(){ console.log('我可以玩游戏!!'); } //方法重写 call(){ console.log('我可以进行视频通话!!'); } //静态方法 static run(){ console.log('我可以运行程序') } static connect(){ console.log('我可以建立连接') } } //实例化对象 const Nokia = new Phone('诺基亚', '灰色', 230); const iPhone6s = new SmartPhone('苹果', '白色', 6088, '4.7inch','500w'); //调用子类方法 iPhone6s.playGame(); //调用重写方法 iPhone6s.call(); //调用静态方法 SmartPhone.run();
- const、let
- const必须进行初始化,值不允许修改
- let不会影响作用域链
- Symbol类型
- 给对象添加属性和方法
- 值唯一
- 不能与其他类型进行运算
- 定义的对象属性不能用for in遍历
- 可用Reflect.ownKeys来获取对象的所有键名
//添加标识的 Symbol let s2 = Symbol('scorpios'); let s2_2 = Symbol('scorpios'); console.log(s2 === s2_2); // false //使用 Symbol for 定义 let s3 = Symbol.for('scorpios'); let s3_2 = Symbol.for('scorpios'); console.log(s3 === s3_2); // true
- promise
- for of循环,迭代器iterator,返回两个值,value和done
function makeRangeIterator(start = 0, end = Infinity, step = 1) { let nextIndex = start; let iterationCount = 0; const rangeIterator = { //对象 next: function() { let result; if (nextIndex < end) { result = { value: nextIndex, done: false } nextIndex += step; iterationCount++; return result; } return { value: iterationCount, done: true } } }; return rangeIterator; } let it = makeRangeIterator(1, 10, 2); let result = it.next(); while (!result.done) { console.log(result.value); // 1 3 5 7 9 result = it.next(); } console.log("Iterated over sequence of size: ", result.value); // 5
- 生成器generator
- 异步编程解决方案
- 允许自有迭代方案,可自己维护状态
- 返回一个迭代器Generator,通过调用生成器的下一个方法消耗值时,将自动执行到yield关键字
- 可以根据需要多次调用该函数,并且每次都返回一个新的 Generator,但每个 Generator 只能迭代一次。
function* makeRangeIterator(start = 0, end = Infinity, step = 1) { for (let i = start; i < end; i += step) { yield i; } } var a = makeRangeIterator(1,10,2) a.next() // {value: 1, done: false} a.next() // {value: 3, done: false} a.next() // {value: 5, done: false} a.next() // {value: 7, done: false} a.next() // {value: 9, done: false} a.next() // {value: undefined, done: true}
代码说明:
- *的位置没有限制
- 调用迭代器对象的 next方法可以得到yield语句后的值
- yield相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next方法,执行一段代码
- next方法可以传递实参,作为 yield语句的返回值
补充:高级生成器
生成器会按需计算它们的产生值,这使得它们能够有效的表示一个计算成本很高的序列,甚至是如上所示的一个无限序列。
The next()
方法也接受一个参数用于修改生成器内部状态。传递给 next()
的参数值会被 yield 接收。要注意的是,传给第一个 next()
的值会被忽略。
function* fibonacci() { var fn1 = 0; var fn2 = 1; while (true) { var current = fn1; fn1 = fn2; fn2 = current + fn1; var reset = yield current; if (reset) { fn1 = 0; fn2 = 1; } } } var sequence = fibonacci(); console.log(sequence.next().value); // 0 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 2 console.log(sequence.next().value); // 3 console.log(sequence.next().value); // 5 console.log(sequence.next().value); // 8 console.log(sequence.next(true).value); // 0 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 2
- 模板文字:反引号+${}
- 解构赋值
- 从数组和对象中进行赋值
//1. 数组的结构 const Hua = ['小花','刘花','赵花','宋花']; let [xiao, liu, zhao, song] = Hua; // 结构赋值完,可以直接使用 console.log(xiao); console.log(liu); console.log(zhao); console.log(song); //2. 对象的解构 const zhao = { name: '赵本山', age: '不详', xiaopin: function(){ console.log("我可以演小品"); } }; let {name, age, xiaopin} = zhao; console.log(name); console.log(age); console.log(xiaopin); xiaopin(); let {xiaopin} = zhao; xiaopin();
get和set方法:
当对某个属性进行获取时,调用get方法;当对某个属性进行修改时,调用set方法。
- Map
- Set
- 模块化
模块功能主要由两个命令构成:export和 import
export命令用于规定模块的对外接口
import命令用于输入其他模块提供的功能
export暴露方式: 统一暴露(暴露对象:export {})、分别暴露(分别使用export)、默认暴露(export default{})。
import 导入方式:通用导入、结构赋值导入、针对默认暴露方式
//1. 通用的导入方式,引入 m1.js 模块内容 import * as m1 from "./src/js/m1.js"; //统一暴露 //2. 解构赋值形式 import {school, teach} from "./src/js/m1.js"; //分别暴露 import {school as guigu, findJob} from "./src/js/m2.js"; import {default as m3} from "./src/js/m3.js"; //3. 简便形式 //默认暴露 import m3 from "./src/js/m3.js"; console.log(m3);
- Spread、Rest
- 简化对象写法
- 对象拓展
- Object.is 比较两个值是否严格相等,与『 ===』行为基本一致
- Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
_proto_
、 setPrototypeOf、 setPrototypeOf可以直接设置对象的原型
//1. Object.is 判断两个值是否完全相等 console.log(Object.is(120, 120));// === console.log(Object.is(NaN, NaN));// === console.log(NaN === NaN);// === //2. Object.assign 对象的合并 const config1 = { host: 'localhost', port: 3306, name: 'root', pass: 'root', test: 'test' }; const config2 = { host: 'http://scorpios.com', port: 33060, name: 'scorpios.com', pass: 'iloveyou', test2: 'test2' } console.log(Object.assign(config1, config2)); //3. Object.setPrototypeOf 设置原型对象 Object.getPrototypeof const school = { name: 'scorpios' } const cities = { xiaoqu: ['北京','上海','深圳'] } Object.setPrototypeOf(school, cities);//不改变school原有属性,只是school的proto指向cities console.log(Object.getPrototypeOf(school)); console.log(school);
参考链接:https://blog.csdn.net/zxd1435513775/article/details/119862852
六、http状态码
我的回答:少说了第一个😅
七、大量数据加载优化问题
我的回答:懒加载、预加载、图片压缩、代码优化
正解:
- 图片优化:将图片压缩、合并或使用 WebP 格式等方式来减小图片大小,从而提高图片加载速度。
- 延迟加载:将不必要的图片或数据延迟加载,例如使用懒加载技术,可以在用户滚动到需要的区域时再加载图片或数据,从而加快页面的初始加载速度。
- 使用 CDN:将静态资源存放到 CDN 上,利用 CDN 的分布式网络和缓存功能,可以更快地获取资源。
- 减少请求数量:将多个小请求合并成一个大请求,或者将多个文件合并成一个文件,可以减少请求数量,从而提高页面的加载速度。
- 使用缓存:将常用的数据缓存在浏览器或服务器端,下次请求时直接从缓存中获取,可以减少请求的响应时间。
- 代码优化:对 JavaScript 和 CSS 进行压缩、合并、精简,可以减少文件大小,从而提高页面的加载速度。
- 使用预渲染技术:将页面的 HTML、CSS 和 JavaScript 提前生成为静态文件,存放到服务器上,用户请求页面时直接返回静态文件,可以加快页面的加载速度。
八、钩子函数为什么不能放在if中
我的回答:可能不能正常触发钩子函数,还有作用域相关问题(瞎猫碰上死耗子哈哈)
正解:
钩子函数是指在特定事件发生时自动执行的函数。它们通常用于在程序执行某些操作之前或之后执行其他操作,例如在提交表单之前验证表单数据。
钩子函数不能放在 if 语句中的原因是,如果条件不成立,该函数将不会被执行。这可能会导致一些不可预测的行为,例如在某些情况下缺少必要的验证或清理步骤。就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
此外,将钩子函数嵌套在 if 语句中会使代码更难以维护和理解。最好将钩子函数放在函数或类中,并在需要时调用它们。这样可以使代码更具可读性,易于维护和重用。
九、账号认证问题,有问到时效性方面
我的回答:没想到什么好的想法😅
正解:
分为cookie和token两种认证方式
cookie:
- cookie登录是有状态的,服务端维护一个session客户端维护一个cookie,cookie只保留sessionID,服务端要保存并跟踪所有活动的session
过程:
- 输入用户名密码登陆。
- 服务器拿到身份并验证后生成一个 session 存到数据库。
- 把 sessionID 返回给客户端存成一个 cookie 保存 sessionID。
- 随后的请求会携带这个包含 sessionID 的 cookie。
- 服务器拿着 sessionID 找到对应的 session 认证用户是否有对应权限啊。
- 登出后,服务端销毁 session 客户端销毁 cookie。
- 输入用户名密码登陆。
- 服务器拿到身份并验证后签发一个 token。
- 客户端拿到 token 并存起来,好多地方都可以存。
- 客户端发送的每一个请求都要携带 token,好多方式可以携带。
- 服务器接收请求后拿到 token 并解析,拿解析的结果进行权限认证(token中可能已经携带权限信息,能被正常解析的 token 被认为是合法机构签发的)。
- 登出后,在客户端销毁 token 即可。
- 无状态,token 是无状态的,服务器端不需要保留任何信息,每个 token 都会包含所有需要的用户信息。服务器端可以只负责签发和解析 token 解放了部分服务器资源,让服务器更单纯的提供接口。
- 跨服务器,无状态优势在此。服务器如果做了负载均衡之类的,你两条请求不一定去同一个服务器,着如果用服务器维护一个 session 的话就显得有些棘手了,一个服务器和一个客户端对应,另一个服务器不一定认得你啊,不对是一定不认得你啊,当然这个问题也不难解决。
- 可以携带其他信息,比如携带具体权限信息之类的,省的还要去查库。
- 性能,解 token 可比查库要省事儿的多。
- 跨域,请求需要跨域的接口的时候 cookie 就力不从心了,不同域就不会携带 cookie ,不携带 cookie 服务器也不知道是哪个 session 啊,token 在此优势明显。
- 配合移动端,cookie 是浏览器端的玩意儿,移动端应用想使用 cookie 还得折腾一下,token 就方便得多。token 让服务器端单纯提供 API 服务,适用性更广。
- CSRF,如果 token 不存放在 cookie 中,防止了跨站请求伪造带来的安全性风险。
token:
token 的认证方式是无状态的,服务端不保存登陆状态,也不关心哪些客户端签发了 token ,每个请求都会携带 token 通常在 header 中,也可以出现在 body 和 query 如下:
token 的认证方式是无状态的,服务端不保存登陆状态,也不关心哪些客户端签发了 token ,每个请求都会携带 token 通常在 header 中,也可以出现在 body 和 query 如下:
我个人感觉token可以通过设置setTimeout回调函数来手动实现时效性权限认证,不知道对不对😅
token的优势
十、捕获、冒泡、及如何通过点击弹窗外关闭弹窗
这道题比较简单,就是通过事件捕获来判断事件触发的位置
十一、CSS样式优先级
我的回答:
当时说的比较乱,大概就说了id选择器、内联样式、类选择器(这里想指的是标签选择器)
正解:
!important(正无穷)》行内样式(1000)》id选择器(0100)》class = 伪类 = 属性(0010)》标签选择器 = 伪元素(0001)》通配符(*)、子类选择器(>)、兄弟选择器(0000)(+),256进制
十二、如何实现左中右的布局,左右宽度固定,中间宽度自适应?
我的回答:
可以使用flex布局
正解:
1、使用flex布局
2、网格布局
相关链接:https://blog.csdn.net/qq_39903567/article/details/1147515
里面介绍了很多常见的CSS布局😁
十三、了解React打包相关的问题么?
我的回答
对React的打包印象不是很深😅,因为之前接触过Vue打包相关的一些东西,这里就结合自己的经历说了之前在使用Vue进行开发时,发现Webpack打包比较慢,就有用Vite进行打包,当时看网上说Vite打包比较快,然后面试官就很打趣的问我:“我感觉你的项目应该不是很大吧,用Webpack打包很慢么?”😅
目前能想到的:
1、目前前端主流的打包工具是Webpack,是一个静态模块打包器(moudule bundler),当webpack处理应用程序时,其会递归地构造一个依赖关系图,其中包含应用程序的每一个模块,然后将所有这些模块打包成一个或多个buddle
2、webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。
核心概念
Entry
入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。
Output
告诉webpack在哪里输出这些bundles,以及如何命名,默认为./dist
Module
Webpack中一切皆模块,一个模块对应着一个文件,Webpack会从配置的Entry开始递归找出所有依赖的模块
Chunk
代码块,由一个或多个模块组成,用于代码合并和分割
Loader
让Webpack可以去处理非JS的文件(Webpack自身只理解JS),loader将文件转换为Webpack可以处理的有效模块
Plugin
用于执行范围更广的任务,从打包优化到压缩,到重新定义环境变量
构建流程
1、初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
2、开始编译:用参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始编译
3、确定入口:根据配置中的 entry 找出所有的入口文件。
4、编译模块:从入口文件出发,调用所有配置的Loader对模块进行翻译,再找出该模块的依赖的模块,递归本步骤直到所有入口依赖文件都经过该步骤
5、完成模块编译:得到每个模块的翻译内容及其依赖关系
6、输出资源:根据入口和模块之间的依赖关系,组装成多个chunk,再把每个chunk转换成一个单独的文件加入到输出列表
7、输出完成:根据配置确定输出路径和文件名,把文件内容写入到文件系统
Webpack会在特定时间点广播出特定事件,插件监听到对应的事件后执行特定的逻辑
打包优化
优化Webpack构建速度
- 使用高版本Webpack(4
- 使用多线程,多实例进行打包
- 缩小打包作用域
- 充分利用缓存提升二次构建的速度
- babel-loader
- cache-loader
- terser-webpack-plugin(多核压缩插件
- thread-loader
- DLL(动态库链接
- DLLPlugin,让一些基本不会改动的代码先打包成静态资源
优化打包体积
- 压缩代码
- 提取页面公共资源
- Tree Shaking
- Scope Shaking
- 图片压缩
性能分析 speed-measure-webpack-plugin(SMP
分析出Webpack打包中loader和plugin的耗时
十四、算法题,两数之和,要求O(n)
我沙比了😅,当时就写了个Onlogn的,面试官提醒之后才想到On的解法
#阿里前端实习面经##24届暑期实习##软件开发2023笔面经#