JavaScript 踩坑指南 (opens new window)

# new操作符

function new(func, ...args) {
  const newObject = Object.create(func.prototype);
  const k = func.apply(newObject, ...args)
  if(typeof k === 'object') {
    return k
  } else {
    return newObject
  }
}
1
2
3
4
5
6
7
8
9

# event loop(事件循环/事件轮询)

  • JS单线程
  • 异步要基于回调来实现
  • event loop就是异步回调的实现原理

JS执行步骤:从前到后,一行一行执行、错误终止执行、先执行同步,再执行异步

菲利普·罗伯茨:到底什么是Event Loop呢? | 欧洲 JSConf 2014 (opens new window)

// Call Stack

function multiply(a, b) {
  return a * b;
}

function square(n) {
  return multiply(n, n);
}

function printSquare(n) {
  var squared = square(n);
  console.log(squared);
}

printSquare(4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Promise 处理异步

const handlerClick = () => {
  const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://api.apiopen.top/getJok');
    xhr.send()
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if(xhr.status >= 200 && shr.status < 300) {
          resolve(xhr.response);
        } else {
          reject(xhr.status);
        }
      }
    }
  })

  p.then((value) => {

  }, error => {

  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 自定义promise

function MyPromise(executor) {
  // 保存初始化状态
  var self = this;

  // 初始化状态
  this.state = PENDING;

  // 用于保存 resolve 或者 rejected 传入的值
  this.value = null;

  // 用于保存 resolve 的回调函数
  this.resolvedCallbacks = [];

  // 用于保存 reject 的回调函数
  this.rejectedCallbacks = [];

  // 状态转变为 resolved 方法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }

    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变,
      if (self.state === PENDING) {
        // 修改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 状态转变为 rejected 方法
  function reject(value) {
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变
      if (self.state === PENDING) {
        // 修改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 将两个方法传入函数执行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到错误时,捕获错误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function (onFulfilled, onReject){
  // 保存前一个promise的this
  const self = this; 
  return new MyPromise((resolve, reject) => {
    // 封装前一个promise成功时执行的函数
    let fulfilled = () => {
      try{
        const result = onFulfilled(self.value); // 承前
        return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后
      }catch(err){
        reject(err)
      }
    }
    // 封装前一个promise失败时执行的函数
    let rejected = () => {
      try{
        const result = onReject(self.reason);
        return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
      }catch(err){
        reject(err)
      }
    }
    switch(self.status){
      case PENDING: 
        self.onFulfilledCallbacks.push(fulfilled);
        self.onRejectedCallbacks.push(rejected);
        break;
      case FULFILLED:
        fulfilled();
        break;
      case REJECT:
        rejected();
        break;
    }
  })
}
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

# 柯里化

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args)
    } else {
      return function (...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  }
}

function curry(fn, ...arges) {
  return fn.length <= args.length
  ? fn(...args)
  : curry.bind(null, fn, ...args);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 深拷贝

const object = {
  a: 1,
  b: 'qwe',
  c: [1,2,3],
  d: {
    test: '',
  }
}

JSON.parse(JSON.stringify())

const deepClone = (cloneData) => {
  if(typeof cloneData !== 'object' || cloneData == null) {
    return cloneData
  }

  let result
  if(cloneData instanceof Array) {
    result = []
  } else {
    result = {}
  }

  for(let key in cloneData) {
    // key 不是原型属性
    if(cloneData.hasOwnProperty(key)) {
      // 递归调用
      result[key] = deepClone(cloneData[key])
    }
  }

  return result
}

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 debonce(func, delay) {
  let timer;

  if(timer) {
    clearTimeout(timer);
  }

  timer = setTimeout(() => {
    func();
  }, delay);
}

function debonce(func, delay) {
  let timer;
  return function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(function ()  {
      func.apply(context, args)
      timer = null;
    }, delay)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 节流

  • 防止多次发出
function throttle(func, delay) {
  let timer;
  return function () {
    if(timer) return
    timer = setTimeout(() => {
      func.apply(context, arguments);
      timer = null;
    }, delay)
  }
}
// setTimeOut
function throttle(func, delay) {
  let timer;
  return function () {
    if(timer) {
      return;
    }
    let context = this;
    let args = arguments;
    timer = setTimeout(function ()  {
      func.apply(context, args);
      timer = null;
    }, delay)
  }
}
// Date
function throttle(func, delay) {
  let pre = 0;
  return function () {
    let context = this;
    let args = arguments;
    let now = new Date();
    if(now - pre > delay) {
      func.apply(context, args);
      pre = now;
    }
  }
}
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

# call、apply、bind

// call
// Function.prototype.newCall = function(obj) {
//   var obj = obj || window;
//   obj.p = this;
//   var newArguments = [];
//   for (var i = 1; i < arguments.length; i++) {
//     newArguments.push(`argumen[${i}]`)
//   }
//   var result = eval(`obj.p(${newArguments})`)
//   delete onj.p;
// }

// // apply
// Function.prototype.newApplay = function(context) {
//   let result = null;
// }

Function.prototype.newCall = function(obj) {
  let result = null;
  let args = argument.slice(1)
  obj.fn = this;
  obj.fn();
  delete obj.p
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# map

Array.prototype._map(fn) {
  for (let i = 0; i < this.length; i ++) {
    return fn(this[i]);
  }
}
1
2
3
4
5

# Search获取参数

function getSearch(name) {
  return (new URLSearchParams(window.location.search)).get(name)
}

function getSearch() {
  const res = {}
  const search = location.search.substr(1)
  search.splite('&').forEach((paramstr) => {
    const arr = paramStr.split('=');
    const key = arr[0]
    const val = arr[1]
    res[key] = val
  })

  return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# EventEmitter

class EventEmitter {
  constructor(defaultMaxListeners = 50) {
    this.listeners = {};
    this.defaultMaxListeners = defaultMaxListeners;
  }

  on(eventName, fn) {
    if (!this.listener[eventName]) {
      this.listeners[eventName] = [];
    }
    if(this.listeners[eventName].length > this.defaultMaxListeners) {
      throw new Error('超出限制');
    }
    this.listeners[eventName].push(fn);
  }

  off(eventName, fn) {
    let callbacks = this.listener[eventName];
    if(!callbacks) return false;
    if(!fn) {
      callbacks = [];
    } else {
      for(let i = 0; i< callbacks.length; i++) {
        if(callbacks[i] === fn) {
          callbacks.splice(i,1)
          i--;
        }
      }
    }
  }

  once(eventName, fn) {
    const on = (...args) => {

    }
  }
}
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

# iFrame

iFrame 知识总结

# JSBridge 相关知识点

现在 hybrid 应用越来越多,不管在移动端或者PC、Mac端,都需要用到JSBridge