【前端面试题】我靠它拿到了大厂Offer 一

简介: 【前端面试题】我靠它拿到了大厂Offer

新的一天,加油!

每日一道笔试题,遇见不一样的自己!

第102题:请输出下列代码执行的结果

//参考:忍者秘籍第二版
console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')

解释:

输出结果:script start->promise1->promise1 end->script end->promise2->settimeout

当JS主线程执行到Promise对象时,

  • promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环队列microtask queue
  • promise1 是 pending: 这个 task 就会放入事件循环队列未来的某个(可能下一个)回合的 microtask queue
  • setTimeout 的回调也是个 task ,它会被放入macrotask queue,即使是 0ms 的情况

第101题:请输出下列代码执行的结果

//参考:忍者秘籍第二版
async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start');
async1();
console.log('script end')

解释:

输出结果:script start->async1 start->async2->script end->async1 end
  • async 函数返回一个 Promise 对象(await通过返回一个Promise对象来实现同步的效果),当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
  • await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。

第100题:请解释下列三个方法在判断 是否是数组类型 时的区别:

- Object.prototype.toString.call()
- instanceof
- Array.isArray()
参考:js高级程序设计第三版

解释

  1. Object.prototype.toString.call()
    每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型。但是,当除了 Object 类型的对象外,其他类型直接使用 toString 方法时,会直接返回内容的字符串,所以我们需要使用call或者apply方法来改变toString方法的执行上下文

例如:

const arr = ['abc','bca'];
arr.toString(); // "abc,bca"
Object.prototype.toString.call(arr); // "[object Array]"
  • 结论:这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。通常,该方法常用于判断浏览器内置对象。
  1. instanceof
    instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
    使用 instanceof判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false
[]  instanceof Array; // true
  1. instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。
    例如:
[]  instanceof Object; // true
  1. Array.isArray()
    Array.isArray()是ES5新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现。
if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}
  1. 同时,Array.isArray()优于instanceof,特别是在检测Array实例时,Array.isArray可以检测出iframes下的Array实例。

例如:

let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
let arr = new xArray(1,2,3); // [1,2,3]
Array.isArray(arr);  // true
arr instanceof Array; // false

第99题:实现下列功能

输入: [2,3,4,6,7,9]
输出: '2~4,6~7,9'
const nums = [2,3,4,6,7,9];
function example(num) {
  let result = [];
  let temp = num[0]
  num.forEach((value, index) => {
    if (value + 1 !== num[index + 1]) {
      if (temp !== value) {
        result.push(`${temp}~${value}`)
      } else {
        result.push(`${value}`)
      }
      temp = num[index + 1]
    }
  })
  return result;
}
console.log(example(nums).join(','))

第98题:如何优化浏览器的Repaint和Reflow

第97题:解释下Vue是如何进行双向数据绑定的?View->Model和Model-View,原理是什么

第96题:输出下列代码执行结果

String('123') == new String('123'); //true,==时做了隐式转换,调用了toString
String('123') === new String('123');//false,两者的类型不一样,前者是string,后者是object
var name = 'abc';
(function() {
    if (typeof name == 'undefined') {
        name = 'cba';
        console.log(name);
    } else {
        console.log(name);
    }
})();
1、首先进入立即执行函数作用域当中,获取name属性
2、在当前作用域没有找到name
3、通过作用域链找到最外层,得到name属性
4、执行else的内容,输出 abc

第95题:输出下列代码执行结果

2 + "3";
3 * "5";
[6, 3] + [3, 6];
"b" + + "c";  

//解释:

2 + "3";
加性操作符:如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接起来
所以值为:“23”
3 * "5";
乘性操作符:如果有一个操作数不是数值,则在后台调用 Number()将其转换为数值
[6, 3] + [3, 6];
Javascript中所有对象基本都是先调用valueOf方法,如果不是数值,再调用toString方法。
所以两个数组对象的toString方法相加,值为:"6,33,6"
"b" + + "c"; 
后边的“+”将作为一元操作符,如果操作数是字符串,将调用Number方法将该操作数转为数值,如果操作数无法转为数值,则为NaN。
所以值为:"bNaN"

第94题:写出如下代码的打印结果

function Foo() {
    Foo.a = function() {
        console.log(1)
    }
    this.a = function() {
        console.log(2)
    }
}
Foo.prototype.a = function() {
    console.log(3)
}
Foo.a = function() {
    console.log(4)
}
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
  • 解析
