小宋爱睡觉 小宋爱睡觉
首页
  • 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)
  • 手写系列
    • Object.create
    • instanceof
    • new操作符
    • 防抖
    • 节流
    • 防抖 + 节流
    • call
    • apply
    • bind
    • 柯里化
    • 实现ajax
    • 用promise封装ajax
    • 浅拷贝
    • 深拷贝
    • JSONP
    • 实现字符串的repeat
    • 洗牌算法
  • Promise
  • 场景应用
  • 数组的方法
  • 正则表达式相关
  • Vue手写
  • 手写系列
Crucials
2021-11-28
Object.create
instanceof
new操作符
防抖
节流
防抖 + 节流
call
apply
bind
柯里化
实现ajax
用promise封装ajax
浅拷贝
深拷贝
JSONP
实现字符串的repeat
洗牌算法

手写系列

# 手写系列

# Object.create

就是将传入的对象方程原型

function create(obj) {
	function F() {}
  F.prototype = obj
  return new F()
}
1
2
3
4
5

# instanceof

判断构造函数的 prototype 对象是否在对象的原型链上

function myInstance(left, right) {
	let proto = Object.getPrototypeOf(left)
	while(true) {
 		if(!proto) return false
    if(proto === right.prototype) return true
    
    proto = Object.getPrototypeOf(proto)
  }
}
1
2
3
4
5
6
7
8
9

# new操作符

function objFactory() {
  let newObj = Object.create(null);

  // 取出参数的第一位
  let constructor = Array.prototype.shift.call(arguments);
  // result为返回值
  let result = null;
  // 判断是不是一个函数
  if (typeof constructor !== "function") {
    console.error("type error");
    return;
  }

  // 新建一个空对象 对象原型为构造函数的prototype对象
  newObj.__proto__ = constructor.prototype;
  // 将this指向新建对象并调用
  result = constructor.apply(newObj, arguments);
  // 返回对象
  // 这里还要看看构造函数的返回值是不是基本类型的值 还是对象或者函数

  return result instanceof Object ? result : newObj;
}

function Otaku(name, age) {
  this.strength = 60;
  this.age = age;

  return {
    name: name,
    habit: "Games",
  };
}

let temp = objFactory(Otaku, "shm", "20");
console.log(temp.name); // shm
console.log(temp.habit); // Games
console.log(temp.strength); // undefined
console.log(temp.age); // 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
27
28
29
30
31
32
33
34
35
36
37
38

# 防抖

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时

