手写系列
# 手写系列
# 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 (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
if (target === null) return target
if (target instanceof Date) return new Date(target)
if (target instanceof RegExp) return new RegExp(target)
if (target instanceof HTMLElement) return target // 处理 DOM元素
if (typeof target !== 'object') return target
if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
const cloneTarget = new target.constructor()
hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里
Reflect.ownKeys(target).forEach(key => {
cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
})
return cloneTarget
}
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
// 完整版
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
上次更新: 2022/03/25, 16:31:05