function Foo() {
    Foo.a = function() {
        console.log(1)
    }
    this.a = function() {
        console.log(2)
    }
}
// 以上只是 Foo 的构建方法,没有产生实例,此刻也没有执行
Foo.prototype.a = function() {
    console.log(3)
}
// 现在在 Foo 上挂载了原型方法 a ,方法输出值为 3
Foo.a = function() {
    console.log(4)
}
// 现在在 Foo 上挂载了直接方法 a ,输出值为 4
Foo.a();
// 立刻执行了 Foo 上的 a 方法,也就是刚刚定义的,所以
// # 输出 4
let obj = new Foo();
/* 这里调用了 Foo 的构建方法。Foo 的构建方法主要做了两件事:
1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。
2. 在新对象上挂载直接方法 a ,输出值为 2。
*/
obj.a();
// 因为有直接方法 a ,不需要去访问原型链,所以使用的是构建方法里所定义的 this.a,
// # 输出 2
Foo.a();
// 构建方法里已经替换了全局 Foo 上的 a 方法,所以
// # 输出 1

第93题:用 JavaScript 写一个函数,输入 int 型,返回整数逆序后的字符串。

如:输入整型 1234,返回字符串“4321”。要求必须使用递归函数调用,不能用全局变量,输入函数必须只有一个参数传入,必须返回字符串。

function fun(num){
    let num1 = num / 10;
    let num2 = num % 10;
    if(num1<1){
        return num;
    }else{
        num1 = Math.floor(num1)
        return `${num2}${fun(num1)}`
    }
}
var a = fun(12345)
console.log(a)

第92题:写出如下代码的打印结果

function changeObjProperty(o) {
  o.siteUrl = "http://www.baidu.com"
  o = new Object()
  o.siteUrl = "http://www.google.com"
} 
let webSite = new Object();
changeObjProperty(webSite);
console.log(webSite.siteUrl);

输出:www.baidu.com //原因:函数的形参是值传递的

第91题:前端加密的常见场景和方法

加密的目的,简而言之就是将明文转换为密文、甚至转换为其他的东西,用来隐藏明文内容本身,防止其他人直接获取到敏感明文信息、或者提高其他人获取到明文信息的难度。

通常我们提到加密会想到密码加密、HTTPS 等关键词

场景-密码传输

前端密码传输过程中如果不加密,在日志中就可以拿到用户的明文密码,对用户安全不太负责。

这种加密其实相对比较简单,可以使用 PlanA-前端加密、后端解密后计算密码字符串的MD5/MD6存入数据库;也可以 PlanB-直接前端使用一种稳定算法加密成唯一值、后端直接将加密结果进行MD5/MD6,全程密码明文不出现在程序中。

  • PlanA
    使用 Base64 / Unicode+1 等方式加密成非明文,后端解开之后再存它的 MD5/MD6
  • PlanB
    直接使用 MD5/MD6 之类的方式取 Hash ,让后端存 Hash 的 Hash 。

场景-数据包加密

应该大家有遇到过:打开一个正经网站,网站底下蹦出个不正经广告——比如X通的流量浮层,X信的插入式广告……(我没有针对谁)

但是这几年,我们会发现这种广告逐渐变少了,其原因就是大家都开始采用 HTTPS 了。

被人插入这种广告的方法其实很好理解:你的网页数据包被抓取->在数据包到达你手机之前被篡改->你得到了带网页广告的数据包->渲染到你手机屏幕。

而 HTTPS 进行了包加密,就解决了这个问题。严格来说我认为从手段上来看,它不算是一种前端加密场景;但是从解决问题的角度来看,这确实是前端需要知道的事情。

  • Plan
    全面采用 HTTPS

场景-展示成果加密

经常有人开发网页爬虫爬取大家辛辛苦苦一点一点发布的数据成果,有些会影响你的竞争力,有些会降低你的知名度,甚至有些出于恶意爬取你的公开数据后进行全量公开……比如有些食谱网站被爬掉所有食谱,站点被克隆;有些求职网站被爬掉所有职位,被拿去卖信息;甚至有些小说漫画网站赖以生存的内容也很容易被爬取。

  • Plan
    将文本内容进行展示层加密,利用字体的引用特点,把拿给爬虫的数据变成“乱码”。
    举个栗子:正常来讲,当我们拥有一串数字“12345”并将其放在网站页面上的时候,其实网站页面上显示的并不是简单的数字,而是数字对应的字体的“12345”。这时我们打乱一下字体中图形和字码的对应关系,比如我们搞成这样:

图形:1 2 3 4 5

字码:2 3 1 5 4

