├── .idea
├── javavscript代码段.iml
├── misc.xml
├── modules.xml
├── vcs.xml
└── workspace.xml
├── README.md
├── debounce.js
├── deepCopy.js
├── flatten.js
├── images
└── 1.png
├── shallowCopy.js
└── type.js
/.idea/javavscript代码段.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 |
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 | 1558078165783
136 |
137 |
138 | 1558078165783
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # javascript-code-segment
2 |
3 |
4 | 本系列会从面试的角度出发围绕JavaScript,Node.js(npm包)以及框架三个方面来对常见的模拟实现进行总结,具体源代码放在github项目上,长期更新和维护
5 | 
6 | # 数组去重
7 |
8 | (一维)数组去重最原始的方法就是使用双层循环,分别循环原始数组和新建数组;或者我们可以使用`indexOf`来简化内层的循环;或者可以将原始数组排序完再来去重,这样会减少一个循环,只需要比较前后两个数即可;当然我们可以使用`ES5`,`ES6`的方法来简化去重的写法,比如我们可以使用`filter`来简化内层循环,或者使用`Set`、`Map`、扩展运算符这些用起来更简单的方法,但是效率上应该不会比原始方法好。二维数组的去重可以在上面方法的基础上再判断元素是不是数组,如果是的话,就进行递归处理。
9 |
10 | ### 双层循环
11 |
12 | ```javascript
13 | var array = [1, 1, '1', '1'];
14 |
15 | function unique(array) {
16 | var res = [];
17 | for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
18 | for (var j = 0, resLen = res.length; j < resLen; j++ ) {
19 | if (array[i] === res[j]) {
20 | break;
21 | }
22 | }
23 | if (j === resLen) {
24 | res.push(array[i])
25 | }
26 | }
27 | return res;
28 | }
29 |
30 | console.log(unique(array)); // [1, "1"]
31 | ```
32 |
33 | ### 利用indexOf
34 |
35 | ```javascript
36 | var array = [1, 1, '1'];
37 |
38 | function unique(array) {
39 | var res = [];
40 | for (var i = 0, len = array.length; i < len; i++) {
41 | var current = array[i];
42 | if (res.indexOf(current) === -1) {
43 | res.push(current)
44 | }
45 | }
46 | return res;
47 | }
48 |
49 | console.log(unique(array));
50 | ```
51 |
52 | ### 排序后去重
53 |
54 | ```javascript
55 | var array = [1, 1, '1'];
56 |
57 | function unique(array) {
58 | var res = [];
59 | var sortedArray = array.concat().sort();
60 | var seen;
61 | for (var i = 0, len = sortedArray.length; i < len; i++) {
62 | // 如果是第一个元素或者相邻的元素不相同
63 | if (!i || seen !== sortedArray[i]) {
64 | res.push(sortedArray[i])
65 | }
66 | seen = sortedArray[i];
67 | }
68 | return res;
69 | }
70 |
71 | console.log(unique(array));
72 | ```
73 |
74 | ### filter
75 |
76 | `filter`可以用来简化外层循环
77 |
78 | **使用indexOf:**
79 |
80 | ```javascript
81 | var array = [1, 2, 1, 1, '1'];
82 |
83 | function unique(array) {
84 | var res = array.filter(function(item, index, array){
85 | return array.indexOf(item) === index;
86 | })
87 | return res;
88 | }
89 |
90 | console.log(unique(array));
91 | ```
92 |
93 | **排序去重:**
94 |
95 | ```javascript
96 | var array = [1, 2, 1, 1, '1'];
97 |
98 | function unique(array) {
99 | return array.concat().sort().filter(function(item, index, array){
100 | return !index || item !== array[index - 1]
101 | })
102 | }
103 |
104 | console.log(unique(array));
105 | ```
106 |
107 | ### ES6方法
108 |
109 | **Set:**
110 |
111 | ```javascript
112 | var array = [1, 2, 1, 1, '1'];
113 |
114 | function unique(array) {
115 | return Array.from(new Set(array));
116 | }
117 |
118 | console.log(unique(array)); // [1, 2, "1"]
119 | ```
120 |
121 | 再简化下
122 |
123 | ```javascript
124 | function unique(array) {
125 | return [...new Set(array)];
126 | }
127 |
128 | //或者
129 | var unique = (a) => [...new Set(a)]
130 | ```
131 |
132 | **Map:**
133 |
134 | ```javascript
135 | function unique (arr) {
136 | const seen = new Map()
137 | return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
138 | }
139 | ```
140 |
141 | # 类型判断
142 |
143 | 类型判断需要注意以下几点
144 |
145 | - `typeof`对六个基本数据类型`Undefined`、`Null`、`Boolean`、`Number`、`String`、`Object`(大写)返回的结果是
146 |
147 | `undefined`、`object`、`boolean`、`number`、`string`、`object`(小写),可以看到`Null`和`Object` 类型都返回了 `object` 字符串;`typeof`却能检测出函数类型;**综上,`typeof`能检测出六种类型,但是不能检测出`null`类型和`Object`下细分的类型,如`Array`,`Function`,`Date`,`RegExp`,`Error`等**。
148 |
149 | - `Object.prototype.toString`的作用非常强大,它能检测出基本数据类型以及`Object`下的细分类型,甚至像
150 | `Math`,`JSON`,`arguments`它都能检测出它们的具体类型,它返回结果形式例如`[object Number] `(注意最后的数据类型是大写).**所以,`Object.prototype.toString`基本上能检测出所有的类型了,只不过有时需要考虑到兼容性低版本浏览器的问题。**
151 |
152 | ### 通用API
153 |
154 | ```javascript
155 |
156 | // 该类型判断函数可以判断六种基本数据类型以及Boolean Number String Function Array Date RegExp Object Error,
157 | // 其他类型因为遇到类型判断的情况较少所以都会返回object,不在进行详细的判断
158 | // 比如ES6新增的Symbol,Map,Set等类型
159 | var classtype = {};
160 |
161 |
162 | "Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item) {
163 | classtype["[object " + item + "]"] = item.toLowerCase();
164 | })
165 |
166 |
167 | function type(obj) {
168 | // 解决IE6中null和undefined会被Object.prototype.toString识别成[object Object]
169 | if (obj == null) {
170 | return obj + "";
171 | }
172 |
173 | //如果是typeof后类型为object下的细分类型(Array,Function,Date,RegExp,Error)或者是Object类型,则要利用Object.prototype.toString
174 | //由于ES6新增的Symbol,Map,Set等类型不在classtype列表中,所以使用type函数,返回的结果会是object
175 | return typeof obj === "object" || typeof obj === "function" ?
176 | classtype[Object.prototype.toString.call(obj)] || "object" :
177 | typeof obj;
178 | }
179 | ```
180 |
181 | ### 判断空对象
182 |
183 | 判断是否有属性,`for`循环一旦执行,就说明有属性,此时返回`false`
184 |
185 | ```javascript
186 | function isEmptyObject( obj ) {
187 | var name;
188 | for ( name in obj ) {
189 | return false;
190 | }
191 | return true;
192 | }
193 |
194 | console.log(isEmptyObject({})); // true
195 | console.log(isEmptyObject([])); // true
196 | console.log(isEmptyObject(null)); // true
197 | console.log(isEmptyObject(undefined)); // true
198 | console.log(isEmptyObject(1)); // true
199 | console.log(isEmptyObject('')); // true
200 | console.log(isEmptyObject(true)); // true
201 | ```
202 |
203 | 我们可以看出`isEmptyObject`实际上判断的并不仅仅是空对象。但是既然` jQuery `是这样写,可能是因为考虑到实际开发中 `isEmptyObject `用来判断 {} 和 {a: 1} 是足够的吧。如果真的是只判断 {},完全可以结合上篇写的 `type `函数筛选掉不适合的情况。
204 |
205 | ### 判断Window对象
206 |
207 | `Window`对象有一个`window`属性指向自身,可以利用这个特性来判断是否是`Window`对象
208 |
209 | ```javascript
210 | function isWindow( obj ) {
211 | return obj != null && obj === obj.window;
212 | }
213 | ```
214 |
215 | ### 判断数组
216 |
217 | `isArray`是数组类型内置的数据类型判断函数,但是会有兼容性问题,一个`polyfill`如下
218 |
219 | ```javascript
220 | isArray = Array.isArray || function(array){
221 | return Object.prototype.toString.call(array) === '[object Array]';
222 | }
223 | ```
224 |
225 | ### 判断类数组
226 |
227 | `jquery`实现的`isArrayLike`,数组和类数组都会返回`true`。所如果` isArrayLike `返回`true`,至少要满足三个条件之一:
228 |
229 | 1. 是数组
230 |
231 | 2. 长度为 0
232 | 比如下面情况,如果我们去掉length === 0 这个判断,就会打印 `false`,然而我们都知道 `arguments` 是一个类数组对象,这里是应该返回 `true` 的
233 |
234 | ```javascript
235 | function a(){
236 | console.log(isArrayLike(arguments))
237 | }
238 | a();
239 | ```
240 |
241 | 3. `lengths` 属性是大于 0 的数字类型,并且`obj[length - 1]`必须存在(考虑到arr = [,,3]的情况)
242 |
243 | ```javascript
244 | function isArrayLike(obj) {
245 |
246 | // obj 必须有 length属性
247 | var length = !!obj && "length" in obj && obj.length;
248 | var typeRes = type(obj);
249 |
250 | // 排除掉函数和 Window 对象
251 | if (typeRes === "function" || isWindow(obj)) {
252 | return false;
253 | }
254 |
255 | return typeRes === "array" || length === 0 ||
256 | typeof length === "number" && length > 0 && (length - 1) in obj;
257 | }
258 | ```
259 |
260 | ### 判断NaN
261 |
262 | 判断一个数是不是` NaN `不能单纯地使用 === 这样来判断, 因为` NaN `不与任何数相等, 包括自身,注意在`ES6`的`isNaN`中只有值为数字类型使用`NaN`才会返回`true`
263 |
264 | ```javascript
265 | isNaN: function(value){
266 | return isNumber(value) && isNaN(value);
267 | }
268 | ```
269 |
270 | ### 判断DOM元素
271 |
272 | 利用`DOM`对象特有的`nodeType`属性(
273 |
274 | ```javascript
275 | isElement: function(obj){
276 | return !!(obj && obj.nodeType === 1);
277 | // 两次感叹号将值转化为布尔值
278 | }
279 | ```
280 |
281 | ### 判断arguments对象
282 |
283 | 低版本的浏览器中`argument`对象通过`Object.prototype.toString`判断后返回的是`[object Object]`,所以需要兼容
284 |
285 | ```javascript
286 | isArguments: function(obj){
287 | return Object.prototype.toString.call(obj) === '[object Arguments]' || (obj != null && Object.hasOwnProperty.call(obj, 'callee'));
288 | }
289 | ```
290 |
291 | # 深浅拷贝
292 |
293 | 如果是数组,实现浅拷贝,比可以`slice`,`concat`返回一个新数组的特性来实现;实现深拷贝,可以利用`JSON.parse`和`JSON.stringify`来实现,但是有一个问题,不能拷贝函数(此时拷贝后返回的数组为`null`)。上面的方法都属于技巧,下面考虑怎么实现一个对象或者数组的深浅拷贝
294 |
295 | ### 浅拷贝
296 |
297 | 思路很简单,遍历对象,然后把属性和属性值都放在一个新的对象就OK了
298 |
299 | ```javascript
300 | var shallowCopy = function(obj) {
301 | // 只拷贝对象
302 | if (typeof obj !== 'object') return;
303 | // 根据obj的类型判断是新建一个数组还是对象
304 | var newObj = obj instanceof Array ? [] : {};
305 | // 遍历obj,并且判断是obj的属性才拷贝
306 | for (var key in obj) {
307 | if (obj.hasOwnProperty(key)) {
308 | newObj[key] = obj[key];
309 | }
310 | }
311 | return newObj;
312 | }
313 | ```
314 |
315 | ### 深拷贝
316 |
317 | 思路也很简单,就是在拷贝的时候判断一下属性值的类型,如果是对象,就递归调用深浅拷贝函数就ok了
318 |
319 | ```javascript
320 | var deepCopy = function(obj) {
321 | if (typeof obj !== 'object') return;
322 | var newObj = obj instanceof Array ? [] : {};
323 | for (var key in obj) {
324 | if (obj.hasOwnProperty(key)) {
325 | newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
326 | }
327 | }
328 | return newObj;
329 | }
330 | ```
331 |
332 | # 扁平化
333 |
334 | ### 递归
335 |
336 | 循环数组元素,如果还是一个数组,就递归调用该方法
337 |
338 | ```javascript
339 | // 方法 1
340 | var arr = [1, [2, [3, 4]]];
341 |
342 | function flatten(arr) {
343 | var result = [];
344 | for (var i = 0, len = arr.length; i < len; i++) {
345 | if (Array.isArray(arr[i])) {
346 | result = result.concat(flatten(arr[i]))
347 | }
348 | else {
349 | result.push(arr[i])
350 | }
351 | }
352 | return result;
353 | }
354 |
355 |
356 | console.log(flatten(arr))
357 | ```
358 |
359 | ### toString()
360 |
361 | 如果数组的元素都是数字,可以使用该方法
362 |
363 | ```javascript
364 | // 方法2
365 | var arr = [1, [2, [3, 4]]];
366 |
367 | function flatten(arr) {
368 | return arr.toString().split(',').map(function(item){
369 | return +item // +会使字符串发生类型转换
370 | })
371 | }
372 |
373 | console.log(flatten(arr))
374 | ```
375 |
376 | ### reduce()
377 |
378 | ```javascript
379 | // 方法3
380 | var arr = [1, [2, [3, 4]]];
381 |
382 | function flatten(arr) {
383 | return arr.reduce(function(prev, next){
384 | return prev.concat(Array.isArray(next) ? flatten(next) : next)
385 | }, [])
386 | }
387 |
388 | console.log(flatten(arr))
389 | ```
390 |
391 | ### ...
392 |
393 | ```javascript
394 | // 扁平化一维数组
395 | var arr = [1, [2, [3, 4]]];
396 | console.log([].concat(...arr)); // [1, 2, [3, 4]]
397 |
398 | // 可以扁平化多维数组
399 | var arr = [1, [2, [3, 4]]];
400 |
401 | function flatten(arr) {
402 |
403 | while (arr.some(item => Array.isArray(item))) {
404 | arr = [].concat(...arr);
405 | }
406 |
407 | return arr;
408 | }
409 |
410 | console.log(flatten(arr))
411 | ```
412 |
413 | # 柯里化
414 |
415 | ### 通用版
416 |
417 | ```javascript
418 | function curry(fn, args) {
419 | var length = fn.length;
420 | var args = args || [];
421 | return function(){
422 | newArgs = args.concat(Array.prototype.slice.call(arguments));
423 | if (newArgs.length < length) {
424 | return curry.call(this,fn,newArgs);
425 | }else{
426 | return fn.apply(this,newArgs);
427 | }
428 | }
429 | }
430 |
431 | function multiFn(a, b, c) {
432 | return a * b * c;
433 | }
434 |
435 | var multi = curry(multiFn);
436 |
437 | multi(2)(3)(4);
438 | multi(2,3,4);
439 | multi(2)(3,4);
440 | multi(2,3)(4);
441 |
442 | ```
443 |
444 | ### ES6版
445 |
446 | ```javascript
447 | const curry = (fn, arr = []) => (...args) => (
448 | arg => arg.length === fn.length
449 | ? fn(...arg)
450 | : curry(fn, arg)
451 | )([...arr, ...args])
452 |
453 | let curryTest=curry((a,b,c,d)=>a+b+c+d)
454 | curryTest(1,2,3)(4) //返回10
455 | curryTest(1,2)(4)(3) //返回10
456 | curryTest(1,2)(3,4) //返回10
457 | ```
458 |
459 | # 防抖与节流
460 |
461 | ### 防抖
462 |
463 | ```javascript
464 | function debounce(fn, wait) {
465 | var timeout = null;
466 | return function() {
467 | if(timeout !== null)
468 | {
469 | clearTimeout(timeout);
470 | }
471 | timeout = setTimeout(fn, wait);
472 | }
473 | }
474 | // 处理函数
475 | function handle() {
476 | console.log(Math.random());
477 | }
478 | // 滚动事件
479 | window.addEventListener('scroll', debounce(handle, 1000));
480 | ```
481 |
482 | ### 节流
483 |
484 | #### 利用时间戳实现
485 |
486 | ```javascript
487 | var throttle = function(func, delay) {
488 | var prev = Date.now();
489 | return function() {
490 | var context = this;
491 | var args = arguments;
492 | var now = Date.now();
493 | if (now - prev >= delay) {
494 | func.apply(context, args);
495 | prev = Date.now();
496 | }
497 | }
498 | }
499 | function handle() {
500 | console.log(Math.random());
501 | }
502 | window.addEventListener('scroll', throttle(handle, 1000));
503 | ```
504 |
505 | #### 利用定时器实现
506 |
507 | ```javascript
508 | var throttle = function(func, delay) {
509 | var timer = null;
510 | return function() {
511 | var context = this;
512 | var args = arguments;
513 | if (!timer) {
514 | timer = setTimeout(function() {
515 | func.apply(context, args);
516 | timer = null;
517 | }, delay);
518 | }
519 | }
520 | }
521 | function handle() {
522 | console.log(Math.random());
523 | }
524 | window.addEventListener('scroll', throttle(handle, 1000));
525 | ```
526 |
527 | # 模拟new
528 |
529 | - `new`产生的实例可以访问`Constructor`里的属性,也可以访问到`Constructor.prototype`中的属性,前者可以通过`apply`来实现,后者可以通过将实例的`proto`属性指向构造函数的`prototype`来实现
530 | - 我们还需要判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么
531 |
532 | ```javascript
533 | function New(){
534 | var obj=new Object();
535 | //取出第一个参数,就是我们要传入的构造函数;此外因为shift会修改原数组,所以arguments会被去除第一个参数
536 | Constructor=[].shift.call(arguments);
537 | //将obj的原型指向构造函数,这样obj就可以访问到构造函数原型中的属性
538 | obj._proto_=Constructor.prototype;
539 | //使用apply改变构造函数this的指向到新建的对象,这样obj就可以访问到构造函数中的属性
540 | var ret=Constructor.apply(obj,arguments);
541 | //要返回obj
542 | return typeof ret === 'object' ? ret:obj;
543 | }
544 | ```
545 |
546 | ```javascript
547 | function Otaku(name,age){
548 | this.name=name;
549 | this.age=age;
550 | this.habit='Games'
551 | }
552 |
553 | Otaku.prototype.sayYourName=function(){
554 | console.log("I am" + this.name);
555 | }
556 |
557 | var person=objectFactory(Otaku,'Kevin','18')
558 |
559 | console.log(person.name)//Kevin
560 | console.log(person.habit)//Games
561 | console.log(person.strength)//60
562 | ```
563 |
564 | # 模拟call
565 |
566 | - `call()`方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法
567 | - 模拟的步骤是:将函数设为对象的属性—>执行该函数—>删除该函数
568 | - `this`参数可以传`null`,当为`null`的时候,视为指向`window`
569 | - 函数是可以有返回值的
570 |
571 | ### 简单版
572 |
573 | ```javascript
574 | var foo = {
575 | value: 1,
576 | bar: function() {
577 | console.log(this.value)
578 | }
579 | }
580 | foo.bar() // 1
581 | ```
582 |
583 | ### 完善版
584 |
585 | ```javascript
586 | Function.prototype.call2 = function(context) {
587 | var context=context||window
588 | context.fn = this;
589 | let args = [...arguments].slice(1);
590 | let result = context.fn(...args);
591 | delete context.fn;
592 | return result;
593 | }
594 | let foo = {
595 | value: 1
596 | }
597 | function bar(name, age) {
598 | console.log(name)
599 | console.log(age)
600 | console.log(this.value);
601 | }
602 | //表示bar函数的执行环境是foo,即bar函数里面的this代表foo,this.value相当于foo.value,然后给bar函数传递两个参数
603 | bar.call2(foo, 'black', '18') // black 18 1
604 | ```
605 |
606 | # 模拟apply
607 |
608 | - `apply()`的实现和`call()`类似,只是参数形式不同
609 |
610 | ```javascript
611 | Function.prototype.apply2 = function(context = window) {
612 | context.fn = this
613 | let result;
614 | // 判断是否有第二个参数
615 | if(arguments[1]) {
616 | result = context.fn(...arguments[1])
617 | } else {
618 | result = context.fn()
619 | }
620 | delete context.fn
621 | return result
622 | }
623 | ```
624 |
625 | # 模拟bind
626 |
627 | ```javascript
628 | Function.prototype.bind2=function(context){
629 | var self=thisl
630 | var args=Array.prototype.slice.call(arguments,1);
631 |
632 | var fNOP=function(){};
633 | var fBound=function(){
634 | var bindArgs=Array.prototype.slice.call(arguments);
635 | return self.apply(this instanceof fNOP ? this : context, args.concat(bindAt))
636 | }
637 | }
638 | ```
639 |
640 | # 模拟instanceof
641 |
642 | ```javascript
643 | function instanceOf(left,right) {
644 |
645 | let proto = left.__proto__;
646 | let prototype = right.prototype
647 | while(true) {
648 | if(proto === null) return false
649 | if(proto === prototype) return true
650 | proto = proto.__proto__;
651 | }
652 | }
653 | ```
654 |
655 | # 模拟JSON.stringify
656 |
657 | > JSON.stringify(value[, replacer [, space]])
658 |
659 | - `Boolean | Number| String` 类型会自动转换成对应的原始值。
660 | - `undefined`、任意函数以及`symbol`,会被忽略(出现在非数组对象的属性值中时),或者被转换成 `null`(出现在数组中时)。
661 | - 不可枚举的属性会被忽略
662 | - 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
663 |
664 | ```javascript
665 | function jsonStringify(obj) {
666 | let type = typeof obj;
667 | if (type !== "object") {
668 | if (/string|undefined|function/.test(type)) {
669 | obj = '"' + obj + '"';
670 | }
671 | return String(obj);
672 | } else {
673 | let json = []
674 | let arr = Array.isArray(obj)
675 | for (let k in obj) {
676 | let v = obj[k];
677 | let type = typeof v;
678 | if (/string|undefined|function/.test(type)) {
679 | v = '"' + v + '"';
680 | } else if (type === "object") {
681 | v = jsonStringify(v);
682 | }
683 | json.push((arr ? "" : '"' + k + '":') + String(v));
684 | }
685 | return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
686 | }
687 | }
688 | jsonStringify({x : 5}) // "{"x":5}"
689 | jsonStringify([1, "false", false]) // "[1,"false",false]"
690 | jsonStringify({b: undefined}) // "{"b":"undefined"}"
691 | ```
692 |
693 | # 模拟JSON.parse
694 |
695 | > JSON.parse(text[, reviver])
696 |
697 | 用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。
698 |
699 | ### 利用eval
700 |
701 | ```javascript
702 | function jsonParse(opt) {
703 | return eval('(' + opt + ')');
704 | }
705 | jsonParse(jsonStringify({x : 5}))
706 | // Object { x: 5}
707 | jsonParse(jsonStringify([1, "false", false]))
708 | // [1, "false", falsr]
709 | jsonParse(jsonStringify({b: undefined}))
710 | // Object { b: "undefined"}
711 | ```
712 |
713 | > 避免在不必要的情况下使用 `eval`,eval() 是一个危险的函数, 他执行的代码拥有着执行者的权利。如果你用 eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码
714 |
715 | ### 利用new Function()
716 |
717 | `Function`与`eval`有相同的字符串参数特性,`eval` 与 `Function` 都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用。
718 |
719 | > var func = new Function(arg1, arg2, ..., functionBody)
720 |
721 | ```javascript
722 | var jsonStr = '{ "age": 20, "name": "jack" }'
723 | var json = (new Function('return ' + jsonStr))();
724 | ```
725 |
726 | # 创建对象
727 |
728 | 创建自定义对象最简单的方式就是创建一个`Object`的实例,然后再为它添加属性和方法,早期的开发人员经常使用这种模式来创建对象,后来对象字面量的方法成了创建对象的首选模式。虽然`object构造函数`或者`对象字面量`的方法都可以用来创建对象,但是这些方法使用同一个接口创建很多对象,会产生大量的重复代码。为了解决这个问题,人们开始使用各种模式来创建对象,在这些模式中,一般推荐使用四种方式,包括`构造函数模式`、`原型模式`、`构造函数和原型组合模式`,`动态原型模式`,其他的方式,包括`工厂模式`、`寄生构造函数模式`、`稳妥构造函数模式`平时使用的较少。而这些方式中,用的最多最推荐的应是**组合模式和动态原型模式**
729 |
730 | ### 构造函数和原型组合模式
731 |
732 | **优点:**
733 |
734 | 1. 解决了原型模式对于引用对象的缺点
735 | 2. 解决了原型模式没有办法传递参数的缺点
736 | 3. 解决了构造函数模式不能共享方法的缺点
737 |
738 | ```javascript
739 | function Person(name) {
740 | this.name = name
741 | this.friends = ['lilei']
742 | }
743 | Person.prototype.say = function() {
744 | console.log(this.name)
745 | }
746 |
747 | var person1 = new Person('hanmeimei')
748 | person1.say() //hanmeimei
749 | ```
750 |
751 | ### 动态原型模式
752 |
753 | **优点:**
754 |
755 | 1. 可以在初次调用构造函数的时候就完成原型对象的修改
756 | 2. 修改能体现在所有的实例中
757 |
758 | ```javascript
759 | function Person(name) {
760 | this.name = name
761 | // 检测say 是不是一个函数
762 | // 实际上只在当前第一次时候没有创建的时候在原型上添加sayName方法
763 | //因为构造函数执行时,里面的代码都会执行一遍,而原型有一个就行,不用每次都重复,所以仅在第一执行时生成一个原型,后面执行就不必在生成,所以就不会执行if包裹的函数,
764 | //其次为什么不能再使用字面量的写法,我们都知道,使用构造函数其实是把new出来的对象作用域绑定在构造函数上,而字面量的写法,会重新生成一个新对象,就切断了两者的联系!
765 | if(typeof this.say != 'function') {
766 | Person.prototype.say = function(
767 | alert(this.name)
768 | }
769 | }
770 | ```
771 | # 继承
772 |
773 | `原型链继承`不仅会带来引用缺陷,而且我们也无法为不同的实例初始化继承来的属性;`构造函数继承`方式可以避免类式继承的缺陷,但是我们无法获取到父类的共有方法,也就是通过原型prototype绑定的方法;`组合继承`解决了上面两种方式的存在的问题,但是它调用了两次父类的构造函数;`寄生组合式继承`强化的部分就是在组合继承的基础上减少一次多余的调用父类的构造函数。**推荐使用组合继承方式、寄生组合方式和ES6 extends继承,建议在实际生产中直接使用ES6的继承方式。**
774 |
775 | ### 组合继承
776 |
777 | ```javascript
778 | // 声明父类
779 | function Animal(color) {
780 | this.name = 'animal';
781 | this.type = ['pig','cat'];
782 | this.color = color;
783 | }
784 |
785 | // 添加共有方法
786 | Animal.prototype.greet = function(sound) {
787 | console.log(sound);
788 | }
789 |
790 | // 声明子类
791 | function Dog(color) {
792 | // 构造函数继承
793 | Animal.apply(this, arguments);
794 | }
795 |
796 | // 类式继承
797 | Dog.prototype = new Animal();
798 |
799 | var dog = new Dog('白色');
800 | var dog2 = new Dog('黑色');
801 |
802 | dog.type.push('dog');
803 | console.log(dog.color); // "白色"
804 | console.log(dog.type); // ["pig", "cat", "dog"]
805 |
806 | console.log(dog2.type); // ["pig", "cat"]
807 | console.log(dog2.color); // "黑色"
808 | dog.greet('汪汪'); // "汪汪"
809 | ```
810 |
811 | 注:组合继承利用上面的方式会使得两次调用父类构造函数,其实我们可以通过Dog.prototype = Animal.prototype; Dog.prototype.constructor = Dog来优化组合继承,当然终极优化方式就是下面的寄生组合方式。想要了解组合继承具体优化的可以参考 [深入理解JavaScript原型链与继承](https://juejin.im/post/5c08ba1ff265da612577e862)
812 |
813 | ### 寄生组合继承
814 |
815 | ```javascript
816 | function Animal(color) {
817 | this.color = color;
818 | this.name = 'animal';
819 | this.type = ['pig', 'cat'];
820 | }
821 |
822 | Animal.prototype.greet = function(sound) {
823 | console.log(sound);
824 | }
825 |
826 |
827 | function Dog(color) {
828 | Animal.apply(this, arguments);
829 | this.name = 'dog';
830 | }
831 | /* 注意下面两行 */
832 | Dog.prototype = Object.create(Animal.prototype);
833 | Dog.prototype.constructor = Dog;
834 |
835 | Dog.prototype.getName = function() {
836 | console.log(this.name);
837 | }
838 |
839 |
840 | var dog = new Dog('白色');
841 | var dog2 = new Dog('黑色');
842 |
843 | dog.type.push('dog');
844 | console.log(dog.color); // "白色"
845 | console.log(dog.type); // ["pig", "cat", "dog"]
846 |
847 | console.log(dog2.type); // ["pig", "cat"]
848 | console.log(dog2.color); // "黑色"
849 | dog.greet('汪汪'); // "汪汪"
850 | ```
851 |
852 | Object.create()的浅拷贝的作用类式下面的函数:
853 |
854 | ```javascript
855 | function create(obj) {
856 | function F() {};
857 | F.prototype = obj;
858 | return new F();
859 | }
860 | ```
861 |
862 | 需注意一点,由于对Animal的原型进行了拷贝后赋给Dog.prototype,因此Dog.prototype上的`constructor`属性也被重写了,所以我们要修复这一个问题:
863 |
864 | ```javascript
865 | Dog.prototype.constructor = Dog;
866 | ```
867 |
868 | ### extends继承
869 |
870 | ```javascript
871 | class Animal {
872 | constructor(color) {
873 | this.color = color;
874 | }
875 | greet(sound) {
876 | console.log(sound);
877 | }
878 | }
879 |
880 | class Dog extends Animal {
881 | constructor(color) {
882 | super(color);
883 | this.color = color;
884 | }
885 | }
886 |
887 | let dog = new Dog('黑色');
888 | dog.greet('汪汪'); // "汪汪"
889 | console.log(dog.color); // "黑色"
890 | ```
891 |
892 | # 模拟ajax
893 |
894 | - `ajax`请求过程:创建`XMLHttpRequest`对象、连接服务器、发送请求、接收响应数据
895 | - 创建后的`XMLHttpRequest`对象实例拥有很多方法和属性
896 | - `open`方法类似于初始化,并不会发起真正的请求;`send`方发送请求,并接受一个可选参数
897 | - 当请求方式为`post`时,可以将请求体的参数传入;当请求方式为`get`时,可以不传或传入`null`;
898 | - 不管是`get`和`post`,参数都需要通过`encodeURIComponent`编码后拼接
899 |
900 | ### 通用版
901 |
902 | ```javascript
903 | //对请求data进行格式化处理
904 | function formateData(data) {
905 | let arr = [];
906 | for (let key in data) {
907 | //避免有&,=,?字符,对这些字符进行序列化
908 | arr.push(encodeURIComponent(key) + '=' + data[key])
909 | }
910 | return arr.join('&');
911 | }
912 |
913 | function ajax(params) {
914 | //先对params进行处理,防止为空
915 | params = params || {};
916 | params.data = params.data || {};
917 |
918 | //普通GET,POST请求
919 | params.type = (params.type || 'GET').toUpperCase();
920 | params.data = formateData(params.data);
921 | //如果是在ie6浏览器,那么XMLHttoRequest是不存在的,应该调用ActiveXObject;
922 | let xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
923 | if (params.type === 'GET') {
924 | xhr.open(params.type, params.url + '?' + params.data, true);
925 | xhr.send();
926 | } else {
927 | xhr.open(params.type, params.url, true);
928 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
929 | xhr.send(params.data);
930 | }
931 | // 这里有两种写法,第一种写法:当xhr.readyState===4的时候,会触发onload事件,直接通过onload事件 进行回调函数处理
932 | xhr.onload = function () {
933 | if (xhr.status === 200 || xhr.status === 304 || xhr.status === 206) {
934 | var res;
935 |
936 | if (params.success && params.success instanceof Function) {
937 | res = JSON.parse(xhr.responseText);
938 | params.success.call(xhr, res);
939 | }
940 | } else {
941 | if (params.error && params.error instanceof Function) {
942 | res = xhr.responseText;
943 | params.error.call(xhr, res);
944 | }
945 | }
946 |
947 | }
948 | //第二种写法,当xhr.readyState===4时候,说明请求成功返回了,进行成功回调
949 | xhr.onreadystatechange = function () {
950 | if (xhr.readyState === 4) {
951 | // 进行onload里面的处理函数
952 | }
953 | }
954 |
955 | }
956 | ```
957 |
958 | ### promise版
959 |
960 | ```javascript
961 | // 使用promise实现一个简单的ajax
962 |
963 | /**
964 | * 首先,可能会使用到的xhr方法或者说属性
965 | * onloadstart // 开始发送时触发
966 | * onloadend // 发送结束时触发,无论成功不成功
967 | * onload // 得到响应
968 | * onprogress // 从服务器上下载数据,每50ms触发一次
969 | * onuploadprogress // 上传到服务器的回调
970 | * onerror // 请求错误时触发
971 | * onabort // 调用abort时候触发
972 | * status // 返回状态码
973 | * setRequestHeader // 设置请求头
974 | * responseType // 请求传入的数据
975 | */
976 |
977 | // 默认的ajax参数
978 | let ajaxDefaultOptions = {
979 | url: '#', // 请求地址,默认为空
980 | method: 'GET', // 请求方式,默认为GET请求
981 | async: true, // 请求同步还是异步,默认异步
982 | timeout: 0, // 请求的超时时间
983 | dataType: 'text', // 请求的数据格式,默认为text
984 | data: null, // 请求的参数,默认为空
985 | headers: {}, // 请求头,默认为空
986 | onprogress: function () {}, // 从服务器下载数据的回调
987 | onuploadprogress: function () {}, // 处理上传文件到服务器的回调
988 | xhr: null // 允许函数外部创建xhr传入,但是必须不能是使用过的
989 | };
990 |
991 | function _ajax(paramOptions) {
992 | let options = {};
993 | for (const key in ajaxDefaultOptions) {
994 | options[key] = ajaxDefaultOptions[key];
995 | }
996 | // 如果传入的是否异步与默认值相同,就使用默认值,否则使用传入的参数
997 | options.async = paramOptions.async === ajaxDefaultOptions.async ? ajaxDefaultOptions.async : paramOptions.async;
998 | // 判断传入的method是否为GET或者POST,否则传入GET 或者可将判断写在promise内部,reject出去
999 | options.method = paramOptions.method ? ("GET" || "POST") : "GET";
1000 | // 如果外部传入xhr,否则创建一个
1001 | let xhr = options.xhr || new XMLHttpRequest();
1002 | // return promise对象
1003 | return new Promise(function (resolve, reject) {
1004 | xhr.open(options.method, options.url, options.async);
1005 | xhr.timeout = options.timeout;
1006 | // 设置请求头
1007 | for (const key in options.headers) {
1008 | xhr.setRequestHeader(key, options.headers[key]);
1009 | }
1010 | // 注册xhr对象事件
1011 | xhr.responseType = options.dataType;
1012 | xhr.onprogress = options.onprogress;
1013 | xhr.onuploadprogress = options.onuploadprogress;
1014 | // 开始注册事件
1015 | // 请求成功
1016 | xhr.onloadend = function () {
1017 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
1018 | resolve(xhr);
1019 | } else {
1020 | reject({
1021 | errorType: "status_error",
1022 | xhr: xhr
1023 | });
1024 | }
1025 | };
1026 | // 请求超时
1027 | xhr.ontimeout = function () {
1028 | reject({
1029 | errorType: "timeout_error",
1030 | xhr: xhr
1031 | });
1032 | }
1033 | // 请求错误
1034 | xhr.onerror = function () {
1035 | reject({
1036 | errorType: "onerror",
1037 | xhr: xhr
1038 | });
1039 | }
1040 | // abort错误(未明白,只知道是三种异常中的一种)
1041 | xhr.onabort = function () {
1042 | reject({
1043 | errorType: "onabort",
1044 | xhr: xhr
1045 | });
1046 | }
1047 | // 捕获异常
1048 | try {
1049 | xhr.send(options.data);
1050 | } catch (error) {
1051 | reject({
1052 | errorType: "send_error",
1053 | error: error
1054 | });
1055 | }
1056 | });
1057 | }
1058 |
1059 |
1060 | // 调用示例
1061 | _ajax({
1062 | url: 'http://localhost:3000/suc',
1063 | async: true,
1064 | onprogress: function (evt) {
1065 | console.log(evt.position / evt.total);
1066 | },
1067 | dataType: 'text/json'
1068 | }).then(
1069 | function (xhr) {
1070 | console.log(xhr.response);
1071 | },
1072 | function (e) {
1073 | console.log(JSON.stringify(e))
1074 | });
1075 | ```
1076 |
1077 | # 模拟jsonp
1078 | ```
1079 | // foo 函数将会被调用 传入后台返回的数据
1080 | function foo(data) {
1081 | console.log('通过jsonp获取后台数据:', data);
1082 | document.getElementById('data').innerHTML = data;
1083 | }
1084 | /**
1085 | * 通过手动创建一个 script 标签发送一个 get 请求
1086 | * 并利用浏览器对