快手海外电商前端一面

81 阅读11分钟

1.盒模型

Q说一下对盒模型的理解

Q对IE盒模型的了解

两者的区别:

①对宽度、高度的计算

标准盒模型只包含content

IE盒模型包含content+padding+border三部分

②默认触发

标准:现代浏览器默认 需正确声明DOCTYPE

IE:IE6以下默认

Q对比一下这两个盒模型的应用场景:

标准:

需要精准控制内容区尺寸的场景

文本/媒体内容容器,需保证内容区尺寸固定不受外边框影响

需要动态调整内边距/边框的元素(如hover效果),但希望内容区尺寸保持稳定

第三方组件/插件集成

IE:

需要精准控制元素总尺寸的场景

网格/栅格布局

表单元素

固定尺寸容器

Q或者说我会选择哪一个 Q为什么不选择IE盒模型

IE盒模型符合直觉的尺寸控制应用更广泛

Q标准盒模型的优势在哪

对内容区尺寸的绝对掌控

2.元素水平垂直居中的实现

<div class="container">
  <div class="content">垂直居中的内容</div>
</div>
①flexbox
.container {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center; /* 垂直居中 */
  height: 100vh; /* 容器高度 */
}
②grid
.container {
  display: grid;
  place-items: center; /* 垂直居中 */
  height: 100vh; /* 容器高度 */
}
③绝对定位和transform 适用于单行内容
.container {
  position: relative;
  height: 100vh; /* 容器高度 */
}
.content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 同时实现水平和垂直居中 */
}
④absolute+负margin

绝对定位的百分比是相对于父元素的宽高

但是是基于子元素的左上角

想要子元素居中显示需要设置负margin

.wp {
    position: relative;
}
.box {
    position: absolute;;
    top: 50%;
    left: 50%;
    margin-left: -50px;
    margin-top: -50px;
}
⑤css-table

3.vw和rem的区别

都是css布局中的长度单位

vw:视口宽度单位

1vw等于视口宽度的1%

视口指浏览器可见区域的宽度,不包含地址栏、工具栏

适用于页面中需要随窗口宽度等比例缩放的元素

快速实现整体布局的响应式

rem:根元素字体大小单位

1rem等于根元素的font-size值

实际开发中经常结合使用:

vw设置根元素的font-size,让rem简洁依赖视口宽度

4.对闭包的理解

在一个作用域中可以访问另一个函数内部的局部变量的函数

基本使用:

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

在displayName这个作用域下访问了另外一个函数makeFunc下的局部变量name

利用了js中作用域链的概念

在js中,在某作用域下访问某个变量时,如果不存在就一直向外层寻找,直到在全局作用域下找到

特性:

可以访问父级函数的变量

访问到父级函数的变量不会销毁

作用:

延伸了变量的作用范围

隐藏变量,避免全局污染

缺点:

因为垃圾回收机制,会导致出现不必要的性能消耗

不恰当的使用会出现内存泄漏

5.对原型链的理解

Q原型链的应用场景:

①实现对象间的继承

②共享方法和属性

③实现模块化和代码复用

例如将工具方法定义在公共原型上让多个构造函数共享

6.事件循环

Q正在执行微任务的时候又遇到一个微任务怎么处理 放到当前队列还是

会被直接添加到当前微任务队列的末尾 在当前微任务队列的执行周期内被处理

微任务队列的执行遵循 “连续执行到底” 的规则:

当事件循环进入 “处理微任务” 阶段时,会从微任务队列中取出第一个微任务执行

执行过程中产生了新的微任务,加到当前队列的尾部(而不是创建新队列)

继续从队列中取下一个微任务执行,直到整个队列被清空

只有当当前微任务队列完全清空后,事件循环才会进入下一个阶段(处理宏任务或渲染等)

7.var let const之间的区别

①作用域不同

var作用域是函数级 在函数外声明则为全局变量

let const作用域是块级,仅在声明它的代码块内有效(如if for {}等)

②变量提升和重复声明

var存在变量提升,变量声明被提升到作用域的顶部,赋值不会

可能导致未声明先使用时出现undefined

允许重复声明,后声明的会覆盖前一个

let和const不存在变量提升,必须先声明后使用否则报错

不允许重复声明

③可修改性

const声明的是常量,必须在声明时赋值,且赋值后不能重新赋值

Qvar为什么会存在变量提升:

本质上是js引擎编译阶段处理变量声明的机制导致的

js先编译后执行

先对代码进行词法分析语法分析生成执行上下文,此时var声明的变量被提升

