手写JavaScript

本文最后更新于 2025年10月31日 中午

参考:https://juejin.cn/post/6946022649768181774

类型判断

1
2
3
function getType(data) {
return Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
}

解析 URL 参数

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
function parseParam(url = location.href) {
const res = {};
const queryStr = url.split("?")[1]?.split("#")[0]; // 去掉 hash
if (!queryStr) return res;

for (const item of queryStr.split("&")) {
if (!item) continue;
let [key, value = true] = item.split("="); // 没有值时给 true
key = decodeURIComponent(key);
value = value === true ? true : decodeURIComponent(value);

if (res[key] !== undefined) {
// 已经有这个 key,转成数组
res[key] = [].concat(res[key], value);
} else {
res[key] = value;
}
}
return res;
}

function parseParam(url) {
let paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
if (/#/.test(paramsStr)) {
paramsStr = /(.+)\#.+$/.exec(paramsStr)[1];
}
const paramsArr = paramsStr.split("&"); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach((param) => {
if (/=/.test(param)) {
// 处理有 value 的参数
let [key, val] = param.split("="); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字

if (paramsObj.hasOwnProperty(key)) {
// 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else {
// 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else {
// 处理没有 value 的参数
paramsObj[param] = true;
}
});
return paramsObj;
}

字符串模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function renderStr(template, data) {
const reg = /\{\{(\d+)\}\}/g;
let match;
while ((match = reg.exec(template))) {
const key = match[1];
template = template.replace(match[0], data[key]);
}
return template;
}

function renderStr(template, data) {
return template.replace(/\{\{(\d+)\}\}/g, (_, key) => data[key]);
}

console.log(renderStr("整理一下{{0}},明天{{1}}", ["东西", "要用"]));
// 整理一下东西,明天要用

事件总线

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
class EventEmitter {
constructor() {
this.cache = {};
}
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn);
} else {
this.cache[name] = [fn];
}
}
off(name, fn) {
const tasks = this.cache[name];
if (tasks) {
const idnex = tasks.findIndex((f) => [f, f.callback].includes(f));
if (index >= 0) {
tasks.splice(index, 1);
}
}
}
emit(name, once = false, ...args) {
if (this.cache[name]) {
const tasks = this.cache[name].slice();
for (let fn of tasks) {
fn(...args);
}
if (once) {
delete this.cache[name];
}
}
}
}

深拷贝

深拷贝实现

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
function Promise(fn) {
this.cbs = [];

const resolve = (value) => {
setTimeout(() => {
this.data = value;
this.cbs.forEach((cb) => cb(value));
});
};

fn(resolve);
}

Promise.prototype.then = function (onResolved) {
return new Promise((resolve) => {
this.cbs.push(() => {
const res = onResolved(this.data);
if (res instanceof Promise) {
res.then(resolve);
} else {
resolve(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
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
178
179
180
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class PromiseSelf {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];

let resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach((fn) => fn());
}
};

let reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) => fn());
}
};

try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}

