面试题库

/*** JS篇 ***/
介绍原型链 https://www.jianshu.com/p/7119f0ab67c0
只有函数有可能有 prototype (同时也有proto) , 实例对象仅有 proto
prototype 是一个对象, prototype 是函数的原型对象, 它会被函数生成实例的 proto 引用
万物皆对象, 所有构造函数的原型链最终都将指向 Object.prototype, Object.prototype.proto === null
函数原型对象中的属性和方法是所有实例共享的
-- 函数的prototype、prototype是一个对象、万物皆对象

介绍作用域与执行上下文 https://juejin.im/post/5bdfd3e151882516c6432c32
作用域:用于维护声明的变量, 确定函数 执行上下文对变量的访问权限, 在编译阶段(函数声明时)确定
作用域链:JS引擎在查询变量值时, 会由内向外在各层级的作用域中查找, 直到全局作用域
执行上下文:函数执行时创建, 执行完销毁, 内部属性有 函数作用域, 外层作用域的指针, this对象, arguments对象, 函数内部定义的变量的值
-- 简单地理解为, 作用域维护了变量地址, 变量的值存在执行上下文中

介绍闭包 https://github.com/dwqs/blog/issues/18
闭包使得函数在声明处之外的地方被调用时, 依然可以访问声明处的作用域
闭包可以用于实现私有变量, 在函数内部定义var变量, 通过return出去的 get 和 set 方法来操作这个私有变量
闭包因会阻止执行上下文的销毁, 可能会造成新生代内存晋升为老生代内存而常驻, 导致内存泄露

介绍事件循环机制
宏任务 -> 微任务 -> 渲染 -> 宏任务 -> ...
宏任务包括 主代码块 / setTimeout / setInterval / requestAnimationFrame / setImmediate(仅IE) / MessageChannel / promise() 定义
微任务包括 promise.then/catch/finally, process.nextTick(Nodejs), MutationObserver(监听DOM结构变化的接口)
promise.then 的执行时机取决于状态改变的时刻, 即 resolve 什么时候调用

函数使用 new 调用时发生了什么
创建一个新对象
新对象的proto会指向构造函数的原型对象
使用call将函数的this指向这个实例
如果函数没有返回对象, 会返回这个实例

变量提升
var变量、函数声明 存在变量提升(先读后写不报错), let 和 const 不存在变量提示(先读后写会报错)
console.log(a); // 报错: a is not defined
console.log(a); var a = 10; // undefined
console.log(fn); function fn(){} // f fn(){}
console.log(a); let a = 10; // 报错: Cannot access 'a' before initialization
函数声明 -> 变量定义 -> 变量赋值, 函数的声明先于变量定义,且在变量提升阶段不会被更改为其他类型
console.log(b); var b = 10; function b() {}; // f b(){}
console.log(b); function b() {}; var b = 10; // f b(){}
var b = 10; function b() {}; console.log(b); // 10, 此时已经过了提升阶段进入赋值阶段
具名立即执行函数属于函数表达式, 仅在函数内部有效, 不可修改
var b = 10;
(function b() {
b = 20;
console.log(b) // f b() {...}
})()
变量提升会阻止内部变量晋升为全局变量
(function () { b = 20; })(); console.log(b) // 20
(function () { b = 20; var b = 10 })(); console.log(b) // 报错: not defined

深浅拷贝
浏览器内存分为栈内存和堆内存, 函数在嵌套执行时会形成一个作用域栈(内存栈), 执行完就释放掉
栈内存中存放 基本类型、引用类型的地址, 堆内存中存放 引用类型的值
浅拷贝 仅拷贝 栈内存中引用类型的地址, 即两个变量指向同一个堆内存空间
Object.assign(a,b) 仅仅把b的属性中的基本类型和引用类型的地址复制给a, a和b属性中引用类型仍然共用同一个内存
与 Object.assign 类似, 数组的 slice、concat 等方法也是浅拷贝
深拷贝 会在堆内存中开辟一块新的空间, 即两个变量指向不同的堆内存空间
简单结构的深拷贝可以用 JSON.parse(JSON.stringify(obj)), 问题是会忽略函数对象(其他还有一些问题先不关注)
带函数对象的深拷贝可以使用递归的方式逐一复制

