• 作者:老汪软件技巧
  • 发表时间:2024-10-14 04:00
  • 浏览量:

一面es6新特性有哪些

新增了let、const关键字替换var声明变量,因为var会导致变量提升,在申明之前可以访问变量。新增了箭头函数,新增了promise、sync await、还有一些高级特性,比如像proxy。

Promise静态方法有哪些?

any、 race、 all等,(reject resolve也是静态方法)。

然后面试官让我说下,all的用法。我当时说多个Promise全部执行为才会调用all。但是面试官让我手写下用法,我给写错了,面试官耐心的引导我写出正确的,正确如下:

Promise.all([new Promise(), new Promise(), new Promise()])
    .then(values => console.log(values))
    .catch(err => console.log(err))

这时面试官又问我,如果把catch里的方法。作为then里的第二个参数,和现在的写法有啥区别吗?

Promise.all([new Promise(), new Promise(), new Promise()])
    .then(
        values => console.log(values),
        err => console.log(err)
    )

如果任何一个Promise被拒绝,会调用then的第二个参数,而不会触发catch,因为错误在then中已经处理了。

普通函数和箭头函数的区别

普通函数内部是有this指向的,指向调用函数的对象,箭头函数本身是没有的,里面的this是箭头函数第一个有this的父作用域里的this。

例如:

let obj = {}
function  fun1 () {
    console.log(this)
}
const fun2 = () => console.log(this)
obj.a = fun1 // this指向obj
obj.b = fun2 // this指向window

接着面试官又问了我一个问题,箭头函数的this是调用的时候确定的还是定义的时候确定的?

我回答调用的时候,调用的时候,对象属于哪个作用域,就是确定为哪个。

实际是定义的时候确定的。

map和set的作用

你知道map和set的作用吗?

我回答了一半,Map允许使用任意类型的键,包括对象,而对象的键只能是字符串或符号。

其实除此之外,Map保留键值对的插入顺序,遍历时按插入顺序返回。Map在增删查操作上有更好的性能,特别是当键不是字符串时。

Set自动去除重复元素,确保集合内的值唯一,Set也提供了更高效的值查找、添加和删除操作。

js类定义静态方法

使用static关键字

js继承类怎么在方法内部调用方法

class A {
  a() {
    console.log('Method a from class A');
  }
}
class B extends A {
  b() {
    // this.a()
    super.a()
  }
}
const instance = new B();
instance.b(); // 输出: Method a from class A

ts的泛型

有个加法函数,有两个参数,参数类型和返回值类型始终一致,string或者number怎么实现,我说可以用泛型实现。

const add = extends number | string>(a: T, b: T): T => a + b

返回值的类型可以不写,因为会自动推断

然后面试官问我,还有别的方法可以实现吗?我想了半天说没有。

其实还有别的方法,利用函数重载机制。

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
  return a + b;
}

ts参数可选

可以使用?指定可选,或者使用工具函数指定可选。Omit

怎么防止多个文件打包进去多余的js和类,按需加载

分割代码,多使用动态import语句

const PageA = lazy(() => import("./Module/PageA"))

使用tree-shaking优化,不过需要使用ESModule,而不是CommondJS。tree-shaking可以通过AST语法树,分析代码中没有使用的模块,或者无用代码的导出。

按需引入代码,而不是引入所有

import { Button } from "antd/lib/Button"

下面的代码有啥问题,怎么修复

function C () {
  const [p, setP] = useState(10)
  return <Page p={[10, 20,]} onChange={(v) => {setP(v); fetch(p)}} />
}

发送请求时,都是使用上次的值,而不是最新值,这是因为useState更新值时是异步的。(react会进行一次批处理优化)解决方法有两种

// 方法1
function C () {
  const [p, setP] = useState(10)
  return <Page p={[10, 20,]} onChange={(v) => {setP(v); fetch(v)}} />
}
//方法2
function C () {
  const [p, setP] = useState(10)
  useEffect(() => fetch(p), [p]}
  return <Page p={[10, 20,]} onChange={(v) => {setP(v)}} />
}

二面找代码问题

前端字节跳动__抖音字节跳动是什么意思

聊完我的项目经历后,立马问我,下面的代码会出现什么问题,怎么解决。

function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return <h1>{count}h1>;
}

上面的代码更新count有问题,因为每次更新count都使用旧的值,而不是最新的,

setCount((prevCount) => prevCount + 1); // 使用函数式更新

接着又给我下面的例子,问我有啥问题

function Counter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);
  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + step);
    }, 1000);
    return () => clearInterval(ref.current);
  }, [step]);
  return (
    <>
      <h1>{count}h1>
      <input value={step} onChange={e => setStep(Number(e.target.value))} />
    
  );
}

上面的组件出现的问题是:每次当step的值发生改变时,都会重新创建新的定时器

修改方法如下,