这时,如果你想让用户看到“12345”,你在页面中渲染的数字就应该是“23154”。这种手段也可以算作一种加密。

具体的实现方法可以看一下《Web 端反爬虫技术方案》

参考

第90题:模拟实现一个深拷贝,并考虑对象相互引用以及 Symbol 拷贝的情况

一个不考虑其他数据类型的公共方法,基本满足大部分场景

function deepCopy(target, cache = new Set()) {
  if (typeof target !== 'object' || cache.has(target)) {
    return target
  }
  if (Array.isArray(target)) {
    target.map(t => {
      cache.add(t)
      return t
    })
  } else {
    return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
      cache.add(target[key])
      res[key] = deepCopy(target[key], cache)
      return res
    }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})
  }
}

主要问题是

  1. symbol作为key,不会被遍历到,所以stringifyparse是不行的
  2. 有环引用,stringifyparse也会报错
    我们另外用getOwnPropertySymbols可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下

另外,如果不考虑用symbolkey,还有两种黑科技深拷贝,可以解决环引用的问题,比stringifyparse优雅强一些。

function deepCopyByHistory(target) {
  const prev = history.state
  history.replaceState(target, document.title)
  const res = history.state
  history.replaceState(prev, document.title)
  return res
}
async function deepCopyByMessageChannel(target) {
  return new Promise(resolve => {
    const channel = new MessageChannel()
    channel.port2.onmessage = ev => resolve(ev.data)
    channel.port1.postMessage(target)
  }).then(data => data)
}

无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有Object.create(target.constructor.prototype)的操作

第89题:vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?

事件代理作用主要是 2 个:

  1. 将事件处理程序代理到父节点,减少内存占用率
  2. 动态生成子节点时能自动绑定事件处理程序到父节点
//不使用事件代理,每个 span 节点绑定一个 click 事件,并指向同一个事件处理程序
<div>
      <span 
        v-for="(item,index) of 100000" 
        :key="index" 
        @click="handleClick">
        {{item}}
      </span>
 </div>
//不使用事件代理,每个 span 节点绑定一个 click 事件,并指向不同的事件处理程序 
<div>
      <span 
        v-for="(item,index) of 100000" 
        :key="index" 
        @click="function () {}">
        {{item}}
      </span>
</div>
// 使用事件代理
<div  @click="handleClick">
      <span 
        v-for="(item,index) of 100000"  
        :key="index">
        {{item}}
      </span>
</div>
使用事件代理无论是监听器数量和内存占用率都比前两者要少

第88题:给定两个大小为m和n的有序数组nums1和nums2。 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))

const findMidNum = function(arr1,arr2) {
    for(let i=0;i<arr2.length;i++) {
        arr1.push(arr2[i]);
    }
    arr1 = arr1.sort((a,b)=>{return b-a;})
    if(arr1.length%2===0) {
        return (arr1[arr1.length/2]+arr1[arr1.length/2-1])/2
    }else {
        return arr1[(arr1.length-1)/2]
    }
}
console.log(findMidNum([1,2],[3,5,6]))

第87题:已知数据格式,实现一个函数 fn 找出链条中所有的父级 id

const data = [{
    id: '1',
    name: 'test1',
    children: [
        {
            id: '11',
            name: 'test11',
            children: [
                {
                    id: '111',
                    name: 'test111'
                },
                {
                    id: '112',
                    name: 'test112'
                }
            ]
        },
        {
            id: '12',
            name: 'test12',
            children: [
                {
                    id: '121',
                    name: 'test121'
                },
                {
                    id: '122',
                    name: 'test122'
                }
            ]
        }
    ]
}];
let res = [];
const findId = (list, value) => {
  let len = list.length;
  for (let i in list) {
    const item = list[i];
    if (item.id == value) {
      return res.push(item.id), [item.id];
    }
    if (item.children) {
      if (findId(item.children, value).length) {
        res.unshift(item.id);
        return res;
      }
    }
    if (i == len - 1) {
      return res;
    }
  }
};

第86题:介绍下 HTTPS 中间人攻击

中间人攻击过程如下:

  1. 服务器向客户端发送公钥。
  2. 攻击者截获公钥,保留在自己手上。
  3. 然后攻击者自己生成一个【伪造的】公钥,发给客户端。
  4. 客户端收到伪造的公钥后,生成加密hash值发给服务器。
  5. 攻击者获得加密hash值,用自己的私钥解密获得真秘钥。
  6. 同时生成假的加密hash值,发给服务器。
  7. 服务器用私钥解密获得假秘钥。
  8. 服务器用加秘钥加密传输信息 。