then(onFulfilled, onRejected) {
// 解决 onFufilled,onRejected 没有传值的问题
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
// 因为错误的值要让后面访问到,所以这里也要抛出错误,不然会在之后 then 的 resolve 中捕获
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err;
};
// 每次调用 then 都返回一个新的 promise
let promise2 = new PromiseSelf((resolve, reject) => {
if (this.status === FULFILLED) {
//Promise/A+ 2.2.4 --- 用 setTimeout 宏任务来模拟微任务
setTimeout(() => {
try {
let x = onFulfilled(this.value);
// x可能是一个proimise
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}

if (this.status === REJECTED) {
//Promise/A+ 2.2.3
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}

if (this.status === PENDING) {
console.log("加入Promise", onFulfilled, onRejected);
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});

this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}

catch(onRejected) {
return this.then(null, onRejected);
}

finally(callback) {
return this.then(
(value) => {
return Promise.resolve(callback()).then(() => value);
},
(error) => {
return Promise.resolve(callback()).then(() => {
throw erro;
});
}
);
}
}
const resolvePromise = (promise2, x, resolve, reject) => {
// 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise Promise/A+ 2.3.1
if (promise2 === x) {
return reject(
new TypeError("Chaining cycle detected for promise #<Promise>")
);
}
// Promise/A+ 2.3.3.3.3 只能调用一次
let called;
// 后续的条件要严格判断 保证代码能和别的库一起使用
if ((typeof x === "object" && x != null) || typeof x === "function") {
try {
// 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候) Promise/A+ 2.3.3.1
let then = x.then;
if (typeof then === "function") {
// 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty Promise/A+ 2.3.3.3
then.call(
x,
(y) => {
// 根据 promise 的状态决定是成功还是失败
if (called) return;
called = true;
// 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
// 只要失败就失败 Promise/A+ 2.3.3.3.2
if (called) return;
called = true;
reject(r);
}
);
} else {
// 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4
resolve(x);
}
} catch (e) {
// Promise/A+ 2.3.3.2
if (called) return;
called = true;
reject(e);
}
} else {
// 如果 x 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.4
resolve(x);
}
};

// 实例判断
new PromiseSelf((resolve) => resolve(1))
.then((res) => {
console.log("step1", res);
return new PromiseSelf((resolve) => {
setTimeout(() => resolve(2), 1000);
});
})
.then((res) => {
console.log("step2", res);
return 3;
})
.then((res) => {
console.log("step3", 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
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
// Promise.resolve
Promise.resolve = function (value) {
return new Promise((resolve) => resolve(value));
};

// Promise.reject
Promise.reject = function (reason) {
return new Promise((resolve, reject) => reject(reason));
};

// Promise.all
Promise.all = function (promiseArr) {
return new Promise((resolve, reject) => {
let count = 0;
const result = [];
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(
(val) => {
count++;
result[i] = val;
if (count === promiseArr.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
});
});
};

// Promise.race
Promise.race = function (promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach((p) => {
Promise.resolve(p).then(
(val) => {
resolve(val);
},
(err) => {
rejecte(err);
}
);
});
});
};

// Promise.allSettled
Promise.allSettled = function (promiseArr) {
return new Promise((resolve, reject) => {
const result = [];
let count = 0;
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(
(value) => {
count++;
result[i] = {
status: "fulfilled",
value,
};
if (count === promiseArr.length) {
resolve(result);
}
},
(reason) => {
count++;
result[i] = {
status: "rejected",
reason,
};
if (count === promiseArr.length) {
resolve(result);
}
}
);
});
});
};

// Promise.any
Promise.any = function (promiseArr) {
return new Promise((resolve, reject) => {
let count = 0;
if (promiseArr.length === 0) return;
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(
(val) => {
resolve(val);
},
(err) => {
count++;
if (count === promiseArr.length) {
reject(new AggregateError("All promises were rejected"));
}
}
);
});
});
};

数组

去重

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
// Set 与扩展操作符去重
function unique(arr) {
return [...new Set(arr)];
}

// Set 与 Array.from()方法去重
function unique(arr) {
return Array.from(new Set(arr));
}

// indexOf()方法去重 1
function unique(arr) {
const res = [];
arr.forEach((ele) => {
if (res.indexOf(ele) === -1) res.push(ele);
});
return res;
}

// indexOf()方法去重 2
function unique(arr) {
return arr.filter((ele, index) => arr.indexOf(ele) === index);
}

// 相邻元素去重
function unique(arr) {
arr = arr.sort();
return arr.filter((ele, index) => ele !== arr[index + 1]);
}

// 利用对象去重
function unique(arr) {
const res = [],
obj = {};
arr.forEach((ele) => {
if (!obj[ele]) {
res.push(ele);
obj[ele] = 1;
} else {
// 可以去掉 else 部分
obj[ele]++;
}
});
return res;
}

function unique(arr) {
const res = [],
map = new Map();
arr.forEach((ele) => {
if (!map.has(ele)) {
res.push(ele);
map.set(ele, 1);
} else {
// 可以去掉 else 部分
map.set(ele, map.get(ele) + 1);
}
});
return res;
}

