一、javascript基础
1. lodash.get 参考 lodash中文文档
var object = { 'a': [{ 'b': { 'c': 3 } }] };
get(object, 'a[0].b.c'); // => 3
get(object, ['a', '0', 'b', 'c']); // => 3
get(object, 'a.b.c', 'default'); // => 'default'
解析:这里可以预先处理,先把所有输入的 path路径,统一转成 ['a', '0', 'b', 'c'] 形式的数组,然后再迭代取值,
get实现如下:
/**
* object: 对象
* path: 输入的路径
* defaultVal: 默认值
**/
function get(object, path, defaultVal='undefined') {
// 先将path处理成统一格式
let newPath = [];
if (Array.isArray(path)) {
newPath = path;
} else {
// 先将字符串中的'['、']'去除替换为'.',split分割成数组形式
newPath = path.replace(/\[/g,'.').replace(/\]/g,'').split('.');
}
// 递归处理,返回最后结果
return newPath.reduce((o, k) => {
console.log(o, k); // 此处o初始值为下边传入的 object,后续值为每次取的内部值
return (o || {})[k]
}, object) || defaultVal;
}
2. 实现一个对象的 flatten 方法
题目描述:
const obj = {
a: {
b: 1,
c: 2,
d: {e: 5}
},
b: [1, 3, {a: 2, b: 3}],
c: 3
}
flatten(obj) 结果返回如下
// {
// 'a.b': 1,
// 'a.c': 2,
// 'a.d.e': 5,
// 'b[0]': 1,
// 'b[1]': 3,
// 'b[2].a': 2,
// 'b[2].b': 3
// c: 3
// }
代码实现
function isObject(val) {
return typeof val === "object" && val !== null;
}
function flatten(obj) {
if (!isObject(obj)) {
return;
}
let res = {};
const dfs = (cur, prefix) => {
if (isObject(cur)) {
if (Array.isArray(cur)) {
cur.forEach((item, index) => {
dfs(item, `${prefix}[${index}]`);
});
} else {
for (let k in cur) {
dfs(cur[k], `${prefix}${prefix ? "." : ""}${k}`);
}
}
} else {
res[prefix] = cur;
}
};
dfs(obj, "");
return res;
}
flatten();
3. 手写数组的 flat
const flat = function (arr, deep = 1) {
// 声明一个新数组
let result = []
arr.forEach(item => {
if (Array.isArray(item) && deep > 0) {
// 层级递减
// deep-- 来自评论区的大佬指正:deep - 1
// 使用concat链接数组
result = result.concat(flat(item, deep - 1))
} else {
result.push(item)
}
})
return result
}
4. 实现 reduce
Array.prototype.reduce = function(func, init = '') {
let result = init;
this.forEach((item, index) => {
result = func(result, item, this);
});
return result;
};
5. 闭包题
- 函数的上级作用域在哪里创建创建的,上级作用域就是谁
var a = 10
function foo(){
console.log(a)
}
function sum() {
var a = 20
foo()
}
sum()
/* 输出
10
/
6. 深拷贝
const isObj = (val) => typeof val === "object" && val !== null;
// 写法1
function deepClone(obj) {
// 通过 instanceof 去判断你要拷贝的变量它是否是数组(如果不是数组则对象)。
// 1. 准备你想返回的变量(新地址)。
const newObj = obj instanceof Array ? [] : {}; // 核心代码。
// 2. 做拷贝;简单数据类型只需要赋值,如果遇到复杂数据类型就再次进入进行深拷贝,直到所找到的数据为简单数据类型为止。
for (const key in obj) {
const item = obj[key];
newObj[key] = isObj(item) ? deepClone(item) : item;
}
// 3. 返回拷贝的变量。
return newObj;
}
// 写法2 利用es6新特性 WeakMap弱引用 性能更好 并且支持 Symbol
function deepClone2(obj, wMap = new WeakMap()) {
if (isObj(obj)) {
// 判断是对象还是数组
let target = Array.isArray(obj) ? [] : {};
// 如果存在这个就直接返回
if (wMap.has(obj)) {
return wMap.get(obj);
}
wMap.set(obj, target);
// 遍历对象
Reflect.ownKeys(obj).forEach((item) => {
// 拿到数据后判断是复杂数据还是简单数据 如果是复杂数据类型就继续递归调用
target[item] = isObj(obj[item]) ? deepClone2(obj[item], wMap) : obj[item];
});
return target;
} else {
return obj;
}
}
7. Promise相关方法
8. 实现有并行限制的 Promise 调度器
题目描述:JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个
addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的输出顺序是:2 3 1 4
整个的完整执行流程:
一开始1、2两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
代码实现
class Scheduler {
constructor(limit) {
this.queue = [];
this.maxCount = limit;
this.runCounts = 0;
}
add(time, order) {
const promiseCreator = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(order);
resolve();
}, time);
});
};
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 scheduler = new Scheduler(2);
const addTask = (time, order) => {
scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();
解法二·
// 实现如下
class Scheduler {
constructor () {
this.tasks = [] // 任务缓冲队列
this.runningTask = [] // 任务队列
}
// promiseCreator 是一个异步函数,return Promise
add (promiseCreator) {
return new Promise((resolve, reject) => {
promiseCreator.resolve = resolve
if (this.runningTask.length < 2) {
this.run(promiseCreator)
} else {
this.tasks.push(promiseCreator)
}
})
}
run (promiseCreator) {
this.runningTask.push(promiseCreator)
promiseCreator().then(() => {
promiseCreator.resolve()
// 删除运行完的任务
this.runningTask.splice(this.runningTask.findIndex(promiseCreator), 1)
if (this.tasks.length > 0) {
this.run(this.tasks.shift())
}
})
}
}
const promiseAll = (promises: (() => Promise<void>)[], limit = 100): Promise<Awaited<any>[]> => new Promise((resolve, reject) => {
let total = promises.length;
if (total === 0) {
resolve([]);
return;
}
let done = 0;
let cur = 0;
let results: any = [];
const run = (index: number) => {
const nextPromise = promises[cur++];
if (nextPromise != null) {
nextPromise().then((res: any) => {
results[index] = res;
if (++done >= total) {
resolve(results);
return;
}
run(cur);
}).catch((err) => {
reject(err);
});
}
};
for (let i = 0; i < limit; i++) {
run(i);
}
});
11. proxy
1. var obj = new Proxy({}, {`
1. `get: function (target, propKey, receiver) {`
1. ``console.log(`getting ${propKey}!`);``
1. `return Reflect.get(target, propKey, receiver);`
1. `},`
1. `set: function (target, propKey, value, receiver) {`
1. ``console.log(`setting ${propKey}!`);``
1. `return Reflect.set(target, propKey, value, receiver);`
1. `}`
1. `});`
12. 实现数据双向绑定
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 = newVal
span.innerHTML = newVal
}
})
// 输入监听
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
14. compose
题目描述:实现一个 compose 函数
// 用法如下:
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function fn4(x) {
return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11
代码实现
function compose(...fn) {
if (!fn.length) return (v) => v;
if (fn.length === 1) return fn[0];
return fn.reduce(
(pre, cur) =>
(...args) =>
pre(cur(...args))
);
}
function compose(...funcs) {
return function (input) {
return funcs.reverse().reduce((result, next) => next.call(this, result), input)
}
}
手写中间件 compose,并支持异步函数
// 文件:app.js
app.compose = function(...fn) {
return Promise.resolve(
fn.reduce((a, b) => arg =>
Promise.resolve(a(() => b(arg)))
)(() => Promise.resolve())
);
};
// 2
function middleware(...funcs) {
return funcs.reverse().reduce((result, next) => (arg) => Promise.resolve(next.call(this, arg, result)), () => Promise.resolve())
}
\
中间件compose
function middleware(...funcs) {
return funcs.reverse().reduce((result, next) => (arg) => next.call(this, arg, result), () => { })
}
const fn1 = (data, next) => {
console.log('enter 1')
data.step1 = 'step1'
next(data)
console.log(data)
console.log('exit 1')
}
const fn2 = (data,next) => {
console.log('enter 2')
data.step2 = 'step2'
next(data)
console.log('exit 2')
}
const fn3 = (data,next) => {
console.log('enter 3')
data.step3 = 'step3'
next()
console.log('exit 3')
}
middleware(fn1,fn2,fn3)
// koa有个特点,调用next参数表示调用下一个函数
function fn1(next) {
console.log(1);
next();
}
function fn2(next) {
console.log(2);
next();
}
function fn3(next) {
console.log(3);
next();
}
middleware = [fn1, fn2, fn3]
function compose(middleware){
function dispatch (index){
if(index == middleware.length) return ;
var curr;
curr = middleware[index];
// 这里使用箭头函数,让函数延迟执行
return curr(() => dispatch(++index))
}
dispatch(0)
};
compose(middleware);
16.事件处理
(1)eventEmitter,事件监听处理器,包括on()、off()、once()、emit()方法
events 是一个对象,用来存储事件名以及对应的回调函数;return this 返回该实例,可以链式调用。
class EventEmitter{
constructor(){
this._events={}
}
on(event,callback){
let callbacks = this._events[event] || []
this._events[event] = callbacks.push(callback);
return this
}
off(event,callback){
let callbacks = this._events[event]
this._events[event] = callbacks && callbacks.filter(function(fn){
return fn !== callback;
})
return this
}
emit(eventName,...args) {
const callbacks = this._events[eventName]
callbacks.map(cb => {
cb(...args)
})
return this;
}
once(event,callback){
let wrap = (...args) => {
callback.apply(this, args)
this.off(event, wrap)
}
this.on(event, wrap )
return this
}
}
(2)EventBus
class EventBus {
constructor() {
this._events = {};
}
on(event, fn) {
if (Array.isArray(event)) {
event.map((item) => this.on(item, fn));
} else {
this._events[event] = this._events[event] || [];
this._events[event].push(fn);
}
return this;
}
off(event, fn) {
if (arguments.length == 0) {
this._events = {};
return;
}
if (Array.isArray(event)) {
event.map((item) => this.off(item, fn));
} else {
const cbs = this._events[event];
if (!cbs) return this;
if (!fn) {
this._events[event] = null;
return this;
}
let cb,
i = cbs.length;
while (i--) {
cb = cbs[i];
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break;
}
}
return this;
}
}
once(event, fn) {
let on = () => {
this.off(event, on);
fn.apply(this, arguments);
};
on.fn = fn;
this.on(event, on);
return this;
}
emit(event) {
let cbs = this._events[event];
if (cbs) {
const args = [].slice.call(arguments, 1);
cbs.map((item) => {
args ? item.apply(this, args) : item.call(this);
});
}
return this;
}
}
(3)订阅发布模式
// 订阅发布模式
class Event {
constructor(){
this.events = {}
}
on(name, event){
let list = this.events[name]||[]
list.push(event)
this.events[name] = list
}
emit(name, ...arg){
const list = this.events[name]||[]
list.forEach(item=>{
item(...arg)
})
}
off(name, event){
const list = this.events[name]
if(list && list.length>0){
this.events[name] = list.filter(item=>item!=event)
}
}
once(name, event){
const fn = (...arg)=>{
event(...arg)
this.off(name, fn)
}
this.on(name, fn)
}
}
const A = new Event()
const B = {
update(data){
console.log('收到A的消息啦', data)
}
}
A.on('update', B.update)
A.emit('update', 'send-info')
// 观察者模式
class Dep {
constructor(){
this.list = []
}
on(watcher){
this.list.push(watcher)
}
emit(data){
this.list.forEach(item=>{
item.update(data)
})
}
off(watcher){
this.list = this.list.filter(item=>item!=watcher)
}
}
class Watcher{
update(){}
add(dep){
dep.on(this)
}
}
const A = new Dep();
const B = new Watcher();
B.add(A);
B.update = function(data){
console.log('收到A的消息啦', data)
}
A.emit('广播啦')
17. nextTick
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => { // 将拿到的回调函数存放到数组中
if (cb) {
try { // 错误捕获
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) { // 如果当前没有在pending的时候,就会执行timeFunc
pending = true
timerFunc() // 多次执行nextTick只会执行一次,timerFunc就是一个异步方法
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
//
数据处理
1. js对象树形结构
(1)对象树形结构根据id返回路径
有一个对象表示的树形结构,大致如下,实际深度不止 2 层 // 实现一个 getPathByNodeId 的方法,传入节点的 id 值,返回节点的路径
const root = {
id: "root",
nodes: [
{
id: "node-123", // path 1 / [1]
nodes: [
{
id: "node-234" // path 1-1 / [1, 1]
} ]
},
{
id: "node-345", // path 2 ...
nodes: [
{
id: "node-456", // path 2-1
} ] } ] }
// getPathByNodeId(root, 'node-456’ ) => 2-1 或 [2, 1]
代码实现
let path = [];
const getPathByNodeId = (root, id) => {
path.push(root.id);
if (root.id === id) {
return path;
}
let result = null;
(root.nodes || []).forEach((node) => {
const nodePathResult = getPathByNodeId(node, id);
if (nodePathResult) {
result = nodePathResult;
}
});
if (!result) {
path = [];
}
return result;
};
(2)将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;
}
(3)获取数组(树)的最大深度
var deepArr = []; //定义存放每条路径深度的数组
getDeep(treeArr,0,deepArr); //调用函数,三个参数分别为,结点,计数器,以及存放深度的数组
function getDeep(data,i,deepArr){
//获取当前结点的子数组,并且打印当前结点的值
console.log(data.name)
var treeRoot = data.child
//如果当前结点没有子数组了(没有子结点)就跳出当前递归,并且使计数器+1,并把计数器i的值存入深度数组中
if(!treeRoot){
i++
deepArr.push(i);
return
}
//如果当前结点有子数组,就要使计数器+1
i++
//通过for循环来找出每一条路径,对遍历到的结点使用递归
for(let j=0;j<treeRoot.length;j++){
getDeep(treeRoot[j],i,deepArr) //递归时传入的就是当前结点的第j个子结点,当这第j个子结点中的所有子孙结点全部遍历完成之后,再去遍历第j+1个结点的所有子孙结点
}
}
//最后的到的这个深度数组,就可以通过对每一项进行比较从而得出最大值即最大深度
console.log(deepArr)
2. 数组转链表
3. 数字转汉字
4. 找出连续整数缺少的数字
给定一个数组,给出上边界和下边界数据,里面的数是连续的,但是缺失了一个,要求找出这个缺失的数 例如:arr=[2,1,3,5,4,8,9,6] 一共9个数,已知上边界为1,下边界为9,要找到缺失的7 思路: 首先遍历数组获取目前数组中个数想加的结果 然后根据高斯求和,求出理论上的和 最后相减即得到了缺失的数
const arr = [2,1,3,5,4,8,9,6];
const upperBound = 1;
const lowerBound = 9;
let prevSum = 0;
MisssionNumber(arr);
function MisssionNumber(arr) {
for(let i=0; i<arr.length; i++){
prevSum += arr[i];
}
const gaosiSum = ((upperBound+lowerBound)*9)/2;
const missionNum = gaosiSum-prevSum;
console.log(missionNum);
}
10. 丢失的数字
5. 判断完全平方数
就是判断一个数字能不能被开平方, 比如9的开平方是3 是对的。 5没法开平方就是错的。
var fn = function (num) {
return num ** 0.5 % 1 == 0
};
6. 二进制加法
实现一个二进制加法,输入输出均为二进制字符串
function addBinary(a,b) {
let ans = '', carry = 0
let pa = a.length-1;
let pb = b.length-1;
while (pa >=0 || pb >= 0) {
const sum = Number(a[pa] || 0) + Number(b[pb] || 0) + carry
carry = Math.floor(sum / 2);
ans = sum % 2 + ans
pa--;
pb--;
}
if(carry !== 0) ans = '1' + ans
return ans
}
7. 随机乱序数组
function shuffle(arr) { // 随机打乱数组
let _arr = arr.slice() // 调用数组副本,不改变原数组
for (let i = 0; i < _arr.length; i++) {
let j = getRandomInt(0, i)
let t = _arr[i]
_arr[i] = _arr[j]
_arr[j] = t
}
return _arr
}
function getRandomInt(min, max) { // 获取min到max的一个随机数,包含min和max本身
return Math.floor(Math.random() * (max - min + 1) + min)
}
function shuffle(arr) {
let i = arr.length;
while (i) {
let j = Math.floor(Math.random() * i--);
[arr[j], arr[i]] = [arr[i], arr[j]];
}
}
let arr = [1,2,3,4,5,6,7]
shuffle(arr)
console.log(arr)
function randomSort(a,b) {
return .5 - Math.random();
}
let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
arr.sort(randomSort);
- 两个数组中完全独立的数据
三、场景应用
1. 实现一个字符串计数方法,规则如下:
// 连续的大小写字母/数字记为 1(可以认为数字、大写字母就是小写字母)
// 每一个中文(可以视为 \u4e00-\u9fa5)记为 1
// 所有符号(如果枚举不完,可以假设符号只有 @ 符号一种)视为空格,空格和符号不计数
// Person123 => 1 你好世界 => 4 Hello world => 2 hello@world => 2 hello 你 world => 3
const count = (str) => {
let count = 0;
// 正则方式
// count += str.match(/([a-zA-Z]+)/g).length - 1;
// count += str.match(/\d+/g).length - 1;
// count += str.match(/[\u4e00-\u9fa5]/g).length - 1;
// return count;
// 字符串遍历方式
const getType = (char) => {
if ((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')) {
return 'character';
}
if (char >= 0 && char <= 9) {
return 'number';
}
if (char >= '\u4e00' && char <= '\u9fa5') {
return 'cn';
}
return '';
};
let currentType = '';
let currentLength = 0;
for (let i = 0; i < str.length; i++) {
const type = getType(str[i]);
if (currentType && currentType === type) {
currentLength++;
} else if (!currentType) {
currentType = type;
currentLength++;
} else if (type === 'cn') {
count++;
currentType = '';
currentLength = 0;
} else {
count++;currentType = type;
currentLength = 1;
} }
return count;
};
17. 实现一个具有优先级的任务队列,如果优先级相同按照先进先出的顺序\
class queue{
constructor(){
this.list = {};
this.count = 0;
this.high = new Set();
}
push(item, order){
if(this.list[order]){
this.list[order].push(item)
}else{
this.list[order] = [item]
}
this.high.add(order);
this.count++
}
shift(){
const highOrder = Math.max(...this.high);
const itemList = this.list[highOrder];
if(itemList&&itemList.length>0){
this.count--
const item = itemList.shift();
if(itemList.length===0){
this.high.delete(highOrder)
delete this.list[highOrder]
}
return item;
}
return
}
get size(){
return this.count;
}
}
const list = new queue();
list.push('a', 1)
list.push('b', 2)
list.push('c', 1)
console.log(list.size)
console.log(list.shift())
console.log(list.shift())
console.log(list.shift())
18. 最多耗时多少能完成全部任务
// 有多个任务,每个任务耗时天数不同,有些任务依赖有前置任务(即必须等前置任务完成才能进行当前任务)
// 写个方法计算最多耗时多少能完成全部任务
var findOrder = function(task, prerequisites) {
let numTask = task.length;
let inDegree = new Array(numTask).fill(0);
let map = {};
prerequisites.map(item => {
inDegree[item[0]]++;
map[item[1]] = (map[item[1]] || []).concat(item[0]);
})
let quene = [], order = [];
inDegree.map((item, index) => {
item == 0 && quene.push(index);
})
while(quene.length) {
let selected = quene.shift();
order.push(selected);
let toQuene = map[selected];
if (toQuene && toQuene.length) {
toQuene.map(to => {
inDegree[to]--;
if(inDegree[to] == 0) {
quene.push(to)
}
})
}
}
if (order.length != numTask) return false;
return task.reduce((val,item) => {
return val + item;
},0)
};
let res = findOrder([1,2], [[1,0]]);
console.log(res);
19. 实现一个LazyMan
// 实现一个LazyMan,可以按照以下方式调用:
// LazyMan(“Hank”)输出:
// Hi! This is Hank!
//
// LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
// Hi! This is Hank!
// //等待10秒..
// Wake up after 10
// Eat dinner~
//
// LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
// Hi This is Hank!
// Eat dinner~
// Eat supper~
//
// LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
// //等待5秒
// Wake up after 5
// Hi This is Hank!
// Eat supper
//
// 以此类推。
class LazyLife {
constructor(name){
this.name = name;
this.timer = null;
this.tasks = []
this.sayHi(name)
setTimeout(() => {
this.next()
}, 0)
}
next() {
const task = this.tasks.shift();
task && task();
}
sayHi() {
console.log('sayHi')
this.tasks.push(() => {
console.log(`Hi This is ${this.name}!`)
this.next()
})
return this
}
eat(something) {
console.log('eat')
this.tasks.push(() => {
zz console.log(`Eat ${something}!`)
this.next()
})
return this
}
sleep(time) {
console.log('sleep')
this.tasks.push(() => {
setTimeout(() => {
console.log(`Wake up after ${time}`)
this.next()
}, time * 1000)
})
return this
}
sleepFirst(time) {
console.log('sleepFirst')
this.tasks.unshift(() => {
setTimeout(() => {
console.log(`Wake up after ${time}`)
this.next()
}, time * 1000)
})
return this
}
}
20. 图片懒加载
// directive/imgLazy.js
// 引入默认图片
import baseImg from "@/assets/logo.png";
let timer = null;
// 创建一个监听器
let observer = new IntersectionObserver((entries) => {
// entries是所有被监听对象的集合
entries.forEach((entry) => {
if (entry.isIntersecting || entry.intersectionRatio > 0) {
// 当被监听元素到临界值且未加载图片时触发。
!entry.target.isLoaded && showImage(entry.target, entry.target.data_src);
}
});
});
function showImage(el, imgSrc) {
const img = new Image();
img.src = imgSrc;
img.onload = () => {
el.src = imgSrc;
el.isLoaded = true;
};
}
export default {
// 这里用inserted和bind都行,因为IntersectionObserver时异步的,以防意外还是用inserted好一点
// inserted和bind的区别在于inserted时元素已经插入页面,能够直接获取到dom元素的位置信息。
inserted(el, binding, vnode) {
clearTimeout(timer); // 初始化时展示默认图片
el.src = baseImg; // 将需要加载的图片地址绑定在dom上
el.data_src = binding.value;
observer.observe(el); // 防抖,这里在组件卸载的时候停止监听
const vm = vnode.context;
timer = setTimeout(() => {
vm.$on("hook:beforeDestroy", () => {
observer.disconnect();
});
}, 20);
}, // 图片更新触发
update(el, binding) {
el.isLoaded = false;
el.data_src = binding.value;
},
// unbind不太好,会执行多次,改进一下用组件的beforeDestroy卸载
// unbind(){ // // 停止监听 // observer.disconnect(); // }
};
vue组件内使用
// main.js
import imgLazy from '@/directive/imgLazy.js'
Vue.directive('imgLazy', imgLazy)
// 在组件中定义directives使用,给当前组件注册指令
import imgLazy from '@/directive/imgLazy.js'
export default {
// ...
directives: {
imgLazy: imgLazy,
},
}
// 在组件中使用
// <template>
// <div class='container'>
// <div v-for="(item,index) in imgSrc" :key="index" >
// <img v-imgLazy="item" >
// </div>
// </div>
// </template>
21. sleep
const sleep = (time) => {
return new Promise((resovle, rej) => {
setTimeout(() => {
resovle();
}, time * 1000);
});
};
22. repeat
const repeat = (fn, count, time) => {
if (count == 0) return;
new Promise((resolve, rej) => {
fn();
setTimeout(() => {
resolve();
}, time * 1000);
})
.then(() => {
repeat(fn, count - 1, time);
})
}
23. 数字转汉字 101 一百零一
方法一:支持7位,也就是最大1234567
案例:this.toChinesNum(10101010) 得到 "一千零一十万一千零一十"
/**
* 数字转成汉字
* @params num === 要转换的数字
* @return 汉字
* */
toChinesNum(num) {
let changeNum = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
let unit = ['', '十', '百', '千', '万']
num = parseInt(num)
let getWan = (temp) => {
let strArr = temp.toString().split('').reverse()
let newNum = ''
let newArr = []
strArr.forEach((item, index) => {
newArr.unshift(item === '0' ? changeNum[item] : changeNum[item] + unit[index])
})
let numArr = []
newArr.forEach((m, n) => {
if (m !== '零') numArr.push(n)
})
if (newArr.length > 1) {
newArr.forEach((m, n) => {
if (newArr[newArr.length - 1] === '零') {
if (n <= numArr[numArr.length - 1]) {
newNum += m
}
} else {
newNum += m
}
})
} else {
newNum = newArr[0]
}
return newNum
}
let overWan = Math.floor(num / 10000)
let noWan = num % 10000
if (noWan.toString().length < 4) {
noWan = '0' + noWan
}
return overWan ? getWan(overWan) + '万' + getWan(noWan) : getWan(num)
}
方法二:支持9位以上也就是亿级别的,如果需要钱的那种单位,把注释放开就行
toChineseBig(num) {
// 将接收到的num转换为字符串
var strNum = String(num)
// 定义单位
// var unit = ['拾', '佰', '仟', '万', '拾', '佰', '仟', '亿', '拾', '佰', '仟']
var unit = ['十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千']
// 结果中放一个符号,用来解决最后的零去不掉的问题
var result = ['@']
// 单位下标
var unitNo = 0
// 从后往前遍历接收到的数据,省略结束条件
for (let i = strNum.length - 1;; i--) {
// 调用转大写函数,将每一个数字转换成中文大写,一次放入一个到结果数组中
result.unshift(numToChinese(strNum[i]))
// 如果不大于0
if (i <= 0) {
// 结束循环
break
}
// 放入一个数字,放入一个单位
result.unshift(unit[unitNo])
// 单位下标加1
unitNo++
}
// 将结果数组转换成字符串,并使用正则替换一些关键位置,让结果符合语法
// return result.join('').replace(/(零[仟佰拾]){1,3}/g, '零').replace(/零{2,}/g, '零').replace(/零([万亿])/g, '$1').replace(/亿万/g, '亿').replace(/零*@/g, '')
return result.join('').replace(/(零[千百十]){1,3}/g, '零').replace(/零{2,}/g, '零').replace(/零([万亿])/g, '$1').replace(/亿万/g, '亿').replace(/零*@/g, '')
function numToChinese(n) {
// var chineseBigNum = '零壹贰叁肆伍陆柒捌玖'
var chineseBigNum = '零一二三四五六七八九'
return chineseBigNum[n]
}
}
}
24. 麦乐鸡块问题
问题:在一个平行宇宙中,麦当劳的麦乐鸡块分为 7 块装、13 块装和 29 块装。有一天,你的老板让你出去购买正好为 n 块(0 < n <= 10000)的麦乐鸡块回来,请提供一个算法判断是否可行。
const checkNuggets = (nuggets) => {
let temp = []
temp[7] = true
temp[13]-= true
temp[29] = true
for (let i = 7; i < nuggets; i+= 1) {
if (temp[i]) {
temp[i + 7] = true
temp[i + 13] = true
temp[i + 29] = true
} else continue
}
return !!temp[nuggets]
}
console.log(checkNuggets(25)) // false
console.log(checkNuggets(26)) // true
console.log(checkNuggets(27)) // true
console.log(checkNuggets(28)) // true
console.log(checkNuggets(29)) // true
console.log(checkNuggets(30)) // false
25. 红绿灯
const task = function (timer, light) {
setTimeout(() => {
console.log(light);
}, timer)
};
const taskRunner = async () => {
await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()
}
taskRunner()
26. 每日一题,已知数据格式,实现一个函数 fn 找出链条中所有的父级 id
/*
* 已知数据格式,实现一个函数 fn 找出链条中所有的父级 id
* 实现: 通过es6的class实现,思路:递归调用,下传当前的父辈的id
*/
const fn = (data, value) => {
let res = []
const dfs = (arr, temp = []) => {
for (const node of arr) {
if (node.children) {
dfs(node.children, temp.concat(node.id))
} else {
if (node.id === value) {
res = temp
return
}
}
}
}
dfs(data)
return res
}
27 解析出网站地址
var str = ‘sdfaswww.dasdfwww.xiaohongshu.comdsjks.comdjsdj’ 期望能够解析出网站的地址。
function getUrl(str){
let reg = /\+.(www.)(\w)(.com)\+./
let url = ''
if(reg.test(str)){
url = 'wwww.'+reg.exec(str)[1]+'.com'
}
return url
}
getUrl(str)
// 使用变量记录
function fn(str){
let stack = [];
let len = str.length;
let i = 0;
let url = [];
while(i<len-4){
let newS= str.substr(i, 4);
if(newS==='www.'){
stack = [i+4]
i+=4
}else if(newS === '.com'){
const start = stack.pop()
if(start>0){
let s = str.substring(start, i)
url.push(`www.${s}.com`)
}
i+=4
}else{
i++
}
}
return url
}
fn(str)
- 实现一个具有优先级的任务队列,如果优先级相同按照先进先出的顺序
class queue{
constructor(){
this.list = {};
this.count = 0;
this.high = new Set();
}
push(item, order){
if(this.list[order]){
this.list[order].push(item)
}else{
this.list[order] = [item]
}
this.high.add(order);
this.count++
}
shift(){
const highOrder = Math.max(...this.high);
const itemList = this.list[highOrder];
if(itemList&&itemList.length>0){
this.count--
const item = itemList.shift();
if(itemList.length===0){
this.high.delete(highOrder)
delete this.list[highOrder]
}
return item;
}
return
}
get size(){
return this.count;
}
}
const list = new queue();
list.push('a', 1)
list.push('b', 2)
list.push('c', 1)
console.log(list.size)
console.log(list.shift())
console.log(list.shift())
console.log(list.shift())
实现一个方法,截断字符串,如果截断处处于一个链接中间,则从链接结尾处开始截断。示例:
示例1
substrRetainLink('1,2,3,4 [http://www.meituan.com](http://www.meituan.com/) end', 4);
返回:1,2,
示例2
substrRetainLink('1,2,3,4 [http://www.meituan.com](http://www.meituan.com/) end', 10);
返回:1,2,3,4 [http://www.meituan.com](http://www.meituan.com/)
示例3
substrRetainLink('1,2,3,4 [美团网|[http://www.meituan.com](http://www.meituan.com/)] end', 10);/
返回:1,2,3,4 [美团网|[http://www.meituan.com](http://www.meituan.com/)]
示例4
substrRetainLink('1,2,3,4 [http://meituan.com](http://meituan.com/) [http://www.meituan.com](http://www.meituan.com/) end', 35);//
返回:1,2,3,4 [http://meituan.com](http://meituan.com/) [http://www.meituan.com](http://www.meituan.com/)
/*
* @param {string} input 待处理的文本
* @param {number index 截断位置
*/
function substrRetainLink(input, length) {
}
/*
* @param {string} input 待处理的文本
* @param {number index 截断位置
*/
function getUrl(str) {
let reg = /(http|https):\/\/((\w)+\.)*(\w)+(.com)/g;
let reg2 = /\[(.+)\]/g;
let url = '';
if (reg2.test(str)) {
url = str.match(reg2);
} else {
url = str.match(reg);
}
return url;
}
function substrRetainLink(input, length) {
let url = '';
let index = null;
let urlLastIndex = null;
if (getUrl(input)) {
url = getUrl(input) && getUrl(input)[0];
index = input.indexOf(url) + 1;
urlLastIndex = index + url.length;
}
let len = input.length;
let res = '';
if (!url || length < index) {
return input.slice(0, length);
} else {
res = input.slice(0, urlLastIndex)
+ (length - urlLastIndex > 0 ? substrRetainLink(input.slice(urlLastIndex), length - urlLastIndex) : '');
}
return res;
}
// var a = substrRetainLink('1,2,3,4 http://www.meituan.com end', 4);
var a = substrRetainLink('1,2,3,4 http://www.meituan.com end', 10);
// var a = substrRetainLink('1,2,3,4 http://www.meituan.com http://www.meituan.com end', 35);
// var a = substrRetainLink('1,2,3,4 http://www.meituan.com http://meituan.com end', 35);
// var a = substrRetainLink('1,2,3,4 [美团网|http://www.meituan.com] end', 10);
console.log(a)
28. 千位分隔
给10000 得到10,000
function fn(num){
let tmp = (num+'').split('.');
let newS = tmp[0];
let len = newS.length;
let i = len%3;
let s = newS.substr(0, i);
while(i<len){
const a = newS.substr(i, 3)
if(s.length>0){
s +=','+a
}else{
s = a
}
i+=3
}
if(tmp.length>1){
s+='.'+tmp[1]
}
return s
}
fn(10000)
fn(10000000)
fn(10000000.88)
29. Count and Say
var countAndSay = function(n) {
let str = "1";
for (let i = 2; i <= n; ++i) {
const sb = [];
let start = 0;
let pos = 0;
while (pos < str.length) {
while (pos < str.length && str[pos] === str[start]) {
pos++;
}
sb.push('' + (pos - start) + str[start]);
start = pos;
}
str = sb.join('');
}
return str;
};
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/count-and-say/solution/wai-guan-shu-lie-by-leetcode-solution-9rt8/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
var countAndSay = function(n) {
if(n==1){
return n.toString()
}
var tempArr = countAndSay(n-1).match(/(\d)\1*/g) // 该正则进行相同分组,调用match方法得出接下来用的数组
var result = ""
tempArr.forEach((item)=>{ // 循环上面得到的数组,然后取每个的长度(题里说的几个),还有第一个数字(题里说的哪个数)
var lth = item.length.toString()
var num = item.substring(0,1)
result = result+lth+num
})
return result //最后返回结果
};
作者:wo-shi-gao-xiao-de
链接:https://leetcode.cn/problems/count-and-say/solution/jsdi-gui-jie-jue-by-wo-shi-gao-xiao-de/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
30. prettyBytes
算法:
/ prettyBytes(1337); //'1.34 KB'
// 1000000 bytes
// B KB MB GB TB
// 文件; 单位进度; 保留几位小数
function prettyBytes(bytes, base = 1000, maximumFractionDigits = 2) {
const map = {
kb: base,
MB: base * base,
GB: base * base * base,
} le
t res = null;
Object.keys(map).some(key => {
if (bytes >= map[key] && bytes < map[key] * base) {
res = `${(bytes / map[key]).toFixed(maximumFractionDigits)}${key}`
}
return res;
})
return res;
} //
console.log(prettyBytes(1000));
31. html片段标签闭合检验
// 给定一个 html 片断, 判断其中元素是否完整闭合。 为简化过程, 我们可以假设所有结点
都是空标签, 且没有自闭合的结点。
// 例子 1. "<div><a></a></div>", 返回 true, 是一个完整闭合的 html 片断,
// 例子 2. "<div><span></div></span>"返回 false
// 例子 3. "<div><div><em></em></div><p></p ></div>", true
function isValidHtml(html) {
const htmlStr = html.replace(/></g, ',').replace(/(<|>)/g, '').split(',');
console.log(htmlStr);
const line = [];
htmlStr.forEach(item => {
const lastItem = [...line].pop();
console.log(lastItem, `${item}`);
if (item === `/${lastItem}`) {
line.pop();
} else {
line.push(item);
}}
);
console.log(line);
return line.length === 0;
}
console.log(isValidHtml('<div><a></a ></div>'));
32. 字符数组转对象
//input ["a","b","c","d","e","f","g"]
//output {"a":{"b":{"c":{"d":{"e":{"f":"g"}}}}}}
//一开始很懵, 后面思路起来想到应该从后往前推就做出来了
function handler(arr){
const len = arr.length;
let prev = {
[arr[len-2]]:arr[len-1]
}
for(let i=len-3;i>=0;i--){
prev = {
[arr[i]]:prev
}
}
return prev
}
33. 如何获取页面中出现的所有dom元素的标签类型
//其实只要知道如何获取页面中所有元素就能做出来, 通过document.querySelectorAll("*")获取
const all = Array.from(document.querySelectorAll("*"));
const hash = {};
const res = [];
all.forEach(it=>{
if(!hash[it.tagName]){
res.push(it.tagName);
hash[it.tagName] = true;
}
})
console.log(res)
鲨鱼哥 最全的手写JS面试题
「2021」高频前端面试题汇总之手写代码篇
-
-
- 1. 实现日期格式化函数
- 2. 交换a,b的值,不能用临时变量
- 3. 实现数组的乱序输出
- 4. 实现数组元素求和
- 5. 实现数组的扁平化
- 6. 实现数组去重
- 7. 实现数组的flat方法
- 8. 实现数组的push方法
- 9. 实现数组的filter方法
- 10. 实现数组的map方法
- 11. 实现字符串的repeat方法
- 12. 实现字符串翻转
- 13. 将数字每千分位用逗号隔开
- 14. 实现非负大整数相加
- 13. 实现 add(1)(2)(3)
- 14. 实现类数组转化为数组
- 15. 使用 reduce 求和
- 16. 将js对象转化为树形结构
- 17. 使用ES5和ES6求函数参数的和
- 18. 解析 URL Params 为对象
-