执行阶段再逐行执行代码,处理赋值运算等逻辑

Q变量提升的好处/坏处:

好处:简化了早期的开发门槛

函数可以在声明前调用

坏处:变量覆盖和重复声明隐患

作用域混淆

可读性下降

8.tcp三次握手

第一次握手:客户端-->服务器端

将报文标志位SYN置为1,随机产生序列号seq=j

第二次握手:服务器端-->客户端

将报文标志位SYN和ACK都置1 ack=j+1 随机序号seq=k

第三次握手:客户端-->服务器端

报文标志位ACK置1,ack=k+1

Q第三次握手取消会怎么样:

连接无法正常建立

服务器:

在第二次握手后进入SYN_RECV状态,启动重传计时器

未收到ACK

按照预设的重试策略,每隔一段时间重传SYN+ACK报文,直到最大重试次数

仍超时,释放半连接

客户端:主动取消/被动未发送

SYN Flood攻击:大量第三次握手被恶意取消,服务端半连接队列被占满

无法接收新的正常SYN请求

9.浏览器输入url之后的事情

先解析url

url包括协议,主机名(域名/IP地址),端口号,路径,查询参数(key=value),锚点(#)

①DNS解析

域名解析成真实的IP地址

浏览器先检查自身的缓存

检查操作系统缓存

请求本地域名服务器(LDNS)

到Root Server服务器

②三次握手建立TCP连接

③浏览器向服务器发送HTTP请求

④浏览器收到服务器端的响应

⑤进行语法解析,渲染页面

解析HTML生成DOM树

解析CSS生成CSSOM树(样式树)

合并成渲染树

布局计算,绘制页面

浏览器回流和重绘

⑥四次挥手关闭TCP连接

10.强缓存弱缓存

强制缓存:

浏览器在第一次请求资源后,会将资源缓存到本地,记录缓存的有效期

有效期内再次请求该资源时,浏览器直接从本地缓存读取

不发送任何请求到服务器

header参数:

Expires:(已基本被cache-control替代)

过期时间,浏览器在设置的时间内直接读取缓存,不再请求

格式:Expires:<GMT时间>资源过期的具体时间

Cache-Control:(HTTP1.1 优先级更高)

格式:Cache-Control:[指令]

常用指令:

max-age=缓存有效期

public/private 资源可被任何缓存(浏览器、CDN等)存储/只能被浏览器缓存

no-cache:必须进入协商缓存

no-store:完全禁止缓存

协商缓存:

强制缓存过期后,浏览器会携带缓存标识向服务器请求,由服务器判断资源是否更新

未更新:服务器返回304Not Modified,浏览器使用本地缓存

已更新:返回新资源(200OK),并更新缓存标识

header参数:

请求头+响应头。

Last-Modifed/If-Modified-Since和Etag/If-None-Match成对出现的

响应头Last-Modifed:服务器返回资源的最后修改时间

请求头If-Modified-Since:

强制缓存过期后,浏览器将Last-Modifed的值作为该字段发送服务器,询问"资源在该时间后是否被修改"

缺陷:

精度仅到秒

某些操作会改变Last-Modifed,实际上资源未变

响应头ETag:服务器生成的资源唯一标识如哈希值,版本号

资源变化时ETag同步更新

请求头If-None-Match:

浏览器将ETag的值作为该字段发送给服务器

询问"资源标识是否仍为该值"

11.react hooks

对hooks的理解 实现原理 使用的时候需要注意的问题

12.react里状态逻辑复用的方案

状态逻辑复用:

将组件中可复用的状态管理逻辑(如数据请求、表单处理、定时器等)抽离出来,

供多个组件共享,减少代码冗余提高维护性

常见方案:HOC 自定义Hook Render Props

HOC

高阶组件是一个函数,接收一个组件作为参数,返回一个新的增强组件

通过包装组件的方式,注入复用的状态逻辑和props

// 定义高阶组件:处理计数器逻辑
function withCounter(WrappedComponent) {
  return class extends React.Component {
    state = { count: 0 };

    increment = () => this.setState({ count: this.state.count + 1 });

    render() {
      // 将状态和方法通过 props 传递给被包装组件
      return (
        <WrappedComponent
          count={this.state.count}
          increment={this.increment}
          {...this.props} // 透传外部 props
        />      );    }  };
}

// 使用高阶组件
const CounterButton = withCounter(({ count, increment, label }) => (
  <button onClick={increment}>
    {label}: {count}
  </button>
));

// 复用:不同组件共享计数器逻辑
<CounterButton label="点赞数" />
<CounterButton label="收藏数" />    

缺点:可能导致包装地狱(多层HOC嵌套)

静态方法需手动赋值,props可能被覆盖

自定义Hook

自定义Hook是一个以use开头的函数,内部可以调用其他Hook

组件通过调用自定义hook直接获取状态和方法

// 定义自定义 Hook:处理计数器逻辑
function useCounter(initialCount = 0) {
  const [count, setCount] = React.useState(initialCount);
  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);
  return { count, increment, decrement }; // 返回状态和方法
}