函数继承
原型链继承 (让 子类的原型 指向 父类的实例 Son.prototype = new Parent() ), Son() 可以继承父类的构造属性和原型属性, 缺点是不能向父类传参
构造函数继承 (在子类找那个实例化父类, Son(args){Parent.call(this, ...args)} ) 可以向父类传参, 继承父类的构造属性, 但无法继承父类的原型属性(因为不是 new 出来的父实例)
原型构造组合继承 (结合两种方案, 常用, 让子类的原型对象指向父类, 以继承父类的构造/原型属性, 然后在子类实例化时, 通过call参数调用父类构造函数, 以覆盖之前继承的父类构造属性)
function Son(args){ Parent.call(this, ...args) }; Son.prototype = new Parent()
原型式继承
寄生式继承

防抖节流的实现
节流: 通过计数或时间戳决定函数执行时机
防抖: 通过定时器实现, 每次触发删除原来的定时器重新设置
需求:频繁触发的背景下, 限制执行间隔, 并保证最后一次执行
function lazy(cb, delay) {
let timer = null
let last = Date.now()
return () => {
let args = arguments
let now = Date.now()
let done = false
if(now - last > delay) { // 每次进入时判断间隔, 间隔短时跳过不执行
cb.call(this, args)
last = now
done = true
}
if(!done) {
if(timer) { clearTimeout(timer); timer = null }
timer = setTimeout(() => {
cb.call(this, args)
}, delay)
}
}
}
window.addEventListener('scroll', lazy(()=>{console.log(1)},500))

/*** ES6篇 ***/
Promise / async、await(ES7)
Promise 的状态分为 pending 等待, fulfilled 已完成, rejected 已拒绝
Promise 构造函数接受一个立即执行函数, 该函数接受 resolve函数 为参数
then(successCb, errorCb) 返回一个 promise实例对象, 用于绑定 状态变为fulfilled时需要触发的回调(在 resolve函数 被调用时触发, resolve函数的实参即为successCb的形参)
catch() 当有实例返回了拒绝状态
finally() 所有实例都返回了状态, 无论是完成/拒绝
all(Promise1, Promise2) 所有的实例都返回了完成状态
race(Promise1, Promise2) 返回最快得到结果的Promise实例, 无论状态
手写 Promise 要点
1、Promise实例内部维护一个状态变量,仅支持 pending -> fulfilled, 或者 pending -> rejected
2、为 Promise 构造函数传入一个立即执行函数, 该函数接受 resolve, reject 作为实参
3、then 方法注册成功/失败回调,当状态变化时触发对应回调

  function MPromise(fn) {
    var state = 'pending';
    var value = null;
    var callbacks = [];
    this.then = function (onFulfilled, onRejected) {
      return new MPromise(function (resolve, reject) {
        handle({
          onFulfilled: onFulfilled || null,
          onRejected: onRejected || null,
          resolve: resolve,
          reject: reject
        });
      });
    };
    function handle(callback) {
      if (state === 'pending') {
        callbacks.push(callback);
        return;
      }
      var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
      if (cb === null) {
        cb = state === 'fulfilled' ? callback.resolve : callback.reject;
        cb(value);
        return;
      }
      callback.resolve(cb(value));
    }
    function resolve(newValue) {
      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
        var then = newValue.then;
        if (typeof then === 'function') {
          then.call(newValue, resolve, reject);
          return;
        }
      }
      state = 'fulfilled';
      value = newValue;
      execute();
    }
    function reject(reason) {
      state = 'rejected';
      value = reason;
      execute();
    }
    function execute() {
      setTimeout(function () {
        callbacks.forEach(function (callback) {
          handle(callback);
        });
      }, 0);
    }
    fn(resolve, reject);
  }
  new MPromise((r)=>{r(11)}).then((v)=>{console.log(v)})
