场景应用
# 场景应用
# 实现add(1)(2)(3)
// 题目
var foo = function(...args) {
// 要求实现函数体
}
var f1 = foo(1,2,3); f1.getValue();
// 6 输出是参数的和
var f2 = foo(1)(2,3); f2.getValue();
// 6
var f3 = foo(1)(2)(3)(4); f3.getValue();
// 10
function foo(...args) {
const target = (...newArgs) => foo(...args, ...newArgs)
target.getValue = () => args.reduce((p, n) => p + n, 0)
return target
}
// es6写法
function curry(fn, ...args) {
// 继续接受参数然后柯里化
return args.length < fn.length
? (...params) => {
return curry(fn, ...args, ...params);
}
: fn(...args);
}
let addCurry = curry(add);
console.log(addCurry(1, 2, 3)); //15
console.log(addCurry(1)(2, 3)); //15
console.log(addCurry(1)(2)(3)); //15
// 不定参数个数
function add(...args) {
//求和
return args.reduce((a, b) => a + b);
}
function curry(fn) {
let args = [];
return function temp(...newArgs) {
if (newArgs.length) {
args = [...args, ...newArgs];
return temp;
} else {
let val = fn.apply(this, args);
args = []; //保证再次调用时清空
return val;
}
};
}
let addCurry = curry(add);
console.log(addCurry(1)(2)(3)(4, 5)()); //15
console.log(addCurry(1)(2)(3, 4, 5)()); //15
console.log(addCurry(1)(2, 3, 4, 5)()); //15
// 这个必须要加一个调用的()
// 定参数个数
function add(x, y, z) {
//求和
return x + y + z;
}
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function () {
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this, fn, newArgs);
} else {
return fn.apply(this, newArgs);
}
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 实现发布-订阅模式
// once 参数表示是否只是触发一次
const wrapCallback = (fn, once = false) => ({ callback: fn, once })
class EventEmitter {
constructor() {
this.events = new Map()
}
on(type, fn, once = false) { // 监听订阅
let handler = this.events.get(type)
if (!handler) {
this.events.set(type, wrapCallback(fn, once)) // 绑定回调
} else if (handler && typeof handler.callback === 'function') {
this.events.set(type, [handler, wrapCallback(fn, once)]) // 超过一个转为数组
} else {
handler.push(wrapCallback(fn, once))
}
}
off(type, fn) { // 删除某个事件的回调,假如回调 <= 1,则等同 allOff 方法
let handler = this.events.get(type)
if (!handler) return;
// 只有一个回调事件直接删除该订阅
if (!Array.isArray(handler) &&
handler.callback === fn.callback) this.events.delete(type)
for (let i=0; i<handler.length; i++) {
let item = handler[i]
if (item.callback === fn.callback) {
handler.splice(i, 1)
i-- // 数组塌陷,i 往前一位
if (handler.length === 1) this.events.set(type, handler[0])
}
}
}
// once:该订阅事件 type 只触发一次,之后自动移除
once(type, fn) {
this.on(type, fn, true)
}
emit(type, ...args) {
let handler = this.events.get(type)
if (!handler) return;
if (Array.isArray(handler)) {
handler.map(item => {
item.callback.apply(this, args) // args 参数少,可以换成 call
if (item.once) this.off(type, item) // 处理 once 的情况,off 移除
})
} else {
handler.callback.apply(this, args) // 处理非数组
}
}
allOff(type) {
let handler = this.events.get(type)
if (!handler) return;
this.events.delete(type)
}
}
let e = new EventEmitter()
e.on('eventA', () => {
console.log('eventA 事件触发')
})
e.on('eventA', () => {
console.log('✨ eventA 事件又触发了 ✨')
})
function f() {
console.log('eventA 事件我只触发一次');
}
e.once('type', f)
e.emit('type')
e.emit('type')
e.allOff('type')
e.emit('type')
// eventA 事件触发
// ✨ eventA 事件又触发了 ✨
// eventA 事件我只触发一次
// eventA 事件触发
// ✨ eventA 事件又触发了 ✨
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
- 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在
# 实现观察者模式
class Subject {
constructor(name = "xiaoSong") {
this.state = name;
this.observer = [];
}
attach(o) {
this.observer.push(o);
}
setState(newVal) {
this.state = newVal;
this.observer.forEach((o) => o.update(this));
}
}
class Observer {
constructor(sex = "male") {
this.sex = sex;
}
update(student) {
console.log(`${this.sex}, ${student.state}`);
}
}
const student = new Subject();
const parent = new Observer();
const teacher = new Observer("female");
student.attach(parent);
student.attach(teacher);
student.setState("xiaoming");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 实现一个异步加法字节
// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {
setTimeout(function () {
callback(null, a + b);
}, 500);
}
2
3
4
5
6
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");
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 实现十进制转为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 + 55));
}
}
return nums36;
}
function scale36(n) {
// 单独的功能函数
// 16进制数: 0-9 A-F 36进制数: 0-9 A-Z
const arr = [];
var nums36 = getNums36();
// 36 10
if(!Number.isInteger(n)){//浮点数判断,目前不支持小数
console.warn('不支持小数转换');
return n;
}
var neg = '';
if(n < 0){//对负数的处理
neg = '-';
n = Math.abs(n)
}
while(n) {
var res = n % 36;
console.log(res,'+++++++');
arr.unshift(nums36[res]);
// 进位
n = parseInt(n/36);
console.log(n,'---------');
}
arr.unshift(neg)
return arr.join("");
}
console.log(scale36(20)); // 10
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 实现十进制转七进制
function getNums7() {
var nums7 = [];
for(var i = 0; i < 7 ; i++) {
nums7.push(i)
}
return nums7;
}
var convertToBase7 = function(num) {
// 单独的功能函数
const arr = [];
var nums7 = getNums7();
var neg = '';
if(num < 0){//对负数的处理
neg = '-';
num = Math.abs(num)
}
if(num == 0) {
return num + "";
}
while(num) {
var res = num % 7; // 对高位数据进行截取
arr.unshift(nums7[res]);
// 进位
num = parseInt(num/7);
}
arr.unshift(neg);
return arr.join("");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 解析URLparams
为对象
function getParams(u) {
const s = new URLSearchParams(u.search)
const obj = {}
s.forEach((v, k) => (obj[k] = v))
return obj
}
const url = 'http://sample.com/?a=1&b=2&c=xx&d=2#hash';
getParams(new URL(url))
// {a: "1", b: "2", c: "xx", d: "2"}
const dismantle = (url) => {
const aimUrl = url.split('?').pop().split('#').shift().split('&');
const res = {};
aimUrl.forEach(item => {
const [key, val] = item.split('=');
res[key] = val;
});
return res;
}
dismantle('http://sample.com/?a=1&b=2&c=xx&d=2#hash')
// {a: "1", b: "2", c: "xx", d: "2"}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 将数组转换为树
// input
let arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]
// output
[
{
id: 1,
name: "部门1",
pid: 0,
children: [
{
id: 2,
name: "部门2",
pid: 1,
children: [],
},
{
id: 3,
name: "部门3",
pid: 1,
children: [
// 结果 ,,,
],
},
],
},
];
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function arrayToTree(items) {
const result = []; // 存放结果集
const itemMap = {}; //
for (const item of items) {
const id = item.id;
const pid = item.pid;
if (!itemMap[id]) {
itemMap[id] = {
children: [],
};
}
itemMap[id] = {
...item,
children: itemMap[id]["children"],
};
const treeItem = itemMap[id];
if (pid === 0) {
result.push(treeItem);
} else {
if (!itemMap[pid]) {
itemMap[pid] = {
children: [],
};
}
itemMap[pid].children.push(treeItem);
}
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 斐波那契数列
// 递归,时间复杂度为 O(2^n)
function fibSequence(n) {
if (n === 1 || n === 2) return n - 1;
return fib(n - 1) + fib(n - 2)
}
// 或者使用迭代,时间复杂度为 O(n),推荐!
function fibSequence(n) {
let a = 0, b = 1, c = a + b
for (let i = 3; i < n; i++) {
a = b
b = c
c = a + b
}
return c
}
// 使用es6优化迭代形式
function fibonacci(n) {
let cur = 0;
let next = 1;
for (let i = 0; i < n; i++) {
[cur, next] = [next, cur + next]
}
return cur;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
尾递归调用
如下图:由于尾调用,理论上可以不记住位置2
,而从函数g
直接带着返回值返回到位置1
(即函数f
的返回位置)
由于尾调用之前的工作已经完成了,所以当前函数帧(即调用时创建的栈帧)上包含局部变量等等大部分的东西都不需要了,当前的函数帧经过适当的变动以后可以直接当做尾调用的函数的帧使用,然后程序就可以跳到被尾调用的函数。
用上图中的例子来解释就是,函数f
尾调用函数g
之前的工作已经完成了,所以调用函数f
时创建的函数帧直接给函数g
用了,就不需要重新给函数g
创建栈帧。
这种函数帧变动重复使用的过程就叫做尾调用消除或尾调用优化
function fibSequence(n, pre = 0, cur = 1) {
if(n === 0) return n
if(n === 1) return cur
return fibSequence(n - 1, cur, pre + cur)
}
fibSequence(10)
2
3
4
5
6
7
8
# 扑克牌算法字节
魔术师手中有一堆扑克牌,观众不知道它的顺序,接下来魔术师:
- 从牌顶拿出一张牌, 放到桌子上
- 再从牌顶拿一张牌, 放在手上牌的底部
如此往复(不断重复以上两步),直到魔术师手上的牌全部都放到了桌子上。
此时,桌子上的牌顺序为: (牌顶) 1,2,3,4,5,6,7,8,9,10,11,12,13
(牌底)。
问:原来魔术师手上牌的顺序,用函数实现。
const calc = (arr) => {
const origin = [];
for (let i = 0; i < arr.length; i++) {
if (origin.length) {
const item = origin.pop();
origin.unshift(item);
}
origin.unshift(result[i])
}
return origin;
}
// 或者(我自己写的)
function sort(nums) {
const res = [];
while (nums.length) {
if (res.length) res.unshift(res.pop());
let card = nums.shift();
res.unshift(card);
}
return res;
}
// 测试
const result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
// 原有顺序
calc(result)
// [13, 2, 12, 6, 11, 3, 10, 5, 9, 1, 8, 4, 7]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 约瑟夫环问题
var lastRemaining = function(n, m) {
let result = 0;
for(let i = 2; i <= n; i++) {
result = (m + result) % i;
}
return result
};
2
3
4
5
6
7
# 实现class
的private
const MyClass = (function() { // 利用闭包和 WeakMap
const _x = new WeakMap()
class InnerClass {
constructor(x) {
_x.set(this, x)
}
getX() {
return x.get(this)
}
}
return InnerClass
})()
let myClass = new MyClass(5)
console.log(myClass.getX()) // 5
console.log(myClass.x) // undefined
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 实现vue-router
的hash
模式
// 定义 Router
class Router {
constructor () {
this.routes = {}; // 存放路由path及callback
this.currentUrl = '';
// 监听路由change调用相对应的路由回调
window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}
route(path, callback){
this.routes[path] = callback;
}
push(path) {
this.routes[path] && this.routes[path]()
}
}
// 使用 router
window.miniRouter = new Router();
miniRouter.route('/', () => console.log('page1'))
miniRouter.route('/page2', () => console.log('page2'))
miniRouter.push('/') // page1
miniRouter.push('/page2') // page2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 实现vue-router
的history
模式
// 定义 Router
class Router {
constructor () {
this.routes = {};
this.listerPopState()
}
init(path) {
history.replaceState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
route(path, callback){
this.routes[path] = callback;
}
push(path) {
history.pushState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
listerPopState () {
window.addEventListener('popstate' , e => {
const path = e.state && e.state.path;
this.routers[path] && this.routers[path]()
})
}
}
// 使用 Router
window.miniRouter = new Router();
miniRouter.route('/', ()=> console.log('page1'))
miniRouter.route('/page2', ()=> console.log('page2'))
// 跳转
miniRouter.push('/page2') // page2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 实现es6
的extends
function B(name){
this.name = name;
};
function A(name,age){
//1.将A的原型指向B
Object.setPrototypeOf(A,B);
//2.用A的实例作为this调用B,得到继承B之后的实例,这一步相当于调用super
Object.getPrototypeOf(A).call(this, name)
//3.将A原有的属性添加到新实例上
this.age = age;
//4.返回新实例对象
return this;
};
var a = new A('poetry',22);
console.log(a);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 实现一个事件委托易错
ul.addEventListener('click', function (e) {
console.log(e,e.target)
if (e.target.tagName.toLowerCase() === 'li') {
console.log('打印') // 模拟fn
}
})
2
3
4
5
6
「有个小bug
,如果用户点击的是 li
里面的 span
,就没法触发 fn
,这显然不对」 👇
<ul id="xxx">下面的内容是子元素1
<li>li内容>>> <span> 这是span内容123</span></li>
下面的内容是子元素2
<li>li内容>>> <span> 这是span内容123</span></li>
下面的内容是子元素3
<li>li内容>>> <span> 这是span内容123</span></li>
</ul>
2
3
4
5
6
7
这样子的场景就是不对的,那我们看看高级版本👇
function delegate(element, eventType, selector, fn) {
element.addEventListener(
eventType,
(e) => {
let el = e.target;
while (!el.matches(selector)) {
if (element === el) {
el = null;
break;
}
el = el.parentNode;
}
el && fn.call(el, e, el);
},
true
);
return element;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 统计DOM
个数
/**
* 2.DOM 的体积过大会影响页面性能,假如你想在用户关闭页面时统计(计算并反馈给服务器)
当前页面中元素节点的数量总和、元素节点的最大嵌套深度以及最大子元素个数,请用 JS 配合
原生 DOM API 实现该需求(不用考虑陈旧浏览器以及在现代浏览器中的兼容性,可以使用任意
浏览器的最新特性;不用考虑 shadow DOM)。比如在如下页面中运行后:
*/
<html>
<head></head>
<body>
<div>
<span>f</span>
<span>o</span>
<span>o</span>
</div>
</body>
</html>
// 会输出:
{
totalElementsCount: 7,
maxDOMTreeDepth: 4,
maxChildrenCount: 3
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const obj = {};
class Ele {
constructor(ele) {
this.ele = ele;
// 统计的深度
this.funum = 1;
}
//取当前节点的元素深度
getEleDepth() {
let fuele = this.ele.parentNode;
if (fuele !== document) {
this.funum++;
this.ele = fuele;
return this.getEleDepth();
} else {
return this.funum;
}
}
//去当前节点的子元素个数
getEleSubNum() {
let zieles = this.ele.childNodes,
zinum = 0;
for (let i = 0; i < zieles.length; i++) {
if (zieles[i].nodeName !== "#text") {
zinum++;
}
}
return zinum;
}
}
const totalElements = document.getElementsByTagName("*");
obj.totalElementsCount = totalElements.length; //dom中的所有节点数量
let eleDepthArr = [];
let eleSubArr = [];
for (let i = 0; i < totalElements.length; i++) {
eleDepthArr.push(new Ele(totalElements[i]).getEleDepth());
eleSubArr.push(new Ele(totalElements[i]).getEleSubNum());
}
eleDepthArr = eleDepthArr.sort((a, b) => b - a);
eleSubArr = eleSubArr.sort((a, b) => b - a);
obj.maxDOMTreeDepth = eleDepthArr[0]; //元素节点的最大嵌套深度
obj.maxChildrenCount = eleSubArr[0]; //最大子元素个数
console.log(obj);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 实现一个sleep
函数
//Promise
const sleep = time => {
return new Promise(resolve => setTimeout(resolve,time))
}
sleep(1000).then(()=>{
console.log(1)
})
//async
function sleep(time) {
return new Promise(resolve => setTimeout(resolve,time))
}
async function output() {
let out = await sleep(1000);
console.log(1);
return out;
}
output();
//ES5
function sleep(callback,time) {
if(typeof callback === 'function')
setTimeout(callback,time)
}
function output(){
console.log(1);
}
sleep(output,1000);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 实现一个可以拖拽的div
<div id="xxx"></div>
var dragging = false
var position = null
xxx.addEventListener('mousedown',function(e){
dragging = true
position = [e.clientX, e.clientY]
})
document.addEventListener('mousemove', function(e){
if(dragging === false) return null
const x = e.clientX
const y = e.clientY
const deltaX = x - position[0]
const deltaY = y - position[1]
const left = parseInt(xxx.style.left || 0)
const top = parseInt(xxx.style.top || 0)
xxx.style.left = left + deltaX + 'px'
xxx.style.top = top + deltaY + 'px'
position = [x, y]
})
document.addEventListener('mouseup', function(e){
dragging = false
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 根据字符出现频率排序
输入tree
输出eert
或eetr
var frequencySort = function(s) {
let map = new Map()
const res = []
for(let i of s) {
if(map.has(i)) {
map.set(i, map.get(i) + 1)
} else {
map.set(i, 1)
}
}
const arr = Array.from(map)
arr.sort((a, b) => a[1] === b[1] ? b[0] - a[0] : b[1] - a[1])
for(let i in arr) {
for(let j = 0; j < arr[i][1]; j++) {
res.push(arr[i][0])
}
}
// return res
return res.join('')
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 观察者模式是由具体目标调度,比如当事件触发,
Dep
就会去调用观察者的方法,所以观察者模 式的订阅者与发布者之间是存在依赖的
# 实现prototype
的继承
function Sup() {
this.name = "xiaosong";
}
function Sub() {
this.sex = "mal";
}
let father = new Sup();
Sub.prototype = father;
let son = new Sub();
console.log(son.name);
console.log(son.sex);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 实现一个JSON.stringify
JSON.stringify(value[, replacer [, space]]):
Boolean | Number| String
类型会自动转换成对应的原始值。undefined
、任意函数以及symbol
,会被忽略(出现在非数组对象的属性值中时),或者被转换成null
(出现在数组中时)。- 不可枚举的属性会被忽略如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
- 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
function jsonStringify(obj) {
let type = typeof obj;
if (type !== "object") {
if (/string|undefined|function/.test(type)) {
obj = '"' + obj + '"';
}
return String(obj);
} else {
let json = []
let arr = Array.isArray(obj)
for (let k in obj) {
let v = obj[k];
let type = typeof v;
if (/string|undefined|function/.test(type)) {
v = '"' + v + '"';
} else if (type === "object") {
v = jsonStringify(v);
}
json.push((arr ? "" : '"' + k + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
}
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1, "false", false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 实现JSON.parse
JSON.parse(text[, reviver])
用来解析
JSON
字符串,构造由字符串描述的JavaScript
值或对象。提供可选的reviver
函数用以在返回之前对所得到的对象执行变换(操作)
第一种:直接调用 eval
function jsonParse(opt) {
return eval('(' + opt + ')');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1, "false", false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}
2
3
4
5
6
7
8
9
避免在不必要的情况下使用
eval
,eval()
是一个危险的函数,他执行的代码拥有着执行者的权利。如果你用eval()
运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。它会执行JS
代码,有XSS
漏洞。
如果你只想记这个方法,就得对参数json做校验。
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (
rx_one.test(
json
.replace(rx_two, "@")
.replace(rx_three, "]")
.replace(rx_four, "")
)
) {
var obj = eval("(" +json + ")");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
第二种:Function
核心:
Function
与eval
有相同的字符串参数特性
var func = new Function(arg1, arg2, ..., functionBody);
在转换JSON
的实际应用中,只需要这么做
var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();
2
eval
与Function
都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用
# 实现数据的双向绑定
let obj = {};
let input = document.querySelector("input");
let span = document.querySelector("span");
Object.defineProperty(obj, "text", {
enumerable: true,
writeable: true,
configurable: true,
set(newVal) {
console.log(newVal);
input.value = newVal;
span.innerHTML = newVal;
},
get() {
console.log("get");
},
});
input.addEventListener("keyup", function (e) {
obj.text = e.target.value;
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 实现简单路由
class Route {
constructor() {
// 路由列表
this.routes = {};
// 路由hash
this.currentHash = "";
this.freshHash = this.freshHash.bind(this);
window.addEventListener("load", this.freshHash, false);
window.addEventListener("hashchange", this.freshHash, false);
}
storeRoute(path, cb) {
this.routes[path] = cb || function () {};
}
freshHash() {
this.currentHash = location.hash.slice(1) || "/";
this.routes[this.currentHash]();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 判断两个变量是否相等
function judgeEqual(x, y) {
if (Object.is) {
return Object.is();
} else {
// SameValue algorithm
if (x === y) {
// Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
return x !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 如何让 (a == 1 && a == 2 && a == 3)
返回 true
// 1.
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}
// 2.
const a = {
i: 1,
valueOf() {
return this.i++
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}
// 3.
var a = new Proxy({ i: 1 }, {
get(target) { return () => target.i++ }
});
// 4.
var a = { i: 1 }
Object.defineProperty(a, 'value', {
enumerable: false,
configurable: false,
get() { return a.i }
set() { a.i++ }
})
// 5.
var i = 1
Object.defineProperty(window, 'a', {
get() { return i++ }
})
if (a === 1 && a === 2 && a === 3) {
console.log('Hello World!');
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 实现无重复字符的最长子串
var lengthOfLongestSubstring = function(s) {
let len = s.length
let set = new Set()
let left = 0
let right = 0
let sum = 0
while(left < len) {
while(right < len && !set.has(s[right])) {
set.add(s[right])
right++
}
// 处理重复的
sum = Math.max(sum, right - left)
set.delete(s[left])
left++
}
return sum
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用setTimeout
模拟setInterval
function mySetInterval(fn, timeout) {
// 控制器,控制定时器是否继续执行
var timer = {
flag: true
};
// 设置递归函数,模拟定时器执行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 启动定时器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
// 完整版
function setInterval1 (handler,timeout,...args) {
let isBrowser = typeof window !== 'undefined'
if(isBrowser && this !== window){
throw 'TypeError: Illegal invocation'
}
let timer = {}
if(isBrowser){
// 浏览器上处理
timer = {
value:-1,
valueOf: function (){
return this.value
}
}
let callback = ()=>{
handler.apply(this,args)
timer.value = setTimeout(callback,timeout)
}
timer.value = setTimeout(callback,timeout)
} else {
// nodejs的处理
let callback = ()=>{
handler.apply(this,args)
Object.assign(timer,setTimeout(callback,timeout))
}
Object.assign(timer,setTimeout(callback,timeout))
}
return timer
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 现在要你完成一个Dialog
组件,说说你设计的思路?它应该有什么功能?
- 该组件需要提供
hook
指定渲染位置,默认渲染在body
下面。 - 然后改组件可以指定外层样式,如宽度等
- 组件外层还需要一层
mask
来遮住底层内容,点击mask
可以执行传进来的onCancel
函数关闭Dialog
。 - 另外组件是可控的,需要外层传入
visible
表示是否可见。 - 然后
Dialog
可能需要自定义头head
和底部footer
,默认有头部和底部,底部有一个确认按钮和取消按钮,确认按钮会执行外部传进来的onOk
事件,然后取消按钮会执行外部传进来的onCancel
事件。 - 当组件的
visible
为true
时候,设置body
的overflow
为hidden
,隐藏body
的滚动条,反之显示滚动条。 - 组件高度可能大于页面高度,组件内部需要滚动条。
- 只有组件的
visible
有变化且为ture
时候,才重渲染组件内的所有内容
# 判断对象是否存在循环引用
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)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 如何渲染几万条数据并不卡住界面
这道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分
DOM
,那么就可以通过requestAnimationFrame
来每16 ms
刷新一次
<!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>Document</title>
</head>
<body>
<ul>控件</ul>
<script>
setTimeout(() => {
// 插入十万条数据
const total = 100000
// 一次插入 20 条,如果觉得性能不好就减少
const once = 20
// 渲染数据总共需要几次
const loopCount = total / once
let countOfRender = 0
let ul = document.querySelector("ul");
function add() {
// 优化性能,插入不会造成回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < once; i++) {
const li = document.createElement("li");
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();
}
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add);
}
}
loop();
}, 0);
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 希望获取到页面中所有的checkbox
怎么做?
不使用第三方框架
var domList = document.getElementsByTagName(‘input’)
var checkBoxList = [];
var len = domList.length; //缓存到局部变量
while (len--) { //使用while的效率会比for循环更高
if (domList[len].type == ‘checkbox’) {
checkBoxList.push(domList[len]);
}
}
2
3
4
5
6
7
8
# Javascript
中callee
和caller
的作用?
caller
是返回一个对函数的引用,该函数调用了当前`函数;callee
是返回正在被执行的function
函数,也就是所指定的function
对象的正文
那么问题来了?如果一对兔子每月生一对兔子;一对新生兔,从第二个月起就开始生兔子;假定每对兔子都是一雌一雄,试问一对兔子,第n个月能繁殖成多少对兔子?(使用
callee
完成)
function fn(n) {
//典型的斐波那契数列
if (n == 1) {
return 1;
} else if (n == 2) {
return 1;
} else {
//argument.callee()表示fn()
return arguments.callee(n - 1) + arguments.callee(n - 2);
}
}
2
3
4
5
6
7
8
9
10
11
# (设计题)想实现一个对页面某个节点的拖曳?如何做?(使用原生JS
)
- 给需要拖拽的节点绑定
mousedown
,mousemove
,mouseup
事件 mousedown
事件触发后,开始拖拽mousemove
时,需要通过event.clientX
和clientY
获取拖拽位置,并实时更新位置mouseup
时,拖拽结束- 需要注意浏览器边界的情况
# 使用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)
2
3
4
5
6
7
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);
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
使用css
实现一个持续的动画效果
animation:mymove 5s infinite;
@keyframes mymove {
from {top:0px;}
to {top:200px;}
}
2
3
4
5
6
animation-name
规定需要绑定到选择器的keyframe
名称。animation-duration
规定完成动画所花费的时间,以秒或毫秒计。animation-timing-function
规定动画的速度曲线。animation-delay
规定在动画开始之前的延迟。animation-iteration-count
规定动画应该播放的次数。animation-direction
规定是否应该轮流反向播放动画
# 封装一个函数,参数是定时器的时间,.then
执行回调函数
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
2
3
# 怎么判断两个对象相等?
obj = {
a:1,
b:2
}
obj2 = {
a:1,
b:2
}
obj3 = {
a:1,
b:'2'
}
2
3
4
5
6
7
8
9
10
11
12
可以转换为字符串来判断
JSON.stringify(obj)==JSON.stringify(obj2);//true
JSON.stringify(obj)==JSON.stringify(obj3);//false
2
# 实现效果,点击容器内的图标,图标边框变成border 1px solid red
,点击空白处重置
const box = document.getElementById('box');
function isIcon(target) {
return target.className.includes('icon');
}
box.onClick = function(e) {
e.stopPropagation();
const target = e.target;
if (isIcon(target)) {
target.style.border = '1px solid red';
}
}
const doc = document;
doc.onclick = function(e) {
const children = box.children;
for(let i; i < children.length; i++) {
if (isIcon(children[i])) {
children[i].style.border = 'none';
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 请简单实现双向数据绑定mvvm
<input id="input"/>
<script>
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data, 'text', {
set(value) {
input.value = value;
this.value = value;
}
});
input.onChange = function(e) {
data.text = e.target.value;
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 实现Storage
,使得该对象为单例,并对localStorage
进行封装设置值setItem(key,value)
和getItem(key)
var instance = null;
class Storage {
static getInstance() {
if (!instance) {
instance = new Storage();
}
return instance;
}
setItem = (key, value) => localStorage.setItem(key, value),
getItem = key => localStorage.getItem(key)
}
// 调用
// Storage.getInstance()
// instance.setItem('xx', 'xxx')
// instance.getItem('xx')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 在输入框中如何判断输入的是一个正确的网址
function isUrl(url) {
try {
new URL(url);
return true;
} catch(err) {
return false;
}
}
2
3
4
5
6
7
8
# 写一个通用的事件侦听器函数
// event(事件)工具集,来源:github.com/markyun
markyun.Event = {
// 视能力分别使用dom0||dom2||IE方式 来绑定事件
// 参数: 操作的元素,事件名称 ,事件处理程序
addEvent: function (element, type, handler) {
if (element.addEventListener) {
//事件类型、需要执行的函数、是否捕捉
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, function () {
handler.call(element);
});
} else {
element["on" + type] = handler;
}
},
// 移除事件
removeEvent: function (element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.datachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
stopPropagation: function (ev) {
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true;
}
},
// 取消事件的默认行为
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 获取事件目标
getTarget: function (event) {
return event.target || event.srcElement;
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 编写一个方法求字符串字节长度
假设:一个英文字符占用一个字节,一个中文字符占用两个字节
function GetBytes(str) {
let len = str.length;
let bytes = len;
for (let i = 0; i < len; i++) {
if (str.charCodeAt(i) > 255) bytes++;
}
return bytes;
}
alert(GetBytes("你好,crucials"));
2
3
4
5
6
7
8
9
10
11
# 下面这个ul
,如何点击每一列的时候alert
其index
考察闭包
<ul id=”test”>
<li>这是第一条</li>
<li>这是第二条</li>
<li>这是第三条</li>
</ul>
<script>
// 方法一:
var lis = document.getElementById("test").getElementsByTagName("li");
for (var i = 0; i < 3; i++) {
lis[i].index = i;
lis[i].onclick = function () {
alert(this.index);
};
}
//方法二:
var lis = document.getElementById("test").getElementsByTagName("li");
for (var i = 0; i < 3; i++) {
lis[i].index = i;
lis[i].onclick = (function (a) {
return function () {
alert(a);
};
})(i);
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 定义一个log
方法,让它可以代理console.log
的方法
可行的方法一:
function log(msg) {
console.log(msg);
}
log("hello world!") // hello world!
2
3
4
5
6
7
如果要传入多个参数呢?显然上面的方法不能满足要求,所以更好的方法是:
function log(){
console.log.apply(console, arguments);
};
2
3
# 写一个function
,清除字符串前后的空格
使用自带接口
trim()
,考虑兼容性:
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/,"");
}
}
// test the function
var str = " \t\n test string ".trim();
alert(str == "test string"); // alerts "true"
2
3
4
5
6
7
8
9
# 实现每隔一秒钟输出1,2,3...
数字
for (var i = 0; i < 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j + 1);
}, j * 1000);
})(i);
}
2
3
4
5
6
7
# js
自定义事件
三要素:
document.createEvent()
event.initEvent()
element.dispatchEvent()
// (en:自定义事件名称,fn:事件处理函数,addEvent:为DOM元素添加自定义事件,triggerEvent:触发自定义事件)
window.onload = function(){
var demo = document.getElementById("demo");
demo.addEvent("test",function(){console.log("handler1")});
demo.addEvent("test",function(){console.log("handler2")});
demo.onclick = function(){
this.triggerEvent("test");
}
}
Element.prototype.addEvent = function(en,fn){
this.pools = this.pools || {};
if(en in this.pools){
this.pools[en].push(fn);
}else{
this.pools[en] = [];
this.pools[en].push(fn);
}
}
Element.prototype.triggerEvent = function(en){
if(en in this.pools){
var fns = this.pools[en];
for(var i=0,il=fns.length;i<il;i++){
fns[i]();
}
}else{
return;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 实现一个简单的监听视窗激活状态
// 窗口激活状态监听
let vEvent = 'visibilitychange';
if (document.webkitHidden != undefined) {
vEvent = 'webkitvisibilitychange';
}
function visibilityChanged() {
if (document.hidden || document.webkitHidden) {
document.title = '客官,别走啊~'
console.log("Web page is hidden.")
} else {
document.title = '客官,你又回来了呢~'
console.log("Web page is visible.")
}
}
document.addEventListener(vEvent, visibilityChanged, false);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17