首页 > 其他分享 >面试

面试

时间:2023-07-19 23:34:22浏览次数:29  
标签:function el const log 插槽 面试 console

JS

任务执行队列

宏任务同步任务 --> 微任务同步任务 --> 微任务异步任务 --> 宏任务异步任务

宏任务:setTimeout setInterval setImmediate (new Promise是宏任务同步任务)

先后顺序:先执行setTimeout 后执行 setImmediate

微任务promise.then() process.nextTick

先后顺序: promise.then() 后执行

 

new Promise(function (resolve) {
    console.log('1')
    resolve()
}).then(function () {
    console.log('2')
})

 

new Promise(function (resolve) {
    console.log('1')
    resolve()
}).then(function () {
    console.log('2')
})

  

new Promise(function (resolve) {
    console.log('1')
    resolve()
}).then(function () {
    console.log('2')
})
process.nextTick(function () {
    console.log('3');
})
setImmediate(() => {
    console.info('4')          
})
new Promise(function (resolve) {
    console.log('5');
    resolve();
}).then(function () {
    console.log('6')
})
setTimeout(function () {
    console.log('7');
    setImmediate(() => {
        console.info('8')
    })
    process.nextTick(function () {
        console.log('9');
    })
    new Promise(function (resolve) {
        console.log('10');
        resolve();
    }).then(function () {
        console.log('11')
    })
})
//    1   5   3   2   6   7   10   9   11  4   8

JS数据类型及深拷贝和浅拷贝,它们的实现方式

Number、String、Boolean、Null、undefined、Object、symbol、bigInt、function、Date

基本类型(单类型): String、Number、boolean、null、undefined。

引用类型:Object、function、Array、Date

简单数据类型存放到栈里面,存储时变量中存储的是值本身

复杂数据类型存放到堆里面,存储的仅仅是地址,真正的对象实例存放在堆空间中,若程序员不释放,由垃圾回收机制回收

浅拷贝共用一个引用地址,如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象

深拷贝会创建新的内存地址,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

方式和原数据是否指向同一对象第一层对象为基本数据类型数据包含子对象
赋值 改变会一同改变 改变会一同改变
浅拷贝 改变不会一同改变 改变会一同改变
深拷贝 改变不会一同改变 改变不会一同改变

浅拷贝实现的方式:

  • Object.assign()

  • 函数库lodash的_.clone方法

  • 展开运算符…

  • Array.prototype.concat()

  • Array.prototype.slice()

深拷贝的实现方式:

  • JSON.parse(JSON.stringify())

  • 函数库lodash的_.cloneDeep方法

  • jQuery.extend()方法 // $.extend(true,{},obj)

  • 手写递归方法,遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。

事件冒泡、事件捕获、事件委托

事件冒泡

在一个对象上触发某类事件(比如单击onclick事件)。这个事件会向这个对象的父级对象传播,从里到外,直至它被处理

text -> div -> body -> document -> window

事件捕获

click事件首先由 document 元素捕获,然后沿DOM树依次向下传播,直至到达实际的目标元素上

window-> document -> body -> div -> text

事件委托

把事件处理器添加到一个上级元素上,这样就避免了把事件处理器添加到多个子级元素上,主要得益于浏览器的事件冒泡机制,可以减少事件注册,节省内存

数组去重的方法(简单数组)

  • Set()

  • indexOf() + for

  • includes + for

  • 双重for、slice

  • Filter+indexOf

  • reduce

  • map

防抖节流及应用场景

防抖

防抖通常用来处理一些高频触发的事件,比如用户连续点击按钮、输入框频繁输入,避免频繁更新UI或者发送请求造成性能浪费

export const debounce = (fn: any, wait: num) => {
let timer: any = null
return () => {
if(timer) {
clearTimerout(timer)
}
timer = setTimeout(fn, wait)
}
}

节流

节流: 是指对于连续触发的事件,每隔一段时间内执行一次,节流里面涉及的时间主要指时间执行的间隔时间。比如 鼠标滚动、窗口resize、滚动条滑动 等操作。

