手写系列
        
  # 手写系列
# Object.create
 就是将传入的对象方程原型
function create(obj) {
	function F() {}
  F.prototype = obj
  return new F()
}
 1
2
3
4
5
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
上次更新: 2025/05/27, 15:44:05