循环打印红黄绿

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

三个亮灯函数:

function red() {console.log('red');
}
function green() {console.log('green');
}
function yellow() {console.log('yellow');
}

这道题复杂的地方在于需要“交替重复”亮灯,而不是“亮完一次”就结束了。

(1)用 callback 实现

const task = (timer, light, callback) => {setTimeout(() => {if (light === 'red') {red()}else if (light === 'green') {green()}else if (light === 'yellow') {yellow()}callback()}, timer)
}
task(3000, 'red', () => {task(2000, 'green', () => {task(1000, 'yellow', Function.prototype)})
})

这里存在一个 bug:代码只是完成了一次流程,执行后红黄绿灯分别只亮一次。该如何让它交替重复进行呢?

上面提到过递归,可以递归亮灯的一个周期:

const step = () => {task(3000, 'red', () => {task(2000, 'green', () => {task(1000, 'yellow', step)})})
}
step()

注意看黄灯亮的回调里又再次调用了 step 方法 以完成循环亮灯。

(2)用 promise 实现

const task = (timer, light) => new Promise((resolve, reject) => {setTimeout(() => {if (light === 'red') {red()}else if (light === 'green') {green()}else if (light === 'yellow') {yellow()}resolve()}, timer)})
const step = () => {task(3000, 'red').then(() => task(2000, 'green')).then(() => task(2100, 'yellow')).then(step)
}
step()

这里将回调移除,在一次亮灯结束后,resolve 当前 promise,并依然使用递归进行。

(3)用 async/await 实现

const taskRunner =  async () => {await task(3000, 'red')await task(2000, 'green')await task(2100, 'yellow')taskRunner()
}
taskRunner()

手写 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);}
};

实现双向数据绑定

let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {configurable: true,enumerable: true,get() {console.log('获取数据了')},set(newVal) {console.log('数据更新了')input.value = newValspan.innerHTML = newVal}
})
// 输入监听
input.addEventListener('keyup', function(e) {obj.text = e.target.value
})

Array.prototype.filter()

Array.prototype.filter = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not undefined');}if (typeof callback !== 'function') {throw new TypeError(callback + 'is not a function');}const res = [];// 让O成为回调函数的对象传递(强制转换对象)const O = Object(this);// >>>0 保证len为number,且为正整数const len = O.length >>> 0;for (let i = 0; i < len; i++) {// 检查i是否在O的属性(会检查原型链)if (i in O) {// 回调函数调用传参if (callback.call(thisArg, O[i], i, O)) {res.push(O[i]);}}}return res;
}

实现类数组转化为数组

类数组转换为数组的方法有这样几种:

  • 通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
  • 通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
  • 通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
  • 通过 Array.from 方法来实现转换
Array.from(arrayLike);

手写 call 函数

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 处理传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性。
  7. 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {// 判断调用对象if (typeof this !== "function") {console.error("type error");}// 获取参数let args = [...arguments].slice(1),result = null;// 判断 context 是否传入,如果未传入则设置为 windowcontext = context || window;// 将调用函数设为对象的方法context.fn = this;// 调用函数result = context.fn(...args);// 将属性删除delete context.fn;return result;
};

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

实现一个迷你版的vue

入口