export const throttle = (fn: any, wait: number) => {
let timer: any = null;
return function(...args) {
if(!timer) {
timer = setTimeout(function() => {
fn(...args);
timer = null
}, wait)
}
}
}

节流中,timer 为什么要 等于 null?

定时器是有返回值的,返回值是当前定时器的 ID号,clearTimeout(timer) ,会清除该定时器函数,但不会改变 id 值。如果我们不将 timer 设置为 null,而是直接设置新的定时器 ID,那么就会无法清除之前的定时器,会导致多个定时器同时执行,达不到节流的效果。所以在每次执行定时器函数之前,将 timer 设置为 null,确保下次事件触发时能正确清除之前的定时器。

es6新特性

1.块级作用域变量(let和const)使得变量只在当前作用域内有效

{
 let a = 10;
 const b = 1;
 console.log(a); // 10
 console.log(b); // 1
}
console.log(a); // ReferenceError: x is not defined
console.log(b); // ReferenceError: y is not defined

2.箭头函数

箭头函数是一种新的函数声明方式,可以更简洁地定义函数,且无需考虑this指向问题

const add = (x, y) => x + y;
console.log(add(1, 2)); // 3

let a = () => {
console.log(1);
}
a();// 1

3.模板字符串

模板字符串可以用来更方便地拼接字符串。

const name = "ES6";
console.log(`Hello, ${name}!`); // Hello, ES6!

4.解构赋值

解构赋值可以用来快速地从数组或对象中提取值并赋给变量

const arr = [1, 2, 3];
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3

const obj = {x: 1, y: 2, z: 3};
const {x, y, z} = obj;
console.log(x, y, z); // 1 2 3

5.默认参数

函数可以设置默认参数,当调用函数时没有传入该参数时,会使用默认值。

const greet = (name = "World") => `Hello, ${name}!`;
console.log(greet()); // Hello, World!
console.log(greet("Alice")); // Hello, Alice!

6.扩展运算符

扩展运算符可以用来将数组或对象展开成一系列参数。

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [...arr1, ...arr2];
console.log(arr3); // [1, 2, 3, 4, 5, 6]

const obj1 = {x: 1, y: 2};
const obj2 = {z: 3};
const obj3 = {...obj1, ...obj2};
console.log(obj3); // {x: 1, y: 2, z: 3}

7.类和继承

ES6引入了class关键字来定义类和继承关系。

class Animal {
 constructor(name) {
   this.name = name;
}
 speak() {
   console.log(`${this.name} makes a noise.`);
}
}

class Dog extends Animal {
 speak() {
   console.log(`${this.name} barks.`);
}
}

const d = new Dog("Rex");
d.speak(); // Rex barks.

8.Promise

Promise是一种新的异步编程方式,可以更方便地处理异步操作,解决地狱回调非常好用

const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
wait(1000).then(() => console.log("1 second passed."));

resolved 成功后调用 rejected 失败后调用 Promise的状态只有这 2 种, 且一个 promise 对象只能改变一次

HTML、CSS

常见的块元素和行内元素

块元素:<h1>~<h6>、<p>、<div>、<ul>、<ol>、<li>

行内元素:<a>、<strong>、<b>、<em>、<i>、<del>、<span>

Sass、Less变量怎么定义

class中嵌套class,从而减少代码的重复,Less可以向上/向下解析;Sass只能向上解析

//Less定义变量@: 
@color: #4D926F;
header {
color: @color;
}
//Sass定义变量$:
$blue : #1875e7; 
div {
color : $blue;
}

如何让元素水平垂直居中

  • Flex

  • Transform的translate

  • Position fixed或absolute

  • 子元素设置display: inline-block,父元素设置text-align: center且line-height等于height

样式优先级

!important > 内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器 > 通配选择器

css引起的重绘和回流

回流必将引起重绘,而重绘不一定会引起回流

Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:colorbackground-colorvisibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

VUE

vue生命周期

beforeCreate 、created(数据初始化) 、beforeMount 、mounted 、beforeUpdate 、updated 、beforeDestroy 、destroyed