async, await
  当 Promise 实例相互依赖时, 执行时长总合是实例各自执行时长之和
  async 返回一个 resolved 状态的 Promise 实例
  await 等待 Promise 实例的异步返回

let、const、var
var存在变量提升, 不管在函数内何处声明(不能跨函数作用域), 都会被移动至函数头部, 此时变量值为undefined
let、const不存在变量提升, 禁止重复声明
const 声明的变量如果是非引用类型, 不可修改其值, 如果是引用类型不可修改类型, 可以修改其属性
块级作用域(let、const、function有效)
块级声明让被声明的变量在块作用域外无法访问, 比如 if 条件中声明的变量无法在 else 条件中访问
函数的扩展功能
默认参数, 给形参赋默认值 function(val = 0){}
剩余参数, function(...args){}
箭头函数, 没有自己的this(对应不能用call、apply、bind改变this、不能作为构造函数, 没有原型属性), 没有arguments(可以用剩余参数代替)
数组的扩展功能
Array.of (解决了Array()传入单个数字时视为数组长度的怪异现象)
Array.from 将类数组转为数组
find / findIndex 检索
fill 填充(会覆盖掉原有的值)
对象扩展功能
Object.is 比较对象(不会进行类型转换, 结果基本与 === 相同, 并解决了 NaN !== NaN 和 +0 === -0 的问题)
Object.assign 合并对象, 属于浅拷贝

/*** CSS篇 ***/
常见的兼容性问题:
不同浏览器的 margin/padding 默认值不同

BFC块级上下文
BFC特性
规定内部Box如何布局, 不影响外部
块级元素会在上下文内部的垂直方向上逐一放置
同一个BFC内部的相邻块级元素会产生 margin重叠
每个块级元素的 margin-box 左边与 包含块的 border-box 左边相接触, 除非该块级元素生成了新的BFC
计算BFC高度时, 会考虑浮动元素
触发BFC的规则:
根元素
float
position: absolute/fixed
inline-block
flex / inline-flex
overflow 不为 visible
table-cell / table-caption
BFC块级上下文的应用:
解决浮动的高度塌陷(利用BFC包含float高度)
防止margin重叠(产生新的BFC即可)
列布局(利用BFC不与float重叠, 即不产生环绕)

margin重叠
浮动与清除浮动
浮动特性
浮动元素脱离正常文档流, 在遇到 包含他的边框 或 其他浮动元素的边框 时停止
浮动元素的高度不计入父元素, 因此会产生 "高度塌陷"
浮动可以产生文字环绕效果, 这是难以模拟的
清除浮动:
1、使用BFC包裹浮动元素
2、通过clearfix, 清除当前元素 前面元素 浮动产生的影响

层叠上下文
层叠顺序
产生方式

盒模型
标准盒模型 content-box 的 width 指的是 content 部分
IE盒模型 border-box 的 width 指的是 content + padding + border 部分
box-sizing:padding-box 的 width 指的是 content + padding 部分

flex布局 (容器+子项)
容器 (display:flex)
设为 Flex布局以后, 子元素的 float、clear、vertical-align 属性失效
flex-direction: 子项的排列方式 row / row-reverse / column / column-reverse
flex-wrap: 处理换行
flex-flow: direction/wrap 的简写
justify-content: 子项在 主轴 上的对齐方式(默认direction时, 理解为横向)
flex-start(默认值):左对齐
flex-end:右对齐
center: 居中
space-between:两端对齐, 项目之间的间隔都相等
space-around:每个项目两侧的间隔相等。所以, 项目之间的间隔比项目与边框的间隔大一倍。
align-items: 子项在 交叉轴 上的对齐方式(默认direction时, 理解为纵向)
flex-start:交叉轴的起点对齐
flex-end:交叉轴的终点对齐
center: 交叉轴的中点对齐
baseline: 项目的第一行文字的基线对齐
stretch(默认值):如果项目未设置高度或设为auto, 将占满整个容器的高度
align-content:定义了多根轴线的对齐方式。如果项目只有一根轴线, 该属性不起作用
子项
order: 排列顺序
flex-grow: 放大比例(基于剩余空间)
flex-shrink: 缩小比例(空间不足的情况)
flex-basis:
flex: 上述三属性的简写
align-self: align-items的单独处理
实例
水平垂直居中: justify-content: center / align-items: center

