小宋爱睡觉 小宋爱睡觉
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
  • 手写系列
  • Promise
  • 场景应用
    • 实现add(1)(2)(3)
    • Pipe的使用
    • 时钟指针的夹角
    • 实现发布-订阅模式
    • 实现观察者模式
    • 将数组转换为树
    • 用 requestAnimationframe 实现一个精准的 setTimeout
    • 实现一个异步加法
    • 实现十进制转为36进制
    • 实现十进制转七进制
    • 解析URLparams为对象
    • 斐波那契数列
    • 扑克牌算法
    • 约瑟夫环问题
    • 实现class的private
    • 实现vue-router的hash模式
    • 实现vue-router的history模式
    • 实现es6的extends
    • 实现一个事件委托
    • 统计DOM个数
    • 实现一个sleep函数
    • 实现一个可以拖拽的div
    • 根据字符出现频率排序
    • 实现prototype的继承
    • 实现一个JSON.stringify
    • 实现JSON.parse
    • 实现数据的双向绑定
    • 实现简单路由
    • 判断两个变量是否相等
    • 如何让 (a == 1 && a == 2 && a == 3) 返回 true
    • 实现无重复字符的最长子串
    • 使用setTimeout模拟setInterval
    • 现在要你完成一个Dialog组件,说说你设计的思路?它应该有什么功能?
    • 判断对象是否存在循环引用
    • 如何渲染几万条数据并不卡住界面
    • 希望获取到页面中所有的checkbox怎么做?
    • Javascript中callee和caller的作用?
    • (设计题)想实现一个对页面某个节点的拖曳?如何做?(使用原生JS)
    • 使用js实现一个持续的动画效果
    • 封装一个函数,参数是定时器的时间,.then执行回调函数
    • 怎么判断两个对象相等?
    • 实现效果,点击容器内的图标,图标边框变成border 1px solid red,点击空白处重置
    • 请简单实现双向数据绑定mvvm
    • 实现Storage,使得该对象为单例,并对localStorage进行封装设置值setItem(key,value)和getItem(key)
    • 在输入框中如何判断输入的是一个正确的网址
    • 写一个通用的事件侦听器函数
    • 编写一个方法求字符串字节长度
    • 下面这个ul,如何点击每一列的时候alert其index
    • 定义一个log方法,让它可以代理console.log的方法
    • 写一个function,清除字符串前后的空格
    • 实现每隔一秒钟输出1,2,3...数字
    • js自定义事件
    • 实现一个简单的监听视窗激活状态
    • IPV4转数字
  • 数组的方法
  • 正则表达式相关
  • Vue手写
  • 手写系列
Crucials
2021-11-28

场景应用

# 场景应用

# 实现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);
    }
  };
}

1
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

# Pipe的使用

实现一个pipe,按顺序执行函数,前一个函数的返回值是后一个函数的入参

function pipe(...fns) {
  return function(initialValue) {
    return fns.reduce((acc, fn) => fn(acc), initialValue);
  }
}
1
2
3
4
5

# 时钟指针的夹角

给定一个小时(hour)和分钟(minutes),返回两根指针之间的最小夹角(单位:度)。

  • 输入:hour(0 <= hour <= 23),minutes(0 <= minutes < 60)
  • 输出:指针之间的最小角度(0~180)
function angleClock(_hour, minutes) {
	const hour = _hour % 12;
  // 时钟每小时走 360 / 12 = 30度,每分钟多走 30 / 60 = 0.5度
  const hourAngle = 30 * hour + 0.5 * mintues;
  // 时针每分钟走 360 / 60 = 6度
  const mintueAngle = 6 * mintue;
  
  const angleDiff = Math.abs(hourAngle - minuteAngle);
	return Math.min(angleDiff, 360 - angleDiff);
}
1
2
3
4
5
6
7
8
9
10

# 实现发布-订阅模式

// 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 事件又触发了 ✨
1
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");
1
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

# 将数组转换为树

// 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: [
          // 结果 ,,,
        ],
      },
    ],
  },
];

1
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 map = new Map();

  for (const item of items) {
    map.set(item.id, { ...item, children: [] });
  }

  for (const item of items) {
    const parent = map.get(item.parentId);
    const treeItem = map.get(item.id);
    if (parent) {
      parent.children.push(treeItem);
    } else {
      result.push(treeItem); // 根节点
    }
  }

  return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 用 requestAnimationframe 实现一个精准的 setTimeout