被缓存的页面还有:activated、deactivated

说说slot插槽、具名插槽、作用域插槽

插槽就是子组件中的提供给父组件使用的一个占位符,在子组件中用< slot>< /slot > 表示。

父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的< slot>< /slot >标签。

默认插槽

我们定义两个组件:parent.vuechild.vue ,在parent组件中引入child组件

//parent.vue
<child>
 world
</child>

//child.vue
<template>
 <div>
   hello <slot>你好</slot>
 </div>
</template>

这样,组件在渲染的时候,<slot></slot>就会被渲染为:hello world,如果在<child></child>中没有放任何内容,就会渲染出子组件的默认内容:hello 你好

具名插槽

有时候,我们在一个组件中需要使用多个插槽,那么为了区分它们,就可以给每个插槽命名,这就是具名插槽。

slot元素有一个特殊的attribute:name,他可以用来指定额外的插槽:

// 子组件
<div class="container">
 <header>
   <slot name="header"></slot>
 </header>
 <main>
   <slot></slot>
 </main>
 <footer>
   <slot name="footer"></slot>
 </footer>
</div>

// 父组件
<base-layout>
 <template v-slot:header>
   <h1>header</h1>
 </template>

 <p>content</p>


 <template v-slot:footer>
   <p>footer</p>
 </template>
</base-layout>

使用v-slot指令来指定该部分要渲染的插槽的名字,这样就可以将三部分内容渲染到相应的位置了。不带name的插槽,会有一个默认的名字default。如果想要更明确一些,可以将它的name设置为default:

<template v-slot:default>
<p> content </p>
</template>

注意:v-slot指令只能添加在template标签上

作用域插槽

一般情况下,是父组件使用查操过程中传入一些内容来决定插槽的内容,他可以访问到父组件中定义的属性,但是不能访问子组件中的数据。如果我们需要子组件以供一些数据,那么就可以使用作用域插槽来解决。

在子组件中动态绑定需要传入的数据:

<slot :data="data"></slot>

export default {
 data () {
   return {
     data: {
       username: 'hello'
    }
  }
}
}

在父组件中访问子元素的数据:

<div>
 <test v-slot:default="slotProps">
  {{slotProps.data.username}}
 </test>
</div>

// 也可以写成下面这样
<div>
 <test v-slot:"slotProps">
  {{slotProps.data.username}}
 </test>
</div>

需要注意: 如果有多个插槽,只要将default改为对应的插槽名称即可。

插槽的解构

作用域插槽的内部工作原理是将插槽的内容包含在一个带有单个参数的函数里,所以slot 的值可以接收任何有效的可以出现在函数定义的参数位置上的 JavaScript 表达式。

原本这样写的代码:

<div>
 <test v-slot:"slotProps">
  {{slotProps.data.username}}
 </test>
</div>

可以改成:

<div>
 <test v-slot:{data}>
  {{data.username}}
 </test>
</div>

这样写代码就更加的简洁。

动态插槽名

在Vue2.6中引入了动态插槽名,动态指令参数使用在v-slot上,来定义动态的插槽名:

<base-layout>
 <template v-slot:[dynamicSlotName]>
   ...
 </template>
</base-layout>

具名插槽的缩写

在Vue2.6中,v-slot可以缩写为#

<template v-slot:header>
   <h1>header</h1>
</template>

// 缩写后
<template #header>
   <h1>header</h1>
</template>

需要注意,该缩写需要在其有名字的情况下才能使用,下面的情况会弹出警告:

<test #:{data}>
  {{data.username}}
</test>

如果非要使用缩写的话,必须给他指定一个插槽名:

<test #default:{data}>
  {{data.username}}
</test>

简要介绍Vuex

包含state、mutations、actions、getters、modules

Vuex为什么要有异步更新action

mutation内部必须是同步函数,因为数据的复用,异步会导致内部状态难以追踪,但有些操作又是异步更新的,所以需要通过action提交dispatch给mutation修改state

store.commit()是否可以带第三个参数

不可以,多个参数需要用数组或对象的方式传递