CSS选择器
!important > 内联样式 > ID选择器 > 类选择器/属性选择器/伪类 > 标签选择器 > 通配符选择器(*)/选择符(+、>、~等)/逻辑组合伪类(:not、:is、:where)
选择器等级相同时, 总值大的优先, 总值一样后定义的优先, 选择器不能跨等级

水平垂直居中
固定宽高 + absolute + 四边取0 + margin: auto
flex + justify-content: center + align-items: center
absolute 50% + trasform: translateY(-50%)

固定+自适应多列布局
float+width / overflow:hidden (产生BFC)
float+width / margin-left: width ( float 会与 非BFC 重叠产生环绕)
width / absolute + left: width
width / flex: 1 (利用 flex-grow 的放大比例, 平分剩余空间)

/*** Vue篇 ***/
双向绑定原理
data -> view 初始化阶段对data进行数据劫持, 在get时收集依赖, 在set时触发依赖, 通知组件重新
view -> data 在模板编译阶段监听DOM事件, 并通过修改状态触发依赖
生命周期
初始化阶段, 初始化属性、事件、生命周期、状态, 进行数据劫持
模板编译阶段, 将html模板解析成render函数
挂载阶段, 根据render函数生成虚拟DOM
销毁阶段
为什么data是函数
组件被定义时可以视为一个类, 可能用于创建多个实例, 所有的实例 data 会挂在组件的 $data 下, 若直接传入对象(引用类型)会导致组件之间的相互覆盖
异步加载组件
配合webpack的代码分割, 在组件定义传入回调的时候传入一个promise, 并利用require/import告知webpack需要进行代码分割
以下几种方式都可以
Vue.component('async-webpack-example', (resolve) => { require(['./my-async-component'], resolve) })
Vue.component('async-webpack-example', () => import('./my-async-component'))
new Vue({
components: {
'my-component': () => import('./my-async-component')
}
})
Vue3 新特性
使用 proxy 重写响应式
解决了 defineProperty 检测不到数组和对象的变化, 同时可以处理 Map 等数据结构
虚拟DOM 升级
在编译阶段完成静态标记, 减少 diff 的计算量
引入了 block 的概念
composition API
取代了传统 option API, Mixin 的方式
使用 TS 重构
自定义渲染
fragment, teleport, suspense
/*** webpack篇 ***/
介绍webpack, webpack是什么, 为什么要用webpack
webpack是程序的静态模块打包器, 根据程序中的依赖关系, 递归地生成一个依赖关系图, 然后将这些模块打包合并成一个或多个bundle
Loader 和 Plugin
loader(资源转换) 本质是一个函数, 对接收到的参数进行转换并返回转换后的结果, 常用于将非JS资源转换成webpack的可用模块
plugin(功能扩展) 是对webpack的扩展, 监听webpack生命周期中广播出来的事件, 并且可以通过webpack提供的api改变输出结果

