校招 未知部门 / 前端 / 一面

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