js原生方法手写实现

# Ajax/jQuery篇

# 实现原生的AJAX请求

const ajax = {
    get(url, fn) {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)// 第三个参数异步与否
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                fn(xhr.responeText)
            }
        }
        xhr.send()
    },
    post(url, data, fn) {
        const xhr = new XMLHttpRequest()
        xhr.open('POST', url, true)
        xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                fn(xhr.responeText)
            }
        }
        xhr.send(data)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 实现JSON.parse

简单版本:

题目描述:实现JSON.parse

function parse (json) {
    return eval("(" + json + ")");
}
1
2
3

复杂版本:

# 手写JSON.stringify

终于重新学完JSON.stringify的众多特性啦!咱们根据这些特性来手写一个简单版本的吧(无replacer函数和space

源码实现

核心

  • result.push("${key}"😒{jsonstringify(value)});

  • return{${result}}.replace(/'/, '"');

  const jsonstringify = (data) => {
    // 确认一个对象是否存在循环引用
    const isCyclic = (obj) => {
    // 使用Set数据类型来存储已经检测过的对象
    let stackSet = new Set()
    let detected = false
  
    const detect = (obj) => {
      // 不是对象类型的话,可以直接跳过
      if (obj && typeof obj != 'object') {
        return
      }
      // 当要检查的对象已经存在于stackSet中时,表示存在循环引用
      if (stackSet.has(obj)) {
        return detected = true
      }
      // 将当前obj存如stackSet
      stackSet.add(obj)
  
      for (let key in obj) {
        // 对obj下的属性进行挨个检测
        if (obj.hasOwnProperty(key)) {
          detect(obj[key])
        }
      }
      // 平级检测完成之后,将当前对象删除,防止误判
      /*
        例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用
        let tempObj = {
          name: 'samy'
        }
        let obj4 = {
          obj1: tempObj,
          obj2: tempObj
        }
      */
      stackSet.delete(obj)
    }
    detect(obj)
    return detected
  }
  
    // 特性七:
    // 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
    if (isCyclic(data)) {
      throw new TypeError('Converting circular structure to JSON')
    }
  
    // 特性九:
    // 当尝试去转换 BigInt 类型的值会抛出错误
    if (typeof data === 'bigint') {
      throw new TypeError('Do not know how to serialize a BigInt')
    }
  
    const type = typeof data
    const commonKeys1 = ['undefined', 'function', 'symbol']
    const getType = (s) => {
      return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/, '$1').toLowerCase()
    }
  
    // 非对象
    if (type !== 'object' || data === null) {
      let result = data
      // 特性四:
      // NaN 和 Infinity 格式的数值及 null 都会被当做 null。
      if ([NaN, Infinity, null].includes(data)) {
        result = 'null'
        // 特性一:
        // `undefined`、`任意的函数`以及`symbol值`被`单独转换`时,会返回 undefined
      } else if (commonKeys1.includes(type)) {
        // 直接得到undefined,并不是一个字符串'undefined'
        return undefined
      } else if (type === 'string') {
        result = '"' + data + '"'
      }
      return String(result)
    } else if (type === 'object') {
      // 特性五:
      // 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化
      // 特性六:
      // Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
      if (typeof data.toJSON === 'function') {
        return jsonstringify(data.toJSON())
      } else if (Array.isArray(data)) {
        let result = data.map((it) => {
          // 特性一:
          // `undefined`、`任意的函数`以及`symbol值`出现在`数组`中时会被转换成 `null`
          return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
        })
  
        return `[${result}]`.replace(/'/g, '"')
      } else {
        // 特性二:
        // 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
        if (['boolean', 'number'].includes(getType(data))) {
          return String(data)
        } else if (getType(data) === 'string') {
          return '"' + data + '"'
        } else {
          let result = []
          // 特性八
          // 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
          Object.keys(data).forEach((key) => {
            // 特性三:
            // 所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
            if (typeof key !== 'symbol') {
              const value = data[key]
              // 特性一
              // `undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略
              if (!commonKeys1.includes(typeof value)) {
                result.push(`"${key}":${jsonstringify(value)}`)
              }
            }
          })
          return `{${result}}`.replace(/'/, '"')
        }
      }
    }
  }
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
107
108
109
110
111
112
113
114
115
116
117
118
119
  // 1. 测试一下基本输出
  console.log(jsonstringify(undefined)) // undefined 
  console.log(jsonstringify(() => { })) // undefined
  console.log(jsonstringify(Symbol('samy'))) // undefined
  console.log(jsonstringify((NaN))) // null
  console.log(jsonstringify((Infinity))) // null
  console.log(jsonstringify((null))) // null
  console.log(jsonstringify({
    name: 'samy',
    toJSON() {
      return {
        name: 'samy2',
        sex: 'boy'
      }
    }
  }))
  // {"name":"samy2","sex":"boy"}
  
  // 2. 和原生的JSON.stringify转换进行比较
  console.log(jsonstringify(null) === JSON.stringify(null));
  // true
  console.log(jsonstringify(undefined) === JSON.stringify(undefined));
  // true
  console.log(jsonstringify(false) === JSON.stringify(false));
  // true
  console.log(jsonstringify(NaN) === JSON.stringify(NaN));
  // true
  console.log(jsonstringify(Infinity) === JSON.stringify(Infinity));
  // true
  let str = "samy";
  console.log(jsonstringify(str) === JSON.stringify(str));
  // true
  let reg = new RegExp("\w");
  console.log(jsonstringify(reg) === JSON.stringify(reg));
  // true
  let date = new Date();
  console.log(jsonstringify(date) === JSON.stringify(date));
  // true
  let sym = Symbol('samy');
  console.log(jsonstringify(sym) === JSON.stringify(sym));
  // true
  let array = [1, 2, 3];
  console.log(jsonstringify(array) === JSON.stringify(array));
  // true
  let obj = {
    name: 'samy',
    age: 18,
    attr: ['coding', 123],
    date: new Date(),
    uni: Symbol(2),
    sayHi: function () {
      console.log("hello world")
    },
    info: {
      age: 16,
      intro: {
        money: undefined,
        job: null
      }
    },
    pakingObj: {
      boolean: new Boolean(false),
      string: new String('samy'),
      number: new Number(1),
    }
  }
  console.log(jsonstringify(obj) === JSON.stringify(obj)) 
  // true
  console.log((jsonstringify(obj)))
  // {"name":"samy","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"samy","number":1}}
  console.log(JSON.stringify(obj))
  // {"name":"samy","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"samy","number":1}}
  
  // 3. 测试可遍历对象
  let enumerableObj = {}
  
  Object.defineProperties(enumerableObj, {
    name: {
      value: 'samy',
      enumerable: true
    },
    sex: {
      value: 'boy',
      enumerable: false
    },
  })
  
  console.log(jsonstringify(enumerableObj))
  // {"name":"samy"}
  
  // 4. 测试循环引用和Bigint
  let obj1 = { a: 'aa' }
  let obj2 = { name: 'samy', a: obj1, b: obj1 }
  obj2.obj = obj2
  
  console.log(jsonstringify(obj2))
  // TypeError: Converting circular structure to JSON
  console.log(jsonStringify(BigInt(1)))
  // TypeError: Do not know how to serialize a BigInt
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

# 将DOM转化成树结构对象

题目描述:

<div>
    <span></span>
    <ul>
        <li></li>
        <li></li>
    </ul>
</div>

//将上方的DOM转化为下面的树结构对象
{
    tag: 'DIV',
    children: [
        { tag: 'SPAN', children: [] },
        {
            tag: 'UL',
            children: [
                { tag: 'LI', children: [] },
                { tag: 'LI', children: [] }
            ]
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

实现如下:

function dom2tree(dom) {
    const obj = {}
    obj.tag = dom.tagName
    obj.children = []
    dom.childNodes.forEach(child => obj.children.push(dom2tree(child)))
    return obj
}
1
2
3
4
5
6
7

# 将树结构转换为DOM

题目描述:

{
    tag: 'DIV',
    children: [
        { tag: 'SPAN', children: [] },
        {
            tag: 'UL',
            children: [
                { tag: 'LI', children: [] },
                { tag: 'LI', children: [] }
            ]
        }
    ]
}

//将上方的树结构对象转化为下面的DOM
<div>
    <span></span>
    <ul>
        <li></li>
        <li></li>
    </ul>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

实现如下:

// 真正的渲染函数
function _render(vnode) {
  // 如果是数字类型转化为字符串
  if (typeof vnode === "number") {
    vnode = String(vnode);
  }
  // 字符串类型直接就是文本节点
  if (typeof vnode === "string") {
    return document.createTextNode(vnode);
  }
  // 普通DOM
  const dom = document.createElement(vnode.tag);
  if (vnode.attrs) {
    // 遍历属性
    Object.keys(vnode.attrs).forEach((key) => {
      const value = vnode.attrs[key];
      dom.setAttribute(key, value);
    });
  }
  // 子数组进行递归操作
  vnode.children.forEach((child) => dom.appendChild(_render(child)));
  return dom;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 简单实现发布订阅模式

题目描述:实现一个发布订阅模式拥有on emit once off方法

class EventEmitter {
    constructor() {
        this.cache = []
    }
    on(name, fn) {
        const tasks = this.cache[name]
        if (tasks) {
            this.cache[name].push(fn)
        } else {
            this.cache[name] = [fn]
        }
    }
    off(name, fn) {
        const tasks = this.cache[name]
        if (task) {
            const index = tasks.findIndex(item => item === fn)
            if (index >= 0) {
                this.cache[name].splice(index, 1)
            }
        }
    }
    emit(name, once = false, ...args) {
        // 复制一份。防止回调里继续on,导致死循环
        const tasks = this.cache[name].slice()
        if (tasks) {
            for (let fn of tasks) {
                fn(...args)
            }
        }
        if (once) {//用完就删除了
            delete this.cache[name]
        }
    }
    once(name, ...args) {
        this.emit(name, true, ...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

# 浅深拷贝

# 浅拷贝

所谓浅拷贝,就是只复制最外一层,里面的都还是相同引用

// 浅拷贝
const a = { name: 'samy', age: 23, arr: [] }
const b = {}
for (let key in a){
    b[key] = a[key]
}

console.log(b) // { name: 'samy', age: 23, arr: [] }
console.log(b === a) // false
console.log(b.arr === a.arr) // true
1
2
3
4
5
6
7
8
9
10

# 深拷贝

简化版本

JSON方式

function deepClone(target) {
    return JSON.parse(JSON.stringify(target))
}

const a = { name: 'samy', age: 23 }
const b = deepClone(a)

console.log(b) // { name: 'samy', age: 23 }
console.log(b === a) // false
1
2
3
4
5
6
7
8
9

虽然大多数时候这么使用是没问题的,但这种方式还是有很多缺点的

  • 1、对象中有字段值为undefined,转换后则会直接字段消失
  • 2、对象如果有字段值为RegExp对象,转换后则字段值会变成{}
  • 3、对象如果有字段值为NaN、+-Infinity,转换后则字段值变成null
  • 4、对象如果有环引用,转换直接报错

image-20211128195450447**

遍历方式

function simpleClone(obj) {//浅拷贝
  let newObj = {};
  for (let i in obj) {
    newObj[i] = obj[i];
  }
  return newObj;
}
//方式一:直接用instancof/typeof判断;【推荐】简洁;
function deepClone(obj){
  //var newObj = Array.isArray(obj) ? [] : {}
  var newObj = obj instanceof Array?[]:{};
  for(var i in obj){
    newObj[i]=typeof obj[i]=='object'? deepClone(obj[i]):obj[i]; 
  }
  return newObj;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

前面实现的方法都没有解决环引用的问题

  • JSON.parse(JSON.stringify(target))报错TypeError: Converting circular structure to JSON,意思是无法处理环引用
  • 递归方法报错Maximum call stack size exceeded,意思是递归不完,爆栈

那怎么解决环引用呢?其实说难也不难,需要用到ES6的数据结构Map

  • 每次遍历到有引用数据类型,就把他当做key放到Map中,对应的value是新创建的对象temp
  • 每次遍历到有引用数据类型,就去Map中找找有没有对应的key,如果有,就说明这个对象之前已经注册过,现
function deepClone(target, map = new Map()) {
    // 基本数据类型直接返回
    if (typeof target !== 'object') {
        return target
    }

    // 引用数据类型特殊处理
    // 判断数组还是对象
    const temp = Array.isArray(target) ? [] : {}

+    if (map.get(target)) {
+        // 已存在则直接返回
+        return map.get(target)
+    }
+    // 不存在则第一次设置
+    map.set(target, temp)

    for (const key in target) {
        // 递归
        temp[key] = deepClone(target[key], map)
    }
    return temp
}

const a = {
    name: 'samy',
    age: 23,
    hobbies: { sports: '篮球', tv: '雍正王朝' },
    works: ['2020', '2021']
}
a.key = a // 环引用
const b = deepClone(a)

console.log(b)
// {
//     name: 'samy',
//     age: 23,
//     hobbies: { sports: '篮球', tv: '雍正王朝' },
//     works: [ '2020', '2021' ],
//     key: [Circular]
// }
console.log(b === a) // 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

最终版本

刚刚咱们只是实现了

  • 基本数据类型的拷贝
  • 引用数据类型中的数组,对象

但其实,引用数据类型可不止只有数组和对象,我们还得解决以下的引用类型的拷贝问题,那怎么判断每个引用数据类型的各自类型呢?可以使用Object.prototype.toString.call()

类型 toString 结果
Map Object.prototype.toString.call(new Map()) [object Map]
Set Object.prototype.toString.call(new Set()) [object Set]
Array Object.prototype.toString.call([]) [object Array]
Object Object.prototype.toString.call({}) [object Object]
Symbol Object.prototype.toString.call(Symbol()) [object Symbol]
RegExp Object.prototype.toString.call(new RegExp()) [object RegExp]
Function Object.prototype.toString.call(function() {}) [object Function]

我们先把以上的引用类型数据分为两类

  • 可遍历的数据类型
  • 不可遍历的数据类型
// 可遍历的类型
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';

// 不可遍历类型
const symbolTag = '[object Symbol]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

// 将可遍历类型存在一个数组里
const canForArr = ['[object Map]', '[object Set]',
                   '[object Array]', '[object Object]']

// 将不可遍历类型存在一个数组
const noForArr = ['[object Symbol]', '[object RegExp]', '[object Function]']

// 判断类型的函数
function checkType(target) {
    return Object.prototype.toString.call(target)
}

// 判断引用类型的temp
function checkTemp(target) {
    const c = target.constructor
    return new c()
}
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

可遍历引用类型

主要处理以下四种类型

  • Map
  • Set
  • Object
  • Array
function deepClone(target, map = new Map()) {
    // 获取类型
+    const type = checkType(target)


    // 基本数据类型直接返回
+    if (!canForArr.concat(noForArr).includes(type)) {
+        return target
+    }

    // 引用数据类型特殊处理
+    const temp = checkTemp(target)

    if (map.get(target)) {
        // 已存在则直接返回
        return map.get(target)
    }
    // 不存在则第一次设置
    map.set(target, temp)
    
    // 处理Map类型
+    if (type === mapTag) {
+        target.forEach((value, key) => {
+            temp.set(key, deepClone(value, map))
+        })
+
+        return temp
+    }

    // 处理Set类型
+    if (type === setTag) {
+        target.forEach(value => {
+            temp.add(deepClone(value, map))
+        })
+
+        return temp
+    }
    
    // 处理数据和对象
    for (const key in target) {
        // 递归
        temp[key] = deepClone(target[key], map)
    }
    return temp
}


const a = {
    name: 'samy',
    age: 23,
    hobbies: { sports: '篮球', tv: '雍正王朝' },
    works: ['2020', '2021'],
    map: new Map([['haha', 111], ['xixi', 222]]),
    set: new Set([1, 2, 3]),
}
a.key = a // 环引用
const b = deepClone(a)

console.log(b)
// {
//     name: 'samy',
//     age: 23,
//     hobbies: { sports: '篮球', tv: '雍正王朝' },
//     works: [ '2020', '2021' ],
//     map: Map { 'haha' => 111, 'xixi' => 222 },
//     set: Set { 1, 2, 3 },
//     key: [Circular]
// }
console.log(b === a) // 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
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

不可遍历引用类型

主要处理以下几种类型

  • Symbol
  • RegExp
  • Function

先把拷贝这三个类型的方法写出来

// 拷贝Function的方法
function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            if (param) {
                const paramArr = param[0].split(',');
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}

// 拷贝Symbol的方法
function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}

// 拷贝RegExp的方法
function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    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
35

# 最终版本

function deepClone(target, map = new Map()) {
    // 获取类型
    const type = checkType(target)
    // 基本数据类型直接返回
    if (!canForArr.concat(noForArr).includes(type)) return target

    // 判断Function,RegExp,Symbol
  +  if (type === funcTag) return cloneFunction(target)
  +  if (type === regexpTag) return cloneReg(target)
  +  if (type === symbolTag) return cloneSymbol(target)

    // 引用数据类型特殊处理
    const temp = checkTemp(target)

    if (map.get(target)) {
        return map.get(target)// 已存在则直接返回
    }
    map.set(target, temp)// 不存在则第一次设置

    // 处理Map类型
    if (type === mapTag) {
        target.forEach((value, key) => {
            temp.set(key, deepClone(value, map))
        })
        return temp
    }

    // 处理Set类型
    if (type === setTag) {
        target.forEach(value => {
            temp.add(deepClone(value, map))
        })
        return temp
    }

    // 处理数据和对象
    for (const key in target) {
        temp[key] = deepClone(target[key], map) // 递归
    }
    return temp
}


const a = {
    name: 'samy',
    age: 23,
    hobbies: { sports: '篮球', tv: '雍正王朝' },
    works: ['2020', '2021'],
    map: new Map([['haha', 111], ['xixi', 222]]),
    set: new Set([1, 2, 3]),
    func: (name, age) => `${name}今年${age}岁啦!!!`,
    sym: Symbol(123),
    reg: new RegExp(/haha/g),
}
a.key = a // 环引用

const b = deepClone(a)
console.log(b)
// {
//     name: 'samy',
//     age: 23,
//     hobbies: { sports: '篮球', tv: '雍正王朝' },
//     works: [ '2020', '2021' ],
//     map: Map { 'haha' => 111, 'xixi' => 222 },
//     set: Set { 1, 2, 3 },
//     func: [Function],
//     sym: [Symbol: Symbol(123)],
//     reg: /haha/g,
//     key: [Circular]
// }
console.log(b === a) // 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
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

# String篇

# slice

参数代表含义

  • start:开始截取的字符索引(包含此字符)
  • end:结束截取的字符索引(不包含此字符)

注意点

  • start > end:返回空字符串
  • start < 0:start = 数组长度 + start
String.prototype.sx_slice = function (start = 0, end) {
    start = start < 0 ? this.length + start : start
    end = !end && end !== 0 ? this.length : end
    if (start >= end) return ''
    let str = ''
    for (let i = start; i < end; i++) {
        str += this[i]
    }
    return str
}

console.log(str.sx_slice(2)) // nshine_lin
console.log(str.sx_slice(-2)) // in
console.log(str.sx_slice(-9, 10)) // shine_l
console.log(str.sx_slice(5, 1)) // ''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# substring

功能与slice大致相同

区别之处

  • start > end:互换值
String.prototype.sx_sunstring = function (start = 0, end) {
    start = start < 0 ? this.length + start : start
    end = !end && end !== 0 ? this.length : end

    if (start >= end) [start, end] = [end, start]
    let str = ''
    for (let i = start; i < end; i++) {
        str += this[i]
    }

    return str
}

console.log(str.sx_sunstring(2)) // nshine_lin
console.log(str.sx_sunstring(-2)) // in
console.log(str.sx_sunstring(-9, 10)) // shine_l
console.log(str.sx_sunstring(5, 1)) // unsh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# substr

参数代表含义

  • start:开始截取的字符索引(包含此字符)
  • length:截取的长度

注意点

  • start < 0:start = 数组长度 + start
  • length超出所能截取范围,需要做处理
  • length < 0:返回空字符串
String.prototype.sx_substr = function (start = 0, length) {
    if (length < 0) return ''

    start = start < 0 ? this.length + start : start
    length = (!length && length !== 0) || length > this.length - start ? this.length : start + length

    let str = ''
    for (let i = start; i < length; i++) {
        str += this[i]
    }
    return str
}

console.log(str.sx_substr(3)) // shine_lin
console.log(str.sx_substr(3, 3)) // shi
console.log(str.sx_substr(5, 300)) // ine_lin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# slice, substring 和 substr 的区别?

方法 参数 描述
slice slice(start, end) 从start开始,截取到end - 1,如果没有end,则截取到左后一个元素;
substring substring(start,end) 返回从start位置开始到end位置的子串**(不包含end)**
substr substr(start,length) 返回从start位置开始length长度的子串

# 数组的slice 与 splice 的区别?

方法 参数 描述
slice slice(start, end) 从start开始,截取到end - 1,如果没有end,则截取到左后一个元素,不影响原数组
splice splice(start, num, item1,item2, ...) 从start索引开始,截取num个元素,并插入item1、item2到原数组里,影响原数组

# Array篇

定义一个测试数组

const players = [
    { name: '科比', num: 24 },
    { name: '詹姆斯', num: 23 },
    { name: '保罗', num: 3 },
    { name: '威少', num: 0 },
    { name: '杜兰特', num: 35 }
]
1
2
3
4
5
6
7

# forEach

参数代表含义

  • item:遍历项
  • index:遍历项的索引
  • arr:数组本身
Array.prototype.sx_forEach = function (callback) {
    for (let i = 0; i < this.length; i++) {
        callback(this[i], i, this)
    }
}

players.sx_forEach((item, index, arr) => {
    console.log(item, index)
})
// { name: '科比', num: 24 } 0
// { name: '詹姆斯', num: 23 } 1
// { name: '保罗', num: 3 } 2
// { name: '威少', num: 0 } 3
// { name: '杜兰特', num: 35 } 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# map

参数代表含义

  • item:遍历项
  • index:遍历项的索引
  • arr:数组本身
Array.prototype.sx_map = function (callback) {
    const res = []
    for (let i = 0; i < this.length; i++) {
        res.push(callback(this[i], i, this))
    }
    return res
}

console.log(players.sx_map((item, index) => `${item.name}--${item.num}--${index}`))
// [ '科比--24--0', '詹姆斯--23--1', '保罗--3--2', '威少--0--3', '杜兰特--35--4' ]
1
2
3
4
5
6
7
8
9
10

# filter

参数代表含义

  • item:遍历项
  • index:遍历项的索引
  • arr:数组本身
Array.prototype.sx_filter = function (callback) {
    const res = []
    for (let i = 0; i < this.length; i++) {
        callback(this[i], i, this) && res.push(this[i])
    }
    return res
}

console.log(players.sx_filter(item => item.num >= 23))
// [
//     { name: '科比', num: 24 },
//     { name: '詹姆斯', num: 23 },
//     { name: '杜兰特', num: 35 }
// ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# every

参数代表含义

  • item:遍历项
  • index:遍历项的索引
  • arr:数组本身
Array.prototype.sx_every = function (callback) {
    let flag = true
    for (let i = 0; i < this.length; i++) {
        flag = callback(this[i], i, this)
        if (!flag) break
    }
    return flag
}

console.log(players.sx_every(item => item.num >= 23)) // false
console.log(players.sx_every(item => item.num >= 0)) // true
1
2
3
4
5
6
7
8
9
10
11

# some

参数代表含义

  • item:遍历项
  • index:遍历项的索引
  • arr:数组本身
Array.prototype.sx_some = function (callback) {
    let flag = false
    for (let i = 0; i < this.length; i++) {
        flag = callback(this[i], i, this)
        if (flag) break
    }
    return flag
}

console.log(players.sx_some(item => item.num >= 23)) // true
console.log(players.sx_some(item => item.num >= 50)) // false
1
2
3
4
5
6
7
8
9
10
11

# reduce

参数代表含义

  • pre:前一项
  • next:下一项
  • index:当前索引
  • arr:数组本身
Array.prototype.sx_reduce = function (callback, initValue) {
    let start = 0, pre;
    //有初始化默认值情况下;
    if (initValue) {
        pre = initValue
    } else {
        //美亚初始化默认值情况下,取数组第一个数据
        pre = this[0]
        start = 1
    }
    for (let i = start; i < this.length; i++) {
        pre = callback(pre, this[i], i, this)
    }
    return pre
}

// 计算所有num相加
const sum = players.sx_reduce((pre, next) => {
    return pre + next.num
}, 0)
console.log(sum) // 85
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# findIndex

参数代表含义

  • item:遍历项
  • index:遍历项的索引
  • arr:数组本身
Array.prototype.sx_findIndex = function (callback) {
    for (let i = 0; i < this.length; i++) {
        if (callback(this[i], i, this)) {
            return i
        }
    }
    return -1
}

console.log(players.sx_findIndex(item => item.name === '科比')) // 0
console.log(players.sx_findIndex(item => item.name === '安东尼')) // -1
1
2
3
4
5
6
7
8
9
10
11

# find

参数代表含义

  • item:遍历项
  • index:遍历项的索引
  • arr:数组本身
Array.prototype.sx_find = function (callback) {
    for (let i = 0; i < this.length; i++) {
        if (callback(this[i], i, this)) {
            return this[i]
        }
    }
    return undefined
}

console.log(players.sx_find(item => item.name === '科比')) // { name: '科比', num: 24 }
console.log(players.sx_find(item => item.name === '安东尼')) // undefined
1
2
3
4
5
6
7
8
9
10
11

# fill

用处:填充数组

参数代表含义

  • initValue:填充的值
  • start:开始填充索引,默认0
  • end:结束填充索引,默认length
Array.prototype.sx_fill = function (value, start = 0, end) {
  end = end || this.length
  for (let i = start; i < end; i++) {
      this[i] = value
  }
  return this
}

console.log(players.sx_fill('samy', 1, 3))
// [
//     { name: '科比', num: 24 },
//     'samy',
//     'samy',
//     'samy',
//     { name: '杜兰特', num: 35 }
// ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# includes

用处:查找元素,查到返回true,反之返回false,可查找NaN

includes可以检测NaN,indexOf不能检测NaN,includes内部使用了Number.isNaNNaN进行了匹配;

Array.prototype.sx_includes = function (value, start = 0) {
    if (start < 0) start = this.length + start
    const isNaN = Number.isNaN(value)
    for (let i = start; i < this.length; i++) {
        if (this[i] === value || Number.isNaN(this[i]) === isNaN) {
            return true
        }
    }
    return false
}

console.log([1, 2, 3].sx_includes(2)) // true
console.log([1, 2, 3, NaN].sx_includes(NaN)) // true
console.log([1, 2, 3].sx_includes(1, 1)) // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# join

用处:将数组用分隔符拼成字符串,分隔符默认为,

Array.prototype.sx_join = function (s = ',') {
    let str = ''
    for(let i = 0; i < this.length; i++) {
        str = i === 0 ? `${str}${this[i]}` : `${str}${s}${this[i]}`
    }
    return str
}

console.log([1, 2, 3].sx_join()) // 1,2,3
console.log([1, 2, 3].sx_join('*')) // 1*2*3
1
2
3
4
5
6
7
8
9
10

# flat

Array.prototype.sx_flat = function () {
    let arr = this
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr)
    }
    return arr
}

const testArr = [1, [2, 3, [4, 5]], [8, 9]]

console.log(testArr.sx_flat())
// [1, 2, 3, 4, 5, 8, 9]
1
2
3
4
5
6
7
8
9
10
11
12

# splice【难】

# Array.prototype.splice

难点

  • 截取长度和替换长度的比较,不同情况
Array.prototype.sx_splice = function (start, length, ...values) {
  if (length === 0) return []
  length = start + length > this.length - 1 ? this.length - start : length
  console.log(length)
  const res = [], tempArr = [...this]
  for (let i = start; i < start + values.length; i++) {
    this[i] = values[i - start]
  }
  this.length = start + values.length
  if (values.length < length) {
    const cha = length - values.length
    console.log(cha)
    for (let i = start + values.length; i < tempArr.length; i++) {
      this[i] = tempArr[i + cha]
    }
    this.length = this.length - cha 
  }
  if (values.length > length) {
    for (let i = start + length; i < tempArr.length; i++) {
      this.push(tempArr[i])
    }
  }
  for (let i = start; i < start + length; i++) {
    res.push(tempArr[i])
  }
  return res
}
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

# 实现数组去重

题目描述:实现一个数组的去重

// 第一种:Map记录
function quchong1(arr) {
    const newArr = []
    arr.reduce((pre, next) => {
        if (!pre[next]) {
            pre[next] = 1
            newArr.push(next)
        }
        return pre
    }, {})
    return newArr
}

// 第二种:Set去重
function quchong2(arr) {
    return [...new Set(arr)]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Function篇

# call

Function.prototype.sx_call = function (obj, ...args) {//要点o:参数不一样,处理剩余参数;
    obj = obj || window
    const fn = Symbol()// Symbol是唯一的,防止重名key
    obj[fn] = this
    return obj[fn](...args)// 执行,返回执行值
}

const testobj = {
    name: 'samy',
    testFn(age) {
        console.log(`${this.name}${age}岁了`)
    }
}
const testobj2 = {
    name: 'samyzh'
}

testobj.testFn.sx_call(testobj2, 22) // samyzh22岁了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# apply

Function.prototype.sx_apply = function (obj, args) {
    obj = obj || window
    const fn = Symbol()// Symbol是唯一的,防止重名key
    obj[fn] = this
    return obj[fn](...args)// 执行,返回执行值
}

const testobj = {
    name: 'samy',
    testFn(age) {
        console.log(`${this.name}${age}岁了`)
    }
}
const testobj2 = {
    name: 'samyzh'
}

testobj.testFn.sx_apply(testobj2, [22]) // samyzh22岁了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# bind【难】

# Function.prototype.bind

难点:

  • bind是返回一个函数,而不是执行结果
  • bind返回的函数,拿来当做构造函数,该怎么处理
Function.prototype.sx_bind = function (obj, ...args) {
    obj = obj || window
    const fn = Symbol() // Symbol是唯一的,防止重名key
    obj[fn] = this
    const _this = this

    const res = function (...innerArgs) {
       // console.log(this, _this)
        if (this instanceof _this) {
            this[fn] = _this
            this[fn](...[...args, ...innerArgs])
            delete this[fn]
        } else {
            obj[fn](...[...args, ...innerArgs])
            delete obj[fn]
        }
    }
    res.prototype = Object.create(this.prototype)
    return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 实现防抖函数[DC]

function debounce(fn, delay = 500) {
    let timer;
    return function () {
        if (timer) {
            clearTimeout(timer)
        }
        const args = arguments        
        timer = setTimeout(() => {
            fn.apply(this, args) // 改变this指向为调用debounce所指的对象
        }, delay)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 实现节流函数[TJ]

function throttle(fn, delay = 200) {
    let flag = true
    return function () {
        if (!flag) return
        flag = false
        const args = arguments
        setTimeout(() => {
            fn.apply(this, args)
            flag = true
        }, delay)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 实现科里化函数

题目要求:

const add = (a, b, c) => a + b + c;
const a = currying(add, 1);
console.log(a(2,3)) // 1 + 2 + 3=6
1
2
3

实现如下:

function currying(fn, ...args1) {
  // 获取fn参数有几个
  const length = fn.length
  let allArgs = [...args1]
  const res = (...arg2) => {
    allArgs = [...allArgs, ...arg2]
    // 长度相等就返回执行结果
    if (allArgs.length === length) {
      return fn(...allArgs)
    } else {
      // 不相等继续返回函数
      return res
    }
  }
  return res
}

// 测试:
const add = (a, b, c) => a + b + c;
const a = currying(add, 1);
console.log(a(2,3))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 实现compose函数【要点】

题目说明:实现以下效果

function fn1(x) {
    return x + 1;
}
function fn2(x) {
    return x + 2;
}
function fn3(x) {
    return x + 3;
}
function fn4(x) {
    return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a)
console.log(a(1)); // 1+2+3+4=11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

实现如下:

function compose(...fn) {
    if (fn.length === 0) return (num) => num
    if (fn.length === 1) return fn[0]
    return fn.reduce((pre, next) => {
        return (num) => {
            return next(pre(num))
        }
    })
}
1
2
3
4
5
6
7
8
9

# 实现add函数

题目描述:实现一个 add 方法 使计算结果能够满足如下预期:

  • add(1)(2)(3)()=6
  • add(1,2,3)(4)()=10
function add(...args1) {
  let allArgs = [...args1]

  function fn(...args2) {
    if (!args2.length) return fn.toString()
    allArgs = [...allArgs, ...args2]
    return fn
  }

  fn.toString = function () {
    return allArgs.reduce((pre, next) => pre + next)
  }
  return fn
}

// 测试
console.log(add(1)(2)(3)())
console.log(add(1,2)(3)())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 实现LRU缓存函数

题目说明:

image-20211028133620089

实现如下:

class LRUCache {
  constructor(size) {
    this.size = size
    this.cache = new Map()
  }

  get(key) {
    const hasKey = this.cache.has(key)
    if (hasKey) {
      const val = this.cache.get(key)
      this.cache.delete(key)
      this.cache.set(key, val)
      return val
    } else {
      return -1
    }
  }

  put(key, val) {
    const hasKey = this.cache.has(key)
    if (hasKey) {
      this.cache.delete(key)
    }
    this.cache.set(key, val)  //删除原来的,再重新设置
    if (this.cache.size > this.size) {//超过最大,删除很少用的;
      this.cache.delete(this.cache.keys().next().value)
    }
  }
}
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

# 实现lazyMan函数

题目描述:

实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!

LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~

LazyMan(“Hank”).eat(“supper”).sleepFirst(5)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

实现如下:

class _LazyMan {
  constructor(name) {
    this.tasks = []
    const task = () => {
      console.log(`Hi! This is ${name}`)
      this.next()
    }
    this.tasks.push(task)
    setTimeout(() => {
      this.next()
    }, 0)
  }
  next() {
    const task = this.tasks.shift()
    task && task()
  }
  sleep(time) {
    this.sleepWrapper(time, false)
    return this
  }
  sleepFirst(time) {
    this.sleepWrapper(time, true)
    return this
  }
  sleepWrapper(time, first) {
    const task = () => {
      setTimeout(() => {
        console.log(`Wake up after ${time}`)
        this.next()
      }, time * 1000)
    }
    if (first) {
      this.tasks.unshift(task)
    } else {
      this.tasks.push(task)
    }
  }
  eat(food) {
    const task = () => {
      console.log(`Eat ${food}`);
      this.next();
    };
    this.tasks.push(task);
    return this;
  }
}

// 测试
const lazyMan = (name) => new _LazyMan(name)
lazyMan('Hank').sleep(1).eat('dinner')
lazyMan('Hank').eat('dinner').eat('supper')
lazyMan('Hank').eat('supper').sleepFirst(5)
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

# Object/Class篇

定义一个测试对象

const obj = {
    name: 'samy',
    age: 22,
    gender: '男'
}
1
2
3
4
5

# entries

用处:将对象转成键值对数组

Object.prototype.sx_entries = function (obj) {
    const res = []
    for (let key in obj) {
        obj.hasOwnProperty(key) && res.push([key, obj[key]])
    }
    return res
}

console.log(Object.sx_entries(obj))
// [ [ 'name', 'samy' ], [ 'age', 22 ], [ 'gender', '男' ] ]
1
2
3
4
5
6
7
8
9
10

# fromEntries

用处:跟entries相反,将键值对数组转成对象

Object.prototype.sx_fromEntries = function (arr) {
    const obj = {}
    for (let i = 0; i < arr.length; i++) {
        const [key, value] = arr[i]
        obj[key] = value
    }
    return obj
}

console.log(Object.sx_fromEntries([['name', 'samy'], ['age', 22], ['gender', '男']]))
// { name: 'samy', age: 22, gender: '男' }
1
2
3
4
5
6
7
8
9
10
11

# keys

用处:将对象的key转成一个数组合集

Object.prototype.sx_keys = function (obj) {
    const keys = []
    for (let key in obj) {
        obj.hasOwnProperty(key) && res.push(key)
    }
    return keys
}

console.log(Object.keys(obj))
// [ 'name', 'age', 'gender' ]
1
2
3
4
5
6
7
8
9
10

# values

用处:将对象的所有值转成数组合集

Object.prototype.sx_values = function (obj) {
    const values = []
    for (let key in obj) {
        obj.hasOwnProperty(key) && values.push(obj[key])
    }
    return values
}

console.log(Object.sx_values(obj))
// [ 'samy', 22, '男' ]
1
2
3
4
5
6
7
8
9
10

# is

用处:Object.is(a, b),判断a是否等于b;

Object.is():用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致;不同之处只有两个:一是+0不等于-0,二是NaN等于自身。Object.is(v1, v2) 修复了 === 的一些BUG (-0和+0, NaN和NaN)

+0 === -0 //true           NaN === NaN//false    
Object.is(+0, -0) //false  Object.is(NaN, NaN) //true
1
2
Object.prototype.sx_is = function (x, y) {
    if (x === y) {
        return x !== 0 || 1 / x === 1 / y// 防止 -0 和 +0
    }
    return x !== x && y !== y// 防止NaN
}

const a = { name: 'samy' }
const b = a
const c = { name: 'samy' }

console.log(Object.sx_is(a, b)) // true
console.log(Object.sx_is(a, c)) // false
1
2
3
4
5
6
7
8
9
10
11
12
13

# assign【难】

# Object.assign

难点

  • assign接收多个对象,并将多个对象合成一个对象
  • 这些对象如果有重名属性,以后来的对象属性值为准
  • assign返回一个对象,这个对象 === 第一个对象
Object.prototype.sx_assign = function (target, ...args) {
    if (target === null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object')
    }
    target = Object(target)

    for (let nextObj of args) {
        for (let key in nextObj) {
            nextObj.hasOwnProperty(key) && (target[key] = nextObj[key])
        }
    }
    return target
}

const testa = { name: 'samy' }
const testb = { name: 'samyzh', age: 22 }
const testc = { age: 18, gender: '男' }

const testd = Object.sx_assign(testa, testb, testc)
console.log(testd) // { name: 'samyzh', age: 18, gender: '男' }
console.log(testa === testd) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 手写instanceOf

用处:A instanceOf B,判断A是否经过B的原型链;

instanceof 内部机制是通过判断对象的原型链中是不是能找到对应的的prototype; 基础类型没有 __proto__

function instanceOf(father, child) {
    const fp = father.prototype
    var cp = child.__proto__
    while (cp) {
        if (cp === fp) {
            return true
        }
        cp = cp.__proto__
    }
    return false
}

function Person(name) {
    this.name = name
}
const sx = new Person('samy')
console.log(instanceOf(Person, sx)) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 手写new过程

function myNew(fn, ...args) {
    const obj = {}
    obj.__proto__ = fn.prototype
    fn.apply(obj, args)
    return obj
}
1
2
3
4
5
6

# 判断对象有环引用

题目描述:验证一个对象有无环引用

var obj = {
    a: {
        c: [
            1, 2
        ]
    },
    b: 1
}
obj.a.c.d = obj
console.log(cycleDetector(obj)) // true
1
2
3
4
5
6
7
8
9
10

实现思路:用一个数组存储每一个遍历过的对象,下次找到数组中存在,则说明环引用

function cycleDetector(obj) {
    const arr = [obj]
    let flag = false

    function cycle(o) {
        const keys = Object.keys(o)
        for (const key of keys) {
            const temp = o[key]
            if (typeof temp === 'object' && temp !== null) {
                if (arr.indexOf(temp) >= 0) {
                    flag = true
                    return
                }
                arr.push(temp)
                cycle(temp)
            }
        }
    }
    cycle(obj)
    return flag
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 计算对象的层数

题目描述:给你一个对象,统计一下它的层数

const obj = {
    a: { b: [1] },
    c: { d: { e: { f: 1 } } }
}

console.log(loopGetLevel(obj)) // 4
1
2
3
4
5
6

实现如下:

function loopGetLevel(obj) {
    var res = 1;

    function computedLevel(obj, level) {
        var level = level ? level : 0;
        if (typeof obj === 'object') {
            for (var key in obj) {
                if (typeof obj[key] === 'object') {
                    computedLevel(obj[key], level + 1);
                } else {
                    res = level + 1 > res ? level + 1 : res;
                }
            }
        } else {
            res = level > res ? level : res;
        }
    }
    computedLevel(obj)
    return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 对象的扁平化

题目描述:

const obj = {
    a: {
        b: 1,
        c: 2,
        d: {e: 5}
    },
    b: [1, 3, {a: 2, b: 3}],
    c: 3
}

flatten(obj) 
//结果返回如下
// {
//  'a.b': 1,
//  'a.c': 2,
//  'a.d.e': 5,
//  'b[0]': 1,
//  'b[1]': 3,
//  'b[2].a': 2,
//  'b[2].b': 3
//   c: 3
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

实现如下:

const isObject = (val) =>  typeof val === "object" && val !== null

function flatten(obj) {
  if (!isObject(obj)) return
  const res = {}
  const dfs = (cur, prefix) => {
    if (isObject(cur)) {
      if (Array.isArray(cur)) {
        cur.forEach((item, index) => {
          dfs(item, `${prefix}[${index}]`)
        })
      } else {
        for(let key in cur) {
          dfs(cur[key], `${prefix}${prefix ? '.' : ''}${key}`)
        }
      }
    } else {
      res[prefix] = cur
    }
  }
  dfs(obj, '')
  return res
}

// 测试
console.log(flatten(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

# 异步篇

# Promise

# 实现pms

// 判断变量否为function
const isFunction = variable => typeof variable === 'function'
// 定义Promise的三种状态常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
  constructor(handle) {
    if (!isFunction(handle)) {
      throw new Error('MyPromise must accept a function as a parameter')
    }
    this._status = PENDING// 添加状态
    this._value = undefined // 添加状态; value 变量用于保存 resolve 或者 reject 中传入的值
    this._fulfilledQueues = []// 添加成功回调函数队列
    this._rejectedQueues = [] // 添加失败回调函数队列
    try { // 执行handle
      handle(this._resolve.bind(this), this._reject.bind(this))
    } catch (err) {
      this._reject(err)
    }
  }
  _resolve(val) {// 添加resovle时执行的函数
    const run = () => {//首先得判断当前状态是否为等待中,因为规范规定只有等待态才可以改变状态
      if (this._status !== PENDING) return
      const runFulfilled = (value) => {// 依次执行成功队列中的函数,并清空队列
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
        //that._fulfilledQueues.map(cb => cb(that.value))
      }
      const runRejected = (error) => {// 依次执行失败队列中的函数,并清空队列
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(error)
        }
      }
      /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
        当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
      */
      if (val instanceof MyPromise) {
        val.then(value => {
          this._value = value
          this._status = FULFILLED
          runFulfilled(value)
        }, err => {
          this._value = err
          this._status = REJECTED
          runRejected(err)
        })
      } else {
        this._value = val
        this._status = FULFILLED
        runFulfilled(val)
      }
    }
    setTimeout(run, 0)// 为了支持同步的Promise,这里采用异步调用
  }
  
  _reject(err) {// 添加reject时执行的函数
    if (this._status !== PENDING) return
    const run = () => {// 依次执行失败队列中的函数,并清空队列
      this._status = REJECTED
      this._value = err
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(err)
      }
    }
    setTimeout(run, 0) // 为了支持同步的Promise,这里采用异步调用
  }
  
  then(onFulfilled, onRejected) {// 添加then方法
    const { _value, _status } = this
    return new MyPromise((onFulfilledNext, onRejectedNext) => {// 返回一个新的Promise对象
      let fulfilled = value => {// 封装一个成功时执行的函数
        try {
          if (!isFunction(onFulfilled)) {
            onFulfilledNext(value)
          } else {
            let res = onFulfilled(value);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res)
            }
          }
        } catch (err) {// 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err)
        }
      }
      let rejected = error => { // 封装一个失败时执行的函数
        try {
          if (!isFunction(onRejected)) {
            onRejectedNext(error)
          } else {
            let res = onRejected(error);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res)
            }
          }
        } catch (err) {// 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err)
        }
      }
      switch (_status) {
        case PENDING:// 当状态为pending时,将then方法回调函数加入执行队列等待执行
          this._fulfilledQueues.push(fulfilled)
          this._rejectedQueues.push(rejected)
          break
        case FULFILLED:// 当状态已经改变时,立即执行对应的回调函数
          fulfilled(_value)
          break
        case REJECTED:
          rejected(_value)
          break
      }
    })
  }
  
  catch(onRejected) {// 添加catch方法
    return this.then(undefined, onRejected)
  }
  
  finally(cb) {// 添加finally方法
    return this.then(
      value => MyPromise.resolve(cb()).then(() => value),
      reason => MyPromise.resolve(cb()).then(() => { throw reason })
    );
  }
  
  static resolve(value) {// 添加静态resolve方法
    if (value instanceof MyPromise) return value // 如果参数是MyPromise实例,直接返回这个实例
    return new MyPromise(resolve => resolve(value))
  }

  static reject(reason) {// 添加静态reject方法
    return new MyPromise((resolve, reject) => reject(reason))
  }

  static all(list) {// 添加静态all方法
    return new MyPromise((resolve, reject) => {
      let values = []//返回值的集合
      let count = 0
      for (let [i, p] of list.entries()) {
        // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
        this.resolve(p).then(res => {
          values[i] = res
          count++
          // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
          if (count === list.length) resolve(values)
        }, err => {
          reject(err)// 有一个被rejected时返回的MyPromise状态就变成rejected
        })
      }
    })
  }

  static race(list) {// 添加静态race方法
    return new MyPromise((resolve, reject) => {
      for (let p of list) {//注意这里的遍历跟上面不一样;
        this.resolve(p).then(res => {// 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
          resolve(res)
        }, err => {
          reject(err)
        })
      }
    })
  }
}
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177

# all

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 如果所有Promise都成功,则返回成功结果数组
  • 如果有一个Promise失败,则返回这个失败结果
function all(promises) {
    const result = []
    let count = 0
    return new MyPromise((resolve, reject) => {
        const addData = (index, value) => {
            result[index] = value
            count++
            if (count === promises.length) resolve(result)
        }
        promises.forEach((promise, index) => {
            if (promise instanceof MyPromise) {
                promise.then(res => {
                    addData(index, res)
                }, err => reject(err))
            } else {
                addData(index, promise)
            }
        })
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# race

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 哪个Promise最快得到结果,就返回那个结果,无论成功失败
function race(promises) {
    return new MyPromise((resolve, reject) => {
        promises.forEach(promise => {
            if (promise instanceof MyPromise) {
                promise.then(res => {
                    resolve(res)
                }, err => {
                    reject(err)
                })
            } else {
                resolve(promise)
            }
        })
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# allSettled

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 把每一个Promise的结果,集合成数组,返回
function allSettled(promises) {
    return new Promise((resolve, reject) => {
        const res = []
        let count = 0
        const addData = (status, value, i) => {
            res[i] = {
                status,
                value
            }
            count++
            if (count === promises.length) {
                resolve(res)
            }
        }
        promises.forEach((promise, i) => {
            if (promise instanceof MyPromise) {
                promise.then(res => {
                    addData('fulfilled', res, i)
                }, err => {
                    addData('rejected', err, i)
                })
            } else {
                addData('fulfilled', promise, i)
            }
        })
    })
}
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

# any

any与all相反

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 如果有一个Promise成功,则返回这个成功结果
  • 如果所有Promise都失败,则报错
function any(promises) {
    return new Promise((resolve, reject) => {
        let count = 0
        promises.forEach((promise) => {
            promise.then(val => {
                resolve(val)
            }, err => {
                count++
                if (count === promises.length) {
                    reject(new AggregateError('All promises were rejected'))
                }
            })
        })
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# finally

  • 接收一个回调函数,但无参数接收
  • 无论成功失败状态,都会执行finally
Promise.prototype.finally = function(callback) {
  return this.then(res => {
    callback()
    return res
  }, err => {
    callback()
    throw err
  })
}
1
2
3
4
5
6
7
8
9

# 实现async/await

其实上方的generator函数Promise+next传参,就很像async/await了,区别在于

  • gen函数执行返回值不是Promise,asyncFn执行返回值是Promise
  • gen函数需要执行相应的操作,才能等同于asyncFn的排队效果
  • gen函数执行的操作是不完善的,因为并不确定有几个yield,不确定会嵌套几次

image-20211129000805036

实现:

function generatorToAsync(generatorFn) {
  return function() {
    const gen = generatorFn.apply(this, arguments) // gen有可能传参
    // 返回一个Promise
    return new Promise((resolve, reject) => {
      function go(key, arg) {
        let res
        try {
          res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise
        } catch (error) {
          return reject(error) // 报错的话会走catch,直接reject
        }
        // 解构获得value和done
        const { value, done } = res
        if (done) {
          // 如果done为true,说明走完了,进行resolve(value)
          return resolve(value)
        } else {
          // 如果done为false,说明没走完,还得继续走
          // value有可能是:常量,Promise,Promise有可能是成功或者失败
          return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
        }
      }
      go("next") // 第一次执行
    })
  }
}

const asyncFn = generatorToAsync(gen)
asyncFn().then(res => console.log(res))
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

示例

async/await版本

async function asyncFn() {
  const num1 = await fn(1)
  console.log(num1) // 2
  const num2 = await fn(num1)
  console.log(num2) // 4
  const num3 = await fn(num2)
  console.log(num3) // 8
  return num3
}
const asyncRes = asyncFn()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8
1
2
3
4
5
6
7
8
9
10
11
12

使用generatorToAsync函数的版本

function* gen() {
  const num1 = yield fn(1)
  console.log(num1) // 2
  const num2 = yield fn(num1)
  console.log(num2) // 4
  const num3 = yield fn(num2)
  console.log(num3) // 8
  return num3
}

const genToAsync = generatorToAsync(gen)
const asyncRes = genToAsync()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 用setTimeout实现setInterval

题目描述:setinterval 用来实现循环定时调用 可能会存在一定的问题 能用 settimeout 解决吗

function mySetTimout(fn, delay) {
    let timer = null
    const interval = () => {
        fn()
        timer = setTimeout(interval, delay)
    }
    setTimeout(interval, delay)
    return {
        cancel: () => {
            clearTimeout(timer)
        }
    }
}

// 测试
const { cancel } = mySetTimout(() => console.log(888), 1000)
mySetTimout(() => {
    cancel()
}, 4000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 用setInterval实现setTimeout

题目说明:没有,就是想刁难你

function mySetInterval(fn, delay) {
    const timer = setInterval(() => {
        fn()
        clearTimeout(timer)
    }, delay)
}

// 测试
mySetInterval(() => console.log(888), 1000)
1
2
3
4
5
6
7
8
9

# 实现限制并发调度器

题目描述:JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个

addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的输出顺序是:2 3 1 4

整个的完整执行流程:

一开始12两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
1
2
3
4
5
6
7
8
9
10
11
12
13

实现如下:

class Scheduler {
  constructor(limit) {
    this.queue = []
    this.limit = limit
    this.count = 0
  }

  add(time, order) {
    const promiseCreator = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(order)
          resolve()
        }, time)
      })
    }
    this.queue.push(promiseCreator)
  }

  taskStart() {
    for(let i = 0; i < this.limit; i++) {
      this.request()
    }
  }

  request() {
    if (!this.queue.length || this.count >= this.limit) return
    this.count++
    this.queue.shift()().then(() => {
      this.count--
      this.request()
    })
  }
}

// 测试
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
  scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();
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

# 参考链接

https://juejin.cn/post/7002248038529892383

上次更新: 2022/04/15, 05:41:27
×