// js/vue.js
class Vue {constructor (options) {// 1. 通过属性保存选项的数据this.$options = options || {}this.$data = options.data || {}this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el// 2. 把data中的成员转换成getter和setter,注入到vue实例中this._proxyData(this.$data)// 3. 调用observer对象,监听数据的变化new Observer(this.$data)// 4. 调用compiler对象,解析指令和差值表达式new Compiler(this)}_proxyData (data) {// 遍历data中的所有属性Object.keys(data).forEach(key => {// 把data的属性注入到vue实例中Object.defineProperty(this, key, {enumerable: true,configurable: true,get () {return data[key]},set (newValue) {if (newValue === data[key]) {return}data[key] = newValue}})})}
}

实现Dep

class Dep {constructor () {// 存储所有的观察者this.subs = []}// 添加观察者addSub (sub) {if (sub && sub.update) {this.subs.push(sub)}}// 发送通知notify () {this.subs.forEach(sub => {sub.update()})}
}

实现watcher

class Watcher {constructor (vm, key, cb) {this.vm = vm// data中的属性名称this.key = key// 回调函数负责更新视图this.cb = cb// 把watcher对象记录到Dep类的静态属性targetDep.target = this// 触发get方法,在get方法中会调用addSubthis.oldValue = vm[key]Dep.target = null}// 当数据发生变化的时候更新视图update () {let newValue = this.vm[this.key]if (this.oldValue === newValue) {return}this.cb(newValue)}
}

实现compiler

class Compiler {constructor (vm) {this.el = vm.$elthis.vm = vmthis.compile(this.el)}// 编译模板,处理文本节点和元素节点compile (el) {let childNodes = el.childNodesArray.from(childNodes).forEach(node => {// 处理文本节点if (this.isTextNode(node)) {this.compileText(node)} else if (this.isElementNode(node)) {// 处理元素节点this.compileElement(node)}// 判断node节点,是否有子节点,如果有子节点,要递归调用compileif (node.childNodes && node.childNodes.length) {this.compile(node)}})}// 编译元素节点,处理指令compileElement (node) {// console.log(node.attributes)// 遍历所有的属性节点Array.from(node.attributes).forEach(attr => {// 判断是否是指令let attrName = attr.nameif (this.isDirective(attrName)) {// v-text --> textattrName = attrName.substr(2)let key = attr.valuethis.update(node, key, attrName)}})}update (node, key, attrName) {let updateFn = this[attrName + 'Updater']updateFn && updateFn.call(this, node, this.vm[key], key)}// 处理 v-text 指令textUpdater (node, value, key) {node.textContent = valuenew Watcher(this.vm, key, (newValue) => {node.textContent = newValue})}// v-modelmodelUpdater (node, value, key) {node.value = valuenew Watcher(this.vm, key, (newValue) => {node.value = newValue})// 双向绑定node.addEventListener('input', () => {this.vm[key] = node.value})}// 编译文本节点,处理差值表达式compileText (node) {// console.dir(node)// {{  msg }}let reg = /\{\{(.+?)\}\}/let value = node.textContentif (reg.test(value)) {let key = RegExp.$1.trim()node.textContent = value.replace(reg, this.vm[key])// 创建watcher对象,当数据改变更新视图new Watcher(this.vm, key, (newValue) => {node.textContent = newValue})}}// 判断元素属性是否是指令isDirective (attrName) {return attrName.startsWith('v-')}// 判断节点是否是文本节点isTextNode (node) {return node.nodeType === 3}// 判断节点是否是元素节点isElementNode (node) {return node.nodeType === 1}
}

实现Observer

class Observer {constructor (data) {this.walk(data)}walk (data) {// 1. 判断data是否是对象if (!data || typeof data !== 'object') {return}// 2. 遍历data对象的所有属性Object.keys(data).forEach(key => {this.defineReactive(data, key, data[key])})}defineReactive (obj, key, val) {let that = this// 负责收集依赖,并发送通知let dep = new Dep()// 如果val是对象,把val内部的属性转换成响应式数据this.walk(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get () {// 收集依赖Dep.target && dep.addSub(Dep.target)return val},set (newValue) {if (newValue === val) {return}val = newValuethat.walk(newValue)// 发送通知dep.notify()}})}
}

使用

<!DOCTYPE html>
<html lang="cn">
<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>Mini Vue</title>
</head>
<body><div id="app"><h1>差值表达式</h1><h3>{{ msg }}</h3><h3>{{ count }}</h3><h1>v-text</h1><div v-text="msg"></div><h1>v-model</h1><input type="text" v-model="msg"><input type="text" v-model="count"></div><script src="./js/dep.js"></script><script src="./js/watcher.js"></script><script src="./js/compiler.js"></script><script src="./js/observer.js"></script><script src="./js/vue.js"></script><script>let vm = new Vue({el: '#app',data: {msg: 'Hello Vue',count: 100,person: { name: 'zs' }}})console.log(vm.msg)// vm.msg = { test: 'Hello' }vm.test = 'abc'</script>
</body>
</html>

数组扁平化

数组扁平化是指将一个多维数组变为一个一维数组

const arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]
方法一:使用flat()
const res1 = arr.flat(Infinity);
方法二:利用正则
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');

但数据类型都会变为字符串

方法三:正则改良版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
方法四:使用reduce
const flatten = arr => {return arr.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);}, [])
}
const res4 = flatten(arr);
方法五:函数递归
const res5 = [];
const fn = arr => {for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {fn(arr[i]);} else {res5.push(arr[i]);}}
}
fn(arr);

