-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path1-2-4.html
922 lines (843 loc) · 30.1 KB
/
1-2-4.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
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="./public/favicon.ico" />
<meta http-equiv="cache-control" content="no-cache" />
<title></title>
<link rel="stylesheet" href=" https://necolas.github.io/normalize.css/8.0.1/normalize.css" />
<link rel="stylesheet" href="./hightlight/default.min.css" />
<link rel="stylesheet" href="./css/main.css" />
<link rel="stylesheet" href="./css/copybutton.css" />
<link rel="stylesheet" href="./css/hightlight.css" />
<script src="./hightlight/hightlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BEVZJDBC7Z"></script>
<script src="./js/gtag.js"></script>
</head>
<body>
<header>
<nav>
<h1>
<span id="toggle-menu"></span>
<a href="index.html"></a>
</h1>
</nav>
</header>
<main>
<aside>
<nav></nav>
</aside>
<article>
<h2 id="1-2-4">1-2-4 高頻考點及基礎題庫</h2>
<h3>JavaScript 資料類型及其判斷</h3>
<p>
對於類型判斷,常用的方法有 typeof、instanceof、Object.prototype.toString、 constructor。
</p>
<h3>使用 typeof 判斷資料類型</h3>
<pre><code class="language-js">
typeof 5 // "number"
typeof 'lucas' // "string"
typeof undefined // "undefined"
typeof true // "boolean"
</code></pre>
<p>但是也存在一些特例,如用 typeof 判斷 null,程式如下。</p>
<pre><code class="language-js">
typeof null // "object"
</code></pre>
<p>判斷複雜資料類型的範例。</p>
<pre><code class="language-js">
const foo = () => 1
typeof foo // "function"
const too = {}
typeof foo // "object"
const Foo = []
typeof foo // "object"
const foo = new Date ()
typeof too // "object"
const foo = Symbol("foo")
typeof Foo // "symbol"
</code></pre>
<p>
透過以上範例,我們可以知道,使用 typeof 可以準確判斯出除 null 以外的基底資料型態,以及
function 類型、symbol 類型;null 會被 typeof 判斷為 object 。
</p>
<h3>使用 instanceof 判斷資料類型</h3>
<p>
a instanceof B 判斷的是,a 是否為 B 的實例,即a 的原型鏈上是否存在 B
的建構函數,範例程式如下。
</p>
<pre><code class="language-js">
function Person (name) {
this.name = name
}
const p = new Person('lucas')
p instanceof Person
// true
</code></pre>
<p>
p 是 Person 創建出來的實體。同時順著 p 的原型鏈也能找到 Object 的建構函數,範例程式如下。
</p>
<pre><code class="language-js">
p.__proto__.__proto__=== Object.prototype
</code></pre>
<p>再來看一個範例。如果我們判斷的是以下關係,則傳回 false。</p>
<pre><code class="language-js">
5 instanceof Number // false
</code></pre>
<p>
因為 5 是基本類型,它並不是 Number
建構函數建置出來的實例物件。而如果稍加修改,使其變為判斷以下關係,則傳回 true
</p>
<pre><code class="language-js">
new Number(5) instanceof Number // true
</code></pre>
<p>關於 instanceof 的原理,可以用以下程式來模擬。</p>
<pre><code class="language-js">
// L表示左運算式, R表示右運算式
const instanceofMock = (L, R) => {
if (typeof L !== 'object') {
return false
}
while (true) {
if (L === null) {
//已經檢查到了頂端
return false
}
if (R.prototype === L.__proto__) {
return true
}
L = L.__proto__
}
}
</code></pre>
<p>根據 L 表示左運算式, R 表示右運算式, instanceofMock 的用法如下。</p>
<pre><code class="language-js">
instanceofMock('', String)
// false
function Person(name){
this.name = name
}
const p = new Person('lucas')
instanceofMock(p, Person)
// true
</code></pre>
<h3>使用 constructor 和 Object.prototype.toString 判斷資料類型</h3>
<p>透過 toString 判斷資料類型好用的方法如下所示。</p>
<pre><code class="language-js">
console.log(Object.prototype.toString.call(1))
// [object Number]
console.log(Object.prototype.toString.call('lucas'))
// [object String]
console.log(Object.prototype.toString.call(undefined))
/ [object Undefined]
console.log(Object.prototype.toString. call(true))
// [object Boolean]
console.log(Object.prototype.toString.call({}))
// [object Object]
console.log(Object.prototype.toString.call([]))
// [object Array]
console.log(Object.prototype.toString.call(function(){}))
// [object Function]
console.log(Object.prototype.toString.call(null))
// [object Null]
console.log(Object.prototype.toString.call(Symbol('lucas')))
// [object Symbol]
</code></pre>
<p>
使用 constructor
可以檢視目標的建構函數,也可以進行資料類型判斷,但其中也存在問題,實際請看以下範例。
</p>
<pre><code class="language-js">
var foo = 5
foo.constructor
// f Number(){ [native code] }
var foo = 'Lucas'
foo.constructor
// f String(){ [native code] }
var foo = true
foo.constructor
// f Boolean(){ [native code] }
var foo = []
foo.constructor
// f Array () { [native code] }
var foo = {}
foo.constructor
// f Object(){ [native code] }
var foo = () => {}
foo.constructor
// f Function () { [native code] }
var foo = new Date()
foo. constructor
// f Date(){ [native code] }
var foo = Symbol("foo")
foo.constructor
// f Symbol(){ [native code] }
var foo = undefined
foo.constructor
// VM257:1 Uncaught TypeError: Cannot read property 'constructor' of undefined at <anonymous>: 1:5
var foo = null
foo.constructor
// VM334:1 Uncaught TypeError: Cannot read property 'constructor' of null at <anonymous>: 1:5
</code></pre>
<p>
我們發現對於 undefined 和 null,如果嘗試讀取其 constructor 屬性,則會顯示出錯,並且
constructor 傳回的是建構函數本身,一般使用它來判斷資料類型 的情況並不多見。
</p>
<h3>JavaScript 資料類型及其轉換</h3>
<p>
先看一個有趣的網站。
<a href="https://jsfuck.com/" target="_blank" rel="noopener noreferrer">
https://jsfuck.com/
</a>
</p>
<p>挖掘源代碼可以看到作者利用了 JS 弱型別自動轉換的原理做了一個轉譯器。</p>
<pre><code class="language-js">
/*! JSFuck 0.5.0 - http://jsfuck.com */
(function(self){
const MIN = 32, MAX = 126;
const SIMPLE = {
'false': '![]',
'true': '!![]',
'undefined': '[][[]]',
'NaN': '+[![]]',
'Infinity': '+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])' // +"1e1000"
};
const CONSTRUCTORS = {
'Array': '[]',
'Number': '(+[])',
'String': '([]+[])',
'Boolean': '(![])',
'Function': '[]["flat"]',
'RegExp': 'Function("return/"+false+"/")()',
'Object': '[]["entries"]()'
};
const MAPPING = {
'a': '(false+"")[1]',
'b': '([]["entries"]()+"")[2]',
'c': '([]["flat"]+"")[3]',
'd': '(undefined+"")[2]',
'e': '(true+"")[3]',
'f': '(false+"")[0]',
'g': '(false+[0]+String)[20]',
'h': '(+(101))["to"+String["name"]](21)[1]',
'i': '([false]+undefined)[10]',
'j': '([]["entries"]()+"")[3]',
'k': '(+(20))["to"+String["name"]](21)',
'l': '(false+"")[2]',
'm': '(Number+"")[11]',
'n': '(undefined+"")[1]',
'o': '(true+[]["flat"])[10]',
'p': '(+(211))["to"+String["name"]](31)[1]',
'q': '("")["fontcolor"]([0]+false+")[20]',
'r': '(true+"")[1]',
's': '(false+"")[3]',
't': '(true+"")[0]',
'u': '(undefined+"")[0]',
'v': '(+(31))["to"+String["name"]](32)',
'w': '(+(32))["to"+String["name"]](33)',
'x': '(+(101))["to"+String["name"]](34)[1]',
'y': '(NaN+[Infinity])[10]',
'z': '(+(35))["to"+String["name"]](36)',
'A': '(NaN+[]["entries"]())[11]',
'B': '(+[]+Boolean)[10]',
'C': 'Function("return escape")()(("")["italics"]())[2]',
'D': 'Function("return escape")()([]["flat"])["slice"]("-1")',
'E': '(RegExp+"")[12]',
'F': '(+[]+Function)[10]',
'G': '(false+Function("return Date")()())[30]',
'H': null,
'I': '(Infinity+"")[0]',
'J': null,
'K': null,
'L': null,
'M': '(true+Function("return Date")()())[30]',
'N': '(NaN+"")[0]',
'O': '(+[]+Object)[10]',
'P': null,
'Q': null,
'R': '(+[]+RegExp)[10]',
'S': '(+[]+String)[10]',
'T': '(NaN+Function("return Date")()())[30]',
'U': '(NaN+Object()["to"+String["name"]]["call"]())[11]',
'V': null,
'W': null,
'X': null,
'Y': null,
'Z': null,
' ': '(NaN+[]["flat"])[11]',
'!': null,
'"': '("")["fontcolor"]()[12]',
'#': null,
'$': null,
'%': 'Function("return escape")()([]["flat"])[21]',
'&': '("")["fontcolor"](")[13]',
'\'': null,
'(': '([]["flat"]+"")[13]',
')': '([0]+false+[]["flat"])[20]',
'*': null,
'+': '(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[2]',
',': '[[]]["concat"]([[]])+""',
'-': '(+(.+[0000001])+"")[2]',
'.': '(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]',
'/': '(false+[0])["italics"]()[10]',
':': '(RegExp()+"")[3]',
';': '("")["fontcolor"](NaN+")[21]',
'<': '("")["italics"]()[0]',
'=': '("")["fontcolor"]()[11]',
'>': '("")["italics"]()[2]',
'?': '(RegExp()+"")[2]',
'@': null,
'[': '([]["entries"]()+"")[0]',
'\\': '(RegExp("/")+"")[1]',
']': '([]["entries"]()+"")[22]',
'^': null,
'_': null,
'`': null,
'{': '(true+[]["flat"])[20]',
'|': null,
'}': '([]["flat"]+"")["slice"]("-1")',
'~': null
};
const GLOBAL = 'Function("return this")()';
function fillMissingDigits(){
var output, number, i;
for (number = 0; number < 10; number++){
output = "+[]";
if (number > 0){ output = "+!" + output; }
for (i = 1; i < number; i++){ output = "+!+[]" + output; }
if (number > 1){ output = output.substr(1); }
MAPPING[number] = "[" + output + "]";
}
}
function replaceMap(){
var character = "", value, i, key;
function replace(pattern, replacement){
value = value.replace(
new RegExp(pattern, "gi"),
replacement
);
}
function digitReplacer(_,x) { return MAPPING[x]; }
function numberReplacer(_,y) {
var values = y.split("");
var head = +(values.shift());
var output = "+[]";
if (head > 0){ output = "+!" + output; }
for (i = 1; i < head; i++){ output = "+!+[]" + output; }
if (head > 1){ output = output.substr(1); }
return [output].concat(values).join("+").replace(/(\d)/g, digitReplacer);
}
for (i = MIN; i <= MAX; i++){
character = String.fromCharCode(i);
value = MAPPING[character];
if(!value) {continue;}
for (key in CONSTRUCTORS){
replace("\\b" + key, CONSTRUCTORS[key] + '["constructor"]');
}
for (key in SIMPLE){
replace(key, SIMPLE[key]);
}
replace('(\\d\\d+)', numberReplacer);
replace('\\((\\d)\\)', digitReplacer);
replace('\\[(\\d)\\]', digitReplacer);
replace("GLOBAL", GLOBAL);
replace('\\+""', "+[]");
replace('""', "[]+[]");
MAPPING[character] = value;
}
}
function replaceStrings(){
var regEx = /[^\[\]\(\)\!\+]{1}/g,
all, value, missing,
count = MAX - MIN;
function findMissing(){
var all, value, done = false;
missing = {};
for (all in MAPPING){
value = MAPPING[all];
if (value && value.match(regEx)){
missing[all] = value;
done = true;
}
}
return done;
}
function mappingReplacer(a, b) {
return b.split("").join("+");
}
function valueReplacer(c) {
return missing[c] ? c : MAPPING[c];
}
for (all in MAPPING){
if (MAPPING[all]){
MAPPING[all] = MAPPING[all].replace(/\"([^\"]+)\"/gi, mappingReplacer);
}
}
while (findMissing()){
for (all in missing){
value = MAPPING[all];
value = value.replace(regEx, valueReplacer);
MAPPING[all] = value;
missing[all] = value;
}
if (count-- === 0){
console.error("Could not compile the following chars:", missing);
}
}
}
function escapeSequence(c) {
var cc = c.charCodeAt(0);
if (cc < 256) {
return '\\' + cc.toString(8);
} else {
var cc16 = cc.toString(16);
return '\\u' + ('0000' + cc16).substring(cc16.length);
}
}
function escapeSequenceForReplace(c) {
return escapeSequence(c).replace('\\', 't');
}
function encode(input, wrapWithEval, runInParentScope){
var output = [];
if (!input){
return "";
}
var unmappped = ''
for(var k in MAPPING) {
if (MAPPING[k]){
unmappped += k;
}
}
unmappped = unmappped.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
unmappped = new RegExp('[^' + unmappped + ']','g');
var unmappedCharactersCount = (input.match(unmappped) || []).length;
if (unmappedCharactersCount > 1) {
// Without this optimization one unmapped caracter has encoded length
// of about 3600 characters. Every additional unmapped character adds
// 2000 to the total length. For example, the lenght of `~` is 3605,
// `~~` is 5600, and `~~~` is 7595.
//
// The loader with replace has encoded length of about 5300 characters
// and every additional character adds 100 to the total length.
// In the same example the length of `~~` becomes 5371 and `~~~` -- 5463.
//
// So, when we have more than one unmapped character we want to encode whole input
// except select characters (that have encoded length less than about 70)
// into an escape sequence.
//
// NOTE: `t` should be escaped!
input = input.replace(/[^0123456789.adefilnrsuN]/g, escapeSequenceForReplace);
} else if (unmappedCharactersCount > 0) {
//Because we will wrap the input into a string we need to escape Backslash
// and Double quote characters (we do not need to worry about other characters
// because they are not mapped explicitly).
// The JSFuck-encoded representation of `\` is 2121 symbols,
// so esacped `\` is 4243 symbols and escaped `"` is 2261 symbols
// however the escape sequence of that characters are
// 2168 and 2155 symbols respectively, so it's more practical to
// rewrite them as escape sequences.
input = input.replace(/["\\]/g, escapeSequence);
//Convert all unmapped characters to escape sequence
input = input.replace(unmappped, escapeSequence);
}
var r = "";
for (var i in SIMPLE) {
r += i + "|";
}
r+= ".";
input.replace(new RegExp(r, 'g'), function(c) {
var replacement = SIMPLE[c];
if (replacement) {
output.push("(" + replacement + "+[])");
} else {
replacement = MAPPING[c];
if (replacement){
output.push(replacement);
} else {
throw new Error('Found unmapped character: ' + c);
}
}
});
output = output.join("+");
if (/^\d$/.test(input)){
output += "+[]";
}
if (unmappedCharactersCount > 1) {
// replace `t` with `\\`
output = "(" + output + ")[" + encode("split") + "](" + encode ("t") + ")[" + encode("join") +"](" + encode("\\") + ")";
}
if (unmappedCharactersCount > 0) {
output = "[][" + encode("flat") + "]"+
"[" + encode("constructor") + "]" +
"(" + encode("return\"") + "+" + output + "+" + encode("\"") + ")()";
}
if (wrapWithEval){
if (runInParentScope){
output = "[][" + encode("flat") + "]" +
"[" + encode("constructor") + "]" +
"(" + encode("return eval") + ")()" +
"(" + output + ")";
} else {
output = "[][" + encode("flat") + "]" +
"[" + encode("constructor") + "]" +
"(" + output + ")()";
}
}
return output;
}
fillMissingDigits();
replaceMap();
replaceStrings();
self.JSFuck = {
encode: encode
};
})(typeof(exports) === "undefined" ? window : exports);
</code></pre>
<p>如需用在自己的小功能可以這樣使用。當然這是一個很鬧的展示,不可能用在正式產品上。</p>
<pre><code class="language-js">
function $(id){
return document.getElementById(id);
}
function encode(){
var output = JSFuck.encode($("input").value, $("eval").checked, $("scope").checked);
$("output").value = output;
$("stats").innerHTML = output.length + " chars";
}
$("encode").onclick = encode;
$("eval").onchange = encode;
$("scope").onchange = encode;
encode();
$("run").onclick = function(){
value = eval($("output").value)
if (!$("eval").checked){
alert('"' + value + '"');
}
return false;
};
</code></pre>
<p>
用以上轉譯器轉出來的一串怪怪的符號組合,拿到執行環境去跑還真的可以執行。只能說 JS
真的是太神奇了。
</p>
<p>
JavaScript
是一種弱類型,或說是一種動態語言。這表示你不用提前宣告變數的資料類型,在程式執行過程中,變數的資料類型會被自動確定。
</p>
<p>如果覺得上面的例子太過燒腦,下面有列舉一些基本案例。</p>
<pre><code class="language-js">
console.log( 1 + '1' )
// 11
console.log( 1 + true )
// 2
console.log( 1 + false )
// 1
console.log( 1 + undefined )
// NaN
console.log( 'lucas' + true )
// lucastrue
console.log( 1+ true )
// 2
console.log( 1 + false )
// 1
console.log( {} + true )
// [object Object]true
console.log( [] + {} )
// [object Object]
console.log( [] + [] )
// ''
console.log( [] + 1 )
// '1'
console.log( [] + 0 )
// '0'
</code></pre>
<p>在+運算子兩側,如果存在複雜資料類型,例如物件,會遵循的一套轉換規則。</p>
<p>
當使用+運算子計算時,如果存在複雜資料類型,那麼它將被轉為基底資料型態再進行運算。這就有關「物件類型轉基本類型」這個過程。這個過程的實際規則是,在轉換時,會呼叫該物件上的
valueOf或 toString 方法,這兩個方法的傳回值是轉換後的結果。
</p>
<p>
那實際呼叫 valueOf 還是 toString 呢?這是 ES 標準所決定的,實際上,這取決於內建的
toPrimitive
的呼叫結果。從主觀上說,這個物件偏好轉換成什麼,就會優先呼叫哪個方法。如果偏好轉為 number
類型,就優先呼叫valueOf;如果偏好轉為 string 類型,就只呼叫
toString。這裡建議大家了解一些常用的轉換結果,對於其他特例情況,會尋找標準即可。
</p>
<p>
很多經典教科書中介紹將物件轉為基底資料型態時,會先呼叫 valueOf,再呼叫
toString,這裡引用的「這個物件偏好轉換成什麼,就會優先呼叫哪個方法」其實取自標準中的
PreferredType 概念,這個概念在這些書中並沒有被提到。事實上,瀏覽器對 PreferredType的
了解比較一致,「物件類型轉為基本類型時,先呼叫 valueOf,再呼叫
toString」也沒有問題。對此有興趣或更加嚴謹可以翻閱相關標準。
</p>
<p>valueOf 及 toString 是可以被開發者重新定義的,範例如下。</p>
<pre><code class="language-js">
const foo = {
toString(){
return 'lucas'
},
valueOf(){
return 1
}
}
</code></pre>
<p>
我們對 foo 物件的 valueOf及 toSting 進行了重新定義,這時候呼叫 alert(foo)將輸出
lucas。這裡就有關自動轉型,在呼叫 alert 列印輸出時,偏好使用 foo 物件的toString 方法,將
foo 轉為基底資料型態,以列印出結果。
</p>
<p>
然而,執行 console.log(1 + foo)將輸出 2 ,這時候的自動轉型則偏好使用 foo 物件的 valueOf
方法,將 foo 轉為基底資料型態,以執行相加操作。
</p>
<p>我們再全面歸納一下,對於加法操作,如果+運算子兩邊都是 number 類型,則其規則如下。</p>
<ul>
<li>如果 + 運算子兩邊存在 NaN ,則結果為 NaN ( 對 NaN 進行 typeof 求值,傳回 number)</li>
<li>如果是 Infinity + Infinity,則結果是 Infinity 。</li>
<li>如果是 -Infinity + (-Infinity),則結果是 -Infinity。</li>
<li>如果是 Infinity + (-Infinity),則結果是 NaN 。</li>
</ul>
<p>如果 + 運算子兩邊有至少一個是字串,則其規則如下。</p>
<ul>
<li>如果 + 運算子兩邊都是字串,則執行字串連接操作。</li>
<li>如果 + 運算子兩邊只有一個是字串,則將另外的值轉為字串,再執行字串連接操作。</li>
<li>
如果 + 運算子兩邊有一個是物件,則呼叫 valueOf 或 toString
方法取得值,將其轉為基底資料型態再進行字串連接。
</li>
</ul>
<h3>JavaScript 函數參數傳遞</h3>
<p>
JavaScript
中有參考設定值和基底資料型態設定值的區別,並了解由此引出的相關話題:深拷貝和淺拷貝。
</p>
<pre><code class="language-js">
let foo = 1
const bar = value => {
value = 2
console.log(value) // 2
}
bar(foo)
console.log(foo) // 1
</code></pre>
<pre><code class="language-js">
let foo = {bar: 1}
const func = obj =>{
obj.bar = 2
console.log(obj.bar) // 2
}
func(foo)
console.log(foo) // { bar : 2 }
</code></pre>
<p>
也就是說,如果函數參數是一個參考類型,那麼當在函數本體內修改這個參考類型參數的某個屬性值時,也將對原來的參數進行修改,因為此時函數本體內的參考位址指向了原來的參數。(簡單說就是控制記憶體位置的指向。)
</p>
<p>但是,如果在函數本體內直接修改對參數的參考,則情況又不一樣,範例如下。</p>
<pre><code class="language-js">
let foo = { bar: 1}
const func = obj => {
obj = 2
console.log(obj) // 2
}
func(foo)
console.log(foo) // { bar: 1}
</code></pre>
<p>這樣的情況了解起來此較困難,分析原因歸納了幾點規則,如下:</p>
<ul>
<li>
函數參數為基底資料型態時,函數本體內複製了一份參數值,任何操作都不會影響原參數的實際值。
</li>
<li>
函數參數是參考類型時,當在函數本體內修改這個值的某個屬性值時,將對原來的參數進行修改。
</li>
<li>
函數參數是參考類型時,如果直接修改這個值的參考位址,則相當於在函數本體內新建立了一個參考,任何操作都不會影響原參數的實際值。
</li>
</ul>
<h3>cannot read property of undefined 問題解決方案</h3>
<p>意外獲得一個空白物件或空值這樣惱人的問題是在所難免的,面對這樣的問題該怎麼辦。</p>
<pre><code class="language-js">
const obj = {
user: {
posts: [
{ title: 'Foo', comments: [ 'Good one!', 'Interesting…..' ] },
{ title: 'Bar' , comments: [ 'Ok' ] },
{ title: 'Baz' , comments: []}
],
comments: []
}
}
</code></pre>
<p>
為了能夠在物件中安全地設定值,需要驗證物件中每一個鍵的存在性。常見的處理方案有以下幾種。
</p>
<h4>1. 透過 && 短路運算子進行可存取性偵測。</h4>
<p>以下為透過 &&短路運算子進行可存取性偵測的實現程式。</p>
<pre><code class="language-js">
obj.user &&
obj.user.posts &&
obj.user.posts[0] &&
obj.user.posts[0].comments
</code></pre>
<h4>2. 透過 || 單元設定預設保底值。</h4>
<p>以下為透過 || 單元設定預設保底值的實現程式。</p>
<pre><code class="language-js">
(((obj.user || {}).posts ||{})[0]||{}).comments
</code></pre>
<h4>3. 使用 try...catch方法</h4>
<p>以下為使用 try…catch 方法的實現程式。</p>
<pre><code class="language-js">
var result try {
result = obj.user.posts[0].comments
}
catch {
result = null
}
</code></pre>
<h4>4.使用 lodash get API</h4>
<p>以下為使用 lodash get API 的實現程式。</p>
<pre><code class="language-js">
function get(object, path, defaultValue) {
const result = object == null ? undefined : baseGet(object, path)
return result === undefined ? defaultValue : result
}
function baseGet(object, path) {
path = castPath(path, object)
let index = 0
const length = path.length
while (object != null && index < length) {
object = object[toKey(path[index++])]
}
return (index && index == length) ? object : undefined
}
</code></pre>
<p>簡易的 get 方法</p>
<pre><code class="language-js">
const get = (p, o)=> p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o)
console.log(get(['user', 'posts', 0, 'comments'], obj))
// ['Good one!','Interesting...' ]
console.log(get(['user', 'post', 0, 'comments'], obj])) // null
</code></pre>
<p>
我們實現的方法可以接收兩個參數,第一個參數表示取得值的路徑(path),第二個參數表示目標物件。
</p>
<p>同樣,為了在設計上顯得更加靈活和抽象,我們可以對方法進行柯里化,程式如下。</p>
<pre><code class="language-js">
const get = p => o =>
p.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, o)
const getUserComments = get(['user', 'posts', 0, 'comments'])
console.log(getUserComments(obj))
// [ 'Good one!','Interesting...' ]
console.log(getUserComments({user:{posts:[]}}))
// null
</code></pre>
<p>
TC39 中有一個新的提案,支援
console.log(obj?.user?.posts[0]?.comments),由此可見,JavaScript 語言也在不斷演進。
</p>
<p>
目前已經實裝。可以參考章節:
<a href="14-4.html">14-4 Operators-Optional chaining (?.)</a>
</p>
<h3>type.js 原始程式解讀</h3>
<p>
type.js 是由顏海鏡撰寫的用於判斷資料類型的方法函數庫,其相容 IE6
瀏覽器,靈活運用了多種判斷資料類型方式。以下是其實現程式。
</p>
<pre><code class="language-js">
const toString = Object.prototype.toString
export function type(x, strict = false) {
strict = !!strict
// fix typeof null = object
if (x === null) {
return 'null'
}
const t = typeof x
// number string boolean undefined symbol
if (t !== 'object') {
return t
}
let cls
let clsLow
try {
cls = toString.call(x).slice(8, -1)
clsLow = cls.toLowerCase()
} catch (e) {
// IE覽器下的 activex 物件
return 'object'
}
if (clsLow !== 'object') {
// 區分 String() 和 new String()
if (strict && (clsLow === 'number' || clsLow === 'boolean' || clsLow === 'string')) {
return cls
}
return clsLow
}
if (x.constructor == Object) {
return clsLow
}
// Object.create(null)
try {
// __proto__ 部分的早期Firefox瀏覽
if (Object.getPrototypeof(x) === null || x.__proto__ === null) {
return 'object'
}
} catch (e) {
// 瀏覽器下無Object.getPrototypeOf會顯示出錯
}
// function A() {}; new A
try {
const cname = x.constructor.name
if (typeof cname === 'string') {
return cname
}
} catch (e) {
//無constructor
}
// function A() {}; A.prototype. constructor = null; new A
return 'unknown'
}
</code></pre>
<p>以上程式中的關鍵點有以下幾個。</p>
<ul>
<li>透過 x === null 來判斷 null 類型。</li>
<li>
對於 typeof x 不為 object 的情況,直接傳回 typeof x的結果,這時可以判斷其是否為
number、string、boolean、undefined、symbol 類型。
</li>
<li>
對於其他情況,瀏覽器為 IE6 以上版本時,使用 Object.prototype.toString
方法進行資料類型判斷。
</li>
<li>
相容性處理,舉例來說,對於不支援 Object.prototype.toString 方法的情況,會傳回
object。對於其他相容性處理,此處不再一一多作說明。
</li>
</ul>
<p>
這裡特別注意一下 Object,prototype.toString
方法,該方法確實可以稱得上是「終極方案」,它最後會傳回一個格式形如 [object
XXXX]的結果,這個結果中的 XXXX
部分就是我們需要的變數資料類型,因此對傳回結果使用,slice(8, -1)方法,切分出結果中的XXXX
部分,就可以更加方便地獲得結果,程式如下。
</p>
<pre><code class="language-js">
Object.prototype.toString.call(true).slice(8,-1)
// "Boolean"
</code></pre>
<pre><code class="language-js">
</code></pre>
<pre><code class="language-js">
</code></pre>
<pre><code class="language-js">
</code></pre>
</article>
</main>
</body>
<script type="module" src="./js/main.js"></script>
<script></script>
</html>