// 组件中使用
function CounterDisplay() {
  const { count, increment } = useCounter(); // 直接调用 Hook 获取逻辑
  return <button onClick={increment}>计数:{count}</button>;
}

function DoubleCounter() {
  const { count, increment } = useCounter(10); // 可传入初始值,灵活复用
  return <button onClick={increment}>翻倍计数:{count * 2}</button>;
}    

优势:简洁直观,可组合多个hook

缺点:必须遵循hook原则,只能在函数组件或自定义hook中使用,不能在条件语句中使用

Render Props

通过一个返回React元素的prop,将复用的状态逻辑传递给组件

组件通过调用该prop渲染内容,接收逻辑中的状态和方法

// 定义 Render Props 组件:处理鼠标位置逻辑
class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (e) => {
    this.setState({ x: e.clientX, y: e.clientY });
  };

  componentDidMount() {
    window.addEventListener('mousemove', this.handleMouseMove);
  }

  componentWillUnmount() {
    window.removeEventListener('mousemove', this.handleMouseMove);
  }

  render() {
    // 调用 render prop,将状态传递给外部
    return this.props.render(this.state);
  }
}

// 使用 Render Props
function MousePosition() {
  return (
    <MouseTracker
      render={({ x, y }) => ( // 通过 render prop 接收状态
        <div>鼠标位置:({x}, {y})</div>
      )}    />  );
}

// 复用:不同组件共享鼠标位置逻辑
function CatOnMouse() {
  return (
    <MouseTracker
      render={({ x, y }) => (
        <img
          src="cat.png"
          style={{ position: 'absolute', left: x, top: y }}
          alt="跟随鼠标的猫"
        />      )}    />  );
}
    

13.diff算法

虚拟DOM技术中,通过比较新旧DOM树差异,计算最小更新范围的算法

只对有变化的部分进行操作,而非渲染整个DOM树

框架优化思路:

同层比较,不跨层级对比

节点类型不同则直接替换

同类型节点通过 “key” 标识复用

14.react router

Q实现 React 生态中用于实现单页应用(SPA)路由管理的核心库

在不刷新页面的情况下,根据 URL 变化渲染对应的组件,提供导航、路由参数、嵌套路由等功能

路由模式:基于浏览器API实现URL监听和修改

单页应用的路由本质:改变URL但不刷新页面

hash模式

利用URL中的哈希(#及后面的部分)作为路由标识

改变哈希不会触发页面刷新,且浏览器会记录哈希变化的历史

实现:

通过 window.location.hash 读取当前哈希值

监听 hashchange 事件,哈希变化时触发路由更新

导航时(如点击 Link 组件),通过修改 window.location.hash 改变 URL,避免页面刷新

history模式(browserrouter)

基于 HTML5 的 History API(window.history),通过 pushState、replaceState 等方法修改 URL 路径(不含 #),且不触发页面刷新

实现: 通过 window.history.pushState() 或 replaceState() 改变 URL(只修改历史记录,不发送请求)

监听 popstate 事件(用户点击前进 / 后退按钮触发),感知 URL 变化并更新路由

导航时,通过 history.push() 或 history.replace() 封装 pushState/replaceState,实现无刷新跳转

核心组件:

路由更新机制:

状态变化触发组件重渲染

当URL变化时:

URL改变,感知变化,更新location

重新匹配路由,渲染匹配组件

15.用过webpack吗

16.对babel的了解

17.代码题

手写深拷贝

function deepCopy(obj){ 
    //不是引用类型就不拷贝
    if(!(obj instanceof Object)) return obj 
    //如果形参obj是数组,就创建数组,如果是对象就创建对象
    let objCopy = obj instanceof Array ? [] : {}  

    for(let key in obj){  
        if(obj instanceof Object){  
            objCopy[key] = deepCopy(obj[key])  
        } else{  
            if(obj.hasOwnProperty(key)){  
                objCopy[key] = obj[key]  
            }
        }  
    }  
    return objCopy  
}