Promise并行限制

就是实现有并行限制的Promise调度器问题

class Scheduler {constructor() {this.queue = [];this.maxCount = 2;this.runCounts = 0;}add(promiseCreator) {this.queue.push(promiseCreator);}taskStart() {for (let i = 0; i < this.maxCount; i++) {this.request();}}request() {if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {return;}this.runCounts++;this.queue.shift()().then(() => {this.runCounts--;this.request();});}
}const timeout = time => new Promise(resolve => {setTimeout(resolve, time);
})const scheduler = new Scheduler();const addTask = (time,order) => {scheduler.add(() => timeout(time).then(()=>console.log(order)))
}addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
scheduler.taskStart()
// 2
// 3
// 1
// 4

实现 jsonp

// 动态的加载js文件
function addScript(src) {const script = document.createElement('script');script.src = src;script.type = "text/javascript";document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 设置一个全局的callback函数来接收回调结果
function handleRes(res) {console.log(res);
}
// 接口返回的数据格式
handleRes({a: 1, b: 2});

实现发布-订阅模式

class EventCenter{// 1. 定义事件容器,用来装事件数组let handlers = {}// 2. 添加事件方法,参数:事件名 事件方法addEventListener(type, handler) {// 创建新数组容器if (!this.handlers[type]) {this.handlers[type] = []}// 存入事件this.handlers[type].push(handler)}// 3. 触发事件,参数:事件名 事件参数dispatchEvent(type, params) {// 若没有注册该事件则抛出错误if (!this.handlers[type]) {return new Error('该事件未注册')}// 触发事件this.handlers[type].forEach(handler => {handler(...params)})}// 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布removeEventListener(type, handler) {if (!this.handlers[type]) {return new Error('事件无效')}if (!handler) {// 移除事件delete this.handlers[type]} else {const index = this.handlers[type].findIndex(el => el === handler)if (index === -1) {return new Error('无该绑定事件')}// 移除事件this.handlers[type].splice(index, 1)if (this.handlers[type].length === 0) {delete this.handlers[type]}}}
}

JSONP

script标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好但仅限于GET请求

const jsonp = ({ url, params, callbackName }) => {const generateUrl = () => {let dataSrc = '';for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`;}}dataSrc += `callback=${callbackName}`;return `${url}?${dataSrc}`;}return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');scriptEle.src = generateUrl();document.body.appendChild(scriptEle);window[callbackName] = data => {resolve(data);document.removeChild(scriptEle);}})
}

实现千位分隔符

// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(num) {num = parseFloat(num.toFixed(3));let [integer, decimal] = String.prototype.split.call(num, '.');integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');return integer + '.' + (decimal ? decimal : '');
}

手写类型判断函数

function getType(value) {// 判断数据是 null 的情况if (value === null) {return value + "";}// 判断数据是引用类型的情况if (typeof value === "object") {let valueClass = Object.prototype.toString.call(value),type = valueClass.split(" ")[1].split("");type.pop();return type.join("").toLowerCase();} else {// 判断数据是基本数据类型的情况和函数的情况return typeof value;}
}

用正则写一个根据name获取cookie中的值的方法

function getCookie(name) {var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]*)'));if (match) return unescape(match[2]);
}
  1. 获取页面上的cookie可以使用 document.cookie

这里获取到的是类似于这样的字符串:

'username=poetry; user-id=12345; user-roles=home, me, setting'

可以看到这么几个信息:

  • 每一个cookie都是由 name=value 这样的形式存储的

  • 每一项的开头可能是一个空串''(比如username的开头其实就是), 也可能是一个空字符串' '(比如user-id的开头就是)

  • 每一项用";"来区分

  • 如果某项中有多个值的时候,是用","来连接的(比如user-roles的值)

  • 每一项的结尾可能是有";"的(比如username的结尾),也可能是没有的(比如user-roles的结尾)

  1. 所以我们将这里的正则拆分一下:
  • '(^| )'表示的就是获取每一项的开头,因为我们知道如果^不是放在[]里的话就是表示开头匹配。所以这里(^| )的意思其实就被拆分为(^)表示的匹配username这种情况,它前面什么都没有是一个空串(你可以把(^)理解为^它后面还有一个隐藏的'');而|表示的就是或者是一个" "(为了匹配user-id开头的这种情况)

  • +name+这没什么好说的

  • =([^;]*)这里匹配的就是=后面的值了,比如poetry;刚刚说了^要是放在[]里的话就表示"除了^后面的内容都能匹配",也就是非的意思。所以这里([^;]*)表示的是除了";"这个字符串别的都匹配(*应该都知道什么意思吧,匹配0次或多次)

  • 有的大佬等号后面是这样写的'=([^;]*)(;|$)',而最后为什么可以把'(;|$)'给省略呢?因为其实最后一个cookie项是没有';'的,所以它可以合并到=([^;]*)这一步。

  1. 最后获取到的match其实是一个长度为4的数组。比如:
["username=poetry;","","poetry",";"
]
  • 第0项:全量
  • 第1项:开头
  • 第2项:中间的值
  • 第3项:结尾

所以我们是要拿第2项match[2]的值。

  1. 为了防止获取到的值是%xxx这样的字符序列,需要用unescape()方法解码。

手写 Promise.then

then 方法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,我们使用一个 callbacks 数组先把传给then的函数暂存起来,等状态改变时再调用。

那么,怎么保证后一个 **then** 里的方法在前一个 **then**(可能是异步)结束之后再执行呢? 我们可以将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的效果:

  • 承前:当前一个 promise 完成后,调用其 resolve 变更状态,在这个 resolve 里会依次调用 callbacks 里的回调,这样就执行了 then 里的方法了
  • 启后:上一步中,当 then 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promiseresolve,让其状态变更,这又会依次调用新 promisecallbacks 数组里的方法,循环往复。。如果返回的结果是个 promise,则需要等它完成之后再触发新 promiseresolve,所以可以在其结果的 then 里调用新 promiseresolve
then(onFulfilled, onReject){// 保存前一个promise的thisconst self = this; return new MyPromise((resolve, reject) => {// 封装前一个promise成功时执行的函数let fulfilled = () => {try{const result = onFulfilled(self.value); // 承前return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后}catch(err){reject(err)}}// 封装前一个promise失败时执行的函数let rejected = () => {try{const result = onReject(self.reason);return result instanceof MyPromise? result.then(resolve, reject) : reject(result);}catch(err){reject(err)}}switch(self.status){case PENDING: self.onFulfilledCallbacks.push(fulfilled);self.onRejectedCallbacks.push(rejected);break;case FULFILLED:fulfilled();break;case REJECT:rejected();break;}})}

注意:

  • 连续多个 then 里的回调方法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考上面的例子和图)
  • 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用 callbacks 数组中提前注册的回调

字符串最长的不重复子串

题目描述

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2:输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。示例 3:输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。示例 4:输入: s = ""
输出: 0

答案

const lengthOfLongestSubstring = function (s) {if (s.length === 0) {return 0;}let left = 0;let right = 1;let max = 0;while (right <= s.length) {let lr = s.slice(left, right);const index = lr.indexOf(s[right]);if (index > -1) {left = index + left + 1;} else {lr = s.slice(left, right + 1);max = Math.max(max, lr.length);}right++;}return max;
};

Promise.all

Promise.all是支持链式调用的,本质上就是返回了一个Promise实例,通过resolvereject来改变实例状态。

Promise.myAll = function(promiseArr) {return new Promise((resolve, reject) => {const ans = [];let index = 0;for (let i = 0; i < promiseArr.length; i++) {promiseArr[i].then(res => {ans[i] = res;index++;if (index === promiseArr.length) {resolve(ans);}}).catch(err => reject(err));}})
}

Object.is

Object.is解决的主要是这两个问题:

+0 === -0  // true
NaN === NaN // false
const is= (x, y) => {if (x === y) {// +0和-0应该不相等return x !== 0 || y !== 0 || 1/x === 1/y;} else {return x !== x && y !== y;}
}

手写 apply 函数

apply 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 将函数作为上下文对象的一个属性。
  4. 判断参数值是否传入
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性
  7. 返回结果
// apply 函数实现
Function.prototype.myApply = function(context) {// 判断调用对象是否为函数if (typeof this !== "function") {throw new TypeError("Error");}let result = null;// 判断 context 是否存在,如果未传入则为 windowcontext = context || window;// 将函数设为对象的方法context.fn = this;// 调用方法if (arguments[1]) {result = context.fn(...arguments[1]);} else {result = context.fn();}// 将属性删除delete context.fn;return result;
};

高级前端必会手写面试题及答案相关推荐

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

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

  2. 百度前端必会手写面试题整理

    请实现一个 add 函数,满足以下功能 add(1); // 1 add(1)(2); // 3 add(1)(2)(3):// 6 add(1)(2, 3); // 6 add(1, 2)(3); ...

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

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

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

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

  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. Oracle新一代数据库机 助所有规模企业迈向云端
  2. Spark-SQL从MySQL中加载数据以及将数据写入到mysql中(Spark Shell方式,Spark SQL程序)
  3. 牛客小白月赛2-B小马过河(求点到直线的垂足)
  4. ubuntu下面的robo3t 使用笔记
  5. 循环队列的介绍与实现
  6. 《钢铁神兵》里的较量的数学题,都是什么级别的难题?
  7. wince模拟器访问网络_在Wince模拟器接入网络的方法
  8. 2017.9.1 最小生成树 失败总结
  9. D1、4CIF、CIF和QCIF的不同
  10. 深度学习软件开发环境搭建
  11. Document.readyState 如何使用和侦听
  12. Qt5开发从入门到精通——第六篇四节( 图像与图片——显示SVG格式图片 )
  13. Go语言 有缓冲通道、协程池
  14. 【读书笔记】信贷周期的产生
  15. 《听读书怪才解读24部名人传记》笔记
  16. Leetcode 92反转链表
  17. java面试题:2年工作经验java简历包装,面试为什么公司不通过
  18. 双网卡共享上网详细设置图解教程(
  19. 服务器怎么用u盘加载硬盘驱动,戴尔服务器安装用U盘加载硬盘控制卡驱动.pdf
  20. VS配置选项MT和MD的区别

热门文章

  1. 中国造车要把百年车企按在地上打?你别说,我看有戏。
  2. Punte-多功能-wordpress免费主题模板下载
  3. font-awesome样式只显示方框
  4. 苹果摊上事了,iPhone 13新机大翻车,坑害无数人
  5. 美国和中国做舆情分析公司前5强
  6. 某站弹幕抓取,视频,评论......
  7. 关于double和float类型的转换
  8. 【JavaEE】网络编程之TCP套接字、UDP套接字
  9. SAP 银企直连交易明细查询的分页与FTP读取
  10. Kafka2.6版本权限认证