const debounce = (fn, wait = 50) {
	let timer = null
 	return function(...args) {
  	if(timer) {
    	clearTimeout(timer)
      timer = null
    }
    timer = setTimeout(() => {
    	fn.apply(this, args)
    }, wait)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

运用场景

防止多次提交,只执行最后一次提交;表单验证需要服务器配合,只执行一段连续的输入事件的最后一次还有搜索联想词功能

# 节流

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效

const throttle = (fn, wait = 50) => {
	let lastTime = 0
  return function(...args) {
  	let now = +new Date()
  	if(now - lastTime > wait) {
      lastTime = now
    	fn.apply(this, args)
    }
   }
}
1
2
3
4
5
6
7
8
9
10

适用场景:

  • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动

  • 缩放场景:监控浏览器resize

  • 动画场景:避免短时间内多次触发动画引起性能问题

# 防抖 + 节流

function throttle(fn, delay) {
  let last = 0, timer = null;
  return function (...args) {
    let context = this;
    let now = new Date();
    if(now - last < delay){
      clearTimeout(timer);
      setTimeout(function() {
        last = now;
        fn.apply(context, args);
      }, delay);
    } else {
      // 这个时候表示时间到了,必须给响应
      last = now;
      fn.apply(context, args);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# call

Function.prototype.myCall = function (context = window, ...args) {
  if (typeof this !== "function") {
    console.error("type error");
  }

  // 把函数存入上下文中
  let key = Symbol("key");
  context[key] = this;
  // 然后调用函数 将返回值存起来
  let result = context[key](...args);
  delete context[key];
  return result;
1
2
3
4
5
6
7
8
9
10
11
12

# apply

Function.prototype.myApply = function (context = window, arr) {
  if (typeof this !== "function") {
    console.error("type error");
  }

  // 把函数存入上下文中
  let key = Symbol("key");
  context[key] = this;
  // 然后调用函数 将返回值存起来
  let result = context[key](...arr);
  delete context[key];
  return result;
1
2
3
4
5
6
7
8
9
10
11
12

# bind

Function.prototype.myBind = function (context = window, ...outerArgs) {
  if (typeof this !== "function") {
    console.error("typeError");
  }
  const _this = this;

  return function Fn(...innerArgs) {
    // 这里将outer和inner合并是相当于支持柯里化
    const finalArgs = [...outerArgs, ...innerArgs];

    if (_this.prototype) {
      // 为什么使用了 Object.create? 因为我们要防止,bound.prototype 的修改而导致self.prototype 被修改。不要写成 bound.prototype = self.prototype; 这样可能会导致原函数的原型被修改
      this.prototype = Object.create(_this.prototype);
      this.prototype.constructor = _this;
    }

    // 判断是否是被new出来的
    if (this instanceof Fn) {
      return new _this(...finalArgs);
    }
    // 如果面试官不让用new 那就用下面这个
    // if (new.target !== undefined) {
    //   // 说明是用new来调用的 调用new的规则
    //   var result = _this.apply(this, finalArgs);
    //   if (result instanceof Object) {
    //     return result;
    //   }
    //   return this;
    // }
    // 把函数执行一下 然后改变this就好了
    return _this.apply(context, [...finalArgs]); // 返回改变了this的函数
  };
};
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

简单一点

Function.prototype.myBind = function (context = window, ...outerArgs) {
  if (typeof this !== "function") throw "caller must be a function";
  let self = this;
  let fn = function () {
    let innerArgs = Array.prototype.slice.call(arguments);
    // bind 函数的参数 + 延迟函数的参数
    self.apply(
      this instanceof self ? this : context,
      outerArgs.concat(innerArgs)
    );
  };
  fn.prototype = Object.create(self.prototype); // 维护原型
  return fn;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 柯里化

function curry(fn, outerArgs = []) {
  // 获取函数需要的参数个数
	let length = fn.length
  return function(...innerArgs) {
  	const finalArgs = [...outerArgs, ...innerArgs]
    // 判断参数的长度是否已经满足函数所需参数的长度
    if(finalArgs.length < length) {
      // 如果不满足,递归返回科里化的函数,等待参数的传入
    	return curry.call(this, fn, finalArgs)
    } else {
      // 如果满足,执行函数
    	return fn.apply(this, finalArgs)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 实现ajax

const SERVER_URL = '/server'

let xhr = new XMLHttpRequest()

// 第三个参数 true表示采用异步,false表示同步,等待结果返回再进行下一步
xhr.open('GET',SERVER_URL, true)

xhr.onreadystatechange() = function() {
  // 0 未调用open方法
  // 1 open方法被调用
  // 2 send方法被调用 响应头已被接受
  // 3 响应体被接受
  // 4 请求操作完成
	if(this.readystate !== 4) return
  if(this.status === 200) {
  	handle(this.response)
  } else {
  	console.error(this.statusText)
  }
}

xhr.onerror = function() {
  console.error(this.statusText)
}

xhr.responseType = 'json'
xhr.setRequestHeader('Accept', 'application/json')

xhr.send(null)
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
  • 优点:
    • 通过异步模式,提升了用户体验.
    • 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用.
    • Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。
    • Ajax可以实现动态不刷新(局部刷新)
  • 缺点:
    • 安全问题 AJAX暴露了与服务器交互的细节。
    • 对搜索引擎的支持比较弱。
    • 不容易调试。

# 用promise封装ajax

function myajax(url) {
	let promise = new Promise((resolve, reject) => {
  	let xhr = new XMLHttpRequest()

    xhr.open('GET',SERVER_URL, true)

    xhr.onreadystatechange() = function() {
      if(this.readystate !== 4) return
      if(this.status === 200) {
       resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    
    xhr.onerror = function() {
      console.error(this.statusText)
    }

    xhr.responseType = 'json'
    xhr.setRequestHeader('Accept', 'application/json')
  })
  
  return promise
}
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

简易版axios

class Axios {
  constructor() {

  }

  request(config) {
    return new Promise(resolve => {
      const {url = '', method = 'get', data = {}} = config;
      // 发送ajax请求
      const xhr = new XMLHttpRequest();
      xhr.open(method, url, true);
      xhr.onload = function() {
        console.log(xhr.responseText)
        resolve(xhr.responseText);
      }
      xhr.send(data);
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 浅拷贝

function shollowCopy(object) {
	if(!object || typeof object !== 'object') return
  
  let newObj = Array.isArray(object) ? [] : {}
  
  for(let key in object) {
  	if(Object.hasOwnProperty(key)) {
    	newObj[key] = object[key]
    }
  }
  return newObj
}
1
2
3
4
5
6
7
8
9
10
11
12

值得注意的是

假设使用的是Object.assign或者扩展操作符进行浅拷贝的话,他有以下限制

  • 它不会拷贝对象的继承属性;
  • 它不会拷贝对象的不可枚举的属性;
  • 可以拷贝 Symbol 类型的属性

# 深拷贝

// 深拷贝的实现
function deepCopy(object) {
  if (!object || typeof object !== "object") return;

  let newObject = Array.isArray(object) ? [] : {};

  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }

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

值得注意的是

用JSON.stringify的局限性

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 无法拷贝不可枚举的属性
  • 无法拷贝对象的原型链
  • 拷贝 RegExp 引用类型会变成空对象
  • 拷贝 Date 引用类型会变成字符串
  • 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null
  • 不能解决循环引用的对象,即对象成环 (obj[key] = obj)。
function deepClone(obj, cache = new WeakMap()) {
  // 基本类型或函数直接返回
  if (obj === null || typeof obj !== 'object') return obj;

  // 处理循环引用
  if (cache.has(obj)) return cache.get(obj);

  // 特殊类型处理
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);

  // 支持 Map
  if (obj instanceof Map) {
    const result = new Map();
    cache.set(obj, result);
    obj.forEach((value, key) => {
      result.set(deepClone(key, cache), deepClone(value, cache));
    });
    return result;
  }

  // 支持 Set
  if (obj instanceof Set) {
    const result = new Set();
    cache.set(obj, result);
    obj.forEach(value => result.add(deepClone(value, cache)));
    return result;
  }

  // 普通对象或数组
  const result = Array.isArray(obj) ? [] : {};
  cache.set(obj, result);

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key], cache);
    }
  }

  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
36
37
38
39
40
41
// 完整版
const getType = (obj) => Object.prototype.toString.call(obj);
const isObject = (target) =>
  (typeof target === "object" || typeof target === "function") &&
  target !== null;

const canTraverse = {
  "[object Map]": true,
  "[object Set]": true,
  "[object Array]": true,
  "[object Object]": true,
  "[object Arguments]": true,
};
const mapTag = "[object Map]";
const setTag = "[object Set]";
const boolTag = "[object Boolean]";
const numberTag = "[object Number]";
const stringTag = "[object String]";
const symbolTag = "[object Symbol]";
const dateTag = "[object Date]";
const errorTag = "[object Error]";
const regexpTag = "[object RegExp]";
const funcTag = "[object Function]";

const handleRegExp = (target) => {
  const { source, flags } = target;
  return new target.constructor(source, flags);
};

const handleFunc = (func) => {
  // 箭头函数直接返回自身
  if (!func.prototype) return func;
  const bodyReg = /(?<={)(.|\n)+(?=})/m;
  const paramReg = /(?<=\().+(?=\)\s+{)/;
  const funcString = func.toString();
  // 分别匹配 函数参数 和 函数体
  const param = paramReg.exec(funcString);
  const body = bodyReg.exec(funcString);
  if (!body) return null;
  if (param) {
    const paramArr = param[0].split(",");
    return new Function(...paramArr, body[0]);
  } else {
    return new Function(body[0]);
  }
};

const handleNotTraverse = (target, tag) => {
  const Ctor = target.constructor;
  switch (tag) {
    case boolTag:
      return new Object(Boolean.prototype.valueOf.call(target));
    case numberTag:
      return new Object(Number.prototype.valueOf.call(target));
    case stringTag:
      return new Object(String.prototype.valueOf.call(target));
    case symbolTag:
      return new Object(Symbol.prototype.valueOf.call(target));
    case errorTag:
    case dateTag:
      return new Ctor(target);
    case regexpTag:
      return handleRegExp(target);
    case funcTag:
      return handleFunc(target);
    default:
      return new Ctor(target);
  }
};

const deepClone = (target, map = new WeakMap()) => {
  if (!isObject(target)) return target;
  let type = getType(target);
  let cloneTarget;
  if (!canTraverse[type]) {
    // 处理不能遍历的对象
    return handleNotTraverse(target, type);
  } else {
    // 这波操作相当关键,可以保证对象的原型不丢失!
    let ctor = target.constructor;
    cloneTarget = new ctor();
  }

  if (map.get(target)) return target;
  map.set(target, true);

  if (type === mapTag) {
    //处理Map
    target.forEach((item, key) => {
      cloneTarget.set(deepClone(key, map), deepClone(item, map));
    });
  }

  if (type === setTag) {
    //处理Set
    target.forEach((item) => {
      cloneTarget.add(deepClone(item, map));
    });
  }

  // 处理数组和对象
  for (let prop in target) {
    if (target.hasOwnProperty(prop)) {
      cloneTarget[prop] = deepClone(target[prop], map);
    }
  }
  return cloneTarget;
};
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

测试数据

const map = new Map();
map.set("key", "value");
map.set("crucials", "male");

const set = new Set();
set.add("crucials");
set.add("male");

const target = {
  field1: 1,
  field2: undefined,
  field3: {
  	child: "child",
  },
  field4: [2, 4, 8],
  empty: null,
  map,
  set,
  bool: new Boolean(true),
  num: new Number(2),
  str: new String(2),
  symbol: Object(Symbol(1)),
  date: new Date(),
  reg: /\d+/,
  error: new Error(),
  func1: () => {
  	console.log("crucials");
  },
  func2: function (a, b) {
  return a + b;
  },
};
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

# JSONP

常见的跨域手段,利用 <script> 标签没有跨域限制的漏洞,来达到与第三方通讯的目的。

const jsonp = ({ url, params, callbackName }) => {
  const generateURL = () => {
    // 根据 URL 格式生成地址
    let dataStr = "";
    for (let key in params) {
      dataStr += `${key}=${params[key]}&`;
    }
    dataStr += `callback=${callbackName}`;
    return `${url}?${dataStr}`;
  };
  return new Promise((resolve, reject) => {
    callbackName = callbackName || Math.random().toString();
    let scriptEle = document.createElement("script");
    scriptEle.src = generateURL();
    document.body.appendChild(scriptEle);
    // 服务器返回字符串 `${callbackName}(${服务器的数据})`,浏览器解析即可执行。
    window[callbackName] = (data) => {
      resolve(data);
      document.body.removeChild(scriptEle); // 别忘了清除 dom
    };
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 实现字符串的repeat

function repeat(s, n) {
  return n > 0 ? String(s).concat(repeat(s, --n)) : "";
}

console.log(repeat(3, 2));
console.log(repeat('abc', 2));
function repeat(s, n) {
    return (new Array(n + 1)).join(s);
}
1
2
3
4
5
6
7
8
9

# 洗牌算法

let arr = [1, 2, 3, 4, 5];

function shuffleAlgorithm(arr) {
  for (let i = 0; i < arr.length; i++) {
    let j = Math.floor(Math.random() * (i + 1)); // 生成 [0, i] 范围的随机整数
    [arr[i], arr[j]] = [arr[j], arr[i]];         // 交换 arr[i] 和 arr[j]
  }
}

shuffleAlgorithm(arr);
console.log(arr);
1
2
3
4
5
6
7
8
9
10
11
上次更新: 2025/05/27, 15:44:05
Promise

Promise→

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