【React】面试题5题

1. 说说你对 dangerouslySetInnerHTML 的理解

dangerouslySetInnerHTML是React中的一个属性,用于将HTML代码作为字符串直接插入到组件中的DOM元素中。它可以用来动态地生成HTML内容,但同时也带来了一些潜在的安全风险。

使用dangerouslySetInnerHTML时,需要通过一个对象来指定要插入的HTML内容,其中的__html属性值是实际的HTML代码。例如:

js 复制代码
<div dangerouslySetInnerHTML={{ __html: '<p>Hello, React!</p>' }}></div>

在使用dangerouslySetInnerHTML时需要注意以下几点来确保安全性:

  1. 避免直接使用用户输入的数据作为dangerouslySetInnerHTML的内容,以防止跨站脚本攻击(XSS)。任何用户提供的内容都应该经过适当的验证和转义,以确保其中没有恶意代码。

  2. 对于动态生成的HTML内容,应该遵循严格的HTML编码规则,确保所有标签和特殊字符都被正确地转义。可以使用第三方库,如React的自带插件react-dom/server中的ReactDOMServer的escape方法,来进行HTML编码。

  3. 尽量避免使用dangerouslySetInnerHTML,而使用React的组件和属性来动态生成DOM和内容。这样可以更好地保持代码的可读性和维护性,也能减少潜在的安全问题。

总的来说,要安全地使用dangerouslySetInnerHTML,需要进行输入验证和HTML编码,尽量避免使用用户输入作为动态内容,并保持警惕性,以防止潜在的安全漏洞。

使用时的消毒dangerouslySetInnerHTML

考虑一下下面的例子,一个JavaScript事件被附加到一个HTML元素上。虽然这些是无害的例子,但它们是概念的证明,表明一个HTML元素如何被利用来运行恶意脚本。

js 复制代码
const App = () => {
  const data = `lorem <b οnmοuseοver="alert('mouseover');">ipsum</b>`;

  return (
    <div
      dangerouslySetInnerHTML={{__html: data}}
    />
  );
}

export default App;


const App = () => {
  const data = `lorem ipsum <img src="" οnerrοr="alert('message');" />`;

  return (
    <div
      dangerouslySetInnerHTML={{__html: data}}
    />
  );
}

export default App;

幸运的是,有针对HTML的净化工具,可以检测出HTML代码中潜在的恶意部分,然后输出一个干净安全的版本。最受欢迎的HTML净化工具是DOMPurify。

让我们使用它的在线演示来对上述HTML代码进行消毒,看看它是如何检测并过滤掉代码中可能在执行时产生危险的部分的。

js 复制代码
Original
lorem <b onmouseover="alert('mouseover');">ipsum</b>

Sanitized
lorem <b>ipsum</b>
js 复制代码
Original
lorem ipsum <img src="" onerror="alert('message');" />

Sanitized
lorem ipsum <img src="">

即使在我们信任数据来源的情况下,使用消毒剂也是很好的做法。在使用DOMPurify包的情况下,上面的一个例子会是这样的。

js 复制代码
import DOMPurify from 'dompurify'

const App = () => {
  const data = `lorem <b οnmοuseοver="alert('mouseover');">ipsum</b>`
  const sanitizedData = () => ({
    __html: DOMPurify.sanitize(data)
  })

  return (
    <div
      dangerouslySetInnerHTML={sanitizedData()}
    />
  );
}

export default App;

sanitizedData 函数返回一个带有__html 键的对象,它有一个从DOMPurify.sanitize 函数返回的值。

正如预期的那样,当我们将鼠标悬停在粗体字上时,并没有执行警报函数。

2. setState 是同步,还是异步的?

在React中,setState既可以是同步的,也可以是异步的,具体取决于调用setState的时机和方式。

  1. 异步更新:当在React的事件处理函数(如onClick)或生命周期方法(如componentDidMount)中调用setState时,React会将多个setState调用合并成一个批量更新。这意味着React会等待当前代码块执行完毕,然后一次性更新组件的状态并重新渲染。这样可以提高性能并避免不必要的重复渲染。在异步更新的情况下,无法立即获取到更新后的状态值。

  2. 同步更新:在某些特殊情况下,setState可能会同步更新状态。例如,在setTimeout或原生事件处理函数中直接调用setState,React会立即更新状态并重新渲染组件。在同步更新的情况下,可以立即获取到更新后的状态值。