/*** node篇 ***/
V8的垃圾回收机制
V8中, js对象以堆的形式存放
V8存在对内存的使用限制, 原因是垃圾回收机制会暂停JS线程, 内存过大会增加回收耗时, 利用堆外内存可以突破限制(如buffer)
V8的垃圾回收机制基于分代式回收机制, 根据对象存活时间分为 新生代(比如局部变量) 与 老生代(比如全局变量), 并使用不同的算法进行回收
新生代采用 scavenge 算法, 将堆内存分为 from 和 to 两部分, 将存活对象由 from 移到 to, 之后两个空间角色互换, 以空间换时间, 过程中会存在向老生代晋升的情况
老生代采用 标记清除(常用, 标记存活对象, 清理死亡对象) 和 标记整理(内存不足以支撑新晋对象的分配时 采用, 将存活对象往一侧移动, 以解决标记清除的空间碎片化问题)
从根节点出发(window), 向下访问, 能访问的到即为存活
如何高效使用内存
多使用局部变量代替全局变量(因为会作为新生代很快被回收, 闭包的情况例外)
主动释放老生代变量, 可通过 delete 或者 重新赋值(更好) a = null/undefined 解除引用关系
谨慎对待闭包的使用(会造成内部函数的定义作用域无法释放, 因此要避免定义作用域中变量缓存的持续增长)

/*** 浏览器篇 ***/
介绍跨域
浏览器的同源策略, 不能执行其他网站的脚本, 协议、域名、端口 其一不相同即视为跨域
跨域的解决方案:
JSONP:原理是带src属性的标签, 不受同源策略的影响, 因此可以创建script, 再请求一个带参网址实现跨域通信
举例: 在 localhost:3000 中取到 "http://10.10.101.102:5652/ISAPI/Security/sessionLogin/capabilities"
直接用 ajax 的话, 浏览器提示跨域错误, 并且ajax进入异常回调
$.ajax("http://10.10.101.102:5652/ISAPI/Security/sessionLogin/capabilities", {
success: function(){ console.log(1) }, error: function(){ console.log(2) }
}) // has been blocked by CORS policy , 执行 error
要想获取到数据, 首先 "http://10.10.101.102:5652" 的脚本里有提供接口, 比如 login.js 有 getCapabilities(url, {success:(data)=>{ callback(data) }}); getCapabilities()
然后在 localhost:3000 的脚本中定义 callback, 通过创建 script 标签获取 login.js, 即可通过 callback 拿到返回的数据
JSONP 只能用于get请求
document.domain + iframe跨域:两个页面都通过js强制设置document.domain为基础主域, 就实现了同域
location.hash + iframe跨域:a欲与b跨域相互通信, 通过中间页c来实现。 三个页面, 不同域之间利用iframe的location.hash传值, 相同域之间直接js访问来通信
window.name + iframe跨域:通过iframe的src属性由外域转向本地域, 跨域数据即由iframe的window.name从外域传递到本地域
postMessage跨域:可以跨域操作的window属性之一
CORS:服务端设置Access-Control-Allow-Origin即可, 前端无须设置, 若要带cookie请求, 前后端都需要设置(服务端解决方案)

介绍本地存储
cookie: 字符串形式, 请求发起时自动加入请求头, 有效期由服务端设定, tab间共享, IE6+
localStorage: 键值对形式, 持久保存, tab间共享, IE8+
sessionStorage: 键值对形式, tab存在期间有效, tab间独立, IE8+
--indexDB/WebSQL: 兼容性差, 没用过

