《JavaScript 教程》學習補遺(七)

數據類型的轉換

強制轉換

Number()

原始類型
// 數值:轉換後仍是原來的值
Number(324) // 324

// 字符串:若是能夠被解析爲數值,則轉換爲相應的數值
Number('324') // 324

// 字符串:若是不能夠被解析爲數值,返回 NaN
Number('324abc') // NaN

// 空字符串轉爲0
Number('') // 0

// 布爾值:true 轉成 1,false 轉成 0
Number(true) // 1
Number(false) // 0

// undefined:轉成 NaN
Number(undefined) // NaN

// null:轉成0
Number(null) // 0

(1)parseIntNumber函數都會自動過濾一個字符串前導和後綴的空格。
(2)Number函數將字符串轉爲數值,要比parseInt函數嚴格不少。基本上,只要有一個字符沒法轉成數值,整個字符串就會被轉爲NaN正則表達式

對象

(2)簡單的規則是,Number方法的參數是對象時,將返回NaN,除非是包含單個數值的數組。編程

Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

(2)Number背後的轉換規則比較複雜。數組

第一步,調用對象自身的valueOf方法。若是返回原始類型的值,則直接對該值使用Number函數,再也不進行後續步驟。ide

第二步,若是valueOf方法返回的仍是對象,則改成調用對象自身的toString方法。若是toString方法返回原始類型的值,則對該值使用Number函數,再也不進行後續步驟。函數

第三步,若是toString方法返回的是對象,就報錯。this

var obj = {x: 1};
Number(obj) // NaN

// 等同於
if (typeof obj.valueOf() === 'object') {
  Number(obj.toString());
} else {
  Number(obj.valueOf());
}

(3)默認狀況下,對象的valueOf方法返回對象自己,因此通常老是會調用toString方法,而toString方法返回對象的類型字符串(好比[object Object])。因此,會有下面的結果。prototype

Number({}) // NaN

(4)valueOftoString方法,都是能夠自定義的。code

Number({
  valueOf: function () {
    return 2;
  }
})
// 2

Number({
  toString: function () {
    return 3;
  }
})
// 3

Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// 2

String()

原始類型值
  • 數值:轉爲相應的字符串。
  • 字符串:轉換後仍是原來的值。
  • 布爾值true轉爲字符串"true"false轉爲字符串"false"
  • undefined:轉爲字符串"undefined"
  • null:轉爲字符串"null"
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
對象

(1)String方法的參數若是是對象,返回一個類型字符串;若是是數組,返回該數組的字符串形式。orm

String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

(2)轉換規則對象

  1. 先調用對象自身的toString方法。若是返回原始類型的值,則對該值使用String函數,再也不進行如下步驟。
  2. 若是toString方法返回的是對象,再調用原對象的valueOf方法。若是valueOf方法返回原始類型的值,則對該值使用String函數,再也不進行如下步驟。
  3. 若是valueOf方法返回的是對象,就報錯。
String({a: 1})
// "[object Object]"

// 等同於
String({a: 1}.toString())
// "[object Object]"
var obj = {
  valueOf: function () {
    return {};
  },
  toString: function () {
    return {};
  }
};

String(obj)
// TypeError: Cannot convert object to primitive value
String({
  toString: function () {
    return 3;
  }
})
// "3"

String({
  valueOf: function () {
    return 2;
  }
})
// "[object Object]"