function Counter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);
  const ref = useRef(null)
  useEffect(() => {
    if (!ref.current) {
      ref.current = setInterval(() => {
        setCount(c => c + step);
      }, 1000);
    }
    // const id = setInterval(() => {
    //   setCount(c => c + step);
    // }, 1000);
    return () => clearInterval(ref.current);
  }, [step]);
  return (
    <>
      <h1>{count}h1>
      <input value={step} onChange={e => setStep(Number(e.target.value))} />
    
  );
}

实现ts里的Picker 和 Omit

这个问题我当时是不会的,说实话,这两个工具函数我用的都很少,更别说实现了

webpack 和 vite 的 tree chunking的区别

tree chunking是一种清除无用代码的方案,主要是通过分析静态的代码导出和导入关系,寻找无用代码,然后删除它,不将它打包,从而优化代码体积,webpack需要开启es module,并且开启optimization选项,而vite天生就是支持es module的,不过vite是使用依赖的es模块加载机制优化的。

webpack loader 和 plugin 的区别

功能区别:Webpack原生只能理解js和JSON文件,而Loader扩展了其能力,使其能够处理非js文件,Loader是文件级别的转换工具,允许你在模块打包时对文件进行处理。plugin是对Webpack的整个构建过程进行扩展和增强.

原理的区别:webpack的loader拿到的是特定类型文件,而plugin可以根据hook处理webpack的各个流程。另外插件的本质是个是个带有apply方法的类,apply方法中参数是complier对象,可以通过这个对象提供的hook监听特定文件的变化进行编译。而loader是个函数,参数是匹配到的文件源码。

module.exports = class MyPlugin {
    apply(compiler) {
        compiler.hooks.done.tap('MyPlugin', (Compilation) => {
            console.log('MyPlugin: Compilation finished!');
        });
    }
}

用法区别:loader需要根据正则匹配特定的类型的文件,plugin需要实例化插件

commondJS 和 ESModule 的区别

commondJS是动态的,只有在运行时才能确定具体导入的内容,而ESModule是静态的,在编译时就能确定引入和导出的模块

commondJS导入使用的是require,require可以出现在代码任何地方(所以是动态的),导出使用exports或者module.exports

ESModule导入语句是import,import只能出现在文件顶部。(所以是静态的,import方法除外),导出使用export

commondJS实际上是被包裹在一个函数中,可以使用this,而ESModule没有this

commondJS会缓存模块。重新加载返回的是缓存,ESModule也是缓存,后续的导入将共享相同的实例

下面的输出内容是啥

// a.js
const b = require('./b');
console.log(exports.x);
exports.x = 'x';
require('./c');
// b.js
const a = require('./a');
console.log(a);
a.x = 'y';
// c.js
const a = require('./a');
console.log(a.x);

输出结果如下

{}
undefined
x

分析步骤:

加载a.js,首先遇到require('./b'),它会开始加载b.js。此时a.js尚未完全加载完成,它处于未完成的状态,因此,Node.js会将一个空的exports对象暴露给b.js

接着加载b.js,在b.js中,有const a = require('./a'),由于a.js还在加载中(未完成),所以 a 会是当前的未完成a.js的导出对象,此时是空的 {}。所以执行console.log(a),打印结果为{},接着执行a.x = 'y',这会修改a模块的导出内容,将{} 变为 { x: 'y' }。但是由于 a.js还未完成执行,exports.x = 'x'; 还没有执行,所以x暂时变为y。

然后回到a.js继续执行:

首先执行console.log(exports.x),此时 exports 对象还没有被赋值,exports.x是undefined。然后执行exports.x = 'x',将a.js的exports对象更新为{ x: 'x' },覆盖了之前b.js 中将x赋值为'y'的修改。

接着,加载require('./c'),开始加载 c.js。

在c.js中,有const a = require('./a')。由于a.js已经完成了加载,因此a的值是最终导出的 exports对象{ x: 'x' }。执行console.log(a.x),此时a.x 是'x',所以会输出 'x'。

写一个加法函数sum,支持sum(1)(2)(3,4)(5,6,7....), console.log(sum(1,2,3)(4)) => 输出 10

我当时写完的函数是这样的,虽然差不多意思,但是不太对。有知道的大佬可以在告诉我下。

const sum = (...args) => {
  const add = (x) =>{
    if (x === undefined) {
      return args.reduce((a, b) => a + b, 0)
    } else {
      args.push(x)
      return add(...args)
    }
  }
  return add(...args)
}

三面

简单问了下项目经历,聊了下项目里的亮点,四十分钟不到结束了。这里就不多赘述了。

总结

从结果来看虽然通过了,但是真的觉得自己很菜,很多底层的东西,可能只是知其然,并没有知其所以然。

虽然es6已经横空出世好多年了,但是里面的内容真的搞懂的不是很多。所以后续还需要多翻书,多研究细节。

最后希望这篇文章可以帮到找工作的同学,也可以翻看我前面的几篇面试文章。

文章中出现错误的地方欢迎指正!