自定义指令

全局注册

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
 // 当被绑定的元素插入到 DOM 中时……
 inserted: function (el) {
   // 聚焦元素
   el.focus()  // 页面加载完成之后自动让输入框获取到焦点的小功能
}
})

局部注册

和data平级
directives: {
 focus: {
   // 指令的定义
   inserted: function (el) {
     el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
  }
}
}

应用场景

输入框防抖

抖这种情况设置一个v-debounce自定义指令来实现

// 1.设置v-debounce自定义指令
Vue.directive('debounce', {
 bind: (el, binding) => {
   let debounceTime = binding.value; // 防抖时间
   if (!debounceTime) { // 用户若不设置防抖时间,则默认2s
     debounceTime = 2000;
  }
   let cbFun;
   el.addEventListener('click', event => {
     if (!cbFun) { // 第一次执行
       cbFun = setTimeout(() => {
         cbFun = null;
      }, debounceTime);
    } else {
       // 阻止事件冒泡并且阻止该元素上同事件类型的监听器被触发
       event && event.stopImmediatePropagation();
    }
  }, true);
},
});
// 2.为button标签设置v-debounce自定义指令

<button @click="sayHello" v-debounce>提交</button>

图片懒加载

设置一个v-lazy自定义指令完成图片懒加载

const LazyLoad = {
   // install方法
   install(Vue,options){
   // 代替图片的loading图
       let defaultSrc = options.default;
       Vue.directive('lazy',{
           bind(el,binding){
               LazyLoad.init(el,binding.value,defaultSrc);
          },
           inserted(el){
               // 兼容处理
               if('IntersectionObserver' in window){
                   LazyLoad.observe(el);
              }else{
                   LazyLoad.listenerScroll(el);
              }
          },
      })
  },
   // 初始化
   init(el,val,def){
       // data-src 储存真实src
       el.setAttribute('data-src',val);
       // 设置src为loading图
       el.setAttribute('src',def);
  },
   // 利用IntersectionObserver监听el
   observe(el){
       let io = new IntersectionObserver(entries => {
           let realSrc = el.dataset.src;
           if(entries[0].isIntersecting){
               if(realSrc){
                   el.src = realSrc;
                   el.removeAttribute('data-src');
              }
          }
      });
       io.observe(el);
  },
   // 监听scroll事件
   listenerScroll(el){
       let handler = LazyLoad.throttle(LazyLoad.load,300);
       LazyLoad.load(el);
       window.addEventListener('scroll',() => {
           handler(el);
      });
  },
   // 加载真实图片
   load(el){
       let windowHeight = document.documentElement.clientHeight
       let elTop = el.getBoundingClientRect().top;
       let elBtm = el.getBoundingClientRect().bottom;
       let realSrc = el.dataset.src;
       if(elTop - windowHeight<0&&elBtm > 0){
           if(realSrc){
               el.src = realSrc;
               el.removeAttribute('data-src');
          }
      }
  },
   // 节流
   throttle(fn,delay){
       let timer;
       let prevTime;
       return function(...args){
           let currTime = Date.now();
           let context = this;
           if(!prevTime) prevTime = currTime;
           clearTimeout(timer);
           if(currTime - prevTime > delay){
               prevTime = currTime;
               fn.apply(context,args);
               clearTimeout(timer);
               return;
          }
           timer = setTimeout(function(){
               prevTime = Date.now();
               timer = null;
               fn.apply(context,args);
          },delay);
      }
  }
}
export default LazyLoad;

一键 Copy的功能