防范方法:

服务端在发送浏览器的公钥中加入CA证书,浏览器可以验证CA证书的有效性


第85题:实现模糊搜索结果的关键词高亮显示

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>auto complete</title>
  <style>
    bdi {
      color: rgb(0, 136, 255);
    }
    li {
      list-style: none;
    }
  </style>
</head>
<body>
  <input class="inp" type="text">
  <section>
    <ul class="container"></ul>
  </section>
</body>
<script>
  function debounce(fn, timeout = 300) {
    let t;
    return (...args) => {
      if (t) {
        clearTimeout(t);
      }
      t = setTimeout(() => {
        fn.apply(fn, args);
      }, timeout);
    }
  }
  function memorize(fn) {
    const cache = new Map();
    return (name) => {
      if (!name) {
        container.innerHTML = '';
        return;
      }
      if (cache.get(name)) {
        container.innerHTML = cache.get(name);
        return;
      }
      const res = fn.call(fn, name).join('');
      cache.set(name, res);
      container.innerHTML = res;
    }
  }
  function handleInput(value) {
    const reg = new RegExp(`\(${value}\)`);
    const search = data.reduce((res, cur) => {
      if (reg.test(cur)) {
        const match = RegExp.$1;
        res.push(`<li>${cur.replace(match, '<bdi>$&</bdi>')}</li>`);
      }
      return res;
    }, []);
    return search;
  }
  const data = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const container = document.querySelector('.container');
  const memorizeInput = memorize(handleInput);
  document.querySelector('.inp').addEventListener('input', debounce(e => {
    memorizeInput(e.target.value);
  }))
</script>
</html>

第84题:设计并实现 Promise.race()

Promise.myrace = function(iterator) {
    return new Promise ((resolve,reject) => {
        try {
            let it = iterator[Symbol.iterator]();
            while(true) {
                let res = it.next();
                console.log(res);
                if(res.done) break;
                if(res.value instanceof Promise) {
                    res.value.then(resolve,reject);
                } else {
                    resolve(res.value)
                }
            }
        } catch (error) {
            reject(error)
        }
    }) 
}

第83题:实现 convert 方法,把原始 list 转换成树形结构,要求尽可能降低时间复杂度

先生成新结构map,用原先的结构与其比较,对原结构改造。

function convert(list) {
      const res = []
      const map = list.reduce((res, v) => (res[v.id] = v, res), {})
      for (const item of list) {
        if (item.parentId === 0) {
          res.push(item)
          continue
        }
        if (item.parentId in map) {
          const parent = map[item.parentId]
          parent.children = parent.children || []
          parent.children.push(item)
        }
      }
      return res
    }
    let list =[
        {id:1,name:'部门A',parentId:0},
        {id:2,name:'部门B',parentId:0},
        {id:3,name:'部门C',parentId:1},
        {id:4,name:'部门D',parentId:1},
        {id:5,name:'部门E',parentId:2},
        {id:6,name:'部门F',parentId:3},
        {id:7,name:'部门G',parentId:2},
        {id:8,name:'部门H',parentId:4}
    ];
    const result = convert(list);
    console.table(result);

第82题:在输入框中如何判断输入的是一个正确的网址

主要解析http,https

function isUrl(url) {
  const a = document.createElement('a')
  a.href = url
  return [
    /^(http|https):$/.test(a.protocol),
    a.host,
    a.pathname !== url,
    a.pathname !== `/${url}`,
  ].find(x => !x) === undefined
}

第81题:算法题之–两数之和

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。

你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

示例:

给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
  • 解析

(1). 直接遍历两次数组

//:时间复杂度为O(N*N)
find2Num([2,7,11,15],9);
function find2Num(arr,sum){
    if(arr == '' || arr.length == 0){
        return false;
    }
  let result = [];
    for(var i = 0; i < arr.length ; i++){
        for(var j = i + 1; j <arr.length; j++){
            if(arr[i] + arr[j] == sum){
              result.push(i);
              result.push(j);
            }
        }
    }
    console.log(result);
}

(2)

先将整型数组排序,排序之后定义两个指针left和right。

left指向已排序数组中的第一个元素,right指向已排序数组中的最后一个元素, 将 arr[left]+arr[right]与

给定的元素比较,若前者大,right–;若前者小,left++; 若相等,则找到了一对整数之和为指定值的元素。