介绍缓存
强缓存(过期缓存):若符合强缓存条件, 则不重新发起请求
http1.0 通过Expires设置资源过期时间, 如未过期则不重新发起请求(可能存在服务器和客户端时间不一致的情况, 被抛弃了)
http1.1 Cache-Control: max-age=3600 设置资源在多少秒后过期, 0则表示每次都要重新获取
Cache-Control: no-cache 将跳过强缓存, 进入协商缓存阶段
协商缓存:
Last-Modified:服务器返回资源时在响应头中加入 Last-Modified 表示更新时间, 作为浏览器再次请求时的 If-Modified-Since 值, 服务端再根据该值与资源最后修改时间做对比, 若等于最后修改时间则返回304, 否则返回新资源并修改 Last-Modified
ETag:与 Last-Modified 类似, 服务端每次修改文件都会为其重新生成唯一标示, 通过响应头发给浏览器, 并作为 If-None-Match 的返回值
执行过程:
发起请求 -> 已缓存 -> [过期策略] 缓存是否过期 -> 已过期 -> [协商策略] 向服务器发起验证 -> 验证通过 -> 304响应 -> [存储策略] 根据响应头更新缓存
-> 验证不通过 -> 发服务器请求资源 -> [存储策略] 响应内容存入缓存 -> 载入资源
-> 未过期 -> 载入资源
-> 未缓存 -> 发服务器请求资源 -> [存储策略] 响应内容存入缓存 -> 载入资源
禁用缓存:
(1) 设置 Cache-Control: no-store
Cache-Control 是金字塔顶尖的规则, 将覆盖其他设置, 他是一个复合规则, 包含多种值, 横跨 存储策略, 过期策略 两种, 同时在请求头和响应头都可设置
(2) 设置 ETag: 资源的唯一标识符, 比 Last-Modified 优先级高
原理与 Last-Modified 类似, 使用的字段为 If-None-Match
(3) 请求头将 If-Modified-Since 设为 0
原理: 服务端返回资源时会在响应头中加入 Last-Modified 表示更新时间, 若浏览器发起请求时携带的 If-Modified-Since 与 Last-Modified 相同则服务端返回304, 否则返回新资源并修改 Last-Modified
(4) 在请求中加时间戳: 浏览器会认为他是一个新的请求, 从而不在请求头里加 If-Modified-Since
(5) 设置 http-equiv:
此方法有兼容性问题, 且因为代理服务器不解析HTML, 也不支持

介绍网络安全
XSS(跨站脚本攻击):在浏览器中执行恶意脚本 ---不要信任用户的输入
危害:窃取Cookie、监听用户输入行为、在页面中生成浮窗广告等
方式:
存储型:通过表单等方式提交恶意脚本写入数据库, 可通过对输入内容转义解决(比如 < 转为 &lt, 过滤掉 script 关键词等)
反射型:恶意脚本作为请求的一部分(比如作为参数写在url里), 然后返回给浏览器并作为html的一部分
文档型:通过恶意软件劫持网络数据包, 修改html文档
解决方案:转义过滤用户的输入、利用CSP、对cookie设置httpOnly等
CSRF(跨站请求伪造):诱导用户点击链接, 利用用户的登录状态发起跨站请求 ---使用cookie外的鉴权方案如token
方式:
诱导或进入黑客页自动发请求, 会带入当期页的cookie
解决方案:
1、为 cookie 设置 SameSite:Strict(strict,不允许第三方请求带cookie, 仅在同域名下携带) / Lax(仅get方法携带) / None(默认, 自动带cookie)
2、验证站点来源:Origin 和 Referer(并不保险, ajax可自定义请求头)
3、token, 服务端返回页面时将随机token植入页面, 浏览器请求时需带上token(通过请求头), 否则不响应
从url输入到展示的过程
1、浏览器构建请求, 根据是否强缓存确定是否发送请求
2、DNS解析, 由域名得到IP地址
3、建立TCP连接
三次握手建立连接
数据传输(接收方需确认, 否则发送方认为数据包丢失重新发送)
浏览器发送http请求(请求头/请求体)
服务器响应http请求(响应头/响应体)
四次挥手断开连接(若请求头或响应头中的Connection为Keep-Alive则不立即断开, 后续的请求资源会继续使用)
4、浏览器解析html并渲染 (DOM树 -> 渲染树 -> 布局树)
渲染引擎 解析html, 构建DOM树:
标记化算法(词法分析) < [a-z] > data < [a-z] />
建DOM树(语法分析), 建树的过程中会将请求传递给网络线程下载次级资源(图片、js、css等)
在这个过程中, 当遇到 "

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务