# 面试
# 手写new
function new (fun){
if(typeof fun !== 'function'){
throw Error(` $ {fun}
is not a function ` )
}
var obj = {}
obj.__proto__ = fun.prototype;
var result = fun.apply(obj,Array.prototype.slice(arguments,1))
return result
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 手写JSON.stringify
- Boolean | Number | String 类型会自动转换成对应的原始值。
- undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。
- 不可枚举的属性会被忽略
- 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
function stringify(obj){
let type = typeof obj;
if (type !== "object") {
if (/string|undefined|function/.test(type)) {
obj = '"' + obj + '"';
}
return String(obj);
} else {
let json = []
let arr = Array.isArray(obj)
for (let k in obj) {
let v = obj[k];
let type = typeof v;
if (/string|undefined|function/.test(type)) {
v = '"' + v + '"';
} else if (type === "object") {
v = jsonStringify(v);
}
json.push((arr ? "" : '"' + k + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 手写JSON.parse
- 最简单的肯定是直接eval了,但是eval()是一个危险的函数, 他执行的代码拥有着执行者的权利。如果你用 eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页 / 扩展程序的权限下,在用户计算机上运行恶意代码。
即会有XSS 漏洞。触发条件:参数 json 并非真正的 JSON 数据,而是可执行的 JS 代码。
function jsonParse(opt) {
return eval('(' + opt + ')');
}
1
2
3
2
3
所以对参数 json 做校验,只有真正符合 JSON 格式,才能调用 eval,具体就是下面这几个正则匹配。
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (
rx_one.test(
json
.replace(rx_two, "@")
.replace(rx_three, "]")
.replace(rx_four, "")
)
) {
var obj = eval("(" +json + ")");
}
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
# 手写一个call或 apply
call
a.call(obj, '1')
Function.prototype.call = function(context){
if(typeof this !== 'function'){
Throw new Error('this is not a function')
}
context = context | window
context.fn = this;
var argus = [...arguments].slice(1)
var result = context.fn(...argus)
delete context.fn
return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
apply
Function.prototype.apply = function(context,arr){
if(typeof this !== 'function'){
Throw new Error('this is not a function')
}
context = context | window
context.fn = this;
if(arr){
var result = context.fn(arr)
}else{
var result = context.fn(arr)
}
delete context.fn;
return result;
}
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.bind
var b = a.bind(obj, 1)
b(2)
Function.prototype.bind(context){
if(typeof this !== 'function'){
Throw new Error('Function.prototype.bind - what is trying to be bound is not callable')
}
var _self = this;
var augus = Array.prototype.slice.call(arguments,1);
var trFn = function(){}
var returnFn = function(){
var bindArgus = Array.prototype.slice.call(arguments);
return _self.apply(this in trFn ? this : context,argus.concat(bindArgus))
}
trFn.prototype = this.prototype;
returnFn.prototype = new trFn()
return returnFn;
}
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
# 手写一个继承
寄生组合式继承
一般只建议写这种,因为其它方式的继承会在一次实例中调用两次父类的构造函数或有其它缺点。
核心实现是:用一个 F 空的构造函数去取代执行了 Parent 这个构造函数。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
function create(proto) {
function F(){}
F.prototype = proto;
return new F();
}
Child.prototype = create(Parent.prototype);
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.sayName(); // parent name: father
var child = new Child('son', 'father');
//牛逼的继承
function Rectangle(length,width){
this.l = length
this.w = width
}
Rectangle.protoType.getArea = function(){
return this.l + this.w
}
function Square(length){
Rectangle.call(this.length,length)
}
Square.protoType = Object.create(Rectangle.prototype,{
constructor:{
value:Square
}
})
var square = new Square(3);
console.log(square.getArea())
console.log(square instanceof Square)
console.log(square instanceof Rectangle)
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
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
# 手写一个JS函数柯里化和反柯里化
function curry(fn,args){
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
//ES6
const curry = (fn, arr = []) => (...args) => (
arg => arg.length === fn.length
? fn(...arg)
: curry(fn, arg)
)([...arr, ...args])
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10
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
# 手写一个Promise
function Promise(executor) {
let that = this;
that.status = 'pending';
that.value = null;
that.reason = null;
that.onFilFulledCallbacks = [];
that.onRejectedCallbacks = [];
function resolve(value) {
if (that.status === 'pending') {
that.status = 'resolved';
that.value = value;
that.onFilFulledCallbacks.forEach((fn) = & gt; {
fn();
});
}
}
function reject(reason) {
if (that.status === 'pending') {
that.status = 'rejected';
that.reason = reason;
that.onRejectedCallbacks.forEach((fn) = & gt; {
fn();
});
}
}
executor(resolve, reject);
}
Promise.prototype.then = function (onFilfulled, onRejected) {
let that = this;
/** 1 让promise2等于一个新的Promise 并将promise2返回 */
let promise2 = new Promise((resolve, reject) = & gt; {
if (that.status === 'resolved') {
/** 2 因为返回了promise2
* 并且第3步resolvePromiseRelation函数中传递了promise2
* 而目前promise2并没有拿到
* 所以加一个定时器 异步执行 等到promise2拿到后
* 再去执行 resolvePromiseRelation()方法 并将promise2传递进去*/
setTimeout(() = & gt; {
try {
let promise3 = onFilfulled(that.value);
/** 3 判断新返回值是什么类型的函数
* 并将当前的promise:promise2 新的返回值:promise3
* 和 成功时回调:esolve 失败时回调:reject 作为参数传进去 */
resolvePromiseRelation(promise2, promise3, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (that.status === 'rejected') {
/** 同2 */
setTimeout(() = & gt; {
try {
let promise3 = onRejected(that.reason);
/** 同3*/
resolvePromiseRelation(promise2, promise3, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (that.status === 'pending') {
that.onFilFulledCallbacks.push(function () {
/** 同2 */
setTimeout(() = & gt; {
try {
let promise3 = onFilfulled(that.value);
/** 同3*/
resolvePromiseRelation(promise2, promise3, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
if (that.status === 'pending') {
that.onRejectedCallbacks.push(function () {
/** 同2 */
setTimeout(() = & gt; {
try {
let promise3 = onRejected(that.reason);
/** 同3*/
resolvePromiseRelation(promise2, promise3, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
/** 同1 */
return promise2;
}
function resolvePromiseRelation(promise2, promise3, resolve, reject) {
/** 4 防止自己等待自己 一直循环等待 */
if (promise2 === promise3) {
return reject(new TypeError('循环引用了!'));
}
/** 8 一个标示 表示当前没有被调用过
* 确保resolve或者reject后的状态不会再次发生变更
*/
let called;
/** 5 保证promise3是一个引用类型
* 判断新返回值promise3的类型
* 如果是普通值常量 就直接resolve导出 */
if (promise3 !== null & amp; & amp;
(typeof promise3 === 'object' || typeof promise3 === 'function')) {
try {
let then = promise3.then;
/** 6 确保promise3是一个Promise
* 判断promise3的then方法
* 如果存在 并且是一个function类型
* 就表示promise3是一个Promise */
if (typeof then === 'function') {
/** 9 执行promise3的then方法
* 因为promise3也是一个Promise
* 需要再次解析promise3的then方法
* 直到解析到最后的返回值不是一个Promise类型为止
*/
then(promise3, (promise4) = & gt; {
/** 同8 */
if (called) return;
called = true;
/** 10 递归解析新的返回值的类型
* 解析到返回值不是一个Promise类型为止
*/
resolvePromiseRelation(promise3, promise4, resolve, reject);
}, (r) = & gt; {
/** 同8 */
if (called) return;
called = true;
reject(r);
});
} else {
/** 7 此时promise3是一个普通对象 直接resolve() */
resolve(promise3);
}
} catch (e) {
/** 同8 */
if (called) return;
called = true;
reject(e);
};
} else {
//常量
/** 同5 普通值直接resolve()*/
resolve(promise3);
}
}
Promise.prototype.all = function(values){
return new Promise((resolve,reject)=>{
/** 2 定义一个存放最终结果的数组result和一个index */
let results = [];
let index = 0;
/** 3 定义一个方法addToArr()
* 让index每次执行增加results数组元素的函数的时候都+1
* 当index === values的长度的时候 说明此时所有promsie都执行完毕并放到的数组中
* 然后直接resolve(results)就行了
*/
function addToArr(key,value){
index++;
results[key] = value;
/** 6 当满足条件时 说明所有的promise都执行完毕 直接resolve(results) */
if(index === values.length){
resolve(results);
}
}
/** 4 循环values中的每一项promsie */
for(let i = 0; i < values.length; i++){
let current = values[i];
/** 5 判断每一项promise的返回值是不是一个Promsie
* 是的话就执行该Promise的then方法 拿到返回值 并放到数组results中
* 是一个普通值的话就直接将该值放到数组results中
*/
if(current && current.then && typeof current.then === 'function'){
current.then((value)=>{
/** 同5 把返回值放到数组results中*/
addToArr(i,value);
},reject);
}else{
/** 同5 把返回值放到数组results中*/
addToArr(i,current);
}
}
});
}
//类方法,多个 Promise 任务同时执行,返回最先执行结束的 Promise 任务的结果,不管这个 Promise 结果是成功还是失败。
。
/** race方法相比较于all方法简单很多
* 因为race中的promsie成功resolve一个
* 整个race就resolve */
Promise.prototype.race = function(values){
return new Promise((resolve,reject)=>{
/** 同4 */
for(let i = 0; i < values.length; i++){
let current = values[i];
/** 同5 */
if(current&&current.then&&typeof current.then === 'function'){
/** 7 直接执行then就好 */
current.then(resolve,reject);
}else{
/** 8 普通值直接resolve */
resolve(current);
}
}
});
}
// resolve方法
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value);
});
}
// reject方法
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
});
}
module.exports = 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
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# 手写防抖(Debouncing)和节流(Throttling)
某些代码不可以在没有间断的情况连续重复执行
- 1 、DOM操作比起非DOM交互,需要更多地内存和CPU时间,连续尝试过多的DOM操作有可能会导致浏览器挂起,甚至奔溃。
- 2 、防止恶意脚本攻击
- 由于节流在resize中是最常用的,如果基于该事件来改变页面布局的话,最好控制处理频率,以确保浏览器不会在极短的时间内进行过多次的计算。
节流
let oBtn=document.getElementById('show');
let oBox=document.getElementById('box');
/*
节流函数
handle:buy函数
wait:规定在一秒钟内只能执行一次
*/
function tab(add, wite){
let lastTime = 0;
return function(e) {
let nowTime = new Date().getTime()
if(nowTime - lastTime > wite){
add()
lastTime = nowTime
}
}
}
function add (){
oBox.innerText = parseInt(oBox.innerText) +1
}
oBtn.onclick = tab(add ,1000)
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
防抖
let oIpt = document.getElementById('ipt');
let time = null;
function debounce (handle, delay) {
let time = null;
return function () {
let self = this,arg = arguments;
clearTimeout(time);
time = setTimeout(function () {
handle.apply(self,arg); //this绑定
},delay)
}
}
function ajax (e) {
console.log(e,this.value)
}
oIpt.oninput = debounce(ajax, 1000) //1s后发出请求
<!--去掉上一个timer,重新加一个-->
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
# 手写一个JS深拷贝(由浅入深多种解法)
# 乞丐版
var newObj = JSON.parse( JSON.stringify( someObj ) );
1
# 递归
function deepCopy(obj){
//判断是否是简单数据类型,
if(typeof obj == "object"){
//复杂数据类型
var result = obj.constructor == Array ? [] : {};
for(let i in obj){
result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
}
}else {
//简单数据类型 直接 == 赋值
var result = obj;
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 手写一个instanceOf原理
function instanceOf(left,right) {
let proto = left.__proto__;
let prototype = right.prototype
while(true) {
if(proto === null) return false
if(proto === prototype) return true
proto = proto.__proto__;
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 手写一个map
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
// 1. 将O赋值为调用map方法的数组.
var O = Object(this);
// 2.将len赋值为数组O的长度.
var len = O.length >>> 0;
// 3.如果callback不是函数,则抛出TypeError异常.
if (Object.prototype.toString.call(callback) != "[object Function]") {
throw new TypeError(callback + " is not a function");
}
// 4. 如果参数thisArg有值,则将T赋值为thisArg;否则T为undefined.
if (thisArg) {
T = thisArg;
}
// 5. 创建新数组A,长度为原数组O长度len
A = new Array(len);
// 6. 将k赋值为0
k = 0;
// 7. 当 k < len 时,执行循环.
while(k < len) {
var kValue, mappedValue;
//遍历O,k为原数组索引
if (k in O) {
//kValue为索引k对应的值.
kValue = O[ k ];
// 执行callback,this指向T,参数有三个.分别是kValue:值,k:索引,O:原数组.
mappedValue = callback.call(T, kValue, k, O);
// 返回值添加到新数组A中.
A[ k ] = mappedValue;
}
// k自增1
k++;
}
// 8. 返回新数组A
return A;
};
}
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
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
# 手写reduce
if (!Array.prototype.reduce) {
Object.defineProperty(Array.prototype, 'reduce', {
value: function(callback /*, initialValue*/) {
if (this === null) {
throw new TypeError( 'Array.prototype.reduce ' +
'called on null or undefined' );
}
if (typeof callback !== 'function') {
throw new TypeError( callback +
' is not a function');
}
// 1. Let O be ? ToObject(this value).
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// Steps 3, 4, 5, 6, 7
var k = 0;
var value;
if (arguments.length >= 2) {
value = arguments[1];
} else {
while (k < len && !(k in o)) {
k++;
}
// 3. If len is 0 and initialValue is not present,
// throw a TypeError exception.
if (k >= len) {
throw new TypeError( 'Reduce of empty array ' +
'with no initial value' );
}
value = o[k++];
}
// 8. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kPresent be ? HasProperty(O, Pk).
// c. If kPresent is true, then
// i. Let kValue be ? Get(O, Pk).
// ii. Let accumulator be ? Call(
// callbackfn, undefined,
// « accumulator, kValue, k, O »).
if (k in o) {
value = callback(value, o[k], k, o);
}
// d. Increase k by 1.
k++;
}
// 9. Return accumulator.
return value;
}
});
}
方法二、
Array.prototype.myReduce = function(fn, init){
var len = this.length;
var pre = init;
var i = 0;
//判断是否传入初始值
if(init == undefined){
//没有传入初始值,数组第一位默认为初始值,当前元素索引值变为1。
pre = this[0];
i = 1;
}
for(i; i < len; i ++){
//当前函数返回值为下一次的初始值
pre = fn(pre, this[i], i)
}
return pre;
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
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
# 手写实现拖拽
<meta charset="UTF-8">
<meta name="Generator" content="EditPlus®">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>Document</title>
<style type="text/css">
*{
margin:0;
padding:0;
}
body{
background:url("img/2345_image_file_copy_1.jpg");
}
#d1{
width:100px;
height:100px;
background:red;
margin-left:300px;
}
</style>
</head>
<body>
<div id="d1"></div>
</body>
<script>
window.onload=function(){
var d1=document.getElementById("d1");
d1.onmousedown=function(e){
var mouseX=e.clientX;
var mouseY=e.clientY;//计算xy
var pianyiX=mouseX-d1.offsetLeft;
var pianyiY=mouseY-d1.offsetTop;
document.onmousemove=function(e){
var newX=e.clientX-pianyiX;
var newY=e.clientY-pianyiY;
d1.style.marginLeft=newX+"px";
d1.style.marginTop=newY+"px";
}
};
document.onmouseup = function(e){
document.onmousemove = null ;
};
}
</script>
</html>
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
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
# 使用setTimeout模拟setInterval
setTimeout(function () {
// 任务
setTimeout(arguments.callee, interval);
}, interval)
1
2
3
4
2
3
4
# 手写实现Object.create的基本原理
Object.create= function (obj, properties) {
function F () {}
F.prototype = obj;
let o = new F();
// 注意Object.create可以传入第二个参数,是一个对象,但格式必须是Object.defineProperties()方法一样
if (typeof properties === 'object') {
Object.defineProperties(o, properties);
}
return o;
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 手写实现一个基本的Event Bus
最简单版
function event (){
// 存储事件
this.events = this.events || new Map()
}
event.prototype.on = function(type,fn){
if(!this.events.get(type)){
this.events.set(type,fn)
}
}
event.prototype.emit = function(type){
var handle = this.events.get(type)
if(!handle){
throw new Error('未监听这个函数')
}else{
handle.apply(this,[...argumnets].slice(1))
}
}
event.prototype.off = function(type,fn){
var handle = this.events.get(type)
if(handle){
this.events.delete(type)
}
}
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
完整版
(function (exporter) {
function isFunc(fn) { return typeof fn === "function" }
function str(s) {
if (s == null) {
return null;
}
s = s.replace(/^\s+|\s+$/g, "");
return s.length > 0 ? s.toLowerCase() : null;
}
function handler() {
var fns = [];
var datas = [];
this.add = function (fn, data) {
fns.push(fn);
datas.push(data);
}
this.remove = function (fn) {
var i = fns.indexOf(fn);
if (i >= 0) {
fns.splice(i, 1);
datas.splice(i, 1);
}
}
this.invoke = function (sender, data) {
fns.forEach((fn, i) => {
try {
fn(sender, data, datas[i])
} catch (error) {
console.error(error);
}
});
}
}
function eventBus() {
var handers = {}
this.on = function (eventName, fnOrData, fn) {
eventName = str(eventName);
if (eventName == null) {
throw new Error("事件名无效");
}
if (!isFunc(fn)) {
var temp = fn;
fn = fnOrData;
fnOrData = temp;
}
if (!isFunc(fn)) {
throw new Error("必须提供事件函数");
}
var handle = handers[eventName];
if (handle == null) {
handle = new handler();
handers[eventName] = handle;
}
handle.add(fn, fnOrData);
}
this.off = function (eventName, fn) {
eventName = str(eventName);
if (eventName == null) {
return;
}
var handle = handers[eventName];
if (handle != null) {
if (fn == null) {
delete handers[eventName];
} else {
handle.remove(fn);
}
}
}
this.fire = this.emit = this.trigger =
function (eventName, sender, data) {
eventName = str(eventName);
if (eventName == null) {
return;
}
var handle = handers[eventName];
if (handle != null) {
handle.invoke(sender, data);
}
}
var bus = this;
this.bindTo = function(obj){
if(obj == null){
throw new Error("obj is null");
}
for (const key in bus) {
if (bus.hasOwnProperty(key) && key !== "bindTo") {
obj[key] = bus[key];
}
}
}
}
var instance = new eventBus();
instance.bindTo(eventBus);
exporter(eventBus);
})(c => window.eventBus = c)
//绑定
eventBus.on("data_completed", function (sender, data, obj) {
console.log({ sender, data, sign: obj.sign });
}, { sign: "F6243749AFF04C0581E1DD178A0B737A" });
//触发
eventBus.emit(eventName[, sender][, data]);
eventBus.trigger(eventName[, sender][, data]);
eventBus.fire(eventName[, sender][, data]);
eventBus.emit("Data_Completed", window, [1,2,3,4,5,6]);
//多次绑定
var f2 = function (sender, data, obj) {
console.log({ sender, data, sign: obj.sign });
}
eventBus.on("data_completed", f2 , { sign: "722F9C19C9704412B93216BD70F2AE52" });
eventBus.emit("Data_Completed", window, [1,2,3,4,5,6]);
//解绑、清空
eventBus.off("Data_Completed", f2);
eventBus.emit("Data_Completed", window, [1,2,3,4,5,6]);
eventBus.off("Data_Completed");
eventBus.emit("Data_Completed", window, [1,2,3,4,5,6]);
//初始化一个新的
var myEventBus = new eventBus();
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
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
# 手写reduce
2 个参数,回调,初始值 有返回值 一个累积的过程
arr.reduce((pre, cur, index, arr) => {
return cur + 1
}, [])
1
2
3
2
3
Array.prototype.reduce(callback,init){
//判断当前的数组是否存在
if(this == null){
throw new TypeError( 'Array.prototype.reduce ' +
'called on null or undefined' );
}
//判断当前的回调是否存在
if(typeof callback == 'function'){
throw new TypeError( callback +
' is not a function');
}
//把数组拿回来,并保存数组长度
var o = Object(this)
var len = o.length >>0
var k = 0; 设置索引
var value; //返回值
if (arguments.length >= 2) {
value = arguments[1];//如果有初始值
} else {
//没有的话,把len赋值给k,判断条件是k小于当前的len并且k索引是否在原数组o上边其实就是判断k的边界值
while (k < len && !(k in o)) {
k++;
}
//如果len为0且initialvalue不存在,引发typeerror异常。
if (k >= len) {
throw new TypeError( 'Reduce of empty array ' +
'with no initial value' );
}
到这块k为len减1,得到初始值为第一个
value = o[k++];
}
// 8. 重复, while k < len
while (k < len) {
if (k in o) {
value = callback(value, o[k], k, o);//回调进行计算并得到累加值
}
k++;
}
return 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
# 设计并实现一个JSONP 函数
- 1.超时处理
- 2.promise
function jsonp(url,params,callback,time){
return new Promise((resolve, reject) => {
var body = document.body
var fnName = "_json"+Math.random(10)
window[fnName] = function(data){ //发回数据回调的内容
callback(data);//用户写的函数
if (data) {
resolve(data)
} else {
reject('没有返回数据')
}
//执行完毕之后,删除该函数
delete window[fnName];
body.removeChild(script);
};
var script = document.createElement('script');
var src = '';
for(let key in params){
src += key + params[key] + '&'
}
src+='callback='+fnName;
script.src = url + '?' + src;
document.body.appendChild(script)
//超时处理
if(time){
var timer = setTimeout(()=>{
//jsonp的超时处理,移除回调函数
body.removeChild(script);
clearTimeout(timer);
},time)
}
// js加载异常的情况
jsNode.addEventListener('error', () => {
delete window[callbackName]
document.body.removeChild(jsNode)
reject('JavaScript资源加载失败')
}, 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
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
# rem基本设置
// 原始配置
function setRem () {
let doc = document.documentElement
let width = doc.getBoundingClientRect().width
let rem = width / 75
doc.style.fontSize = rem + 'px'
}
// 监听窗口变化
addEventListener("resize", setRem)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 手写实现AJAX
# 简单流程
// 实例化
let xhr = new XMLHttpRequest()
// 初始化
xhr.open(method, url, async)
// 发送请求
xhr.send(data)
// 设置状态变化回调处理请求结果
xhr.onreadystatechange = () => {
if (xhr.readyStatus === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 基于promise实现
function ajax (options) {
// 请求地址
const url = options.url
// 请求方法
const method = options.method.toLocaleLowerCase() || 'get'
// 默认为异步true
const async = options.async
// 请求参数
const data = options.data
// 实例化
const xhr = new XMLHttpRequest()
// 请求超时
if (options.timeout && options.timeout > 0) {
xhr.timeout = options.timeout
}
// 返回一个Promise实例
return new Promise ((resolve, reject) => {
xhr.ontimeout = () => reject && reject('请求超时')
// 监听状态变化回调
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
// 200-300 之间表示请求成功,304资源未变,取缓存
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
resolve && resolve(xhr.responseText)
} else {
reject && reject()
}
}
}
// 错误回调
xhr.onerror = err => reject && reject(err)
let paramArr = []
let encodeData
// 处理请求参数
if (data instanceof Object) {
for (let key in data) {
// 参数拼接需要通过 encodeURIComponent 进行编码
paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
encodeData = paramArr.join('&')
}
// get请求拼接参数
if (method === 'get') {
// 检测url中是否已存在 ? 及其位置
const index = url.indexOf('?')
if (index === -1) url += '?'
else if (index !== url.length -1) url += '&'
// 拼接url
url += encodeData
}
// 初始化
xhr.open(method, url, async)
// 发送请求
if (method === 'get') xhr.send(null)
else {
// post 方式需要设置请求头
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
xhr.send(encodeData)
}
})
}
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
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
# 手写sleep函数
# 简单粗暴法
缺点:确实sleep了,会卡死了,cpu增高,无论你的服务器多么牛逼
function sleep(sleepTime){
for(var start = new Date();new Date() - start > sleepTime ){}
}
var t1 = +new Date()
sleep(3000)
var t2 = +new Date()
console.log(t2-t1)
1
2
3
4
5
6
7
2
3
4
5
6
7
# 使用promise实现,
缺点:嵌套太多,不方便使用
function sleep(sleepTime){
return new promise(reslove => setTimeout(reslove,sleepTime))
}
const t1 = +new Date()
sleep(3000).then(()=>{
var t2 = +new Date()
console.log(t2-t1)
})
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 加入 async / Await
!async function test(){
const t1 = +new Date()
awit sleep(3000)
var t2 = +new Date()
console.log(t2-t1)
}
1
2
3
4
5
6
2
3
4
5
6
# sleep模块
开源库也可以做到
var sleep = require('sleep')
const t1 = +new Date()
sleep.msleep(3000)
const t2 = +new Date()
console.log(t2 - t1)
1
2
3
4
5
6
7
2
3
4
5
6
7
# 实现vue的双向数据绑定
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('获取数据了')
},
set(newVal) {
console.log('数据更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 输入监听
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 实现一个简单路由
// hash路由
class Route{
constructor(){
// 路由存储对象
this.routes = {}
// 当前hash
this.currentHash = ''
// 绑定this,避免监听时this指向改变
this.freshRoute = this.freshRoute.bind(this)
// 监听
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存储
storeRoute (path, cb) {
this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {
this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 原生js实现自定义事件
使用是三种原生的方法 实现事件
- new Event(typeArg, eventInit);
- CustomEvent(DOMString type, optional CustomEventInit eventInitDict)
- document.createEvent
[参考](https : //blog.csdn.net/jyb123/article/details/86574365)
# 实现斐波那契函数 四种方法
# 递归
function feibonaqie(n){
if(n<0){
throw new Error('输入的数不能小于0')
}
if(n == 1 || n == 2){
return 1
}
return feibonaqie(n- 1) + feibonaqie(n - 2)
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 动态规划
function feibonaqie(n){
if(n<0){
throw new Error('输入的数不能小于0')
}
let arr = [0,1,1];
if(n<2){
return arr[n]
}
for(var i = 3; i<=n;i++){
arr[i] = arr[i-1] + arr[i-2]
}
return arr[n]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 使用闭包保存变量
function feibonaqie(n){
if(n<0){ throw new Error('输入的数不能小于0') }
var arr = [0,1]
function calc(n){
if(arr[n] !== undefined){
return arr[n]
}
let data = calc(n - 1) + calc(n-2)
arr[n] = data;
return data
}
return calc(n)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 直接暴力
function feibonaqie(n){
var pre = 0,cur=1,data;
if(n<0){ throw new Error('输入的数不能小于0') }
if(n==1){ return 1}
if(n==2){ return 1}
for(var i = 2;i<=n;i++){
data = pre+cur;
pre = cur;
cur = data
}
return data
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 最简单的递归
function feibonaqie(n,n1,n2){
if(n<=1){return n1}
return feibonaqie(n-1,n2,n1+n2)
}
1
2
3
4
2
3
4
# 爬楼梯问题 1 步或者2步
1 2 3 5 8 ....
1
function palouti(n,n1,n2){
if(n == 1){return 1}
if(n == 2){return 2}
return palouti(n-1)+palouti(n-2)
}
1
2
3
4
5
6
2
3
4
5
6
# lzay
# 手写洋葱模型
koa 举例他有一个中间件的数组专门储存,当使用use的时候就把他push进去
app = {
middlewares = [],
app:function(fn){
this.middlewares.push(fn)
}
}
app.compose = function (middlewares){
return async function (){
// 从第一个函数开始
await dispath(0);
async function dispath(idx){
// 说明所有中间件都执行结束
if(idx == middlewares.length) return ;
// 取出当前函数
var fn = middlewares[idx];
// 执行当前函数,传入next函数
fn(function next(){
// 并将下一个函数放入next中
await dispath(idx+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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
简单的compose函数
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const add1ThenDouble = pipe(
add1,
double
);
add1ThenDouble(2); // 6
简单函数实现组合
let middleware = []
middleware.push((next) => {
console.log(0)
next()
console.log(3)
})
middleware.push((next) => {
console.log(1)
next()
console.log(1.1)
})
middleware.push(() => {
console.log(2)
})
let fn = compose(middleware)
function compose(middleware) {
let next = () => {}
function creatNext(older, news) {
return () => {
older(news)
}
}
let len = middleware.length - 1;
for (let i = len; i >= 0; i--) {
next = creatNext(middleware[i], next)
}
next()
}
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
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
# 实现一个懒加载x
<ul>
<li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li>
<li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li>
</ul>
let imgs = document.querySelectorAll('img')
// 可视区高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
function lazyLoad () {
// 滚动卷去的高度
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
for (let i = 0; i < imgs.length; i ++) {
// 图片在可视区冒出的高度
let x = clientHeight + scrollTop - imgs[i].offsetTop
// 图片在可视区内
if (x > 0 && x < clientHeight+imgs[i].height) {
imgs[i].src = imgs[i].getAttribute('data')
}
}
}
// addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000)
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
# React
- hooks相关
# 实现useState
function useState(initialValue) {
var _val = initialValue
function setState(newVal) {
_val = newVal
}
return [_val, setState]
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 如何在 web 端实现异步请求结果的复用,即只产生一次 ajax 请求并保证所有调用方获取到数据 ···
# 简单版
通过 localstorage function getAjax(url, method, parmas, flag = true) {
var request = function (url, method, parmas) {
var xhr = new XMLHttpRequest();
var cache;
xhr.open(method, url, true);
xhr.send(parmas);
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
// 200-300 之间表示请求成功,304资源未变,取缓存
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
localstorage.setItem(`${method}${url}`, responseText)
} else {
throw new Error('请求错误')
}
}
}
if (flag) {
request(url, method, parmas);
}
cache = localstorage.getItem(`${method}${url}
return cache
}
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
# 通过promise做
function getAjax(url, method, parmas, flag = true) {
return new promise((resolve, reject) => {
var request = function (url, method, parmas) {
var xhr = new XMLHttpRequest();
var cache;
xhr.open(method, url, true);
xhr.send(parmas);
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
// 200-300 之间表示请求成功,304资源未变,取缓存
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) resolve && resolve(xhr.responseText)
} else {
reject && reject()
}
}
}
})
}
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
← Promise、 什么是正则表达式呢? →