//时间复杂度为O(NlogN)
function find2Num(arr,sum){
    if(arr == '' || arr.length == 0){
        return false;
    }
    var left = 0, right = arr.length -1,result = [];
    while(left < right){
        if(arr[left] + arr[right] > sum){
            right--;
        }
        else if(arr[left] + arr[right] < sum){
            left++;
        }
        else{
            console.log(arr[left] + " + " + arr[right] + " = " + sum); 
            result.push(left);
            result.push(right);
            left++;
            right--;
        }
    }
    console.log(result);
}

第80题:react-router 里的 标签和 标签有什么区别

  • <Link>react-router 里实现路由跳转的链接,一般配合 <Route> 使用,react-router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link> 的“跳转”行为只会触发相匹配的 <Route> 对应的页面内容更新,而不会刷新整个页面。

Link点击事件handleClick部分源码:

if (_this.props.onClick) _this.props.onClick(event);
      if (!event.defaultPrevented && // onClick prevented default
      event.button === 0 && // ignore everything but left clicks
      !_this.props.target && // let browser handle "target=_blank" etc.
      !isModifiedEvent(event) // ignore clicks with modifier keys
      ) {
          event.preventDefault();
          var history = _this.context.router.history;
          var _this$props = _this.props,
              replace = _this$props.replace,
              to = _this$props.to;
          if (replace) {
            history.replace(to);
          } else {
            history.push(to);
          }
        }

Link做了3件事情:

  1. 有onclick那就执行onclick
  2. click的时候阻止a标签默认事件(这样子点击<a href="/abc">123</a>就不会跳转和刷新页面)
  3. 再取得跳转href(即是to),用history(前端路由两种方式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面
  • <a> 标签就是普通的超链接了,用于从当前页面跳转到 href 指向的另一个页面(非锚点情况)。

如何禁掉 <a> 标签默认事件,禁掉之后如何实现跳转

  • 禁掉 a 标签的默认事件,可以在点击事件中执行 event.preventDefault();
  • 禁掉默认事件的 a 标签 可以使用 history.pushState() 来改变页面 url,这个方法还会触发页面的 hashchange 事件,Router 内部通过捕获监听这个事件来处理对应的跳转逻辑。


相关文章
|
1月前
|
前端开发 JavaScript 网络协议
前端最常见的JS面试题大全
【4月更文挑战第3天】前端最常见的JS面试题大全
54 5
|
4月前
|
设计模式 前端开发 算法
No210.精选前端面试题,享受每天的挑战和学习
No210.精选前端面试题,享受每天的挑战和学习
No210.精选前端面试题,享受每天的挑战和学习
|
4月前
|
消息中间件 缓存 前端开发
No209.精选前端面试题,享受每天的挑战和学习
No209.精选前端面试题,享受每天的挑战和学习
No209.精选前端面试题,享受每天的挑战和学习
|
2月前
|
存储 缓存 监控
2024年春招小红书前端实习面试题分享
春招已经拉开帷幕啦! 春招的拉开,意味着新一轮的求职大战已经打响,希望每位求职者都能充分准备,以最佳的状态迎接挑战,找到心仪的工作,开启职业生涯的新篇章。祝愿每位求职者都能收获满满,前程似锦!
80 3
|
2月前
|
前端开发 数据可视化 安全
2024金三银四必看前端面试题!简答版精品!
2024金三银四必看前端面试题!2w字精品!简答版 金三银四黄金期来了 想要跳槽的小伙伴快来看啊
98 3
|
3月前
|
存储 前端开发 JavaScript
前端面试:如何实现并发请求数量控制?
前端面试:如何实现并发请求数量控制?
100 0
|
9月前
|
Web App开发 前端开发 JavaScript
前端学习笔记202307学习笔记第五十七天-模拟面试笔记react-fiber解决了什么问题
前端学习笔记202307学习笔记第五十七天-模拟面试笔记react-fiber解决了什么问题
98 0
|
9月前
|
前端开发 定位技术
前端学习笔记202305学习笔记第二十三天-地图单线程配置
前端学习笔记202305学习笔记第二十三天-地图单线程配置
68 0
前端学习笔记202305学习笔记第二十三天-地图单线程配置
|
9月前
|
前端开发 API
前端学习笔记202307学习笔记第五十七天-模拟面试笔记react-react-redux的工作流程
前端学习笔记202307学习笔记第五十七天-模拟面试笔记react-react-redux的工作流程
55 0
|
9月前
|
前端开发
前端学习笔记202306学习笔记第五十一天-工厂模式4
前端学习笔记202306学习笔记第五十一天-工厂模式
34 0