请实现一个 add 函数,满足以下功能

add(1);             // 1
add(1)(2);      // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
function add(...args) {// 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值let fn = function(...newArgs) {return add.apply(null, args.concat(newArgs))}// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回fn.toString = function() {return args.reduce((total,curr)=> total + curr)}return fn
}

考点:

  • 使用闭包, 同时要对JavaScript 的作用域链(原型链)有深入的理解
  • 重写函数的 toSting()方法
// 测试,调用toString方法触发求值add(1).toString();             // 1
add(1)(2).toString();      // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(1, 2, 3).toString(); // 6

判断是否是电话号码

function isPhone(tel) {var regx = /^1[34578]\d{9}$/;return regx.test(tel);
}

解析 URL Params 为对象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果
{ user: 'anonymous',id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型city: '北京', // 中文需解码enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中let paramsObj = {};// 将 params 存到对象中paramsArr.forEach(param => {if (/=/.test(param)) { // 处理有 value 的参数let [key, val] = param.split('='); // 分割 key 和 valueval = decodeURIComponent(val); // 解码val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值paramsObj[key] = [].concat(paramsObj[key], val);} else { // 如果对象没有这个 key,创建 key 并设置值paramsObj[key] = val;}} else { // 处理没有 value 的参数paramsObj[param] = true;}})return paramsObj;
}