function unique(arr) {
const res = [],
set = new Set();
arr.forEach((ele) => {
if (!set.has(ele)) {
set.add(ele);
res.push(ele);
}
});
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
28
29
30
31
32
33
34
35
36
37
38
39
40
const animals = ["🐷", ["🐶", "🐂"], ["🐎", ["🐑", ["🐲"]], "🐛"]];

// es5
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}

// es6
function flatten(arr) {
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
function flatten(arr, result = []) {
if (arr.length === 0) return result;
const [first, ...rest] = arr;
if (Array.isArray(first)) {
return flatten([...first, ...rest], result); // ✅ 尾递归调用
} else {
return flatten(rest, [...result, first]); // ✅ 尾递归调用
}
}
function flatten(arr) {
return arr.reduce((pre, ele) => {
if (Array.isArray(ele)) {
return [...pre, ...flatten(ele)];
} else {
return [...pre, ...ele];
}
}, []);
}

原型方法

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
// foreach
Array.prototype.forEach2 = function (callback, thisArg) {
if (this == null) {
throw new TypeError("called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}

// 1. 转成对象(处理类数组)
const arr = Object(this);

// 2. >>> 0:保证 length 转换为无符号 32 位整数
const len = arr.length >>> 0;

for (let i = 0; i < len; i++) {
if (i in arr) {
callback.call(thisArg, arr[i], i, arr);
}
}
};

// map
Array.prototype.map2 = function (callback, thisArg) {
if (this == null) {
throw new TypeError("called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("callbakc is not a function");
}
const arr = Object(this);
const res = [];
for (let i = 0; i < arr.length; i++) {
if (i in arr) {
res[i] = callback.call(thisArg, arr[i], i, arr);
}
}
return res;
};

// filter
Array.prototype.filter2 = function (callback, thisArg) {
if (this == null) {
throw new TypeError("called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("callback is not a function");
}
const arr = Object(this),
res = [];
for (let i = 0; i < arr.length; i++) {
if (i in arr) {
const item = arr[i];
const bool = callback.call(thisArg, item, i, arr);
if (bool) {
res.push(item);
}
}
}
return res;
};

// some
Array.prototype.some2 = function (callback, thisArg) {
if (this == null) {
throw new TypeError("called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("callback is not a function");
}
const arr = Object(this);
for (let i = 0; i < arr.length; i++) {
if (i in arr) {
const item = arr[i];
const bool = callback.call(thisArg, item, i, arr);
if (bool) {
return true;
}
}
}
return false;
};

// reduce
Array.prototype.reduce2 = function (callback, initialValue) {
if (this == null) {
throw new TypeError("called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("callback is not a function");
}
const arr = Object(this);
const len = arr.length >>> 0;
let res;
let k = 0;
if (arguments.length >= 2) {
res = initialValue;
} else {
while (k < len && !(k in arr)) {
k++;
}
if (k >= len) {
throw new TypeError("arr is empty");
}
res = arr[k++];
}
while (k < len) {
if (k in arr) {
res = callback(res, arr[k], k, arr);
}
k++;
}
return res;
};

// forEach/map 这种方法的设计初衷是 「遍历 + 回调」,
// 回调里通常会操作某个外部对象(比如 DOM 节点、Vue/React 组件实例等),
// 所以允许开发者显式指定 this
// reduce 的设计哲学是 「把数组规约成一个值」,回调函数是纯计算逻辑
// 回调被设计成一个「纯函数」,避免外部上下文干扰,也更符合函数式编程的风格

函数

节流防抖

节流

节流:事件在 n 秒内最多只能执行一次函数。

事件第一次被触发立即执行函数,想要再次执行函数,需等待 n 秒,之后再次触发事件才能再次执行函数。在 n 秒内连续多次触发事件,只有第一次执行函数。加入了节流以后,触频繁发事件不会一直执行函数,一段时间内只执行一次函数。

简单版

简单版:使用时间戳来实现,立即执行一次,然后每 n 秒执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function throttle(fun, wait) {
let last = 0;
return function (...args) {
const now = +new Date();
if (now - last >= wait) {
last = now;
return fun.apply(this, args);
}
};
}
// 使用
function clickThrottle() {
console.log(arguments);
console.log("这是简单版节流函数");
}
const thro = document.getElementById("thro");
thro.addEventListener("click", throttle(clickThrottle, 3000), false);

升级版

增加功能:

  • 支持取消
  • 通过传入第三个参数 options
    • leading 来表示是否可以立即执行一次,默认为 true
    • trailing 表示结束调用的时候是否还要执行一次,默认为 true
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
function throttle(fun, wait, options) {
let timeout,
context,
args,
result,
prev = 0;
const { leading, trailing } = options || {};

const later = function () {
prev = leading === false ? 0 : +new Date();
timeout = null;
fun.apply(context, args);
if (!timeout) {
context = args = null;
}
};

const throttled = function (...args) {
const now = +new Date();
if (!prev && leading === false) {
prev = now;
}
const remaining = wait - (now - prev);
const context = this;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
prev = now;
fun.apply(context, args);
if (!timeout) {
context = args = null;
}
} else if (!timeout && trailing !== false) {
timeout = setTimeout(later, remaing);
}
};

throttle.cancel = function () {
clearTimeout(timeout);
prev = 0;
timeout = null;
};
return throttled;
}

防抖

防抖:事件被触发 n 秒之后再执行函数。

如果在这 n 秒内事件又被触发,则重新计时 n 秒,之后再执行函数。加入了防抖以后,频繁触发事件并不会一直执行函数,只有在指定间隔内没有再次触发事件,才会执行对应的函数。

简单版

简单版:函数内部支持使用 this 和 event 对象。

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

升级版

升级版增加功能:

  • 支持取消
  • 支持立即执行
  • 函数可能有返回值
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
function debounce(fun, wait, immediate) {
let timeout = null,
result;

const debounced = function (...args) {
const context = this;
if (timeout) {
clearTimeout(timeout);
}
if (immediate) {
if (!timeout) {
result = fun.apply(context, args);
}
timeout = setTimeout(() => {
timeout = null;
}, wait);
} else {
timeout = setTimeout(() => {
timeout = null;
result = fun.apply(context, args);
}, wait);
}
return result;
};
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}

总结

函数防抖和函数节流都是防止某一时间频繁触发,稀释函数的执行频率,但是原理不一样。

  • 节流应用场景
    • 鼠标不断点击触发 mousedown(单位时间内只触发一次)
    • 监听滚动事件,比如是否滑到底部自动加载更多,用节流来判断
  • 防抖应用场景
    • search 搜索联想,用户在不断输入值时,用防抖来节约请求资源
    • window 触发 resize 时,不断调整浏览器窗口大小会不断触发事件,用防抖来让只触发一次

柯里化

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
function curry1(fun, len = fun.length) {
function _curry(fun, len, ..._args) {
return function (...args) {
const params = [..._args, ...args];
if (params.length >= len) {
return fun.apply(this, params);
} else {
return _curry.bind(this, fun, len, ...params);
}
};
}
return _curry.bind(this, fun, len);
}

function curry2(fun, len = fun.length) {
function _curry(fun, len, ..._args) {
return function (...args) {
const params = [..._args, ...args];
if (params.length >= len) {
return fun.apply(this, params);
} else {
return _curry.call(this, fun, len, ...params);
}
};
}
return _curry.bind(this, fun, len);
}

// 上面两者效果相同,即:
// 如果A函数返回的结果是一个函数,那么调用A时用call或者bind或者apply都不影响最终结果

function curry3(fn, len = fn.length) {
return function _curry(..._args) {
return function (...args) {
const par = [..._args, ...args];
if (par.length >= len) {
return fn.apply(this, par);
} else {
return _curry.call(this, ...par);
}
};
};
}

/**
* 将函数柯里化
* @param fn 待柯里化的原函数
* @param len 所需的参数个数,默认为原函数的形参个数
*/
function curry4(fn, len = fn.length) {
return _curry.call(this, fn, len);
}
/**
* 中转函数
* @param fn 待柯里化的原函数
* @param len 所需的参数个数
* @param args 已接收的参数列表
*/
function _curry(fn, len, ...args) {
return function (...params) {
let _args = [...args, ...params];
if (_args.length >= len) return fn.apply(this, _args);
else return _curry.call(this, fn, len, ..._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
// call
Function.prototype.call2 = function (context, ...args) {
context = context == null ? globalThis : Object(context);
const sym = Symbol("fun");
context[sym] = this;
const res = context[sym](...args);
delete context[sym];
return res;
};

// apply
Function.prototype.apply2 = function (context, args) {
if (args != null && !Array.isArray(args) && typeof args !== "object") {
throw new TypeError("CreateListFromArrayLike called on non-object");
}
context = context == null ? globalThis : Object(context);
const sym = Symbol("fun");
context[sym] = this;
const res = context[sym](...args);
delete context[sym];
return res;
};

// bind
Function.prototype.bind2 = function (context, ...args) {
const fun = this;
const bound = function (..._args) {
const isNew = this instanceof bound;
return fun.apply(isNew ? this : context, args.concat(_args));
};
bound.prototype = Object.create(fun.prototype);
return bound;
};

对象

原型方法

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
// Object.create
Object.create2 = function (pro, propertyObject = undefined) {
if (pro !== null && ["object", "function"].includes(typeof pro)) {
throw new TypeError("prototype only be an Object or null");
}
const obj = {};
Object.setPrototypeOf(obj, pro);
if (propertyObject !== undefined) {
Object.defineProperties(obj, propertyObject);
}
return obj;
};

Object.create2 = function (pro) {
if (pro !== null && ["object", "function"].includes(typeof pro)) {
throw new TypeError("prototype only be an Object or null");
}
function F() {}
F.prototype = pro;
const res = new F();
if (propertyObject !== undefined) {
Object.defineProperties(obj, propertyObject);
}
return res;
};

// Object.assign
Object.assign2 = function (target, ...sources) {
if (target == null) {
throw new TypeError("Cannot convert undefined or null to object");
}
let to = Object(target);

for (const source of sources) {
if (source != null) {
// 只拷贝自有属性(包括 Symbol)
for (const key of Reflect.ownKeys(source)) {
if (Object.prototype.propertyIsEnumerable.call(source, key)) {
to[key] = source[key];
}
}
}
}
return to;
};

操作符

参考:https://juejin.cn/post/6844903613584654344

new

1
2
3
4
5
6
7
8
9
function myNew(fun, ...args) {
const obj = Object.create(fun.prototype);
const res = fun.call(obj, ...args);
if (res && ["object", "function"].includes(typeof res)) {
return res;
} else {
return obj;
}
}

typeof

JS 在底层存储变量的时候,会在变量的机器码的低位 1-3 位存储其类型信息:

  • 000:对象
  • 010:浮点数
  • 100:字符串
  • 110:布尔值
  • 1:整数
  • null:所有机器码均为 0
  • undefined:用 −2^30 整数来表示

因此,由于 null 的所有机器码均为 0,因此直接被当做了对象来看待。

instanceof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 原理:判断 pro.prototype 是否出现在 obj 的原型链上
function myInstanceOf(obj, pro) {
if (typeof pro !== "function") {
throw new TypeError("Right-hand side of 'instanceof' is not callable");
}

let proto = Object.getPrototypeOf(obj); // 获取 obj 的原型
const prototype = pro.prototype; // 构造函数的原型

while (proto !== null) {
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto); // 沿原型链向上查找
}

return false;
}

创建对象

工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function CreatePerson1(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log(this.name);
};
return o;
}

let person1 = CreatePerson1("Nich", 25, "Teacher");
let person2 = CreatePerson1("Greg", 29, "Doctor");
person1.sayName(); // "Nich"
person2.sayName(); // "Greg"

构造函数模式

1
2
3
4
5
6
7
8
9
10
11
12
13
function CreatePerson2(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}

let person1 = new CreatePerson2("Nich", 25, "Teacher");
let person2 = new CreatePerson2("Greg", 29, "Doctor");
person1.sayName(); // "Nich"
person2.sayName(); // "Greg"

原型模式

1
2
3
4
5
6
7
8
9
10
11
12
function CreatePerson3() {}
CreatePerson3.prototype.name = "Nich";
CreatePerson3.prototype.age = 25;
CreatePerson3.prototype.job = "Teacher";
CreatePerson3.prototype.sayName = function () {
console.log(this.name);
};

let person1 = new CreatePerson3();
let person2 = new CreatePerson3();
person1.sayName(); // "Nich"
person2.sayName(); // "Nich"

总结

方式 分析
工厂模式 可解决创建多个类似对象的问题,但没解决对象标识的问题(即新创建的对象是什么类型)
构造函数模式 1.定义自定义构造函数可以确保其实例被标识为特定类型,相比工厂模式这是一个很大的好处。2.用构造函数定义的属性和方法会在每个实例上都创建一遍,创建时可以向构造函数传递参数,所有类型的数据都不会共享。3.正因为如此,主要问题在于创建实例时会重复创建相同的方法。
原型模式 1.同一个构造函数创建的所有实例共享同一个原型对象上的属性和方法,在构造函数原型对象上定义的方法不用在每一个实例上重复创建。2.正因为如此,原型模式弱化了向构造函数传递参数的能力,会导致所有实例默认都取得相同的属性值。3.其主要问题在于创建实例时原型对象上的引用值属性会在所有实例间共享混用。

继承

原型链继承

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
function SuperType1() {
this.name = "Nich";
this.colors = ["red", "blue", "green"];
}
SuperType1.prototype.sayName = function () {
return this.name;
};
function SubType1() {
this.age = 22;
}
SubType1.prototype = new SuperType1();
SubType1.prototype.sayAge = function () {
return this.age;
};

let instance11 = new SubType1();
let instance12 = new SubType1();
instance11.colors.push("black");
console.log(instance11.colors); // ["red, blue, green, black"]
console.log(instance12.colors); // ["red, blue, green, black"]
instance11.sayName(); // 'Nich'
instance11.sayAge(); // 22
instance11.sayName === instance12.sayName; // true
instance11.sayAge === instance12.sayAge; // true
instance11 instanceof SubType1; // true
instance11 instanceof SuperType1; // true
instance11 instanceof Object; // true
instance11 instanceof Function; // 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
function SuperType2(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
this.sayName = function () {
return this.name;
};
}
function SubType2(name) {
SuperType2.call(this, name);
this.age = 22;
this.sayAge = function () {
return this.age;
};
}

let instance21 = new SubType2("Nich");
let instance22 = new SubType2("Greg");
instance21.colors.push("black");
console.log(instance21.colors); // ["red, blue, green, black"]
console.log(instance22.colors); // ["red, blue, green"]
instance21.sayName(); // 'Nich'
instance21.sayAge(); // 22
instance21.sayName === instance22.sayName; // false
instance21.sayAge === instance22.sayAge; // 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
function SuperType3(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType3.prototype.sayName = function () {
return this.name;
};
function SubType3(name) {
SuperType3.call(this, name);
this.age = 22;
}
SubType3.prototype = new SuperType3();
SubType3.prototype.sayAge = function () {
return this.age;
};

let instance31 = new SubType3("Nich");
let instance32 = new SubType3("Greg");
instance31.colors.push("black");
console.log(instance31.colors); // ["red, blue, green, black"]
console.log(instance32.colors); // ["red, blue, green"]
instance31.sayName(); // 'Nich'
instance31.sayAge(); // 22
instance31.sayName === instance32.sayName; // true
instance31.sayAge === instance32.sayAge; // true

原型式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
let person = {
name: "Nich",
colors: ["red", "blue", "green"],
sayName() {
return this.name;
},
};

let instance41 = object(person);
let instance42 = object(person);
instance41.colors.push("black");
console.log(instance41.colors); // ["red, blue, green, black"]
console.log(instance42.colors); // ["red, blue, green, black"]
instance41.sayName(); // 'Nich'
instance41.sayName === instance42.sayName; // true

即等于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let person = {
name: "Nich",
colors: ["red", "blue", "green"],
sayName() {
return this.name;
},
};

let instance43 = Object.create(person);
let instance44 = Object.create(person);
instance43.colors.push("black");
console.log(instance43.colors); // ["red, blue, green, black"]
console.log(instance44.colors); // ["red, blue, green, black"]
instance43.sayName(); // 'Nich'
instance43.sayName === instance44.sayName; // true

寄生式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createAnother(original) {
let clone = Object.create(original); // 不一定必须使用 Object.create()
clone.sayHi = function () {
console.log("hi");
};
return clone;
}
let person = {
name: "Nich",
colors: ["red", "blue", "green"],
sayName() {
return this.name;
},
};

let instance51 = createAnother(person);
let instance52 = createAnother(person);
instance51.sayName === instance52.sayName; // true
instance51.sayHi === instance52.sayHi; // 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
function SuperType6(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType6.prototype.sayName = function () {
return this.name;
};
function SubType6(name) {
SuperType6.call(this, name);
this.age = 22;
}
function inheritPrototype(subType, superType) {
let prototype = Object.create(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 赋值对象
}
inheritPrototype(SubType6, SuperType6);
// 运行完 inheritPrototype(SubType6, SuperType6) 再添加需要的方法,否则无效
SubType6.prototype.sayAge = function () {
return this.age;
};

let instance61 = new SubType6("Nich");
let instance62 = new SubType6("Greg");
instance61.colors.push("black");
console.log(instance61.colors); // ["red, blue, green, black"]
console.log(instance62.colors); // ["red, blue, green"]
instance61.sayName(); // 'Nich'
instance61.sayAge(); // 22
instance61.sayName === instance62.sayName; // true
instance61.sayAge === instance62.sayAge; // true

总结

方式 分析
原型链继承 有着原型模式一样的优缺点。是 ECMAScript 的主要继承方式。构造函数的原型对象是另一个构造函数的实例,以此类推,就在实例和原型之间构造了一条原型链。
借用构造函数继承 有着构造函数模式一样的优缺点。创建子类构造函数实例时,都会在子类构造函数上运行父类构造函数中的所有初始化代码,结果是每一个子类构造函数的实例都有父类构造函数的属性,亮点是可以在子类构造函数中借用多个父类构造函数。
组合继承 使用最多的继承模式。综合了原型链继承和借用构造函数继承,将两者的优点集中起来:使用原型链继承原型对象上的方法,使用借用构造函数继承属性。既可以把方法定义在原型对象上以实现重用,又可以让每个实例都有自己的属性。缺点是父类构造函数会被调用两次。
原型式继承 有着原型模式一样的优缺点。对原型模式的封装,Object.create() 方法将该概念规范化。
寄生式继承 有着构造函数模式一样的优缺点。以某种方式增强对象,然后返回这个对象。
寄生式组合继承 引用类型继承的最佳模式。组合继承的改进版:不通过调用父类构造函数给子类构造函数原型对象赋值,而是取得父类构造函数原型对象的一个副本,赋值给子类构造函数原型对象,同时指定该副本的 constructor 属性为子类构造函数。这样父类构造函数不会被调用两次。

复制文字到剪切板

参考:https://www.zhangxinxu.com/wordpress/2021/10/js-copy-paste-clipboard/

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
const execCommandCopy = () => {
const textarea = document.createElement("textarea");
document.body.appendChild(textarea);
// 隐藏此输入框
textarea.style.position = "fixed";
textarea.style.clip = "rect(0 0 0 0)";
textarea.style.top = "10px";
// 赋值
textarea.value = text;
// 选中
textarea.select();
// 复制
document.execCommand("copy", true);
// 移除输入框
document.body.removeChild(textarea);
};

// clipboard api
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(
(res) => {
console.log("res---", res);
},
(err) => {
console.log("err---", err);
execCommandCopy();
}
);
} else {
execCommandCopy();
}

手写JavaScript
https://xuekeven.github.io/2021/07/20/手写JavaScript/
作者
Keven
发布于
2021年7月20日
更新于
2025年10月31日
许可协议