import { Message } from 'ant-design-vue';
const vCopy = { //
 /*
  bind 钩子函数,第一次绑定时调用,可以在这里做初始化设置
  el: 作用的 dom 对象
  value: 传给指令的值,也就是我们要 copy 的值
*/
 bind(el, { value }) {
   el.$value = value; // 用一个全局属性来存传进来的值,因为这个值在别的钩子函数里还会用到
   el.handler = () => {
     if (!el.$value) {
     // 值为空的时候,给出提示
       Message.warning('无复制内容');
       return;
    }
     // 动态创建 textarea 标签
     const textarea = document.createElement('textarea');
     // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
     textarea.readOnly = 'readonly';
     textarea.style.position = 'absolute';
     textarea.style.left = '-9999px';
     // 将要 copy 的值赋给 textarea 标签的 value 属性
     textarea.value = el.$value;
     // 将 textarea 插入到 body 中
     document.body.appendChild(textarea);
     // 选中值并复制
     textarea.select();
     // textarea.setSelectionRange(0, textarea.value.length);
     const result = document.execCommand('Copy');
     if (result) {
       Message.success('复制成功');
    }
     document.body.removeChild(textarea);
  };
   // 绑定点击事件,就是所谓的一键 copy 啦
   el.addEventListener('click', el.handler);
},
 // 当传进来的值更新的时候触发
 componentUpdated(el, { value }) {
   el.$value = value;
},
 // 指令与元素解绑的时候,移除事件绑定
 unbind(el) {
   el.removeEventListener('click', el.handler);
},
};
export default vCopy;

关于自定义指令还有很多应用场景,如:拖拽指令、页面水印、权限校验等等应用场景

跨域

协议http、域名192.168.43.122 、端口8080其中有一个不同即为跨域,正常不允许通信

跨域解决方案

  1. 通过jsonp跨域

  2. document.domain + iframe跨域

  3. location.hash + iframe

  4. window.name + iframe跨域

  5. postMessage跨域

  6. 跨域资源共享(CORS)

  7. nginx代理跨域

  8. nodejs中间件代理跨域

  9. WebSocket协议跨域

v-if和v-show的区别

v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在

v-if显示隐藏是将dom元素整个添加或删除

V-if和v-for能不能同时使用,两者的优先级

不能。v-for的优先级是高于v-if,即每一次v-if都需要遍历整个数组,浪费性能

$nextTick()的使用场景

异步更新DOM、异步获取数据、异步更新数据

hash及history路由实现原理

Hash路由

Hash可以改变 url ,但是不会刷新页面, 这并不算是一次 http 请求,所以这种模式不利于 SEO 优化

hash 通过 window.onhashchange 的方式,来监听 hash 的改变,借此实现无刷新跳转的功能

hash 永远不会提交到 server 端(可以理解为只在前端自生自灭)

history路由

更新页面而不发送 http 请求

使用 history 模式时,需要通过服务端来允许地址可访问

新的 url 可以是与当前 url 同源的任意 url ,也可以是与当前 url 一样的地址

通过 history.state ,添加任意类型的数据到记录中。

通过 pushState 、 replaceState 来实现无刷新跳转的功能。

使用场景:

to B 的系统推荐用 hash ,相对简单且容易使用,且因为 hash 对 url 规范不敏感;

to C 的系统,可以考虑选择 H5 history ,但是需要服务端支持

cookie、SessionStorage和localStorage的区别

存储的时间有效期不同

  1. cookie的有效期是可以设置的,默认的情况下是关闭浏览器后失效document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00

  2. GMT"sessionStorage的有效期是仅保持在当前页面,关闭当前会话页或者浏览器后就会失效

  3. localStorage的有效期是在不进行手动删除的情况下是一直有效的

存储的大小不同

  1. cookie的存储是4kb左右,存储量较小,一般页面最多存储20条左右信息

  2. localStorage和sessionStorage的存储容量是5Mb(官方介绍,可能和浏览器有部分差异性)

与服务端的通信

  1. cookie会参与到与服务端的通信中,一般会携带在http请求的头部中,例如一些关键密匙验证等。

  2. localStorage和sessionStorage是单纯的前端存储,不参与与服务端的通信

vue2和vue3的区别

vue2 的响应式原理是利⽤es5 的⼀个 API ,Object.defineProperty()对数据进⾏劫持结合发布订阅模式的⽅式来实现的。

vue3 中使⽤了 es6 的 proxy API 对数据代理,通过 reactive() 函数给每⼀个对象都包⼀层 Proxy,通过 Proxy 监听属性的变化,从⽽ 实现对数据的监控。

