共计 9815 个字符,预计需要花费 25 分钟才能阅读完成。
字符串最长的不反复子串
题目形容
给定一个字符串 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;
};
图片懒加载
能够给 img 标签对立自定义属性 data-src='default.png'
,当检测到图片呈现在窗口之后再补充src 属性,此时才会进行图片资源加载。
function lazyload() {const imgs = document.getElementsByTagName('img');
const len = imgs.length;
// 视口的高度
const viewHeight = document.documentElement.clientHeight;
// 滚动条高度
const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
for (let i = 0; i < len; i++) {const offsetHeight = imgs[i].offsetTop;
if (offsetHeight < viewHeight + scrollHeight) {const src = imgs[i].dataset.src;
imgs[i].src = src;
}
}
}
// 能够应用节流优化一下
window.addEventListener('scroll', lazyload);
前端手写面试题具体解答
实现 JSON.parse
var json = '{"name":"cxk","age":25}';
var obj = eval("(" + json + ")");
此办法属于黑魔法,极易容易被 xss 攻打,还有一种 new Function
大同小异。
实现数组的 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.race
该办法的参数是 Promise 实例数组, 而后其 then 注册的回调办法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态 只能扭转一次, 那么咱们只须要把 Promise.race 中产生的 Promise 对象的 resolve 办法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.
Promise.race = function (args) {return new Promise((resolve, reject) => {for (let i = 0, len = args.length; i < len; i++) {args[i].then(resolve, reject)
}
})
}
实现类数组转化为数组
类数组转换为数组的办法有这样几种:
- 通过 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);
模板引擎实现
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄 18,性别 undefined
function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的构造
}
return template; // 如果模板没有模板字符串间接返回
}
判断对象是否存在循环援用
循环援用对象原本没有什么问题,然而序列化的时候就会产生问题,比方调用 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].length
let 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
}
深克隆(deepclone)
简略版:
const newObj = JSON.parse(JSON.stringify(oldObj));
局限性:
- 他无奈实现对函数、RegExp 等非凡对象的克隆
- 会摈弃对象的 constructor, 所有的构造函数会指向 Object
- 对象有循环援用, 会报错
面试版:
/**
* 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);
};
局限性:
- 一些非凡状况没有解决: 例如 Buffer 对象、Promise、Set、Map
- 另外对于确保没有循环援用的对象,咱们能够省去对循环援用的非凡解决,因为这很耗费工夫
原理详解实现深克隆
字符串解析问题
var a = {
b: 123,
c: '456',
e: '789',
}
var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;
// => 'a123aa456aa {a.d}aaaa'
实现函数使得将 str 字符串中的 {}
内的变量替换,如果属性不存在放弃原样(比方{a.d}
)
相似于模版字符串,但有一点出入,实际上原理大差不差
const fn1 = (str, obj) => {
let res = '';
// 标记位,标记后面是否有{
let flag = false;
let start;
for (let i = 0; i < str.length; i++) {if (str[i] === '{') {
flag = true;
start = i + 1;
continue;
}
if (!flag) res += str[i];
else {if (str[i] === '}') {
flag = false;
res += match(str.slice(start, i), obj);
}
}
}
return res;
}
// 对象匹配操作
const match = (str, obj) => {const keys = str.split('.').slice(1);
let index = 0;
let o = obj;
while (index < keys.length) {const key = keys[index];
if (!o[key]) {return `{${str}}`;
} else {o = o[key];
}
index++;
}
return o;
}
模仿 Object.create
Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。
// 模仿 Object.create
function create(proto) {function F() {}
F.prototype = proto;
return new F();}
判断是否是电话号码
function isPhone(tel) {var regx = /^1[34578]\d{9}$/;
return regx.test(tel);
}
转化为驼峰命名
var s1 = "get-element-by-id"
// 转化为 getElementById
var f = function(s) {return s.replace(/-\w/g, function(x) {return x.slice(1).toUpperCase();})
}
手写类型判断函数
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;
}
}
event 模块
实现 node 中回调函数的机制,node 中回调函数其实是外部应用了 观察者模式。
观察者模式:定义了对象间一种一对多的依赖关系,当指标对象 Subject 产生扭转时,所有依赖它的对象 Observer 都会失去告诉。
function EventEmitter() {this.events = new Map();
}
// 须要实现的一些办法:// addListener、removeListener、once、removeAllListeners、emit
// 模仿实现 addlistener 办法
const wrapCallback = (fn, once = false) => ({callback: fn, once});
EventEmitter.prototype.addListener = function(type, fn, once = false) {const hanlder = this.events.get(type);
if (!hanlder) {
// 没有 type 绑定事件
this.events.set(type, wrapCallback(fn, once));
} else if (hanlder && typeof hanlder.callback === 'function') {
// 目前 type 事件只有一个回调
this.events.set(type, [hanlder, wrapCallback(fn, once)]);
} else {
// 目前 type 事件数 >=2
hanlder.push(wrapCallback(fn, once));
}
}
// 模仿实现 removeListener
EventEmitter.prototype.removeListener = function(type, listener) {const hanlder = this.events.get(type);
if (!hanlder) return;
if (!Array.isArray(this.events)) {if (hanlder.callback === listener.callback) this.events.delete(type);
else return;
}
for (let i = 0; i < hanlder.length; i++) {const item = hanlder[i];
if (item.callback === listener.callback) {hanlder.splice(i, 1);
i--;
if (hanlder.length === 1) {this.events.set(type, hanlder[0]);
}
}
}
}
// 模仿实现 once 办法
EventEmitter.prototype.once = function(type, listener) {this.addListener(type, listener, true);
}
// 模仿实现 emit 办法
EventEmitter.prototype.emit = function(type, ...args) {const hanlder = this.events.get(type);
if (!hanlder) return;
if (Array.isArray(hanlder)) {
hanlder.forEach(item => {item.callback.apply(this, args);
if (item.once) {this.removeListener(type, item);
}
})
} else {hanlder.callback.apply(this, args);
if (hanlder.once) {this.events.delete(type);
}
}
return true;
}
EventEmitter.prototype.removeAllListeners = function(type) {const hanlder = this.events.get(type);
if (!hanlder) return;
this.events.delete(type);
}
手写 new 操作符
在调用 new
的过程中会产生以上四件事件:
(1)首先创立了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)
(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
// 判断参数是否是一个函数
if (typeof constructor !== "function") {console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, arguments);
// 判断返回对象
let flag = result && (typeof result === "object" || typeof result === "function");
// 判断返回后果
return flag ? result : newObject;
}
// 应用办法
objectFactory(构造函数, 初始化参数);
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;}
}
实现数组元素求和
- arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce((total,i) => total += i,0);
console.log(sum);
- arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]
let arr= arr.toString().split(',').reduce((total,i) => total += Number(i),0);
console.log(arr);
递归实现:
let arr = [1, 2, 3, 4, 5, 6]
function add(arr) {if (arr.length == 1) return arr[0]
return arr[0] + add(arr.slice(1))
}
console.log(add(arr)) // 21
实现斐波那契数列
// 递归
function fn (n){if(n==0) return 0
if(n==1) return 1
return fn(n-2)+fn(n-1)
}
// 优化
function fibonacci2(n) {const arr = [1, 1, 2];
const arrLen = arr.length;
if (n <= arrLen) {return arr[n];
}
for (let i = arrLen; i < n; i++) {arr.push(arr[i - 1] + arr[i - 2]);
}
return arr[arr.length - 1];
}
// 非递归
function fn(n) {
let pre1 = 1;
let pre2 = 1;
let current = 2;
if (n <= 2) {return current;}
for (let i = 2; i < n; i++) {
pre1 = pre2;
pre2 = current;
current = pre1 + pre2;
}
return current;
}
instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否呈现在某个实例对象的原型链上。
const myInstanceof = (left, right) => {
// 根本数据类型都返回 false
if (typeof left !== 'object' || left === null) return false;
let proto = Object.getPrototypeOf(left);
while (true) {if (proto === null) return false;
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}