解析 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 和 value
val = 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;
}
实现 forEach 办法
Array.prototype.myForEach = function(callback, context=window) {
// this=>arr
let self = this,
i = 0,
len = self.length;
for(;i<len;i++) {typeof callback == 'function' && callback.call(context,self[i], i)
}
}
设计一个办法提取对象中所有 value 大于 2 的键值对并返回最新的对象
实现:
var obj = {a: 1, b: 3, c: 4}
foo(obj) // {b: 3, c: 4}
办法有很多种,这里提供一种比拟简洁的写法,用到了 ES10
的Object.fromEntries()
:
var obj = {a: 1, b: 3, c: 4}
function foo (obj) {
return Object.fromEntries(Object.entries(obj).filter(([key, value]) => value > 2)
)
}
var obj2 = foo(obj) // {b: 3, c: 4}
console.log(obj2)
// ES8 中 Object.entries()的作用:var obj = {a: 1, b: 2}
var entries = Object.entries(obj); // [['a', 1], ['b', 2]]
// ES10 中 Object.fromEntries()的作用:Object.fromEntries(entries); // {a: 1, b: 2}
实现 map 办法
- 回调函数的参数有哪些,返回值如何解决
- 不批改原来的数组
Array.prototype.myMap = function(callback, context){
// 转换类数组
var arr = Array.prototype.slice.call(this),// 因为是 ES5 所以就不必... 开展符了
mappedArr = [],
i = 0;
for (; i < arr.length; i++){// 把以后值、索引、以后数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr))
mappedArr.push(callback.call(context, arr[i], i, this));
}
return mappedArr;
}
实现一个繁难的 MVVM
实现一个繁难的
MVVM
我会分为这么几步来:
- 首先我会定义一个类
Vue
,这个类接管的是一个options
,那么其中可能有须要挂载的根元素的id
,也就是el
属性;而后应该还有一个data
属性,示意须要双向绑定的数据 - 其次我会定义一个
Dep
类,这个类产生的实例对象中会定义一个subs
数组用来寄存所依赖这个属性的依赖,曾经增加依赖的办法addSub
,删除办法removeSub
,还有一个notify
办法用来遍历更新它subs
中的所有依赖,同时 Dep 类有一个动态属性target
它用来示意以后的观察者,当后续进行依赖收集的时候能够将它增加到dep.subs
中。 - 而后设计一个
observe
办法,这个办法接管的是传进来的data
,也就是options.data
,外面会遍历data
中的每一个属性,并应用Object.defineProperty()
来重写它的get
和set
,那么这外面呢能够应用new Dep()
实例化一个dep
对象,在get
的时候调用其addSub
办法增加以后的观察者Dep.target
实现依赖收集,并且在set
的时候调用dep.notify
办法来告诉每一个依赖它的观察者进行更新 - 实现这些之后,咱们还须要一个
compile
办法来将 HTML 模版和数据联合起来。在这个办法中首先传入的是一个node
节点,而后遍历它的所有子级,判断是否有firstElmentChild
,有的话则进行递归调用 compile 办法,没有firstElementChild
的话且该child.innderHTML
用正则匹配满足有/\{\{(.*)\}\}/
项的话则示意有须要双向绑定的数据,那么就将用正则new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm')
替换掉是其为msg
变量。 - 实现变量替换的同时,还须要将
Dep.target
指向以后的这个child
,且调用一下this.opt.data[key]
,也就是为了触发这个数据的get
来对以后的child
进行依赖收集,这样下次数据变动的时候就能告诉child
进行视图更新了,不过在最初要记得将Dep.target
指为null
哦(其实在Vue
中是有一个targetStack
栈用来寄存target
的指向的) - 那么最初咱们只须要监听
document
的DOMContentLoaded
而后在回调函数中实例化这个Vue
对象就能够了
coding :
须要留神的点:
childNodes
会获取到所有的子节点以及文本节点(包含元素标签中的空白节点)firstElementChild
示意获取元素的第一个字元素节点,以此来辨别是不是元素节点,如果是的话则调用compile
进行递归调用,否则用正则匹配- 这外面的正则真的不难,大家能够看一下
残缺代码如下:
<!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>MVVM</title>
</head>
<body>
<div id="app">
<h3> 姓名 </h3>
<p>{{name}}</p>
<h3> 年龄 </h3>
<p>{{age}}</p>
</div>
</body>
</html>
<script>
document.addEventListener(
"DOMContentLoaded",
function () {let opt = { el: "#app", data: { name: "期待批改...", age: 20} };
let vm = new Vue(opt);
setTimeout(() => {opt.data.name = "jing";}, 2000);
},
false
);
class Vue {constructor(opt) {
this.opt = opt;
this.observer(opt.data);
let root = document.querySelector(opt.el);
this.compile(root);
}
observer(data) {Object.keys(data).forEach((key) => {let obv = new Dep();
data["_" + key] = data[key];
Object.defineProperty(data, key, {get() {Dep.target && obv.addSubNode(Dep.target);
return data["_" + key];
},
set(newVal) {obv.update(newVal);
data["_" + key] = newVal;
},
});
});
}
compile(node) {[].forEach.call(node.childNodes, (child) => {if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {let key = RegExp.$1.trim();
child.innerHTML = child.innerHTML.replace(new RegExp("\\{\\{\\s*" + key + "\\s*\\}\\}", "gm"),
this.opt.data[key]
);
Dep.target = child;
this.opt.data[key];
Dep.target = null;
} else if (child.firstElementChild) this.compile(child);
});
}
}
class Dep {constructor() {this.subNode = [];
}
addSubNode(node) {this.subNode.push(node);
}
update(newVal) {this.subNode.forEach((node) => {node.innerHTML = newVal;});
}
}
</script>
简化版 2
function update(){console.log('数据变动~~~ mock update view')
}
let obj = [1,2,3]
// 变异办法 push shift unshfit reverse sort splice pop
// Object.defineProperty
let oldProto = Array.prototype;
let proto = Object.create(oldProto); // 克隆了一分
['push','shift'].forEach(item=>{proto[item] = function(){update();
oldProto[item].apply(this,arguments);
}
})
function observer(value){ // proxy reflect
if(Array.isArray(value)){
// AOP
return value.__proto__ = proto;
// 重写 这个数组里的 push shift unshfit reverse sort splice pop
}
if(typeof value !== 'object'){return value;}
for(let key in value){defineReactive(value,key,value[key]);
}
}
function defineReactive(obj,key,value){observer(value); // 如果是对象 持续减少 getter 和 setter
Object.defineProperty(obj,key,{get(){return value;},
set(newValue){if(newValue !== value){observer(newValue);
value = newValue;
update();}
}
})
}
observer(obj);
// AOP
// obj.name = {n:200}; // 数据变了 须要更新视图 深度监控
// obj.name.n = 100;
obj.push(123);
obj.push(456);
console.log(obj);
手写常见排序
冒泡排序
冒泡排序的原理如下,从第一个元素开始,把以后元素和下一个索引元素进行比拟。如果以后元素大,那么就替换地位,反复操作直到比拟到最初一个元素,那么此时最初一个元素就是该数组中最大的数。下一轮反复以上操作,然而此时最初一个元素曾经是最大数了,所以不须要再比拟最初一个元素,只须要比拟到
length - 1
的地位。
function bubbleSort(list) {
var n = list.length;
if (!n) return [];
for (var i = 0; i < n; i++) {
// 留神这里须要 n - i - 1
for (var j = 0; j < n - i - 1; j++) {if (list[j] > list[j + 1]) {var temp = list[j + 1];
list[j + 1] = list[j];
list[j] = temp;
}
}
}
return list;
}
疾速排序
快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值比照大小。比基准值小的放数组右边,大的放左边,比照实现后将基准值和第一个比基准值大的值替换地位。而后将数组以基准值的地位分为两局部,持续递归以上操作
ffunction quickSort(arr) {if (arr.length<=1){return arr;}
var baseIndex = Math.floor(arr.length/2);// 向下取整,选取基准点
var base = arr.splice(baseIndex,1)[0];// 取出基准点的值,// splice 通过删除或替换现有元素或者原地增加新的元素来批改数组, 并以数组模式返回被批改的内容。此办法会扭转原数组。// slice 办法返回一个新的数组对象, 不会更改原数组
// 这里不能间接 base=arr[baseIndex], 因为 base 代表的每次都删除的那个数
var left=[];
var right=[];
for (var i = 0; i<arr.length; i++){
// 这里的 length 是变动的,因为 splice 会扭转原数组。if (arr[i] < base){left.push(arr[i]);// 比基准点小的放在右边数组,}
}else{right.push(arr[i]);// 比基准点大的放在左边数组,}
return quickSort(left).concat([base],quickSort(right));
}
抉择排序
function selectSort(arr) {
// 缓存数组长度
const len = arr.length;
// 定义 minIndex,缓存以后区间最小值的索引,留神是索引
let minIndex;
// i 是以后排序区间的终点
for (let i = 0; i < len - 1; i++) {
// 初始化 minIndex 为以后区间第一个元素
minIndex = i;
// i、j 别离定义以后区间的上下界,i 是左边界,j 是右边界
for (let j = i; j < len; j++) {
// 若 j 处的数据项比以后最小值还要小,则更新最小值索引为 j
if (arr[j] < arr[minIndex]) {minIndex = j;}
}
// 如果 minIndex 对应元素不是目前的头部元素,则替换两者
if (minIndex !== i) {[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
return arr;
}
// console.log(selectSort([3, 6, 2, 4, 1]));
插入排序
function insertSort(arr) {for (let i = 1; i < arr.length; i++) {
let j = i;
let target = arr[j];
while (j > 0 && arr[j - 1] > target) {arr[j] = arr[j - 1];
j--;
}
arr[j] = target;
}
return arr;
}
// console.log(insertSort([3, 6, 2, 4, 1]));
参考 前端进阶面试题具体解答
请实现一个 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
数组去重办法汇总
首先: 我晓得多少种去重形式
1. 双层 for 循环
function distinct(arr) {for (let i=0, len=arr.length; i<len; i++) {for (let j=i+1; j<len; j++) {if (arr[i] == arr[j]) {arr.splice(j, 1);
// splice 会扭转数组长度,所以要将数组长度 len 和下标 j 减一
len--;
j--;
}
}
}
return arr;
}
思维: 双重
for
循环是比拟蠢笨的办法,它实现的原理很简略:先定义一个蕴含原始数组第一个元素的数组,而后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不反复则增加到新数组中,最初返回新数组;因为它的工夫复杂度是O(n^2)
,如果数组长度很大,效率会很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) {let arr = a.concat(b);
return arr.filter((item, index)=> {//return arr.indexOf(item) === index
return arr.includes(item)
})
}
思维: 利用
indexOf
检测元素在数组中第一次呈现的地位是否和元素当初的地位相等,如果不等则阐明该元素是反复元素
3. ES6 中的 Set 去重
function distinct(array) {return Array.from(new Set(array));
}
思维: ES6 提供了新的数据结构 Set,Set 构造的一个个性就是成员值都是惟一的,没有反复的值。
4. reduce 实现对象数组去反复
var resources = [{ name: "张三", age: "18"},
{name: "张三", age: "19"},
{name: "张三", age: "20"},
{name: "李四", age: "19"},
{name: "王五", age: "20"},
{name: "赵六", age: "21"}
]
var temp = {};
resources = resources.reduce((prev, curv) => {
// 如果长期对象中有这个名字,什么都不做
if (temp[curv.name]) { }else {
// 如果长期对象没有就把这个名字加进去,同时把以后的这个对象退出到 prev 中
temp[curv.name] = true;
prev.push(curv);
}
return prev
}, []);
console.log("后果", resources);
这种办法是利用高阶函数
reduce
进行去重,这里只须要留神initialValue
得放一个空数组[],不然没法push
实现 getValue/setValue 函数来获取 path 对应的值
// 示例
var object = {a: [{ b: { c: 3} }] }; // path: 'a[0].b.c'
var array = [{a: { b: [1] } }]; // path: '[0].a.b[0]'
function getValue(target, valuePath, defaultValue) {}
console.log(getValue(object, "a[0].b.c", 0)); // 输入 3
console.log(getValue(array, "[0].a.b[0]", 12)); // 输入 1
console.log(getValue(array, "[0].a.b[0].c", 12)); // 输入 12
实现
/**
* 测试属性是否匹配
*/
export function testPropTypes(value, type, dev) {const sEnums = ['number', 'string', 'boolean', 'undefined', 'function']; // NaN
const oEnums = ['Null', 'Object', 'Array', 'Date', 'RegExp', 'Error'];
const nEnums = ['[object Number]',
'[object String]',
'[object Boolean]',
'[object Undefined]',
'[object Function]',
'[object Null]',
'[object Object]',
'[object Array]',
'[object Date]',
'[object RegExp]',
'[object Error]',
];
const reg = new RegExp('\\[object (.*?)\\]');
// 齐全匹配模式,type 应该传递相似格局[object Window] [object HTMLDocument] ...
if (reg.test(type)) {
// 排除 nEnums 的 12 种
if (~nEnums.indexOf(type)) {if (dev === true) {console.warn(value, 'The parameter type belongs to one of 12 types:number string boolean undefined Null Object Array Date RegExp function Error NaN');
}
}
if (Object.prototype.toString.call(value) === type) {return true;}
return false;
}
}
const syncVarIterator = {getter: function (obj, key, defaultValue) {
// 后果变量
const defaultResult = defaultValue === undefined ? undefined : defaultValue;
if (testPropTypes(obj, 'Object') === false && testPropTypes(obj, 'Array') === false) {return defaultResult;}
// 后果变量,临时指向 obj 持有的援用,后续将可能被一直的批改
let result = obj;
// 失去晓得值
try {
// 解析属性档次序列
const keyArr = key.split('.');
// 迭代 obj 对象属性
for (let i = 0; i < keyArr.length; i++) {
// 如果第 i 层属性存在对应的值则迭代该属性值
if (result[keyArr[i]] !== undefined) {result = result[keyArr[i]];
// 如果不存在则返回未定义
} else {return defaultResult;}
}
} catch (e) {return defaultResult;}
// 返回获取的后果
return result;
},
setter: function (obj, key, val) {
// 如果不存在 obj 则返回未定义
if (testPropTypes(obj, 'Object') === false) {return false;}
// 后果变量,临时指向 obj 持有的援用,后续将可能被一直的批改
let result = obj;
try {
// 解析属性档次序列
const keyArr = key.split('.');
let i = 0;
// 迭代 obj 对象属性
for (; i < keyArr.length - 1; i++) {
// 如果第 i 层属性对应的值不存在,则定义为对象
if (result[keyArr[i]] === undefined) {result[keyArr[i]] = {};}
// 如果第 i 层属性对应的值不是对象(Object)的一个实例,则抛出谬误
if (!(result[keyArr[i]] instanceof Object)) {throw new Error('obj.' + keyArr.splice(0, i + 1).join('.') + 'is not Object');
}
// 迭代该层属性值
result = result[keyArr[i]];
}
// 设置属性值
result[keyArr[i]] = val;
return true;
} catch (e) {return false;}
},
};
应用 promise 来实现
创立 enhancedObject
函数
const enhancedObject = (target) =>
new Proxy(target, {get(target, property) {if (property in target) {return target[property];
} else {return searchFor(property, target); // 理论应用时要对 value 值进行复位
}
},
});
let value = null;
function searchFor(property, target) {for (const key of Object.keys(target)) {if (typeof target[key] === "object") {searchFor(property, target[key]);
} else if (typeof target[property] !== "undefined") {value = target[property];
break;
}
}
return value;
}
应用 enhancedObject
函数
const data = enhancedObject({
user: {
name: "test",
settings: {theme: "dark",},
},
});
console.log(data.user.settings.theme); // dark
console.log(data.theme); // dark
以上代码运行后,控制台会输入以下代码:
dark
dark
通过观察以上的输入后果可知,应用
enhancedObject
函数解决过的对象,咱们就能够不便地拜访一般对象外部的深层属性。
怎么在制订数据源外面生成一个长度为 n 的不反复随机数组 能有几种办法 工夫复杂度多少(字节)
第一版 工夫复杂度为 O(n^2)
function getTenNum(testArray, n) {let result = [];
for (let i = 0; i < n; ++i) {const random = Math.floor(Math.random() * testArray.length);
const cur = testArray[random];
if (result.includes(cur)) {
i--;
break;
}
result.push(cur);
}
return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 10);
第二版 标记法 / 自定义属性法 工夫复杂度为 O(n)
function getTenNum(testArray, n) {let hash = {};
let result = [];
let ranNum = n;
while (ranNum > 0) {const ran = Math.floor(Math.random() * testArray.length);
if (!hash[ran]) {hash[ran] = true;
result.push(ran);
ranNum--;
}
}
return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 10);
第三版 交换法 工夫复杂度为 O(n)
function getTenNum(testArray, n) {const cloneArr = [...testArray];
let result = [];
for (let i = 0; i < n; i++) {
debugger;
const ran = Math.floor(Math.random() * (cloneArr.length - i));
result.push(cloneArr[ran]);
cloneArr[ran] = cloneArr[cloneArr.length - i - 1];
}
return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 14);
值得一提的是操作数组的时候应用交换法 这种思路在算法外面很常见
最终版 边遍历边删除 工夫复杂度为 O(n)
function getTenNum(testArray, n) {const cloneArr = [...testArray];
let result = [];
for (let i = 0; i < n; ++i) {const random = Math.floor(Math.random() * cloneArr.length);
const cur = cloneArr[random];
result.push(cur);
cloneArr.splice(random, 1);
}
return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 14);
基于 Generator 函数实现 async/await 原理
外围:传递给我一个
Generator
函数,把函数中的内容基于Iterator
迭代器的特点一步步的执行
function readFile(file) {
return new Promise(resolve => {setTimeout(() => {resolve(file);
}, 1000);
})
};
function asyncFunc(generator) {const iterator = generator(); // 接下来要执行 next
// data 为第一次执行之后的返回后果,用于传给第二次执行
const next = (data) => {let { value, done} = iterator.next(data); // 第二次执行,并接管第一次的申请后果 data
if (done) return; // 执行结束 (到第三次) 间接返回
// 第一次执行 next 时,yield 返回的 promise 实例 赋值给了 value
value.then(data => {next(data); // 当第一次 value 执行结束且胜利时,执行下一步(并把第一次的后果传递下一步)
});
}
next();};
asyncFunc(function* () {
// 生成器函数:控制代码一步步执行
let data = yield readFile('a.js'); // 等这一步骤执行执行胜利之后,再往下走,没执行完的时候,间接返回
data = yield readFile(data + 'b.js');
return data;
})
给定两个数组,写一个办法来计算它们的交加
例如:给定 nums1 = [1, 2, 2, 1],nums2 = [2, 2],返回 [2, 2]。
function union (arr1, arr2) {
return arr1.filter(item => {return arr2.indexOf(item) > - 1;
})
}
const a = [1, 2, 2, 1];
const b = [2, 3, 2];
console.log(union(a, b)); // [2, 2]
实现一个治理本地缓存过期的函数
封装一个能够设置过期工夫的
localStorage
存储函数
class Storage{constructor(name){this.name = 'storage';}
// 设置缓存
setItem(params){
let obj = {
name:'', // 存入数据 属性
value:'',// 属性值
expires:"", // 过期工夫
startTime:new Date().getTime()// 记录何时将值存入缓存,毫秒级
}
let options = {};
// 将 obj 和传进来的 params 合并
Object.assign(options,obj,params);
if(options.expires){
// 如果 options.expires 设置了的话
// 以 options.name 为 key,options 为值放进去
localStorage.setItem(options.name,JSON.stringify(options));
}else{
// 如果 options.expires 没有设置,就判断一下 value 的类型
let type = Object.prototype.toString.call(options.value);
// 如果 value 是对象或者数组对象的类型,就先用 JSON.stringify 转一下,再存进去
if(Object.prototype.toString.call(options.value) == '[object Object]'){options.value = JSON.stringify(options.value);
}
if(Object.prototype.toString.call(options.value) == '[object Array]'){options.value = JSON.stringify(options.value);
}
localStorage.setItem(options.name,options.value);
}
}
// 拿到缓存
getItem(name){let item = localStorage.getItem(name);
// 先将拿到的试着进行 json 转为对象的模式
try{item = JSON.parse(item);
}catch(error){
// 如果不行就不是 json 的字符串,就间接返回
item = item;
}
// 如果有 startTime 的值,阐明设置了生效工夫
if(item.startTime){let date = new Date().getTime();
// 何时将值取出减去刚存入的工夫,与 item.expires 比拟,如果大于就是过期了,如果小于或等于就还没过期
if(date - item.startTime > item.expires){
// 缓存过期,革除缓存,返回 false
localStorage.removeItem(name);
return false;
}else{
// 缓存未过期,返回值
return item.value;
}
}else{
// 如果没有设置生效工夫,间接返回值
return item;
}
}
// 移出缓存
removeItem(name){localStorage.removeItem(name);
}
// 移出全副缓存
clear(){localStorage.clear();
}
}
用法
let storage = new Storage();
storage.setItem({
name:"name",
value:"ppp"
})
上面我把值取出来
let value = storage.getItem('name');
console.log('我是 value',value);
设置 5 秒过期
let storage = new Storage();
storage.setItem({
name:"name",
value:"ppp",
expires: 5000
})
// 过期后再取出来会变为 false
let value = storage.getItem('name');
console.log('我是 value',value);
字符串最长的不反复子串
题目形容
给定一个字符串 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;
};
实现一个函数判断数据类型
function getType(obj) {if (obj === null) return String(obj);
return typeof obj === 'object'
? Object.prototype.toString.call(obj).replace('[object', '').replace(']','').toLowerCase()
: typeof obj;
}
// 调用
getType(null); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date
异步串行 | 异步并行
// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {setTimeout(function () {callback(null, a + b);
}, 500);
}
// 解决方案
// 1. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {asyncAdd(a, b, (err, res) => {if (err) {reject(err)
} else {resolve(res)
}
})
})
// 2. 串行解决
async function serialSum(...args) {return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}
// 3. 并行处理
async function parallelSum(...args) {if (args.length === 1) return args[0]
const tasks = []
for (let i = 0; i < args.length; i += 2) {tasks.push(promiseAdd(args[i], args[i + 1] || 0))
}
const results = await Promise.all(tasks)
return parallelSum(...results)
}
// 测试
(async () => {console.log('Running...');
const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res1)
const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res2)
console.log('Done');
})()
实现深拷贝
简洁版本
简略版:
const newObj = JSON.parse(JSON.stringify(oldObj));
局限性:
- 他无奈实现对函数、RegExp 等非凡对象的克隆
- 会摈弃对象的
constructo
r, 所有的构造函数会指向Object
- 对象有循环援用, 会报错
面试简版
function deepClone(obj) {
// 如果是 值类型 或 null,则间接 return
if(typeof obj !== 'object' || obj === null) {return obj}
// 定义后果对象
let copy = {}
// 如果对象是数组,则定义后果数组
if(obj.constructor === Array) {copy = []
}
// 遍历对象的 key
for(let key in obj) {
// 如果 key 是对象的自有属性
if(obj.hasOwnProperty(key)) {
// 递归调用深拷贝办法
copy[key] = deepClone(obj[key])
}
}
return copy
}
调用深拷贝办法,若属性为值类型,则间接返回;若属性为援用类型,则递归遍历。这就是咱们在解这一类题时的外围的办法。
进阶版
- 解决拷贝循环援用问题
- 解决拷贝对应原型问题
// 递归拷贝 (类型判断)
function deepClone(value,hash = new WeakMap){ // 弱援用,不必 map,weakMap 更适合一点
// null 和 undefiend 是不须要拷贝的
if(value == null){return value;}
if(value instanceof RegExp) {return new RegExp(value) }
if(value instanceof Date) {return new Date(value) }
// 函数是不须要拷贝
if(typeof value != 'object') return value;
let obj = new value.constructor(); // [] {}
// 阐明是一个对象类型
if(hash.get(value)){return hash.get(value)
}
hash.set(value,obj);
for(let key in value){ // in 会遍历以后对象上的属性 和 __proto__指代的属性
// 补拷贝 对象的__proto__上的属性
if(value.hasOwnProperty(key)){
// 如果值还有可能是对象 就持续拷贝
obj[key] = deepClone(value[key],hash);
}
}
return obj
// 辨别对象和数组 Object.prototype.toString.call
}
// test
var o = {};
o.x = o;
var o1 = deepClone(o); // 如果这个对象拷贝过了 就返回那个拷贝的后果就能够了
console.log(o1);
实现残缺的深拷贝
1. 简易版及问题
JSON.parse(JSON.stringify());
预计这个 api 能笼罩大多数的利用场景,没错,谈到深拷贝,我第一个想到的也是它。然而实际上,对于某些严格的场景来说,这个办法是有微小的坑的。问题如下:
- 无奈解决
循环援用
的问题。举个例子:
const a = {val:2};
a.target = a;
拷贝
a
会呈现零碎栈溢出,因为呈现了有限递归的状况。
- 无奈拷贝一些非凡的对象,诸如
RegExp, Date, Set, Map
等 - 无奈拷贝
函数
(划重点)。
因而这个 api 先 pass 掉,咱们从新写一个深拷贝,简易版如下:
const deepClone = (target) => {if (typeof target === 'object' && target !== null) {const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop]);
}
}
return cloneTarget;
} else {return target;}
}
当初,咱们以刚刚发现的三个问题为导向,一步步来欠缺、优化咱们的深拷贝代码。
2. 解决循环援用
当初问题如下:
let obj = {val : 100};
obj.target = obj;
deepClone(obj);// 报错: RangeError: Maximum call stack size exceeded
这就是循环援用。咱们怎么来解决这个问题呢?
创立一个 Map。记录下曾经拷贝过的对象,如果说曾经拷贝过,那间接返回它行了。
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;
const deepClone = (target, map = new Map()) => {if(map.get(target))
return target;
if (isObject(target)) {map.set(target, true);
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop],map);
}
}
return cloneTarget;
} else {return target;}
}
当初来试一试:
const a = {val:2};
a.target = a;
let newA = deepClone(a);
console.log(newA)//{val: 2, target: { val: 2, target: [Circular] } }
如同是没有问题了, 拷贝也实现了。但还是有一个潜在的坑, 就是 map 上的 key 和 map 形成了强援用关系,这是相当危险的。我给你解释一下与之绝对的弱援用的概念你就明确了
在计算机程序设计中,弱援用与强援用绝对,
被弱援用的对象能够在任何时候被回收,而对于强援用来说,只有这个强援用还在,那么对象无奈被回收。拿下面的例子说,map 和 a 始终是强援用的关系,在程序完结之前,a 所占的内存空间始终不会被开释。
怎么解决这个问题?
很简略,让 map 的 key 和 map 形成弱援用即可。ES6 给咱们提供了这样的数据结构,它的名字叫 WeakMap,它是一种非凡的 Map, 其中的键是弱援用的。其键必须是对象,而值能够是任意的
略微革新一下即可:
const deepClone = (target, map = new WeakMap()) => {//...}
3. 拷贝非凡对象
可持续遍历
对于非凡的对象,咱们应用以下形式来甄别:
Object.prototype.toString.call(obj);
梳理一下对于可遍历对象会有什么后果:
["object Map"]
["object Set"]
["object Array"]
["object Object"]
["object Arguments"]
以这些不同的字符串为根据,咱们就能够胜利地甄别这些对象。
const getType = Object.prototype.toString.call(obj);
const canTraverse = {'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Object]': true,
'[object Arguments]': true,
};
const deepClone = (target, map = new Map()) => {if(!isObject(target))
return target;
let type = getType(target);
let cloneTarget;
if(!canTraverse[type]) {
// 解决不能遍历的对象
return;
}else {
// 这波操作相当要害,能够保障对象的原型不失落!let ctor = target.prototype;
cloneTarget = new ctor();}
if(map.get(target))
return target;
map.put(target, true);
if(type === mapTag) {
// 解决 Map
target.forEach((item, key) => {cloneTarget.set(deepClone(key), deepClone(item));
})
}
if(type === setTag) {
// 解决 Set
target.forEach(item => {target.add(deepClone(item));
})
}
// 解决数组和对象
for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop]);
}
}
return cloneTarget;
}
不可遍历的对象
const boolTag = '[object Boolean]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
对于不可遍历的对象,不同的对象有不同的解决。
const handleRegExp = (target) => {const { source, flags} = target;
return new target.constructor(source, flags);
}
const handleFunc = (target) => {// 待会的重点局部}
const handleNotTraverse = (target, tag) => {
const Ctor = targe.constructor;
switch(tag) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(target);
case regexpTag:
return handleRegExp(target);
case funcTag:
return handleFunc(target);
default:
return new Ctor(target);
}
}
4. 拷贝函数
- 尽管函数也是对象,然而它过于非凡,咱们独自把它拿进去拆解。
- 提到函数,在 JS 种有两种函数,一种是一般函数,另一种是箭头函数。每个一般函数都是
- Function 的实例,而箭头函数不是任何类的实例,每次调用都是不一样的援用。那咱们只须要
- 解决一般函数的状况,箭头函数间接返回它自身就好了。
那么如何来辨别两者呢?
答案是: 利用原型。箭头函数是不存在原型的。
const handleFunc = (func) => {
// 箭头函数间接返回本身
if(!func.prototype) return func;
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
// 别离匹配 函数参数 和 函数体
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if(!body) return null;
if (param) {const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {return new Function(body[0]);
}
}
5. 残缺代码展现
const getType = obj => Object.prototype.toString.call(obj);
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;
const canTraverse = {'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Object]': true,
'[object Arguments]': true,
};
const mapTag = '[object Map]';
const setTag = '[object Set]';
const boolTag = '[object Boolean]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
const handleRegExp = (target) => {const { source, flags} = target;
return new target.constructor(source, flags);
}
const handleFunc = (func) => {
// 箭头函数间接返回本身
if(!func.prototype) return func;
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
// 别离匹配 函数参数 和 函数体
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if(!body) return null;
if (param) {const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {return new Function(body[0]);
}
}
const handleNotTraverse = (target, tag) => {
const Ctor = target.constructor;
switch(tag) {
case boolTag:
return new Object(Boolean.prototype.valueOf.call(target));
case numberTag:
return new Object(Number.prototype.valueOf.call(target));
case stringTag:
return new Object(String.prototype.valueOf.call(target));
case symbolTag:
return new Object(Symbol.prototype.valueOf.call(target));
case errorTag:
case dateTag:
return new Ctor(target);
case regexpTag:
return handleRegExp(target);
case funcTag:
return handleFunc(target);
default:
return new Ctor(target);
}
}
const deepClone = (target, map = new WeakMap()) => {if(!isObject(target))
return target;
let type = getType(target);
let cloneTarget;
if(!canTraverse[type]) {
// 解决不能遍历的对象
return handleNotTraverse(target, type);
}else {
// 这波操作相当要害,能够保障对象的原型不失落!let ctor = target.constructor;
cloneTarget = new ctor();}
if(map.get(target))
return target;
map.set(target, true);
if(type === mapTag) {
// 解决 Map
target.forEach((item, key) => {cloneTarget.set(deepClone(key, map), deepClone(item, map));
})
}
if(type === setTag) {
// 解决 Set
target.forEach(item => {cloneTarget.add(deepClone(item, map));
})
}
// 解决数组和对象
for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
}
判断括号字符串是否无效(小米)
题目形容
给定一个只包含 '(',')','{','}','[',']' 的字符串 s,判断字符串是否无效。无效字符串需满足:- 左括号必须用雷同类型的右括号闭合。- 左括号必须以正确的程序闭合。示例 1:输出:s = "()"
输入:true
示例 2:输出:s = "()[]{}"
输入:true
示例 3:输出:s = "(]"
输入:false
答案
const isValid = function (s) {if (s.length % 2 === 1) {return false;}
const regObj = {"{": "}",
"(": ")",
"[": "]",
};
let stack = [];
for (let i = 0; i < s.length; i++) {if (s[i] === "{" || s[i] === "(" || s[i] === "[") {stack.push(s[i]);
} else {const cur = stack.pop();
if (s[i] !== regObj[cur]) {return false;}
}
}
if (stack.length) {return false;}
return true;
};
树形构造转成列表(解决菜单)
[
{
id: 1,
text: '节点 1',
parentId: 0,
children: [
{
id:2,
text: '节点 1_1',
parentId:1
}
]
}
]
转成
[
{
id: 1,
text: '节点 1',
parentId: 0 // 这里用 0 示意为顶级节点
},
{
id: 2,
text: '节点 1_1',
parentId: 1 // 通过这个字段来确定子父级
}
...
]
实现代码如下:
function treeToList(data) {let res = [];
const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);
delete item.children;
}
res.push(item);
});
};
dfs(data);
return res;
}
实现 find 办法
find
接管一个办法作为参数,办法外部返回一个条件find
会遍历所有的元素,执行你给定的带有条件返回值的函数- 合乎该条件的元素会作为
find
办法的返回值 - 如果遍历完结还没有合乎该条件的元素,则返回
undefined
var users = [{id: 1, name: '张三'},
{id: 2, name: '张三'},
{id: 3, name: '张三'},
{id: 4, name: '张三'}
]
Array.prototype.myFind = function (callback) {// var callback = function (item, index) {return item.id === 4}
for (var i = 0; i < this.length; i++) {if (callback(this[i], i)) {return this[i]
}
}
}
var ret = users.myFind(function (item, index) {return item.id === 2})
console.log(ret)