String({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// "3"

Boolean()

(1)它的轉換規則相對簡單:除了如下五個值的轉換結果爲false,其餘的值所有爲true

  • undefined
  • null
  • 0(包含-0+0
  • NaN
  • ''(空字符串)
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean(true) // true
Boolean(false) // false

(2)全部對象(包括空對象)的轉換結果都是true

Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true

自動轉換

(1)自動轉換的規則是這樣的:預期什麼類型的值,就調用該類型的轉換函數。
(2)因爲自動轉換具備不肯定性,並且不易除錯,建議在預期爲布爾值、數值、字符串的地方,所有使用Boolean()Number()String()函數進行顯式轉換。
(3)加法運算符(+)有可能把運算子轉爲字符串

null + 1 // 1
undefined + 1 // NaN

(4)一元運算符也會把運算子轉成數值。

+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0

錯誤處理機制

Error 實例對象

(1)JavaScript 原生提供Error構造函數,全部拋出的錯誤都是這個構造函數的實例。

var err = new Error('出錯了');
err.message // "出錯了"

(2)Error的屬性

  • message:錯誤提示信息
  • name:錯誤名稱(非標準屬性)
  • stack:錯誤的堆棧(非標準屬性)
if (error.name) {
  console.log(error.name + ': ' + error.message);
}
function throwit() {
  throw new Error('');
}

function catchit() {
  try {
    throwit();
  } catch(e) {
    console.log(e.stack); // print stack trace
  }
}

catchit()
// Error
//    at throwit (~/examples/throwcatch.js:9:11)
//    at catchit (~/examples/throwcatch.js:3:9)
//    at repl:1:5

原生錯誤類型

(1)SyntaxError`對象是解析代碼時發生的語法錯誤。

// 變量名錯誤
var 1a;
// Uncaught SyntaxError: Invalid or unexpected token

// 缺乏括號
console.log 'hello');
// Uncaught SyntaxError: Unexpected string

(2)ReferenceError對象是引用一個不存在的變量時發生的錯誤。

// 使用一個不存在的變量
unknownVariable
// Uncaught ReferenceError: unknownVariable is not defined

(3)將一個值分配給沒法分配的對象,好比對函數的運行結果賦值。

// 等號左側不是變量
console.log() = 1
// Uncaught ReferenceError: Invalid left-hand side in assignment

(4)RangeError對象是一個值超出有效範圍時發生的錯誤。主要有幾種狀況,一是數組長度爲負數,二是Number對象的方法參數超出範圍,以及函數堆棧超過最大值。

// 數組長度不得爲負數
new Array(-1)
// Uncaught RangeError: Invalid array length

(5)TypeError對象是變量或參數不是預期類型時發生的錯誤。好比,對字符串、布爾值、數值等原始類型的值使用new命令,就會拋出這種錯誤,由於new命令的參數應該是一個構造函數。

new 123
// Uncaught TypeError: number is not a func

var obj = {};
obj.unknownMethod() // 調用對象不存在的方法
// Uncaught TypeError: obj.unknownMethod is not a function

(6)URIError對象是 URI 相關函數的參數不正確時拋出的錯誤,主要涉及encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()escape()unescape()這六個函數。

decodeURI('%2')
// URIError: URI malformed

(7)eval函數沒有被正確執行時,會拋出EvalError錯誤。該錯誤類型已經再也不使用了,只是爲了保證與之前代碼兼容,才繼續保留。
(8)以上這6種派生錯誤,連同原始的Error對象,都是構造函數。開發者可使用它們,手動生成錯誤對象的實例。這些構造函數都接受一個參數,表明錯誤提示信息(message)。

var err1 = new Error('出錯了!');
var err2 = new RangeError('出錯了,變量超出有效範圍!');
var err3 = new TypeError('出錯了,變量類型無效!');

err1.message // "出錯了!"
err2.message // "出錯了,變量超出有效範圍!"
err3.message // "出錯了,變量類型無效!"

自定義錯誤

function UserError(message) {
  this.message = message || '默認信息';
  this.name = 'UserError';
}

UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
new UserError('這是自定義的錯誤!');

throw 語句

(1)throw語句的做用是手動中斷程序執行,拋出一個錯誤。

if (x <= 0) {
  throw new Error('x 必須爲正數');
}
// Uncaught ReferenceError: x is not defined

(2)throw也能夠拋出自定義錯誤。

function UserError(message) {
  this.message = message || '默認信息';
  this.name = 'UserError';
}

throw new UserError('出錯了!');
// Uncaught UserError {message: "出錯了!", name: "UserError"}

(3)throw能夠拋出任何類型的值。也就是說,它的參數能夠是任何值。

// 拋出一個字符串
throw 'Error!';
// Uncaught Error!

// 拋出一個數值
throw 42;
// Uncaught 42

// 拋出一個布爾值
throw true;
// Uncaught true

// 拋出一個對象
throw {
  toString: function () {
    return 'Error!';
  }
};
// Uncaught {toString: ƒ}

try...catch 結構

(1)avaScript 提供了try...catch結構,容許對錯誤進行處理,選擇是否往下執行。

try {
  throw new Error('出錯了!');
} catch (e) {
  console.log(e.name + ": " + e.message);
  console.log(e.stack);
}
// Error: 出錯了!
//   at <anonymous>:3:9
//   ...

(2)catch代碼塊捕獲錯誤以後,程序不會中斷,會按照正常流程繼續執行下去。

try {
  throw "出錯了";
} catch (e) {
  console.log(111);
}
console.log(222);
// 111
// 222

(3)catch代碼塊之中,還能夠再拋出錯誤,甚至使用嵌套的try...catch結構。

var n = 100;

try {
  throw n;
} catch (e) {
  if (e <= 50) {
    // ...
  } else {
    throw e;
  }
}
// Uncaught 100

(4)爲了捕捉不一樣類型的錯誤,catch代碼塊之中能夠加入判斷語句。

try {
  foo.bar();
} catch (e) {
  if (e instanceof EvalError) {
    console.log(e.name + ": " + e.message);
  } else if (e instanceof RangeError) {
    console.log(e.name + ": " + e.message);
  }
  // ...
}

finally 代碼塊

(1)表示不論是否出現錯誤,都必需在最後運行的語句。

function cleansUp() {
  try {
    throw new Error('出錯了……');
    console.log('此行不會執行');
  } finally {
    console.log('完成清理工做');
  }
}

cleansUp()
// 完成清理工做
// Uncaught Error: 出錯了……
//    at cleansUp (<anonymous>:3:11)
//    at <anonymous>:10:1

(2)return語句的執行是排在finally代碼以前,只是等finally代碼執行完畢後才返回。

var count = 0;
function countUp() {
  try {
    return count;
  } finally {
    count++;
  }
}

countUp()
// 0
count
// 1

(3)catch代碼塊結束執行以前,會先執行finally代碼塊。

function f() {
  try {
    console.log(0);
    throw 'bug';
  } catch(e) {
    console.log(1);
    return true; // 這句本來會延遲到 finally 代碼塊結束再執行
    console.log(2); // 不會運行
  } finally {
    console.log(3);
    return false; // 這句會覆蓋掉前面那句 return
    console.log(4); // 不會運行
  }

  console.log(5); // 不會運行
}

var result = f();
// 0
// 1
// 3

result
// false

(4)進入catch代碼塊以後,一遇到throw語句,就會去執行finally代碼塊,其中有return false語句,所以就直接返回了,再也不會回去執行catch代碼塊剩下的部分了。

function f() {
  try {
    throw '出錯了!';
  } catch(e) {
    console.log('捕捉到內部錯誤');
    throw e; // 這句本來會等到finally結束再執行
  } finally {
    return false; // 直接返回
  }
}

try {
  f();
} catch(e) {
  // 此處不會執行
  console.log('caught outer "bogus"');
}

(5)try代碼塊內部,還能夠再使用try代碼塊。

try {
  try {
    consle.log('Hello world!'); // 報錯
  }
  finally {
    console.log('Finally');
  }
  console.log('Will I run?');
} catch(error) {
  console.error(error.message);
}
// Finally
// consle is not defined

編程風格

(1)縮進,可以使用空格,也可使用TAB
(2)老是使用大括號表示區塊。
(3)JavaScript 會自動添加句末的分號,致使一些難以察覺的錯誤。

return
{
  key: value
};

// 至關於
return;
{
  key: value
};

(4)圓括號

  1. 表示函數調用時,函數名與左括號之間沒有空格。
  2. 表示函數定義時,函數名與左括號之間沒有空格。
  3. 其餘狀況時,前面位置的語法元素與左括號之間,都有一個空格。

(5)行尾不使用分號的狀況

  • for 和 while 循環
  • 分支語句:if,switch,try
  • 函數的聲明語句

(6)do...while循環是有分號的。
(7)函數表達式仍然要使用分號。

var f = function f() {
};

(8)若是沒有使用分號,大多數狀況下,JavaScript 會自動添加。

var a = 1
// 等同於
var a = 1;

(9)若是下一行的開始能夠與本行的結尾連在一塊兒解釋,JavaScript 就不會自動添加分號。

// 等同於 var a = 3
var
a
=
3

// 等同於 'abc'.length
'abc'
.length

// 等同於 return a + b;
return a +
b;

// 等同於 obj.foo(arg1, arg2);
obj.foo(arg1,
arg2);

// 等同於 3 * 2 + 10 * (27 / 6)
3 * 2
+
10 * (27 / 6)
x = y
(function () {
  // ...
})();

// 等同於
x = y(function () {...})();
// 引擎解釋爲 c(d+e)
var a = b + c
(d+e).toString();

// 引擎解釋爲 a = b/hi/g.exec(c).map(d)
// 正則表達式的斜槓,會看成除法運算符
a = b
/hi/g.exec(c).map(d);

// 解釋爲'b'['red', 'green'],
// 即把字符串看成一個數組,按索引取值
var a = 'b'
['red', 'green'].forEach(function (c) {
  console.log(c);
})

// 解釋爲 function (x) { return x }(a++)
// 即調用匿名函數,結果f等於0
var a = 0;
var f = function (x) { return x }
(a++)

(10)只有下一行的開始與本行的結尾,沒法放在一塊兒解釋,JavaScript 引擎纔會自動添加分號。

if (a < 0) a = 0
console.log(a)

// 等同於下面的代碼,
// 由於 0console 沒有意義
if (a < 0) a = 0;
console.log(a)

(11)若是一行的起首是「自增」(++)或「自減」(--)運算符,則它們的前面會自動添加分號。

a = b = c = 1

a
++
b
--
c

console.log(a, b, c)
// 1 2 0
// 等同於
a = b = c = 1;
a;
++b;
--c;

(12)若是continuebreakreturnthrow這四個語句後面,直接跟換行符,則會自動添加分號。
(13)因爲解釋引擎自動添加分號的行爲難以預測,所以編寫代碼的時候不該該省略行尾的分號。
(14)有的代碼庫在第一行語句開始前,會加上一個分號。能夠避免與其餘腳本合併時,排在前面的腳本最後一行語句沒有分號,致使運行出錯的問題。

;var a = 1;
// ...

(15)建議避免使用全局變量。若是不得不使用,能夠考慮用大寫字母表示變量名,這樣更容易看出這是全局變量,好比UPPER_CASE
(16)JavaScript 會自動將變量聲明「提高」(hoist)到代碼塊(block)的頭部。

if (!x) {
  var x = {};
}

// 等同於
var x;
if (!x) {
  x = {};
}

(17)全部函數都應該在使用以前定義。函數內部的變量聲明,都應該放在函數的頭部。
(18)with能夠減小代碼的書寫,可是會形成混淆。

with (o) {
 foo = bar;
}

上面的代碼,能夠有四種運行結果:

o.foo = bar;
o.foo = o.bar;
foo = bar;
foo = o.bar;

這四種結果均可能發生,取決於不一樣的變量是否有定義。所以,不要使用with語句。
(19)相等運算符會自動轉換變量類型,形成不少意想不到的狀況。

0 == ''// true
1 == true // true
2 == true // false
0 == '0' // true
false == 'false' // false
false == '0' // true
' trn ' == 0 // true

所以,建議不要使用相等運算符(==),只使用嚴格相等運算符(===)。
(20)建議不要將不一樣目的的語句,合併成一行。
(21)全部的++運算符均可以用+= 1代替。

++x
// 等同於
x += 1;

(22)switch...case不使用大括號,不利於代碼形式的統一。此外,這種結構相似於goto語句,容易形成程序流程的混亂,使得代碼結構混亂不堪,不符合面向對象編程的原則。建議switch...case結構能夠用對象結構代替。

function doAction(action) {
  switch (action) {
    case 'hack':
      return 'hack';
    case 'slash':
      return 'slash';
    case 'run':
      return 'run';
    default:
      throw new Error('Invalid action.');
  }
}

上面的代碼建議改寫成對象結構。

function doAction(action) {
  var actions = {
    'hack': function () {
      return 'hack';
    },
    'slash': function () {
      return 'slash';
    },
    'run': function () {
      return 'run';
    }
  };

  if (typeof actions[action] !== 'function') {
    throw new Error('Invalid action.');
  }

  return actions[action]();
}
相關文章
相關標籤/搜索