function rafTimeout(callback, delay) {
  const start = performance.now();

  function tick(now) {
    if (now - start >= delay) {
      callback();
    } else {
      requestAnimationFrame(tick);
    }
  }

  requestAnimationFrame(tick);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

实现一个 setInterval

function rafInterval(callback, interval) {
  let last = performance.now();
  function loop(now) {
    if (now - last >= interval) {
      callback();
      last = now;
    }
    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);
}
1
2
3
4
5
6
7
8
9
10
11

# 实现一个异步加法字节

// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 500);
}
1
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");
})();
1
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
1
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("");
}
1
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"}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 斐波那契数列

// 递归,时间复杂度为 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;
}
1
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的返回位置)

img

由于尾调用之前的工作已经完成了,所以当前函数帧(即调用时创建的栈帧)上包含局部变量等等大部分的东西都不需要了,当前的函数帧经过适当的变动以后可以直接当做尾调用的函数的帧使用,然后程序就可以跳到被尾调用的函数。

用上图中的例子来解释就是,函数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)
1
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]
1
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

# 约瑟夫环问题

约瑟夫环 (opens new window)

var lastRemaining = function(n, m) {
    let result = 0;
    for(let i = 2; i <= n; i++) {
        result = (m + result) % i;
    }
    return result
};
1
2
3
4
5
6
7

img

# 实现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
1
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  
1
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  
1
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);
1
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
  }
})
1
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>
1
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;
}
1
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
}
1
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);
1
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);
1
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
})
1
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('')
};
1
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 实现一个JSON.stringify

JSON.stringify(value[, replacer [, space]]):
1
  • 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"}"
1
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])
1

用来解析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"}
1
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 + ")");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

第二种:Function

核心:Function与eval有相同的字符串参数特性

var func = new Function(arg1, arg2, ..., functionBody);
1

在转换JSON的实际应用中,只需要这么做

var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();
1
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;
});
1
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]();
  }
}
1
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;
    }
  }
}
1
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!');
}
1
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
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 使用setTimeout模拟setInterval

function simulateInterval(callback, delay) {
  function loop() {
    callback();
    setTimeout(loop, delay);
  }

  setTimeout(loop, delay);
}

simulateInterval(() => {
  console.log('执行一次', new Date().toLocaleTimeString());
}, 1000);



可扩展版本(支持停止)
function simulateInterval(callback, delay) {
  let timerId;

  function loop() {
    timerId = setTimeout(() => {
      callback();
      loop(); // 下一次循环
    }, delay);
  }

  loop();

  return () => clearTimeout(timerId); // 返回一个停止函数
}

const stop = simulateInterval(() => {
  console.log('模拟 setInterval');
}, 1000);

setTimeout(() => {
  stop(); // 5 秒后停止
  console.log('已停止');
}, 5000);
1
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

# 现在要你完成一个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)
1
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>
1
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]);
   }
 }
1
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);
  }
}
1
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)
1
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);
})();
1
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;}
}
1
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));
}
1
2
3

# 怎么判断两个对象相等?

obj = {
  a:1,
  b:2
}
obj2 = {
  a:1,
  b:2
}
obj3 = {
  a:1,
  b:'2'
}
1
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
1
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';
    }
  }
}
1
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>
1
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')
1
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;
  }
}
1
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;
  },
};
1
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"));
1
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>
1
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!
1
2
3
4
5
6
7

如果要传入多个参数呢?显然上面的方法不能满足要求,所以更好的方法是:

 function log(){
     console.log.apply(console, arguments);
 };
1
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"
1
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);
}
1
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;
  }
}
1
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# IPV4转数字

192.168.0.1

192 => 11000000 168 => 10101000 0 => 00000000 1 => 00000001

function ipv4Toint(num) {
  const parts = ip.split('.').map(Number);
  return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
}
1
2
3
4

数字转IPV4

num & 255 截取最低的 8 位(最低字节)。

function intToIp(int) {
  return [
    (int >>> 24) & 255,
    (int >>> 16) & 255,
    (int >>> 8) & 255,
    int & 255
  ].join('.');
}
1
2
3
4
5
6
7
8
上次更新: 2025/05/28, 09:28:27
Promise
数组的方法

← Promise 数组的方法→

Copyright © 2021-2025 粤ICP备2021165371号
  • 跟随系统
  • 浅色模式
  • 深色模式