diff --git a/blog/2015/03/26/README.md b/blog/2015/03/26/README.md new file mode 100644 index 000000000..02530a251 --- /dev/null +++ b/blog/2015/03/26/README.md @@ -0,0 +1,92 @@ +JavaScript Idiosyncrasies +--- +Time to mention a few JavaScript idiosyncrasies that newcomers may not be aware of, and how I deal with them. + +Also, see my previous posts on [PCjs Coding Conventions](/blog/2014/09/30/) and [JavaScript Negativity](/blog/2014/10/26/). + +### Strict Equality + +Most sites will advise you to *never* use the "==" and "!=" JavaScript operators, because when they compare variables +containing different data types, JavaScript will coerce one of the operands to a matching type, sometimes in unexpected +ways. We can thank the early days of JavaScript for this feature, when it was trying to be extraordinarily forgiving +of sloppy code. I'm not going to list all the odd results that can arise from JavaScript's operand coercion, because +there are more than enough examples on the web already. + +To avoid unexpected coercion, and thus unexpected matches and/or mismatches, the usual advice is to *always* use +strict equality operators instead ("===" and "!=="). + +I disagree. In properly written code, you should always know what type of data your variables contain. In fact, +the more you're able to use JSDoc types to declare the data types of all your parameters, return values, and other +variables, the fewer errors you'll have. And coercion will never be a problem as long as you're always comparing +variables with matching types, because no coercion will be performed. + +Another problem with strict equality operators is that they require more work to check for both *undefined* and *null* +values. For example, when I write a method with optional parameters, I generally allow those parameters to either +be omitted or set to *null*. Using "==", you can check both cases with a single comparison: + + if (parameter == null) { ... } + +whereas strict equality requires more work: + + if (parameter === undefined || parameter === null) { ... } + +This is one of the few times I think coercion (of *undefined* to *null*) is beneficial, so I rely on it. + +When I recommend that you *not* use strict comparisons, I'm not saying that coercion is good. I agree that it +generally should be avoided (except in situations like the last example). The point is, know your variable data +types, only compare variables of the same type, and you'll never have to worry about coercion. + +### Enumerating Array or Object Properties + +When using *for*...*in* loops like this: + + var a = [100, 200, 300]; + for (var i in a) { ... } + +the type of variable *i* will be **string** rather than **number**; that is, it will contain "0", "1" and "2" rather +than 0, 1 and 2. If you then use *i* to set a matching element in another array, that element will not be stored in +the same (numeric) position as the original array. + +One solution is to convert *i* to a **number**: + + parseInt(i, 10); + +However, a more elegant solution is to use the unary "+" operator to coerce the **string** to a **number**: + + +i; + +### Shift Counts For Bit-wise Shifts + +It turns out that shifting an integer value by more than 31 bits in either direction may not shift as many bits as +you'd expect. For example: + + n = 0x10000000; + n >>>= 32; + +will not change n at all. This is because, just like the shift instructions on Intel processors, JavaScript converts +the shift count to a *mod 32* value (in other words, it truncates the shift count to a 5-bit value). + +So the above example is equivalent to: + + n >>>= 0; + +If you really need larger shift counts to work in a consistent manner, you can perform multiple shifts, where each +shift count is in the range 0-31: + + n = (n >>> 31) >>> 1; + +Also, it's not quite correct to say that a shift count of zero has *no* effect on a value: + + n = 0x88888888|0; // n is displayed as -2004318072 + n >>>= 0; // n is displayed as 2290649224 + +It's true that the bottom 32 bits of the value were not changed, but a side-effect of the unsigned shift operator is +that all the upper sign bits are stripped from the (64-bit) result. + +However, as soon as you perform another bit-wise operation on the value, even one that has no effect on the lower 32 +bits, the upper bits will once be updated to match the sign of the lower 32-bit value: + + n |= 0; // n is displayed as -2004318072 again + +*[@jeffpar](http://twitter.com/jeffpar)* +*March 26, 2015* diff --git a/modules/pcjs/lib/bus.js b/modules/pcjs/lib/bus.js index 1267cee0f..f0cc4016d 100644 --- a/modules/pcjs/lib/bus.js +++ b/modules/pcjs/lib/bus.js @@ -1316,11 +1316,7 @@ Bus.prototype.addPortInputTable = function(component, table, offset) { if (offset === undefined) offset = 0; for (var port in table) { - /* - * JavaScript coerces property keys to strings, so we use parseInt() to coerce them back to numbers. - */ - port = parseInt(port, 10); - this.addPortInputNotify(port + offset, port + offset, component, table[port]); + this.addPortInputNotify(+port + offset, +port + offset, component, table[port]); } }; @@ -1440,11 +1436,7 @@ Bus.prototype.addPortOutputTable = function(component, table, offset) { if (offset === undefined) offset = 0; for (var port in table) { - /* - * JavaScript converts property keys to strings (brilliant), so we use parseInt() to convert them back to numbers. - */ - port = parseInt(port, 10); - this.addPortOutputNotify(port + offset, port + offset, component, table[port]); + this.addPortOutputNotify(+port + offset, +port + offset, component, table[port]); } }; diff --git a/modules/pcjs/lib/chipset.js b/modules/pcjs/lib/chipset.js index 1d8e2588c..3ec43fbfc 100644 --- a/modules/pcjs/lib/chipset.js +++ b/modules/pcjs/lib/chipset.js @@ -2187,7 +2187,7 @@ ChipSet.prototype.toggleSwitch = function(control) this.setSwitch(control, f); var sID = control.getAttribute("id"); var asParts = sID.split("-"); - var b = (0x1 << (parseInt(asParts[1], 10) - 1)); + var b = (0x1 << (+asParts[1] - 1)); switch (asParts[0]) { case "sw1": this.sw1Init = (this.sw1Init & ~b) | (f? 0 : b); @@ -4795,13 +4795,16 @@ ChipSet.prototype.intBIOSRTC = function(addr) * parseSwitches(s, def) * * @this {ChipSet} - * @param {string|undefined} s describing switch settings (can't simply use parseInt() with a base of 2, because the bit order is reversed, as well as the bit sense) + * @param {string|undefined} s describing switch settings * @param {number} def is a default value to use if s is undefined * @return {number} value representing the switch settings */ ChipSet.prototype.parseSwitches = function(s, def) { if (s === undefined) return def; + /* + * NOTE: We can't simply use parseInt() with a base of 2, because the bit order is reversed, as well as the bit sense. + */ var b = 0, bit = 0x1; for (var i = 0; i < s.length; i++) { if (s.charAt(i) == "0") b |= bit; diff --git a/modules/pcjs/lib/computer.js b/modules/pcjs/lib/computer.js index 1bbbc2097..1b74f93e9 100644 --- a/modules/pcjs/lib/computer.js +++ b/modules/pcjs/lib/computer.js @@ -187,10 +187,10 @@ function Computer(parmsComputer, parmsMachine, fSuspended) { var sStatePath = null; var sResume = parmsComputer['resume']; if (sResume !== undefined) { - if (sResume.length > 1) { - sStatePath = this.sResumePath = sResume; + if (sResume.length == 1) { + this.resume = +sResume; } else { - this.resume = parseInt(sResume, 10); + sStatePath = this.sResumePath = sResume; } } diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index f64266351..02702cea3 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -4015,7 +4015,7 @@ if (DEBUGGER) { var fWords = (sCmd == "dw"); if (sLen !== undefined) { if (sLen.charAt(0) == "l") sLen = sLen.substr(1); - cLines = parseInt(sLen, 10); + cLines = +sLen; if (cLines) cLines = fWords? ((cLines + 7) >> 3) : ((cLines + 15) >> 4); } if (!cLines) cLines = 8; @@ -4061,7 +4061,7 @@ if (DEBUGGER) { if (aAddr[0] == null) return; for (var i = 2; i < asArgs.length; i++) { - var b = parseInt(asArgs[i], 16); + var b = str.parseInt(asArgs[i], 16); this.println("setting " + this.hexAddr(aAddr) + " to " + str.toHexByte(b)); this.setByte(aAddr, b, 1); } @@ -4133,7 +4133,7 @@ if (DEBUGGER) { var iHistory = this.iOpcodeHistory; var aHistory = this.aOpcodeHistory; if (aHistory.length) { - var n = (sCount === undefined? this.nextHistory : parseInt(sCount, 10)); + var n = (sCount === undefined? this.nextHistory : +sCount); if (isNaN(n)) n = cLines; else @@ -4203,7 +4203,7 @@ if (DEBUGGER) { var fPrint = false; if (sCategory == "DUMP") { var sDump = ""; - var cLines = (sEnable === undefined? -1 : parseInt(sEnable, 10)); + var cLines = (sEnable === undefined? -1 : +sEnable); var i = this.iTraceBuffer; do { var s = this.aTraceBuffer[i++]; @@ -4509,9 +4509,7 @@ if (DEBUGGER) { switch (asArgs[1]) { case "cs": var nCycles; - if (asArgs[3] !== undefined) { - nCycles = parseInt(asArgs[3], 10); - } + if (asArgs[3] !== undefined) nCycles = +asArgs[3]; switch (asArgs[2]) { case "int": this.cpu.aCounts.nCyclesChecksumInterval = nCycles; @@ -4533,7 +4531,7 @@ if (DEBUGGER) { break; case "sp": if (asArgs[2] !== undefined) { - this.cpu.setSpeed(parseInt(asArgs[2], 10)); + this.cpu.setSpeed(+asArgs[2]); } this.println("target speed: " + this.cpu.getSpeedTarget() + " (" + this.cpu.getSpeed() + "x)"); break; @@ -4610,7 +4608,7 @@ if (DEBUGGER) { this.println("missing value for " + asArgs[1]); return; } - var w = parseInt(sValue, 16); + var w = str.parseInt(sValue, 16); if (!isNaN(w)) { var sRegMatch = sReg.toUpperCase(); if (sRegMatch.charAt(0) == 'E' && this.cchReg <= 4) { @@ -4938,7 +4936,7 @@ if (DEBUGGER) { { var dbg = this; var fRegs = (sCmd == "tr"); - var count = (sCount != null? parseInt(sCount, 10) : 1); + var count = (sCount != null? +sCount : 1); var nCycles = (count == 1? 0 : 1); web.onCountRepeat( count, diff --git a/modules/pcjs/lib/fdc.js b/modules/pcjs/lib/fdc.js index bc2eb643d..345857558 100644 --- a/modules/pcjs/lib/fdc.js +++ b/modules/pcjs/lib/fdc.js @@ -1194,7 +1194,7 @@ FDC.prototype.loadSelectedDrive = function(sDisketteName, sDiskettePath, file) { var iDrive; var controlDrives = this.bindings["listDrives"]; - if (controlDrives && !isNaN(iDrive = parseInt(controlDrives.value, 10)) && iDrive >= 0 && iDrive < this.aDrives.length) { + if (controlDrives && !isNaN(iDrive = str.parseInt(controlDrives.value, 10)) && iDrive >= 0 && iDrive < this.aDrives.length) { if (!sDiskettePath) { this.unloadDrive(iDrive); @@ -1437,7 +1437,7 @@ FDC.prototype.displayDiskette = function(iDrive, fUpdateDrive) * Next, make sure the drive whose disk we're updating is the currently selected drive. */ var i; - var iDriveSelected = parseInt(controlDrives.value, 10); + var iDriveSelected = str.parseInt(controlDrives.value, 10); var sTargetPath = (drive.fLocal? "?" : drive.sDiskettePath); if (!isNaN(iDriveSelected) && iDriveSelected == iDrive) { for (i = 0; i < controlDisks.options.length; i++) { @@ -1452,7 +1452,7 @@ FDC.prototype.displayDiskette = function(iDrive, fUpdateDrive) } if (fUpdateDrive) { for (i = 0; i < controlDrives.options.length; i++) { - if (parseInt(controlDrives.options[i].value, 10) == drive.iDrive) { + if (str.parseInt(controlDrives.options[i].value, 10) == drive.iDrive) { if (controlDrives.selectedIndex != i) { controlDrives.selectedIndex = i; } diff --git a/modules/pcjs/lib/keyboard.js b/modules/pcjs/lib/keyboard.js index 2bf6b1e6e..e1416b2c6 100644 --- a/modules/pcjs/lib/keyboard.js +++ b/modules/pcjs/lib/keyboard.js @@ -1090,7 +1090,7 @@ Keyboard.prototype.findBinding = function(simCode, sType, fDown) if (this.cSoftCodes) { for (var code in Keyboard.SHIFTED_KEYCODES) { if (simCode == Keyboard.SHIFTED_KEYCODES[code]) { - simCode = parseInt(code, 10); + simCode = +code; code = Keyboard.STUPID_KEYCODES[code]; if (code) simCode = code; break; diff --git a/modules/pcjs/lib/rom.js b/modules/pcjs/lib/rom.js index 0183e5074..0d2050866 100644 --- a/modules/pcjs/lib/rom.js +++ b/modules/pcjs/lib/rom.js @@ -256,7 +256,7 @@ ROM.prototype.onLoadROM = function(sROMFile, sROMData, nErrorCode) var asHexData = sHexData.split(" "); this.abROM = new Array(asHexData.length); for (var i = 0; i < asHexData.length; i++) { - this.abROM[i] = parseInt(asHexData[i], 16); + this.abROM[i] = str.parseInt(asHexData[i], 16); } } this.copyROM(); diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 8e9f02390..464ea9fcb 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -761,13 +761,15 @@ X86CPU.prototype.initProcessor = function() this.aOps[X86.OPCODE.PUSHSP] = X86.opPUSHSP; if (I386 && this.model >= X86.MODEL_80386) { + var bOpcode; this.aOps[X86.OPCODE.FS] = X86.opFS; this.aOps[X86.OPCODE.GS] = X86.opGS; this.aOps[X86.OPCODE.OS] = X86.opOS; this.aOps[X86.OPCODE.AS] = X86.opAS; this.aOps0F = X86.aOps0F.slice(); - this.aOps0F[0x20] = X86.opMOVrcr; - this.aOps0F[0x22] = X86.opMOVcrr; + for (bOpcode in X86.aOps0F386) { + this.aOps0F[+bOpcode] = X86.aOps0F386[bOpcode]; + } /* * Extend the opcode table by creating a mirror of the first 256 opcodes, but with dword-based * opcode handlers (as defined in aOpsD) instead word-based opcode handlers. Whenever dataSize @@ -775,8 +777,8 @@ X86CPU.prototype.initProcessor = function() * bOpcodeBias from 0 to 256. */ this.aOps = this.aOps.concat(this.aOps); - for (var bOpcode in X86.aOpsD) { - this.aOps[parseInt(bOpcode, 10) + 256] = X86.aOpsD[bOpcode]; + for (bOpcode in X86.aOpsD) { + this.aOps[+bOpcode + 256] = X86.aOpsD[bOpcode]; } } } diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index 91d52fb97..ac82fdee9 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -311,76 +311,30 @@ X86.opMOVcrr = function MOVcrr() } }; -X86.aOps0F = [ - X86.opGrp6, X86.opGrp7, X86.opLAR, X86.opLSL, // 0x00-0x03 - X86.opUndefined, X86.opLOADALL, X86.opCLTS, X86.opUndefined, // 0x04-0x07 - /* - * On all processors (except the 8086/8088, of course), 0x0F,0x0B is also referred to as "UD2": an - * instruction guaranteed to raise a #UD (Invalid Opcode) exception (INT 0x06) on all future x86 processors. - */ - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opInvalid, // 0x08-0x0B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x0C-0x0F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x10-0x13 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x14-0x17 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x18-0x1B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x1C-0x1F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x20-0x23 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x24-0x27 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x28-0x2B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x2C-0x2F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x30-0x33 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x34-0x37 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x38-0x3B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x3C-0x3F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x40-0x43 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x44-0x47 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x48-0x4B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x4C-0x4F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x50-0x53 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x54-0x57 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x58-0x5B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x5C-0x5F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x60-0x63 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x64-0x67 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x68-0x6B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x6C-0x6F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x70-0x73 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x74-0x77 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x78-0x7B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x7C-0x7F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x80-0x83 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x84-0x87 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x88-0x8B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x8C-0x8F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x90-0x93 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x94-0x97 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x98-0x9B - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0x9C-0x9F - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xA0-0xA3 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xA4-0xA7 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xA8-0xAB - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xAC-0xAF - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xB0-0xB3 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xB4-0xB7 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xB8-0xBB - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xBC-0xBF - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xC0-0xC3 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xC4-0xC7 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xC8-0xCB - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xCC-0xCF - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xD0-0xD3 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xD4-0xD7 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xD8-0xDB - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xDC-0xDF - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xE0-0xE3 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xE4-0xE7 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xE8-0xEB - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xEC-0xEF - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xF0-0xF3 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xF4-0xF7 - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined, // 0xF8-0xFB - X86.opUndefined, X86.opUndefined, X86.opUndefined, X86.opUndefined // 0xFC-0xFF -]; +X86.aOps0F = new Array(256); + +X86.aOps0F[0x00] = X86.opGrp6; +X86.aOps0F[0x01] = X86.opGrp7; +X86.aOps0F[0x02] = X86.opLAR; +X86.aOps0F[0x03] = X86.opLSL; +X86.aOps0F[0x05] = X86.opLOADALL; +X86.aOps0F[0x06] = X86.opCLTS; + +/* + * On all processors (except the 8086/8088, of course), 0x0F,0x0B is also referred to as "UD2": an + * instruction guaranteed to raise a #UD (Invalid Opcode) exception (INT 0x06) on all future x86 processors. + */ +X86.aOps0F[0x0B] = X86.opInvalid; + +for (var i = 0; i < X86.aOps0F.length; i++) { + if (!X86.aOps0F[i]) X86.aOps0F[i] = X86.opUndefined; +} + +if (I386) { + X86.aOps0F386 = []; + X86.aOps0F386[0x20] = X86.opMOVrcr; + X86.aOps0F386[0x22] = X86.opMOVcrr; +} /* * These instruction groups are not as orthogonal as the original 8086/8088 groups (Grp1 through Grp4): some of