这⾥是引相⽐于vue2版本,使⽤proxy的优势如下

  1. defineProperty只能监听某个属性,不能对全对象监听

  2. 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)

  3. 可以监听数组,不⽤再去单独的对数组做特异性操作,通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。 (vue3.x可以检测到数组内部数据的变化)

 

标签:function,el,const,log,插槽,面试,console
From: https://www.cnblogs.com/JC30705/p/17567072.html

相关文章

  • tcp/ip 面试问题总结
    tcp三次握手为什么三次典型场景:1客户端对服务器说:大哥你好这是我的窗口大小,以及初始序号2服务器对客户端说:好的老弟,这是我的窗口大小和初始序号3客户端对服务器说:好的大哥......
  • 《对线面试官》| 高频 Python 面试题 pt.1
    1.聊聊python中的值传递和引用传递吧值传递:值传递意味着在函数调用时,将实际参数的值复制一份传递给函数的形式参数在函数内部,形式参数将作为局部变量使用,对形式参数的修改不会影响原始变量的值引用传递引用传递意味着在函数调用时,将实际参数的引用(内存地址)传递给函数的......
  • 一个面试题:计算时间偏移量,怎么设计你的程序?
    计算时间偏移量,例如,计算当前时间向前偏移30秒的时间,我们利用java.util.Calendar很容易实现。Calendarcal=Calendar.getInstance();cal.setTime(newDate());cal.add(Calendar.SECOND,-30);System.out.println(cal.getTime()); 我曾经在进行面试的......
  • mysql字符串类型面试题
    mysql有哪些字符串类型?MySQL中有以下几种常见的字符串类型:CHAR:固定长度字符串,最多可以存储255个字符。VARCHAR:可变长度字符串,最多可以存储65535个字符。TEXT:用于存储较长的文本字符串,最多可以存储65535个字符。TINYTEXT:用于存储非常短的文本字符串,最多可以......
  • 面试准备
    58分布式事务***2PC,3PC,一致性哈希,RaftRaft算法 是分布式系统开发首选的共识算法,Raft算法是通过一切以领导者为准的方式,实现一系列值的共识和各节点日志的一致。分三种角色:跟随者(Follower):普通群众,默默接收和来自领导者的消息,当领导者心跳信息超时的时候,就主动站出来,推......
  • hvv面试常问的几个漏洞
    hvv面试常问的几个漏洞一、Struts21.1简介Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts2是Struts的下一代产品,是在struts1和WebWork的技术基础上进行了合并的全新......
  • 面试官:一个 SpringBoot 项目能处理多少请求?(小心有坑)
    你好呀,我是歪歪。这篇文章带大家盘一个读者遇到的面试题哈。根据读者转述,面试官的原问题就是:一个SpringBoot项目能同时处理多少请求?不知道你听到这个问题之后的第一反应是什么。我大概知道他要问的是哪个方向,但是对于这种只有一句话的面试题,我的第一反应是:会不会有坑?所以并......
  • 2023-07面试题
    1,spring中有哪些方式可以把bean注册进ioc容器?①使用xml的方式声明bean的定义,spring容器在启动时会加载和解析这个xml,把bean装载进ioc容器中。②使用@ComponentScan注解去扫描声明了@Controller@Service@Repository@Component注解的类,然后把这些类加载进ioc容器。③使用@Conf......
  • 字典,元组,元组内置方法、相关面试题 、 集合的内置方法 、字符编码 、文件操作 、函数
    字典的内置方法1.定义方式 d={'usernamne':"kevin"}#定义空字典d={}info=dict(username='kevin',age=18)#{'username':'kevin','age':18} print(info) #dic={#'name':�......
  • java真实业务场景面试题
    Java真实业务场景面试题Java是一种广泛使用的面向对象编程语言,在各个行业中都有广泛的应用。因此,在Java开发人员的面试中,经常会涉及到一些与真实业务场景相关的问题。本文将为您介绍一些常见的Java真实业务场景面试题,并提供相应的代码示例。1.数据库连接池数据库连接池是Java中......