数组去重

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
方法一:利用Set
const res1 = Array.from(new Set(arr));
方法二:两层for循环+splice
const unique1 = arr => {let len = arr.length;for (let i = 0; i < len; i++) {for (let j = i + 1; j < len; j++) {if (arr[i] === arr[j]) {arr.splice(j, 1);// 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能len--;j--;}}}return arr;
}
方法三:利用indexOf
const unique2 = arr => {const res = [];for (let i = 0; i < arr.length; i++) {if (res.indexOf(arr[i]) === -1) res.push(arr[i]);}return res;
}

当然也可以用include、filter,思路大同小异。

方法四:利用include
const unique3 = arr => {const res = [];for (let i = 0; i < arr.length; i++) {if (!res.includes(arr[i])) res.push(arr[i]);}return res;
}
方法五:利用filter
const unique4 = arr => {return arr.filter((item, index) => {return arr.indexOf(item) === index;});
}
方法六:利用Map
const unique5 = arr => {const map = new Map();const res = [];for (let i = 0; i < arr.length; i++) {if (!map.has(arr[i])) {map.set(arr[i], true)res.push(arr[i]);}}return res;
}

Function.prototype.bind

Function.prototype.bind = function(context, ...args) {if (typeof this !== 'function') {throw new Error("Type Error");}// 保存this的值var self = this;return function F() {// 考虑new的情况if(this instanceof F) {return new self(...args, ...arguments)}return self.apply(context, [...args, ...arguments])}
}

深拷贝

递归的完整版本(考虑到了Symbol属性):

const cloneDeep1 = (target, hash = new WeakMap()) => {// 对于传入参数处理if (typeof target !== 'object' || target === null) {return target;}// 哈希表中存在直接返回if (hash.has(target)) return hash.get(target);const cloneTarget = Array.isArray(target) ? [] : {};hash.set(target, cloneTarget);// 针对Symbol属性const symKeys = Object.getOwnPropertySymbols(target);if (symKeys.length) {symKeys.forEach(symKey => {if (typeof target[symKey] === 'object' && target[symKey] !== null) {cloneTarget[symKey] = cloneDeep1(target[symKey]);} else {cloneTarget[symKey] = target[symKey];}})}for (const i in target) {if (Object.prototype.hasOwnProperty.call(target, i)) {cloneTarget[i] =typeof target[i] === 'object' && target[i] !== null? cloneDeep1(target[i], hash): target[i];}}return cloneTarget;
}

参考 前端进阶面试题详细解答

实现节流函数(throttle)

防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

// 手写简化版

// 节流函数
const throttle = (fn, delay = 500) => {let flag = true;return (...args) => {if (!flag) return;flag = false;setTimeout(() => {fn.apply(this, args);flag = true;}, delay);};
};

适用场景:

  • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
  • 缩放场景:监控浏览器resize
  • 动画场景:避免短时间内多次触发动画引起性能问题

手写 bind 函数

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 保存当前函数的引用,获取其余传入参数值。
  3. 创建一个函数返回
  4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。
// bind 函数实现
Function.prototype.myBind = function(context) {// 判断调用对象是否为函数if (typeof this !== "function") {throw new TypeError("Error");}// 获取参数var args = [...arguments].slice(1),fn = this;return function Fn() {// 根据调用方式,传入不同绑定值return fn.apply(this instanceof Fn ? this : context,args.concat(...arguments));};
};

实现数组的filter方法

Array.prototype._filter = function(fn) {if (typeof fn !== "function") {throw Error('参数必须是一个函数');}const res = [];for (let i = 0, len = this.length; i < len; i++) {fn(this[i]) && res.push(this[i]);}return res;
}

实现Promise

var PromisePolyfill = (function () {// 和reject不同的是resolve需要尝试展开thenable对象function tryToResolve (value) {if (this === value) {// 主要是防止下面这种情况// let y = new Promise(res => setTimeout(res(y)))throw TypeError('Chaining cycle detected for promise!')}// 根据规范2.32以及2.33 对对象或者函数尝试展开// 保证S6之前的 polyfill 也能和ES6的原生promise混用if (value !== null &&(typeof value === 'object' || typeof value === 'function')) {try {// 这里记录这次then的值同时要被try包裹// 主要原因是 then 可能是一个getter, 也也就是说//   1. value.then可能报错//   2. value.then可能产生副作用(例如多次执行可能结果不同)var then = value.then// 另一方面, 由于无法保证 then 确实会像预期的那样只调用一个onFullfilled / onRejected// 所以增加了一个flag来防止resolveOrReject被多次调用var thenAlreadyCalledOrThrow = falseif (typeof then === 'function') {// 是thenable 那么尝试展开// 并且在该thenable状态改变之前this对象的状态不变then.bind(value)(// onFullfilledfunction (value2) {if (thenAlreadyCalledOrThrow) returnthenAlreadyCalledOrThrow = truetryToResolve.bind(this, value2)()}.bind(this),// onRejectedfunction (reason2) {if (thenAlreadyCalledOrThrow) returnthenAlreadyCalledOrThrow = trueresolveOrReject.bind(this, 'rejected', reason2)()}.bind(this))} else {// 拥有then 但是then不是一个函数 所以也不是thenableresolveOrReject.bind(this, 'resolved', value)()}} catch (e) {if (thenAlreadyCalledOrThrow) returnthenAlreadyCalledOrThrow = trueresolveOrReject.bind(this, 'rejected', e)()}} else {// 基本类型 直接返回resolveOrReject.bind(this, 'resolved', value)()}}function resolveOrReject (status, data) {if (this.status !== 'pending') returnthis.status = statusthis.data = dataif (status === 'resolved') {for (var i = 0; i < this.resolveList.length; ++i) {this.resolveList[i]()}} else {for (i = 0; i < this.rejectList.length; ++i) {this.rejectList[i]()}}}function Promise (executor) {if (!(this instanceof Promise)) {throw Error('Promise can not be called without new !')}if (typeof executor !== 'function') {// 非标准 但与Chrome谷歌保持一致throw TypeError('Promise resolver ' + executor + ' is not a function')}this.status = 'pending'this.resolveList = []this.rejectList = []try {executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))} catch (e) {resolveOrReject.bind(this, 'rejected', e)()}}Promise.prototype.then = function (onFullfilled, onRejected) {// 返回值穿透以及错误穿透, 注意错误穿透用的是throw而不是return,否则的话// 这个then返回的promise状态将变成resolved即接下来的then中的onFullfilled// 会被调用, 然而我们想要调用的是onRejectedif (typeof onFullfilled !== 'function') {onFullfilled = function (data) {return data}}if (typeof onRejected !== 'function') {onRejected = function (reason) {throw reason}}var executor = function (resolve, reject) {setTimeout(function () {try {// 拿到对应的handle函数处理this.data// 并以此为依据解析这个新的Promisevar value = this.status === 'resolved'? onFullfilled(this.data): onRejected(this.data)resolve(value)} catch (e) {reject(e)}}.bind(this))}// then 接受两个函数返回一个新的Promise// then 自身的执行永远异步与onFullfilled/onRejected的执行if (this.status !== 'pending') {return new Promise(executor.bind(this))} else {// pendingreturn new Promise(function (resolve, reject) {this.resolveList.push(executor.bind(this, resolve, reject))this.rejectList.push(executor.bind(this, resolve, reject))}.bind(this))}}// for prmise A+ testPromise.deferred = Promise.defer = function () {var dfd = {}dfd.promise = new Promise(function (resolve, reject) {dfd.resolve = resolvedfd.reject = reject})return dfd}// for prmise A+ testif (typeof module !== 'undefined') {module.exports = Promise}return Promise
})()PromisePolyfill.all = function (promises) {return new Promise((resolve, reject) => {const result = []let cnt = 0for (let i = 0; i < promises.length; ++i) {promises[i].then(value => {cnt++result[i] = valueif (cnt === promises.length) resolve(result)}, reject)}})
}PromisePolyfill.race = function (promises) {return new Promise((resolve, reject) => {for (let i = 0; i < promises.length; ++i) {promises[i].then(resolve, reject)}})
}

滚动加载

原理就是监听页面滚动事件,分析clientHeightscrollTopscrollHeight三者的属性关系。

window.addEventListener('scroll', function() {const clientHeight = document.documentElement.clientHeight;const scrollTop = document.documentElement.scrollTop;const scrollHeight = document.documentElement.scrollHeight;if (clientHeight + scrollTop >= scrollHeight) {// 检测到滚动至页面底部,进行后续操作// ...}
}, false);

判断对象是否存在循环引用

循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON.

下面方法可以用来判断一个对象中是否已存在循环引用:

const isCycleObject = (obj,parent) => {const parentArr = parent || [obj];for(let i in obj) {if(typeof obj[i] === 'object') {let flag = false;parentArr.forEach((pObj) => {if(pObj === obj[i]){flag = true;}})if(flag) return true;flag = isCycleObject(obj[i],[...parentArr,obj[i]]);if(flag) return true;}}return false;
}const a = 1;
const b = {a};
const c = {b};
const o = {d:{a:3},c}
o.c.b.aa = a;console.log(isCycleObject(o)

查找有序二维数组的目标值:

var findNumberIn2DArray = function(matrix, target) {if (matrix == null || matrix.length == 0) {return false;}let row = 0;let column = matrix[0].length - 1;while (row < matrix.length && column >= 0) {if (matrix[row][column] == target) {return true;} else if (matrix[row][column] > target) {column--;} else {row++;}}return false;
};

二维数组斜向打印:

function printMatrix(arr){let m = arr.length, n = arr[0].lengthlet res = []// 左上角,从0 到 n - 1 列进行打印for (let k = 0; k < n; k++) {for (let i = 0, j = k; i < m && j >= 0; i++, j--) {res.push(arr[i][j]);}}// 右下角,从1 到 n - 1 行进行打印for (let k = 1; k < m; k++) {for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {res.push(arr[i][j]);}}return res
}

实现日期格式化函数

输入:

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{var day = dateInput.getDate() var month = dateInput.getMonth() + 1  var year = dateInput.getFullYear()   format = format.replace(/yyyy/, year)format = format.replace(/MM/,month)format = format.replace(/dd/,day)return format
}

字符串出现的不重复最长长度

用一个滑动窗口装没有重复的字符,枚举字符记录最大值即可。用 map 维护字符的索引,遇到相同的字符,把左边界移动过去即可。挪动的过程中记录最大长度:

var lengthOfLongestSubstring = function (s) {let map = new Map();let i = -1let res = 0let n = s.lengthfor (let j = 0; j < n; j++) {if (map.has(s[j])) {i = Math.max(i, map.get(s[j]))}res = Math.max(res, j - i)map.set(s[j], j)}return res
};

手写 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";function MyPromise(fn) {// 保存初始化状态var self = this;// 初始化状态this.state = PENDING;// 用于保存 resolve 或者 rejected 传入的值this.value = null;// 用于保存 resolve 的回调函数this.resolvedCallbacks = [];// 用于保存 reject 的回调函数this.rejectedCallbacks = [];// 状态转变为 resolved 方法function resolve(value) {// 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变if (value instanceof MyPromise) {return value.then(resolve, reject);}// 保证代码的执行顺序为本轮事件循环的末尾setTimeout(() => {// 只有状态为 pending 时才能转变,if (self.state === PENDING) {// 修改状态self.state = RESOLVED;// 设置传入的值self.value = value;// 执行回调函数self.resolvedCallbacks.forEach(callback => {callback(value);});}}, 0);}// 状态转变为 rejected 方法function reject(value) {// 保证代码的执行顺序为本轮事件循环的末尾setTimeout(() => {// 只有状态为 pending 时才能转变if (self.state === PENDING) {// 修改状态self.state = REJECTED;// 设置传入的值self.value = value;// 执行回调函数self.rejectedCallbacks.forEach(callback => {callback(value);});}}, 0);}// 将两个方法传入函数执行try {fn(resolve, reject);} catch (e) {// 遇到错误时,捕获错误,执行 reject 函数reject(e);}
}MyPromise.prototype.then = function(onResolved, onRejected) {// 首先判断两个参数是否为函数类型,因为这两个参数是可选参数onResolved =typeof onResolved === "function"? onResolved: function(value) {return value;};onRejected =typeof onRejected === "function"? onRejected: function(error) {throw error;};// 如果是等待状态,则将函数加入对应列表中if (this.state === PENDING) {this.resolvedCallbacks.push(onResolved);this.rejectedCallbacks.push(onRejected);}// 如果状态已经凝固,则直接执行对应状态的函数if (this.state === RESOLVED) {onResolved(this.value);}if (this.state === REJECTED) {onRejected(this.value);}
};

将js对象转化为树形结构

// 转换前:
source = [{id: 1,pid: 0,name: 'body'}, {id: 2,pid: 1,name: 'title'}, {id: 3,pid: 2,name: 'div'}]
// 转换为:
tree = [{id: 1,pid: 0,name: 'body',children: [{id: 2,pid: 1,name: 'title',children: [{id: 3,pid: 1,name: 'div'}]}}]

代码实现:

function jsonToTree(data) {// 初始化结果数组,并判断输入数据的格式let result = []if(!Array.isArray(data)) {return result}// 使用map,将当前对象的id与当前对象对应存储起来let map = {};data.forEach(item => {map[item.id] = item;});// data.forEach(item => {let parent = map[item.pid];if(parent) {(parent.children || (parent.children = [])).push(item);} else {result.push(item);}});return result;
}

手写节流函数

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

// 函数节流的实现;
function throttle(fn, delay) {let curTime = Date.now();return function() {let context = this,args = arguments,nowTime = Date.now();// 如果两次时间间隔超过了指定时间,则执行函数。if (nowTime - curTime >= delay) {curTime = Date.now();return fn.apply(context, args);}};
}

Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not defined');}if (typeof callback !== 'function') {throw new TypeError(callback + ' is not a function');}const res = [];// 同理const O = Object(this);const len = O.length >>> 0;for (let i = 0; i < len; i++) {if (i in O) {// 调用回调函数并传入新数组res[i] = callback.call(thisArg, O[i], i, this);}}return res;
}

实现apply方法

apply原理与call很相似,不多赘述

// 模拟 apply
Function.prototype.myapply = function(context, arr) {var context = Object(context) || window;context.fn = this;var result;if (!arr) {result = context.fn();} else {var args = [];for (var i = 0, len = arr.length; i < len; i++) {args.push("arr[" + i + "]");}result = eval("context.fn(" + args + ")");}delete context.fn;return result;
};

深克隆(deepclone)

简单版:

const newObj = JSON.parse(JSON.stringify(oldObj));

局限性:

  1. 他无法实现对函数 、RegExp等特殊对象的克隆

  2. 会抛弃对象的constructor,所有的构造函数会指向Object

  3. 对象有循环引用,会报错

面试版:

/*** deep clone* @param  {[type]} parent object 需要进行克隆的对象* @return {[type]}        深克隆后的对象*/
const clone = parent => {// 判断类型const isType = (obj, type) => {if (typeof obj !== "object") return false;const typeString = Object.prototype.toString.call(obj);let flag;switch (type) {case "Array":flag = typeString === "[object Array]";break;case "Date":flag = typeString === "[object Date]";break;case "RegExp":flag = typeString === "[object RegExp]";break;default:flag = false;}return flag;};// 处理正则const getRegExp = re => {var flags = "";if (re.global) flags += "g";if (re.ignoreCase) flags += "i";if (re.multiline) flags += "m";return flags;};// 维护两个储存循环引用的数组const parents = [];const children = [];const _clone = parent => {if (parent === null) return null;if (typeof parent !== "object") return parent;let child, proto;if (isType(parent, "Array")) {// 对数组做特殊处理child = [];} else if (isType(parent, "RegExp")) {// 对正则对象做特殊处理child = new RegExp(parent.source, getRegExp(parent));if (parent.lastIndex) child.lastIndex = parent.lastIndex;} else if (isType(parent, "Date")) {// 对Date对象做特殊处理child = new Date(parent.getTime());} else {// 处理对象原型proto = Object.getPrototypeOf(parent);// 利用Object.create切断原型链child = Object.create(proto);}// 处理循环引用const index = parents.indexOf(parent);if (index != -1) {// 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象return children[index];}parents.push(parent);children.push(child);for (let i in parent) {// 递归child[i] = _clone(parent[i]);}return child;};return _clone(parent);
};

局限性:

  1. 一些特殊情况没有处理: 例如Buffer对象、Promise、Set、Map
  2. 另外对于确保没有循环引用的对象,我们可以省去对循环引用的特殊处理,因为这很消耗时间

原理详解实现深克隆

百度前端必会手写面试题整理相关推荐

  1. 高级前端必会手写面试题及答案

    循环打印红黄绿 下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次:如何让三个灯不断交替重复亮灯? 三个亮灯函数: functi ...

  2. 社招前端必会手写面试题集锦

    查找字符串中出现最多的字符和个数 例: abbcccddddd -> 字符最多的是d,出现了5次 let str = "abcabcabcbbccccc"; let num ...

  3. 前端常考手写面试题汇总

    实现一个函数判断数据类型 function getType(obj) {if (obj === null) return String(obj);return typeof obj === 'obje ...

  4. 高级前端常考手写面试题(必备)

    实现单例模式 核心要点: 用闭包和Proxy属性拦截 function proxy(func) {let instance;let handler = {constructor(target, arg ...

  5. 社招前端常考手写面试题总结

    手写 Promise const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTE ...

  6. 前端为什么有的接口明明是成功回调却执行了.catch失败回调_前端进阶高薪必看-手写源码篇(高频技术点)...

    前言 此系列作为笔者之前发过的前端高频面试整理的补充 会比较偏向中高前端面试问题 当然大家都是从新手一路走过来的 感兴趣的朋友们都可以看哈 初衷 我相信不少同学面试的时候最怕的一个环节就是手写代码 大 ...

  7. all方法 手写promise_前端进阶高薪必看手写源码篇

    前言 此系列作为笔者之前发过的前端高频面试整理的补充 会比较偏向中高前端面试问题 当然大家都是从新手一路走过来的 感兴趣的朋友们都可以看哈 初衷 我相信不少同学面试的时候最怕的一个环节就是手写代码 大 ...

  8. 【2022前端面试】CSS手写面试题汇总(加紧收藏)

    [2022前端面试]CSS手写面试题汇总(加紧收藏) 更新时间:2022年3月3日 把答案一起写上,但是希望大家在看之前思考一下,如果有好的建议,跪求改正! 本文致力于建设前端面试题库,欢迎兄弟们投稿 ...

  9. 前端面试高频手写代码题

    前端面试高频手写代码题 一.实现一个解析URL参数的方法 方法一:String和Array的相关API 方法二: Web API 提供的 URL 方法三:正则表达式+string.replace方法 ...

最新文章

  1. Cell子刊:建立因果关系-合成菌群在植物菌群研究中的机会
  2. 【浙大出品】基于扩展FPN的小目标检测方法
  3. 皮一皮:狭路相逢勇者胜...
  4. python常用模块大全总结-python常用模块整理
  5. SpringBoot+Swagger2.7.0实现汉化(2.8.0不行)
  6. 【Flink】Flink AM container is launched, waiting for AM container to Register with RM
  7. SQL Server 2014中的混合云和Hekaton功能
  8. Shell 的概述,操作命令
  9. 《51单片机应用开发从入门到精通》——1.3 Keil uVision2集成开发环境
  10. 限流, 熔断,降级笔记
  11. linux下的web安全机制,linux http服务器web网页的不同安全机制
  12. 数据恢复基础和进阶教程(二)
  13. D. Relatively Prime Graph(构造+数论)
  14. 格创东智品牌形象升级,新Logo尽显创新活力
  15. postman数据保存在哪里_Postman教程——使用数据文件
  16. Intel Xeon Cooper Lake处理器CPU主频睿频性能详解
  17. android安卓-开源框架汇总
  18. Rust(9):枚举类型
  19. google外链重要性高吗?谷歌外链作用大不大
  20. C# 中实现注册表的写入和读取

热门文章

  1. 【msm8953】带clk的gpio口模拟pwm
  2. python setattr无限递归_在python中引入setattr时出现递归错误
  3. 清空C++ sstream缓冲区
  4. 【初识C++】(关键字,命名空间)
  5. jsp ajax加载html页面,Ajax中的load()方法实现指定区域加载或刷新html与jsp
  6. 用Pcharm 字典的方式获取图片
  7. 实在RPA:银行数字化,业务流程自动化“一小步”,贷款审核效率“一大步”
  8. java舞伴配对_真心求助【舞伴问题】用JAVA实现
  9. 签字后被开除_谁能让“常程签字门”不再发生?
  10. 0欧姆电阻能流过无穷大电流吗