需要注意的是,无论是同步还是异步更新,React会对多个setState调用进行合并,以减少不必要的更新。这意味着在同一个事件处理函数或生命周期方法中连续多次调用setState,实际上只会触发一次更新。

为了在setState更新完成后执行一些操作,可以使用回调函数作为setState的第二个参数。回调函数会在更新完成并重新渲染组件后被调用。通过回调函数,可以获取到更新后的状态值。

示例代码:

js 复制代码
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 }, () => {
      console.log("Updated count:", this.state.count);
    });
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

在上述代码中,点击按钮会更新count的状态,并在回调函数中打印更新后的count值。这里展示了setState的异步更新行为,以及如何使用回调函数获取更新后的状态值。

错误使用案例:

js 复制代码
  const [number, setNumber] = useState(0);

  return (
    <div className="app">
      <button
        onClick={() => {
          setNumber(number + 1);
          console.log("number", number); // 这里永远拿不到最新的number值
        }}
      >
        add
      </button>

onClick事件中,setState是异步的,所以后面console.log中打印的值永远不是最新的值。

3. 我们应该在什么场景下使用 useMemo 和 useCallback ?

useMemo 和 useCallback 是 React 的内置 Hook,通常作为优化性能的手段被使用。他们可以用来缓存函数、组件、变量,以避免两次渲染间的重复计算。但是实践过程中,他们经常被过度使用:担心性能的开发者给每个组件、函数、变量、计算过程都套上了 memo,以至于它们在代码里好像失控了一样,无处不在。

本文希望通过分析 useMemo/useCallback 的目的、方式、成本,以及具体使用场景,帮助开发者正确的决定如何适时的使用他们。赶时间的读者可以直接拉到底部看结论。

我们先从 useMemo/useCallback 的目的说起。

为什么使用 useMemo 和 useCallback

使用 memo 通常有三个原因:

  1. 防止不必要的 effect。
  2. 防止不必要的 re-render。
  3. 防止不必要的重复计算。
    后两种优化往往被误用,导致出现大量的无效优化或冗余优化。下面详细介绍这三个优化方式。

防止不必要的 effect

如果一个值被 useEffect 依赖,那它可能需要被缓存,这样可以避免重复执行 effect。

js 复制代码
const Component = () => {
  // 在 re-renders 之间缓存 a 的引用
  const a = useMemo(() => ({ test: 1 }), []);

  useEffect(() => {
    // 只有当 a 的值变化时,这里才会被触发
    doSomething();
  }, [a]);

  // the rest of the code
};

在这段代码中,通过使用useMemo将对象{ test: 1 }缓存起来,确保在组件重新渲染时,a的引用不会发生变化。这样做是为了在依赖项变化时,控制useEffect的执行时机。

在useEffect中,通过将a作为依赖项传递给useEffect的第二个参数,即[a],只有当a的引用发生变化时,即使其他依赖项没有变化,useEffect也会被触发。

如果没有使用useMemo缓存a的引用,每次组件重新渲染时,a的引用都会发生变化,导致useEffect在每次重新渲染时都被触发。

通过使用useMemo缓存a的引用,可以确保只有当a的值实际上发生了变化时,才会触发useEffect。这样可以避免不必要的useEffect调用,提高性能和效率。

需要注意的是,useMemo和useEffect的依赖项是独立的。即使a的引用没有变化,但是useEffect的其他依赖项发生了变化,useEffect也会被触发。因此,在使用useMemo和useEffect时,需要根据具体的业务逻辑和需求,合理定义依赖项,以达到预期的效果。

useCallback 同理:

js 复制代码
const Component = () => {
  // 在 re-renders 之间缓存 fetch 函数
  const fetch = useCallback(() => {
    console.log('fetch some data here');
  }, []);

  useEffect(() => {
    // 仅fetch函数的值被改变时,这里才会被触发
    fetch();
  }, [fetch]);

  // the rest of the code

};

当变量直接或者通过依赖链成为 useEffect 的依赖项时,那它可能需要被缓存。这是 useMemo 和 useCallback 最基本的用法。

防止不必要的 re-render

进入重点环节了!!!。正确的阻止 re-render 需要我们明确三个问题:

  1. 组件什么时候会 re-render。
  2. 如何防止子组件 re-render。
  3. 如何判断子组件需要缓存。

1. 组件什么时候会 re-render

三种情况:

  • 当本身的 props 或 state 改变时。
  • Context value 改变时,使用该值的组件会 re-render。
  • 当父组件重新渲染时,它所有的子组件都会 re-render,形成一条 re-render 链。

第三个 re-render 时机经常被开发者忽视,导致代码中存在大量的无效缓存。

例如:

js 复制代码
const App = () => {
  const [state, setState] = useState(1);

  const onClick = useCallback(() => {
    console.log('Do something on click');
  }, []);

  return (
	// 无论 onClick 是否被缓存,APP re-render 时 Page 都会 re-render 
    <Page onClick={onClick} />
  );
};

当使用 setState 改变 state 时,App 会 re-render,作为子组件的 Page 也会跟着 re-render。这里 useCallback 是完全无效的,它并不能阻止 Page 的 re-render。

如何防止子组件 re-render?
必须同时缓存 onClick 和组件本身,才能实现 Page 不触发 re-render。

js 复制代码
const PageMemoized = React.memo(Page);

const App = () => {
  const [state, setState] = useState(1);

  const onClick = useCallback(() => {
    console.log('Do something on click');
  }, []);

  return (
    // Page 和 onClick 同时 memorize
    <PageMemoized onClick={onClick} />
  );
};

由于使用了React.memo,PageMemoized 会浅比较 props 的变化后再决定是否 re-render。onClick 被缓存后不会再变化,所以 PageMemoized 不再 re-render。

然而,如果 PageMemoized 再添加一个未被缓存的 props,一切就前功尽弃 。。。。 :

js 复制代码
const PageMemoized = React.memo(Page);

const App = () => {
  const [state, setState] = useState(1);

  const onClick = useCallback(() => {
    console.log('Do something on click');
  }, []);

  return (
    // page WILL re-render because value is not memoized
    <PageMemoized onClick={onClick} value={[1, 2, 3]} />
  );
};

由于 value 会随着 App 的 re-render 重新定义,引用值发生变化,导致 PageMemoized 仍然会触发 re-render。

现在可以得出结论了,必须同时满足以下两个条件,子组件才不会 re-render:

  1. 子组件自身被缓存。
  2. 子组件所有的 prop 都被缓存。

如何判断子组件需要缓存

我们已经了解,为了防止子组件 re-render,需要以下成本:

  1. 开发者工作量的增加: 一旦使用缓存,就必须保证组件本身以及所有 props 都缓存,后续添加的所有 props 都要缓存。
  2. 代码复杂度和可读性的变化:代码中出现大量缓存函数,这会增加代码复杂度,并降低易读性。
    除此之外还有另外一个成本:性能成本。 组件的缓存是在初始化时进行,虽然每个组件缓存的性能耗费很低,通常不足1ms,但大型程序里成百上千的组件如果同时初始化缓存,成本可能会变得很可观。

所以局部使用 memo,比全局使用显的更优雅、性能更好,坏处是需要开发者主动去判断是否需要缓存该子组件。

🤨 那应该什么时候缓存组件,怎么判断一个组件的渲染是昂贵的?

很遗憾,似乎没有一个简单&无侵入&自动的衡量方式。通常来说有两个方式:

  1. 人肉判断,开发或者测试人员在研发过程中感知到渲染性能问题,并进行判断。
  2. 通过工具,目前有一些工具协助开发者在查看组件性能:

另外,React 在 16.5版本后提供了 Profiler API:它可以识别出应用中渲染较慢的部分,或是可以使用类似 memoization 优化的部分。所以可以通过 puppeteer 或 cypress 在自动化集成中测试组件性能,这很适合核心组件的性能测试。

防止不必要的重复计算

React 文档所说,useMemo 的基本作用是,避免在每次渲染时都进行高开销的计算。

那什么是"高开销的计算"?

高开销的计算其实极少出现,如下示例,对包含 250 个 item 的数组 countries 进行排序、渲染,并计算耗时。

js 复制代码
const List = ({ countries }) => {
  const before = performance.now();
  const sortedCountries = orderBy(countries, 'name', sort);
  // this is the number we're after
  const after = performance.now() - before;

  return (
    // same
  )
};

完整代码:https://codesandbox.io/s/measure-without-memo-tnhggk?file=/src/page.tsx

结果如图所示,排序耗时仅用了 2.7毫秒,而渲染图中的 List 组件(仅仅只是 button + 文字)却用了 13毫秒,5倍的差距。

大部分情况下,我们的计算量要比这个 250 个 item 的数组少,而组件渲染要比这个 List 组件复杂的多,所以真实程序中,计算和渲染的性能差距会更大。

可见,组件渲染才是性能的瓶颈,应该把 useMemo 用在程序里渲染昂贵的组件上,而不是数值计算上。当然,除非这个计算真的很昂贵,比如阶乘计算。

至于为什么不给所有的组件都使用 useMemo,上文已经解释了。useMemo 是有成本的,它会增加整体程序初始化的耗时,并不适合全局全面使用,它更适合做局部的优化。

为什么 React 没有把缓存组件作为默认配置?

  1. 缓存是有成本的,小的成本可能会累加过高。
  2. 默认缓存无法保证足够的正确性。

结论

讲到这里我们可以总结出 useMemo/useCallback 使用准则了:

  1. 大部分的 useMemo 和 useCallback 都应该移除,他们可能没有带来任何性能上的优化,反而增加了程序首次渲染的负担,并增加程序的复杂性。
  2. 使用 useMemo 和 useCallback 优化子组件 re-render 时,必须同时满足以下条件才有效。
  • 子组件已通过 React.memo 或 useMemo 被缓存
  • 子组件所有的 prop 都被缓存
  1. 不推荐默认给所有组件都使用缓存,大量组件初始化时被缓存,可能导致过多的内存消耗,并影响程序初始化渲染的速度。

4. react是否支持给标签设置自定义的属性,比如给video标签设置webkit-playsinline?

如果你在react中这么样写:

js 复制代码
// Your code:
<div mycustomattribute="something" />

在react 15中将被渲染成:

js 复制代码
// React 15 output:
<div />

在react 16及之后的版本中将被渲染成:

js 复制代码
// React 16 output:
<div mycustomattribute="something" />

但这个会有限制,如果自定义的属性不是 string, number 或者 object,该属性依然会被忽略。

所以目前可以这样添加 webkit-playsinline 属性:

js 复制代码
<video webkitPlaysinline={true} controls>
  <source src="video.mp4" type="video/mp4" />
</video>

在上述代码中,webkitPlaysinline被作为video标签的自定义属性,并设置为true。这样可以达到给video标签设置webkit-playsinline的效果。

需要注意的是,设置自定义属性时,React会将驼峰命名法的属性转换为小写和连字符的形式。例如,webkitPlaysinline会被转换为webkit-playsinline。因此,使用驼峰命名法来设置自定义属性时,需要注意属性名的转换规则。

另外,对于一些特殊的属性,如data-*属性或aria-*属性,React会将其保持不变,不进行转换。这样可以方便地设置自定义的数据属性或辅助功能属性。

还可以通过 setAttribute 进行设置,比如:

js 复制代码
import * as React from 'react';
import { Component } from 'react';

export class VideoComponent extends Component {
  videoContainer: HTMLDivElement;
  componentDidMount() {
    const video = document.createElement('video');
    video.autoplay = true;
    video.loop = true;
    video.muted = true; // fixes autoplay in chrome
    video.setAttribute('playsinline', 'true'); // fixes autoplay in webkit (ie. mobile safari)

    const source = document.createElement('source');
    source.src = '/path/to/your/video.mp4';
    source.type = 'video/mp4';
    video.appendChild(source);

    this.videoContainer.appendChild(video);
  }
  render() {
    return (
      <div ref={(ref) => { this.videoContainer = ref; }} />
    );
  }
}

5. 说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?

react hooks 一出现便受到了许多开发人员的追捧,或许在使用react hooks 的时候遇到 "闭包陷阱" 是每个开发人员在开发的时候都遇到过的事情,有的两眼懵逼、有的则稳如老狗瞬间就定义到了问题出现在何处。

(以下react示范demo,均为react 16.8.3 版本)

你一定遭遇过以下这个场景:

js 复制代码
function App(){
    const [count, setCount] = useState(1);
    useEffect(()=>{
        setInterval(()=>{
            console.log(count)
        }, 1000)
    }, [])
}

在这个定时器里面去打印 count 的值,会发现,不管在这个组件中的其他地方使用 setCount 将 count 设置为任何值,还是设置多少次,打印的都是1。是不是有一种,尽管历经千帆,我记得的还是你当初的模样的感觉? hhh... 接下来,我将尽力的尝试将我理解的,为什么会发生这么个情况说清楚,并且浅谈一些hooks其他的特性。如果有错误,希望各位同学能救救孩子,不要让我带着错误的认知活下去了。。。

1. 一个熟悉的闭包场景

首先从一个各位jser都很熟悉的场景入手。

js 复制代码
for ( var i=0; i<5; i++ ) {
    setTimeout(()=>{
        console.log(i)
    }, 0)
}

我就不说为什么最终,打印的都是5的原因了。直接贴出使用闭包打印 0...4的代码:

js 复制代码
for ( var i=0; i<5; i++ ) {
   (function(i){
         setTimeout(()=>{
            console.log(i)
        }, 0)
   })(i)
}

这个原理其实就是使用闭包,定时器的回调函数去引用立即执行函数里定义的变量,形成闭包保存了立即执行函数执行时 i 的值,异步定时器的回调函数才如我们想要的打印了顺序的值。

其实,useEffect 的那个场景的原因,跟这个是一样的,useEffect 闭包陷阱场景的出现,是 react 组件更新流程以及 useEffect 的实现的自然而然结果。

2. 浅谈hooks原理,理解useEffect 的 "闭包陷阱" 出现原因。

首先,可能都听过react的 Fiber 架构,其实可以认为一个 Fiber节点就对应的是一个组件。对于 classComponent 而言,有 state 是一件很正常的事情,Fiber对象上有一个 memoizedState 用于存放组件的 state。ok,现在看 hooks 所针对的 FunctionComponnet。 无论开发者怎么折腾,一个对象都只能有一个 state 属性或者 memoizedState 属性,可是,谁知道可爱的开发者们会在 FunctionComponent 里写上多少个 useState,useEffect 等等 ? 所以,react用了链表这种数据结构来存储 FunctionComponent 里面的 hooks。比如

js 复制代码
function App(){
    const [count, setCount] = useState(1)
    const [name, setName] = useState('chechengyi')
    useEffect(()=>{
        
    }, [])
    const text = useMemo(()=>{
        return 'ddd'
    }, [])
}

在组件第一次渲染的时候,为每个hooks都创建了一个对象

js 复制代码
type Hook = {
  memoizedState: any,
  baseState: any,
  baseUpdate: Update<any, any> | null,
  queue: UpdateQueue<any, any> | null,
  next: Hook | null,
};

最终形成了一个链表。

这个对象的memoizedState属性就是用来存储组件上一次更新后的 state,next毫无疑问是指向下一个hook对象。在组件更新的过程中,hooks函数执行的顺序是不变的,就可以根据这个链表拿到当前hooks对应的Hook对象,函数式组件就是这样拥有了state的能力。当前,具体的实现肯定比这三言两语复杂很多。

所以,知道为什么不能将hooks写到if else语句中了把?因为这样可能会导致顺序错乱,导致当前hooks拿到的不是自己对应的Hook对象。

useEffect 接收了两个参数,一个回调函数和一个数组。数组里面就是 useEffect 的依赖,当为 [] 的时候,回调函数只会在组件第一次渲染的时候执行一次。如果有依赖其他项,react 会判断其依赖是否改变,如果改变了就会执行回调函数。说回最初的场景:

js 复制代码
function App(){
    const [count, setCount] = useState(1);
    useEffect(()=>{
        setInterval(()=>{
            console.log(count)
        }, 1000)
    }, [])
    function click(){ setCount(2) }
}

好,开动脑袋开始想象起来,组件第一次渲染执行 App(),执行 useState 设置了初始状态为1,所以此时的 count 为1。然后执行了 useEffect,回调函数执行,设置了一个定时器每隔 1s 打印一次 count。

接着想象如果 click 函数被触发了,调用 setCount(2) 肯定会触发react的更新,更新到当前组件的时候也是执行 App(),之前说的链表已经形成了哈,此时 useState 将 Hook 对象 上保存的状态置为2, 那么此时 count 也为2了。然后在执行 useEffect 由于依赖数组是一个空的数组,所以此时回调并不会被执行。

ok,这次更新的过程中根本就没有涉及到这个定时器,这个定时器还在坚持的,默默的,每隔1s打印一次 count。 注意这里打印的 count ,是组件第一次渲染的时候 App() 时的 count, count的值为1,因为在定时器的回调函数里面被引用了,形成了闭包一直被保存。

3. 难道真的要在依赖数组里写上的值,才能拿到新鲜的值?

仿佛都习惯性都去认为,只有在依赖数组里写上我们所需要的值,才能在更新的过程中拿到最新鲜的值。那么看一下这个场景:

js 复制代码
function App() {
  return <Demo1 />
}

function Demo1(){
  const [num1, setNum1] = useState(1)
  const [num2, setNum2] = useState(10)

  const text = useMemo(()=>{
    return `num1: ${num1} | num2:${num2}`
  }, [num2])

  function handClick(){
    setNum1(2)
    setNum2(20)
  }

  return (
    <div>
      {text}
      <div><button onClick={handClick}>click!</button></div>
    </div>
  )
}

text 是一个 useMemo ,它的依赖数组里面只有num2,没有num1,却同时使用了这两个state。当点击button 的时候,num1和num2的值都改变了。那么,只写明了依赖num2的 text 中能否拿到 num1 最新鲜的值呢?

如果你装了 react 的 eslint 插件,这里也许会提示你错误,因为在text中你使用了 num1 却没有在依赖数组中添加它。 但是执行这段代码会发现,是可以正常拿到num1最新鲜的值的。

如果理解了之前第一点说的"闭包陷阱"问题,肯定也能理解这个问题。

为什么呢,再说一遍,这个依赖数组存在的意义,是react为了判定,在本次更新中,是否需要执行其中的回调函数,这里依赖了的num2,而num2改变了。回调函数自然会执行, 这时形成的闭包引用的就是最新的num1和num2,所以,自然能够拿到新鲜的值。

4. 为什么使用useRef能够每次拿到新鲜的值?

因为初始化的 useRef 执行之后,返回的都是同一个对象。

举个js的例子:

js 复制代码
var A = {name: 'chechengyi'}
var B = A
B.name = 'baobao'
console.log(A.name) // baobao

对,这就是这个场景成立的最根本原因。

也就是说,在组件每一次渲染的过程中。 比如 ref = useRef() 所返回的都是同一个对象,每次组件更新所生成的ref指向的都是同一片内存空间, 那么当然能够每次都拿到最新鲜的值了。

所以,提出一个合理的设想。只要我们能保证每次组件更新的时候,useState 返回的是同一个对象的话?我们也能绕开闭包陷阱这个情景吗? 试一下吧。

js 复制代码
function App() {
  // return <Demo1 />
  return <Demo2 />
}

function Demo2(){
  const [obj, setObj] = useState({name: 'chechengyi'})

  useEffect(()=>{
    setInterval(()=>{
      console.log(obj)
    }, 2000)
  }, [])
  
  function handClick(){
    setObj((prevState)=> {
      var nowObj = Object.assign(prevState, {
        name: 'baobao',
        age: 24
      })
      console.log(nowObj == prevState)
      return nowObj
    })
  }
  return (
    <div>
      <div>
        <span>name: {obj.name} | age: {obj.age}</span>
        <div><button onClick={handClick}>click!</button></div>
      </div>
    </div>
  )
}

简单说下这段代码,在执行 setObj 的时候,传入的是一个函数。这种用法就不用我多说了把?然后 Object.assign 返回的就是传入的第一个对象。总儿言之,就是在设置的时候返回了同一个对象。

执行这段代码发现,确实点击button后,定时器打印的值也变成了:

js 复制代码
{
    name: 'baobao',
    age: 24 
}
相关推荐
codingandsleeping5 分钟前
Express入门
javascript·后端·node.js
Vaclee7 分钟前
JavaScript-基础语法
开发语言·javascript·ecmascript
拉不动的猪29 分钟前
前端常见数组分析
前端·javascript·面试
小吕学编程1 小时前
ES练习册
java·前端·elasticsearch
Asthenia04121 小时前
Netty编解码器详解与实战
前端
袁煦丞1 小时前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛2 小时前
vue组件间通信
前端·javascript·vue.js
一笑code2 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员3 小时前
layui时间范围
前端·javascript·layui
NoneCoder3 小时前
HTML响应式网页设计与跨平台适配
前端·html