前端编码
编码能力: 逻辑是否清晰、边界是否考虑到、思维是否活跃
手写 reduce
function MyReduce(fun, initValue) {
if (this === null) {
// this 不存在,抛出错误
throw new TypeError(
"Array.prototype.reduce " + "called on null or undefined"
);
}
if (typeof fun !== "function") {
// fun 不是function时,抛出错误
throw new TypeError(fun + " is not a function");
}
const value = Object(this);
let preValue, curValue, curIndex;
// TODO: arr 初始值?
if (initValue !== undefined) {
preValue = initValue;
curValue = arr[0];
curIndex = 0;
} else {
preValue = arr[0];
curValue = arr[1];
curIndex = 1;
}
for (let i = curIndex; i < value.length; i++) {
const item = value[i];
preValue = fun(preValue, item, i, arr);
}
return preValue;
}
// 把方法写入到原型链上
Array.prototype.MyReduce = MyReduce;
实现一个算法,字符串包含"[]" , "()" , "{}",判断是否正确闭合
利用双指针加字典解法。
- 设置头尾双指针。
- 分别判断头尾两个指针对应的元素是否是闭合元素,如果不是闭合元素,头指针向右移动,尾指针向左移动,直到两个指针指向的都是闭合元素。
- 判断头尾对应的元素是不是闭合元素,不是则返回下标,重复 2 步骤。
var dir = {
"{": "}",
"[": "]",
"(": ")",
")": "(",
"}": "{",
};
function atWhereNoClose(str) {
let len = str.length;
// 双指针
let left = 0;
let right = len - 1;
// 标记字符串是否闭合
let flg = true;
while (left < right) {
// 未匹配左符号
while (!/(\[|\{|\()/.test(str[left]) && left < right) {
left++;
}
// 未匹配有符号
while (!/(\}|\]|\))/.test(str[right]) && left < right) {
right--;
}
// 如果左侧符号与右侧符号不是匹配的
if (dir[str[left]] !== str[right]) {
// 不匹配的情况下,返回未闭合符号的下标
if (dir[str[left]] && left <= right) {
flg = left;
break;
}
} else {
// 如果匹配则移动两个指针
left++;
right--;
}
}
return flg;
}
console.log(atWhereNoClose("{[[ab}}"));
console.log(atWhereNoClose("{ab}"));
console.log(atWhereNoClose("{({[ab]})}"));
console.log(atWhereNoClose("ab}"));
JS 打乱数组
function getArrRandomly(arr) {
var len = arr.length;
for (var i = 0; i < len; i++) {
var randomIndex = Math.floor(Math.random() * (len - i)); // 这里一定要注意,后面不管是(i+1)还是(len-i),它们是时变的。
// 随机下标字符与 i 位字符进行交接
var itemAtIndex = arr[randomIndex];
arr[randomIndex] = arr[i];
arr[i] = itemAtIndex;
}
return arr;
}
懒加载实现
let lazyImages = [...document.querySelectorAll(".lazy-image")];
let inAdvance = 300; // 自定义一个高度,当距离 300px 到达图片时加载
function lazyLoad() {
lazyImages.forEach((image) => {
if (image.offsetTop < window.innerHeight + window.pageYOffset + inAdvance) {
// 从预设值中获取真实的图片 url.
image.src = image.dataset.src;
image.onload = () => image.classList.add("loaded");
}
});
}
lazyLoad();
window.addEventListener("scroll", _.debounce(lazyLoad, 16)); // 用到了 lodash 的防抖函数.
window.addEventListener("resize", _.debounce(lazyLoad, 16));
JS 发布订阅模式
const event = {
clientList: [],
listen: function (key, fn) {
if (this.clientListen[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function () {
const key = Array.prototype.shift.call(arguments);
const fns = this.clientList[key];
if (!fns || fns.length === 0) {
return false;
}
for (let i = 0, fn; (fn = fns[i++]); ) {
fn.apply(this, arguments);
}
},
remove: function (key, fn) {
const fns = this.clientList[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (let l = fns.length - 1; l >= 0; l--) {
const _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1);
}
}
}
},
};
深拷贝
1. JSON.parse JSON.stringify
简单的做法:JSON.parse(JSON.stringify(obj)), 但是该方法也是有局限性的:
- 会忽略
undefined,symbol, 函数 - 不能解决循环引用的对象 (会报错)
2. MessageChannel
如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel。 这种方法有局限性:
- 注意该方法是异步的
- 当属性值是函数的时候,会报错
function structuralClone(obj) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port2.onmessage = (ev) => resolve(ev.data);
port1.postMessage(obj);
});
}
var obj = {
a: 1,
b: {
c: 2,
},
};
obj.b.d = obj.b;
// 可以处理 undefined 和循环引用对象
const test = async () => {
const clone = await structuralClone(obj);
console.log(clone);
};
test();
3. for 循环深拷贝
// 判断属性值类型是原始类型和引用类型
function isObj(obj) {
return typeof obj === "object" && obj !== null;
}
// 为什么要用 WeakMap, key 可以使用对象? 更安全?
function deepClone(obj, map = new WeakMap()) {
// 解决环的情况
if (map.has(obj)) return map.get(obj);
// 数组 or 对象
let cloneObj = Array.isArray(obj) ? [] : {};
map.set(obj, cloneObj);
// for-in 会遍历原型链上的属性
for (let key in obj) {
// 需要判断是否是原型链上的属性,不是原型链才拷贝
if (obj.hasOwnProperty(key)) {
// 原始类型直接赋值(注意 null)
cloneObj[key] = isObj(obj[key]) ? deepClone(obj[key], map) : obj[key];
}
}
return cloneObj;
}
手写 call、apply 及 bind 函数
Function.prototype.myCall = function (context) {
context = context || window;
// 通过将函数挂载到 context 对象上来使得 this 改变
context.fn = this;
const args = [...arguments].slice(1);
const result = context.fn(...args);
delete context.fn;
return result;
};
Function.prototype.myApply = function (context) {
context = context || window;
context.fn = this;
let result;
// 处理参数和 call 有区别
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
delete context.fn;
return result;
};
bind 实现
bind 的实现对比其他两个函数略微地复杂了一点,因为 bind 需要返回一个函数,需要判断一些边界问题,以下是 bind 的实现
Function.prototype.myBind = function (context) {
if (typeof this !== "function") {
throw new TypeError(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
const that = this;
const args = [...arguments].slice(1);
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
// 通过 `new` 的方式,对于 `new` 的情况来说,不会被任何方式改变 `this`,所以对于这种情况我们需要忽略传入的 `this`
if (this instanceof F) {
return new that(...args, ...arguments);
}
return that.apply(context, args.concat(...arguments));
};
};
Function.prototype.bind = function () {
var self = this, // 保存原函数
context = [].shift.call(arguments), // 保存需要绑定的 this 上下文
args = [].slice.call(arguments); // 剩余的参数转为数组
return function () {
// 返回一个新函数
self.apply(context, [].concat.call(args, [].slice.call(arguments)));
};
};
数组去重(对象、非对象)
// set 去重
[...new Set(arr)];
// 利用 indexOf 去重
const newArr = arr.filter((item, index, arr) => index === arr.indexOf(item));
// 利用对象去重
let objA = {};
const newArrA = arr.filter((item, index, arr) =>
objA.hasOwnProperty(item) ? false : (objA[item] = true)
);
数组对象去重
根据对象的某一个属性去重
function removeDuplication(arr = [], key = "") {
if (!key) return Array.from(new Set(arr));
const map = {};
return arr.reduce((result, next) => {
map[next[key]] ? "" : (map[next[key]] = true && result.push(next));
return result;
}, []);
}
实现 flatten 扁平化函数
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组。
竟然原生就有这个 flat 函数,用来拍平数组。flat 函数的参数是层级。Infinity 无限大。 会拍平数组中的所有数组值。
Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b);
自己实现:
const flatten = (arr) =>
arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
递归实现
function flatten(arr, result = []) {
arr.forEach((item) => {
if (Array.isArray(item)) {
flatten(item, result);
} else {
result.push(item);
}
});
return result;
}
有层级的数组扁平化
function flatten(arr, deep) {
if (!deep) return arr;
let temp = [];
arr.forEach((item) => {
if (Array.isArray(item)) {
temp.push(...flatten(item, --deep));
} else {
temp.push(item);
}
});
return temp;
}
给出数组超过半数的数字,不存在的话输出没有(要求时间复杂度最低)
数组中有一个数字出现的次数超过数组长度的一半,说明它出现的次数比其他所有数字出现次数的和还要多。
function moreThanHalfNum(numbers) {
var obj = {};
var len = numbers.length;
// 统计字符出现的次数
numbers.forEach(function (s) {
if (obj[s]) {
obj[s]++;
} else {
obj[s] = 1;
}
});
for (var i in obj) {
if (obj[i] > Math.floor(len / 2)) {
return i;
}
}
return 0;
}
实现一下 curry(柯里化)
柯里化好处:
- 保留上一步的传参,能够进行延迟计算
- 优雅的写法,允许你写出来的代码更干净、更有表达力。
function curry(fn, arr = []) {
return fn.length === arr.length
? fn.apply(null, arr)
: function (...args) {
return curry(fn, arr.concat(args));
};
}
const curry = (fn, arr = []) =>
fn.length === arr.length
? fn(...arr)
: (...args) => curry(fn, [...arr, ...args]);
add 柯里化函数
用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数
add(2, 5); // 7
add(2)(5); // 7
实现
function add() {
var args = Array.prototype.slice.call(arguments);
// 返回一个函数,需要可以继续执行。
var fn = function () {
// 保存上一次传入的所有参数
var fn_args = Array.prototype.slice.call(arguments);
return add.apply(null, args.concat(fn_args));
};
// 返回指定对象的原始值。 不加这个的话,最终返回的是一个函数,无法最终拿到结果
fn.valueOf = function () {
return args.reduce(function (a, b) {
return a + b;
});
};
return fn;
}
实现一个 EventEmitter 类
function EventEmitter() {
this.callbacksMap = {};
}
EventEmitter.prototype.on = function (type, handler) {
const callbacks = this.callbacksMap[type];
if (!callbacks) {
this.callbacksMap[type] = [handler];
} else {
callbacks.push(handler);
}
return this;
};
EventEmitter.prototype.off = function (type, handler) {
const list = this.callbacksMap[type] || [];
for (let i = list.length; i >= 0; --i) {
if (!handler || list[i] === handler) {
list.splice(i, 1);
}
}
return this;
};
EventEmitter.prototype.emit = function (type, data) {
const list = this.callbacksMap[type];
if (list) {
for (let i = 0, len = list.length; i < len; ++i) {
list[i].call(this, data);
}
}
};
EventEmitter.prototype.once = function (type, handler) {
const self = this;
function wrapper() {
handler.apply(self, arguments);
self.off(type, wrapper);
}
this.on(type, wrapper);
return this;
};
解析一个参数
function getQueryString(name) {
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
const r = window.location.search.slice(1).match(reg);
if (r != null) {
return r[2];
}
return null;
}
url 所有参数转化为一个对象
var parseQueryString = function (search) {
if (!search) return {};
const regExp = /([^&=]+)=([\w\W]*?)(&|$)/g;
const ret = {};
search = search[0] === "?" ? search.slice(1) : search;
while ((result = regExp.exec(search)) != null) {
ret[result[1]] = result[2];
}
return ret;
};
手写原生 ajax
手写的 ajax 是否兼容 IE , IE 下面的 ajax 与普通浏览器的 ajax 对象不一样
function ajax(url, cb) {
let xhr;
// 创建 XMLHttpRequest 对象
if (window.XMLHttpRequest) {
// `XMLHttpRequest`只有在高级浏览器中才支持. 非 IE 内核
xhr = new XMLHttpRequest();
} else {
// IE内核
xhr = ActiveXObject("Microsoft.XMLHTTP");
}
// 绑定 onreadystatechange 事件
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
// 获取异步调用返回的数据
cb(xhr.responseText);
}
};
// 向服务器发送请求. 第三个参数表示是否异步
xhr.open("GET", url, true);
xhr.send();
}
实现 destructuringArray 方法
destructuringArray([1,[2,4],3], "[a,[b],c]");
result: { a:1, b:2, c:3 }
const destructuringArray = (value, keys) => {
const obj = {};
// "[a,[b],c]".replace(/\w+/g, '"$&"'). 能够将 keys 变成字符串数组
const arr = JSON.parse(keys.replace(/\w+/g, '"$&"'));
const iterate = (value, keys) => {
keys.forEach((item, index) => {
if (Array.isArray(item)) iterate(value[index], item);
else obj[item] = value[index];
});
};
iterate(value, arr);
return obj;
};
reduce 实现 map
Array.prototype.map = function (fn) {
const arr = this;
return arr.reduce((acc, cur, i) => {
acc.push(fn(cur, i, arr));
return acc;
}, []);
};
千分位
如 12000000.11 转化为 12,000,000.11
function test1(num) {
var str = +num + "";
var len = str.length;
if (len <= 3) return str;
num = "";
while (len > 3) {
len -= 3;
num = "," + str.substr(len, 3) + num;
}
return str.substr(0, len) + num;
}
function test2(num) {
// ?= 正向匹配:匹配位置
// ?! 正向不匹配:排除位置
var str = (+num).toString();
var reg = /(?=(?!\b)(\d{3})+$)/g;
return str.replace(reg, ",");
}
function commafy(num) {
// 取绝对值
const val = Math.abs(num);
// 取正负值
const isPositive = num === val;
// 正则表达式 ?= 前瞻:exp1(?=exp2) 查找exp2前面的exp1
// 正则表达式 ?! 负前瞻:exp1(?=exp2) exp1(?!exp2) 查找后面不是exp2的exp1
const result =
val &&
val.toString().replace(/(\d)(?=(\d{3})+\.)/g, function ($1, $2) {
return $2 + ",";
});
return isPositive ? result : `-${result}`;
}
function format(str) {
return str.replace(/(\d)(?=(?:\d{3})+$)/g, "$1,");
}
a~z 有 26 个字母,按照 1~26 编码,现在给定一个数字字符串,输出所有可能的解码结果,如:输入 1234,输出 ['axd', 'abcd', 'lcd']
/**
* @param {string} s
* @return {number}
*/
var numDecodings = function (s) {
if (s.charAt(0) == "0") return;
const chars = s.split();
return decode(chars, chars.length - 1);
};
function decode(chars, index) {
// 处理到了第一个字符,只能有一种解码方法,返回 1
if (index <= 0) return 1;
let count = 0;
const curr = chars[index];
const prev = chars[index - 1];
// 当前字符比 “0” 大,则直接利用它之前的字符串所求得的结果
if (curr > "0") {
count = decode(chars, index - 1);
}
// 由前一个字符和当前字符所构成的数字,值必须要在 1 到 26 之间,否则无法进行解码
if (prev == "1" || (prev == "2" && curr <= "6")) {
count += decode(chars, index - 2);
}
return count;
}
写一个大数相乘的解决方案。传两个字符串进来,返回一个字符串
function isAN(number) {
return /^d+$/.test(number);
}
/**
* @param {string} num1
* @param {string} num2
* @return {string}
*/
let multiply = function (num1 = "", num2 = "") {
//判断输入是不是数字
if (isAN(num1) || isAN(num2)) return "";
let len1 = num1.length,
len2 = num2.length;
let ans = [];
//这里倒过来遍历很妙,不需要处理进位了
for (let i = len1 - 1; i >= 0; i--) {
for (let j = len2 - 1; j >= 0; j--) {
let index1 = i + j,
index2 = i + j + 1;
let mul = num1[i] * num2[j] + (ans[index2] || 0);
// 当前位
ans[index1] = Math.floor(mul / 10) + (ans[index1] || 0);
// 进位
ans[index2] = mul % 10;
}
}
//去掉前置 0
let result = ans.join("").replace(/^0+/, "");
//不要转成数字判断,否则可能会超精度!
return !result ? "0" : result;
};
大数相加
1000000000 + 1000000000 允许返回字符串。处理大数。大数问题其实就是通过字符串来处理,从后往前加,然后处理进位的问题。
function add(a, b) {
// 取两个数字的最大长度
let maxLength = Math.max(a.length, b.length);
// 用 0 去补齐长度
a = a.padStart(maxLength, 0); //"0009007199254740991"
b = b.padStart(maxLength, 0); //"1234567899999999999"
// 定义加法过程中需要用到的变量
let t = 0;
let f = 0; // 进位
let sum = "";
for (let i = maxLength - 1; i >= 0; i--) {
t = parseInt(a[i]) + parseInt(b[i]) + f;
// Math.floor 向下取整。 Math.ceil 向上取整。
f = Math.floor(t / 10); // 获取需要进位的值
sum = (t % 10) + sum; // 余数是当前为数的值
}
// 最后一位的进位
if (f == 1) {
sum = "1" + sum;
}
return sum;
}
加法函数
写一个处理加法可能产生精度的函数,比如 0.1 + 0.2 = 0.3
function accAdd(arg1, arg2) {
var r1, r2, m;
try {
r1 = arg1.toString().split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
// 计算出一个倍数。看小数点后面的值的长度
m = Math.pow(10, Math.max(r1, r2));
// 先乘这个倍数,再除以这个倍数
return (arg1 * m + arg2 * m) / m;
}
var result = accAdd(0.1, 0.2);
console.log(result); // 0.3
自定义处理函数,放大指定的位数,最后再缩小。
// f代表需要计算的表达式,digit代表小数位数
Math.formatFloat = function (f, digit) {
// Math.pow(指数,幂指数)
var m = Math.pow(10, digit);
// Math.round() 四舍五入
return Math.round(f * m, 10) / m;
};
console.log(Math.formatFloat(0.3 * 8, 1)); // 2.4
console.log(Math.formatFloat(0.35 * 8, 2)); // 2.8
js 浮点数运算不精确如何解决?
实现一个判断变量类型的函数
function getType(obj) {
// 分开判断引用类型和基本类型
return typeof obj === "object"
? Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
: typeof obj;
}
二进制相加,给两字符串求值
对输入的字符串:去除其中的字符'b';去除相邻的'a'和'c'。 'aabcd' -> 'ad' 'aaabbccc' -> '' 不允许使用类似 string.replace 函数。要求时间、空间复杂度尽量优化
js 中处理大数。如果后端传给前端一个很大的数,前端会怎么样,该怎么处理?
找出两个有序数组中的重复项,分析时间和空间复杂度
工厂生产了十批原件,其中有一批是次品。正常的原件每个重量都是 100 克,次品的是 99 克。现在给你一个秤,要求只称一次,怎么确定哪批原件是次品。
比较版本号
var compareVersion = function (version1, version2) {
const arr1 = version1.split(".");
const arr2 = version2.split(".");
let len = Math.max(arr1.length, arr2.length);
for (let i = 0; i < len; i++) {
let data1 = 0,
data2 = 0;
if (i < arr1.length) {
data1 = parseInt(arr1[i]);
}
if (i < arr2.length) {
data2 = parseInt(arr2[i]);
}
if (data1 < data2) {
return -1;
} else if (data1 > data2) {
return 1;
}
}
return 0;
};
手写 JSONP
jsonp 原理:因为 jsonp 发送的并不是 ajax 请求,其实是动态创建 script 标签 script 标签是没有同源限制的,把 script 标签的 src 指向请求的服务端地址。
function jsonp(url, data = {}, callback = "callback") {
//处理json对象,拼接url
data.callback = callback;
let params = [];
for (let key in data) {
params.push(key + "=" + data[key]);
}
let script = document.createElement("script");
script.src = url + "?" + params.join("&");
document.body.appendChild(script);
//返回Promise
return new Promise((resolve, reject) => {
window[callback] = (data) => {
try {
resolve(data);
} catch (e) {
reject(e);
} finally {
//移除 script 元素
script.parentNode.removeChild(script);
console.log(script);
}
};
});
}
//请求数据
jsonp(
"http://photo.sina.cn/aj/index",
{
page: 1,
cate: "recommend",
},
"jsonCallback"
).then((data) => {
console.log(data);
});
Jsonp 方案需要服务端怎么配合 ?
用两个栈实现队列
用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。
思路
一个栈用来存储插入队列数据,一个栈用来从队列中取出数据。从第一个栈向第二个栈转移数据的过程中:数据的性质已经从后入先出变成了先入先出。
var CQueue = function () {
this.outStack = [];
this.inStack = [];
};
/**
* @param {number} value
* @return {void}
*/
CQueue.prototype.appendTail = function (value) {
if (value) {
// 新插入队列的数据都放在 inStack
this.inStack.push(value);
}
};
/**
* @return {number}
*/
CQueue.prototype.deleteHead = function () {
const { outStack, inStack } = this;
// 如果 outStack 为空,那么将 inStack 中的元素都转移过来
if (!outStack.length) {
while (inStack.length) {
outStack.push(inStack.pop());
}
}
return !outStack.length ? -1 : outStack.pop();
};
js 实现一个拖拽?
首先是三个事件,分别是 mousedown,mousemove,mouseup 当鼠标点击按下的时候,需要一个 tag 标识此时已经按下,可以执行 mousemove 里面的具体方法。
clientX,clientY 标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用 offsetX 和 offsetY 来表示元素的元素的初始坐标,移动的举例应该是: 鼠标移动时候的坐标-鼠标按下去时候的坐标。
也就是说定位信息为:
鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的 offetLeft.
还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,我们改变的是绝对定位条件下的 left 以及 top 等等值。 div:
<div class="drag" style="left:0;top:0;width:100px;height:100px">按住拖动</div>
<style>
.drag {
background-color: skyblue;
position: absolute;
line-height: 100px;
text-align: center;
}
</style>
js:
// 获取DOM元素
let dragDiv = document.getElementsByClassName("drag")[0];
// 鼠标按下事件 处理程序
let putDown = function (event) {
dragDiv.style.cursor = "pointer";
let offsetX = parseInt(dragDiv.style.left); // 获取当前的x轴距离
let offsetY = parseInt(dragDiv.style.top); // 获取当前的y轴距离
let innerX = event.clientX - offsetX; // 获取鼠标在方块内的x轴距
let innerY = event.clientY - offsetY; // 获取鼠标在方块内的y轴距
// 按住鼠标时为div添加一个border
dragDiv.style.borderStyle = "solid";
dragDiv.style.borderColor = "red";
dragDiv.style.borderWidth = "3px";
// 鼠标移动的时候不停的修改div的left和top值
document.onmousemove = function (event) {
dragDiv.style.left = event.clientX - innerX + "px";
dragDiv.style.top = event.clientY - innerY + "px";
// 边界判断
if (parseInt(dragDiv.style.left) <= 0) {
dragDiv.style.left = "0px";
}
if (parseInt(dragDiv.style.top) <= 0) {
dragDiv.style.top = "0px";
}
if (
parseInt(dragDiv.style.left) >=
window.innerWidth - parseInt(dragDiv.style.width)
) {
dragDiv.style.left =
window.innerWidth - parseInt(dragDiv.style.width) + "px";
}
if (
parseInt(dragDiv.style.top) >=
window.innerHeight - parseInt(dragDiv.style.height)
) {
dragDiv.style.top =
window.innerHeight - parseInt(dragDiv.style.height) + "px";
}
};
// 鼠标抬起时,清除绑定在文档上的mousemove和mouseup事件
// 否则鼠标抬起后还可以继续拖拽方块
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
// 清除border
dragDiv.style.borderStyle = "";
dragDiv.style.borderColor = "";
dragDiv.style.borderWidth = "";
};
};
// 绑定鼠标按下事件
dragDiv.addEventListener("mousedown", putDown, false);
需要通过 threshold 参数控制调用函数频率
const yourFunction = function (func, threshold) {
// 请实现
};
const triggerSearch = yourFunction((val) => {
const { onSearch } = this.props;
onSearch(val);
}, 300);
triggerSearch(searchText);
实现
const yourFunction = function (func, threshold) {
let timeOut;
return function () {
if (!timeOut) {
timeOut = setTimeout(() => {
timeOut = null;
func.apply(this, arguments);
}, threshold);
}
};
};
const triggerSearch = yourFunction((val) => {
const { onSearch } = this.props;
onSearch(val);
}, 300);
事件触发器
兼容所有浏览器
var fireEvent = function (element, event) {
if (document.createEventObject) {
var mockEvent = document.createEventObject();
return element.fireEvent("on" + event, mockEvent);
} else {
var mockEvent = document.createEvent("HTMLEvents");
mockEvent.initEvent(event, true, true);
return element.dispatchEvent(mockEvent);
}
};
代码中 a 在什么情况下会打印 1?
var a = ?;
if(a == 1 && a == 2 && a == 3){
console.log(1);
}
考察隐式转换,重写 toString 方法即可
var a = {
i: 1,
toString() {
return a.i++;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log(1);
}
let a = {
i: 1,
valueOf() {
return a.i++;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log("1");
}
var a = [1, 2, 3];
a.join = a.shift;
if (a == 1 && a == 2 && a == 3) {
console.log("1");
}
let a = {
[Symbol.toPrimitive]: (
(i) => () =>
++i
)(0),
};
if (a == 1 && a == 2 && a == 3) {
console.log("1");
}
实现 (5).add(3).minus(2) 功能
Number.prototype.add = function (value) {
let number = parseFloat(value);
if (typeof number !== "number" || Number.isNaN(number)) {
throw new Error("请输入数字或者数字字符串~");
}
return this + number;
};
Number.prototype.minus = function (value) {
let number = parseFloat(value);
if (typeof number !== "number" || Number.isNaN(number)) {
throw new Error("请输入数字或者数字字符串~");
}
return this - number;
};
要求设计 LazyMan 类,实现以下功能
LazyMan("Tony");
// Hi I am Tony
LazyMan("Tony").sleep(10).eat("lunch");
// Hi I am Tony
// 等待了10秒...
// I am eating lunch
LazyMan("Tony").eat("lunch").sleep(10).eat("dinner");
// Hi I am Tony
// I am eating lunch
// 等待了10秒...
// I am eating diner
LazyMan("Tony")
.eat("lunch")
.eat("dinner")
.sleepFirst(5)
.sleep(10)
.eat("junk food");
// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了10秒...
// I am eating junk food
答案:
class LazyManClass {
constructor(name) {
this.taskList = [];
this.name = name;
console.log(`Hi I am ${this.name}`);
setTimeout(() => {
this.next();
}, 0);
}
eat(name) {
var that = this;
var fn = (function (n) {
return function () {
console.log(`I am eating ${n}`);
that.next();
};
})(name);
this.taskList.push(fn);
return this;
}
sleepFirst(time) {
var that = this;
var fn = (function (t) {
return function () {
setTimeout(() => {
console.log(`等待了${t}秒...`);
that.next();
}, t * 1000);
};
})(time);
this.taskList.unshift(fn);
return this;
}
sleep(time) {
var that = this;
var fn = (function (t) {
return function () {
setTimeout(() => {
console.log(`等待了${t}秒...`);
that.next();
}, t * 1000);
};
})(time);
this.taskList.push(fn);
return this;
}
next() {
var fn = this.taskList.shift();
fn && fn();
}
}
function LazyMan(name) {
return new LazyManClass(name);
}
LazyMan("Tony")
.eat("lunch")
.eat("dinner")
.sleepFirst(5)
.sleep(4)
.eat("junk food");
实现函数字符串转对象
'a.b.c'
=>
a: {
b: {
c: null
}
}
输入字符串输出二维数组
`
12312
1 3
12 3
`
=>
[
['12312'],
['13'],
['123']
]
function handler(str = "") {
return str
.split("\n")
.filter(Boolean)
.map((val) => val.replace(/\s/g, ""));
}
实现 toFix 函数
atoi 把任意进制的数转为十进制的数。 需要考虑负数。
十六进制转十进制
// 10进制转16进制
function handler(params) {
typeof params == "string" ? (params = Number(params)) : "";
console.log(params.toString(16)); //a8
}
handler("168");
// 16进制转10进制
function handler2(params) {
console.log(Number("0x" + params)); //168
}
handler2("a8");
// 16进制转10进制
function handler3(params) {
console.log(parseInt(params, 16)); //168
}
handler3("a8");
实现 36 进制转换
function getNums36() {
var nums36 = [];
for (var i = 0; i < 36; i++) {
if (i >= 0 && i <= 9) {
nums36.push(i);
} else {
nums36.push(String.fromCharCode(i + 87));
}
}
return nums36;
}
//十进制数转成36进制
function scale36(n) {
var arr = [];
var nums36 = getNums36();
while (n) {
var res = n % 36;
//作为下标,对应的36进制数,转换成
arr.unshift(nums36[res]);
//去掉个位
n = parseInt(n / 36);
}
return arr.join("");
}
解析对象属性
function find(obj, str) {
var arr = str.split("."); // 将传入的字符串按照"."切割成数组
var pointer = obj; // 初始化指针变量为传入的对象本身
for (var i = 0; i < arr.length; i++) {
var key = arr[i];
if (pointer.hasOwnProperty(key)) {
// 判断当前指针变量所指的对象中是否包含该属性
pointer = pointer[key]; // 将指针变量指向该属性对应的对象
} else {
return undefined; // 如果不存在该属性,返回undefined
}
}
return pointer; // 循环结束时,指针变量所指的对象即为所要查找的属性值
}
// 示例
var obj = { a: { b: { c: 1 } } };
console.log(find(obj, "a.b.c")); // 1
console.log(find(obj, "a.d.c")); // undefined
实现一个定时器函数 myTimer(fn, a, b)
实现一个定时器函数 myTimer(fn, a, b), 让 fn 执行, 第一次执行是 a 毫秒后, 第二次执行是 a+b 毫秒后, 第三次是 a+2b 毫秒, 第 N 次执行是 a+Nb 毫秒后
要求: 1、白板手撕 2、myTimer 要有返回值,并且返回值是一个函数,调用该函数,可以让 myTimer 停掉
function myTimer(fn, a, b) {
let timerId;
let count = 0;
function schedule() {
const delay = a + count * b;
timerId = setTimeout(() => {
fn();
count++;
schedule();
}, delay);
}
schedule();
return function () {
clearTimeout(timerId);
};
}
实现一个持续的动画效果
js 定时器实现
var e = document.getElementById("e");
var flag = true;
var left = 0;
setInterval(() => {
left == 0 ? (flag = true) : left == 100 ? (flag = false) : "";
flag ? (e.style.left = ` ${left++}px`) : (e.style.left = ` ${left--}px`);
}, 1000 / 60);
js API requestAnimationFrame 实现
//兼容性处理
window.requestAnimFrame = (function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
}
);
})();
var e = document.getElementById("e");
var flag = true;
var left = 0;
function render() {
left == 0 ? (flag = true) : left == 100 ? (flag = false) : "";
flag ? (e.style.left = ` ${left++}px`) : (e.style.left = ` ${left--}px`);
}
(function animloop() {
render();
requestAnimFrame(animloop);
})();
优势:
浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果 解决毫秒的不精确性 避免过度渲染(渲染频率太高、tab 不可见暂停等等) 注:requestAnimFrame 和 定时器一样也头一个类似的清除方法 cancelAnimationFrame
css 实现:
.test {
animation: mymove 5s infinite;
@keyframes mymove {
from {
top: 0px;
}
to {
top: 200px;
}
}
}
原生 js 实现 MVVM
<span id="box">
<h1 id="text"></h1>
<input type="text" id="input" oninput="inputChange(event)" />
<button id="button" onclick="clickChange()">
Click me
</button>
</span>
const input = document.getElementById("input");
const text = document.getElementById("text");
const button = document.getElementById("button");
const data = {
value: "",
};
function defineProperty(obj, attr) {
let val;
Object.defineProperty(obj, attr, {
set(newValue) {
console.log("set");
if (val === newValue) {
return;
}
val = newValue;
input.value = newValue;
text.innerHTML = newValue;
},
get() {
console.log("get");
return val;
},
});
}
defineProperty(data, "value");
function inputChange(event) {
data.value = event.target.value;
}
function clickChange() {
data.value = "hello";
}
实现模板字符串解析
描述:实现函数使得将 template 字符串中的 {{}} 内的变量替换。
核心:使用字符串替换方法 str.replace(regexp|substr, newSubStr|function),使用正则匹配代换字符串。
实现:
function render(template, data) {
// 模板字符串正则 /\{\{(\w+)\}\}/, 加 g 为全局匹配模式, 每次匹配都会调用后面的函数
let computed = template.replace(/\{\{(\w+)\}\}/g, function (match, key) {
// match: 匹配的子串; key:括号匹配的字符串
return data[key];
});
return computed;
}
// 测试
let template = "我是{{name}},年龄{{age}},性别{{sex}}";
let data = {
name: "张三",
age: 18,
};
console.log(render(template, data)); // 我是张三,年龄18,性别undefined
实现一个构造函数,new 的时候每次加 1
new 操作符干了什么
找出字符串中出现次数最多的那一个字符
// 1、转换成键值格式数据 eg.'程序员程序员' -> {'程': 2, '序': 2, '员': 2}
// 2、再转换成数组格式 eg.{'程': 2, '序': 2, '员': 2} ->[2]:['程', '序', '员']。
function getMostChar2(str) {
var strArr = str.split(""),
obj = {},
arr = [],
len = strArr.length,
i,
key;
for (i = 0; i < len; i++) {
obj[strArr[i]] ? obj[strArr[i]]++ : (obj[strArr[i]] = 1); //记录数目
}
for (key in obj) {
arr[obj[key]] ? arr[obj[key]].push(key) : (arr[obj[key]] = [key]); //取出
}
return { mostChar: arr[arr.length - 1].join(), maxLen: arr.length - 1 };
}
请写一个将字符串转成驼峰的方法?
例如:border-bottom-color -> borderBottomColor
function camelCase(str) {
return (
str &&
str.replace(/-([a-z]|[0-9])/gi, function (all, $1) {
return ($1 + "").toUpperCase();
})
);
}
数字转换中文大写
toChineseNum(12345); // 一万二千三百四十五
const toChineseNum = (num) => {
const keys = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
const count = ["", "十", "百", "千"];
var str = "",
nums = num.toString().split("").reverse();
nums.map(function (value, index) {
str =
keys[value] +
(value == 0 ? "" : count[index > 3 ? index % 4 : index]) +
(index == 4 ? "万" : "") +
str;
});
/*
* 需要去掉的零:
* 1.后面跟着零的零
* 2.最后连续的零
* 3.万前面的零
*/
return str.replace(/零(?=零)|零$|零(?=万)/g, "");
};
js 去除字符串空格
去除所有空格
str.replace(/\s/g, "");
去除两边空格
str.replace(/^\s+|\s+\$/g, "");
原生方法
str.trim();
怎么去除字符串紧邻相同的字符。比如:'aaaabb111222' => 'ab12'
"aaaabb111222".replace(/(.)\1+/g, "$1");
异步加法
假设有一台本地机器,无法做加减乘除运算(包括位运算),因此无法执行 a + b、a+ = 1 这样的 JS 代码,然后我们提供一个服务器端的 HTTP API,可以传两个数字类型的参数,响应结果是这两个参数的和,这个 HTTP API 的 JS SDK(在本地机器上运行)的使用方法如下:
// 异步加法
function asyncAdd(a, b) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(a + b);
}, Math.floor(Math.random() * 1000));
});
}
// 要求 sum 能在最短的时间里返回以上结果
async function sum() {
console.time("sum");
function queue(nums) {
let len = nums.length;
// 边界条件
if (len === 0) return Promise.resolve(0);
if (len === 1) return Promise.resolve(nums[0]);
const result = [];
let executing = [];
while (len >= 2) {
result.push([nums[len - 1], nums[len - 2]]);
len = len - 2;
}
if (len === 1) {
executing.push(Promise.resolve(nums[0]));
}
executing = [...executing, ...result.map((item) => asyncAdd(...item))];
return Promise.all(executing).then((data) => queue(data));
}
const result = await queue([...arguments]);
console.timeEnd("sum");
console.log(result);
return result;
}
async function sum1(...nums) {
console.time("sum1");
// 主运行队列
let executing = [];
// 副运行队列
let executingCopy = [];
function queue() {
let len = nums.length;
if (len === 0) return Promise.resolve(0);
if (len === 1) return Promise.resolve(nums[0]);
const result = [];
while (len >= 2) {
result.push([nums[len - 1], nums[len - 2]]);
len = len - 2;
}
if (len === 1) {
executingCopy.push(nums[0]);
}
executing = result.map((item) => {
const p = asyncAdd(...item).then((data) => {
executing.splice(executing.indexOf(p), 1);
return data;
});
return p;
});
function loop() {
return Promise.race(executing).then((data) => {
executingCopy.push(data);
// 副队列满 2 个,创建一个新的 promise 加入主队列
if (executingCopy.length >= 2) {
const a = executingCopy.shift();
const b = executingCopy.shift();
const p = asyncAdd(a, b).then((data) => {
executing.splice(executing.indexOf(p), 1);
return data;
});
executing.push(p);
}
// 退出条件:主队列执行完毕,副队列只剩最后一个结果
if (!executing.length) {
return executingCopy[0];
}
return loop();
});
}
return loop();
}
const result = await queue([...arguments]);
console.timeEnd("sum1");
console.log(result);
return result;
}
(async () => {
await sum(1, 4, 6, 9, 2, 4);
await sum1(1, 4, 6, 9, 2, 4);
await sum(3, 4, 9, 2, 5, 3, 2, 1, 7);
await sum1(3, 4, 9, 2, 5, 3, 2, 1, 7);
await sum(1, 6, 0, 5);
await sum1(1, 6, 0, 5);
})();
// 思考:
// 1. 并行计算
// 2. 本地缓存