类型
1、JavaScript 有七种内置类 型:null 、undefined 、boolean 、number 、string 、object 和 symbol ,可以使用 typeof 运算符来查看。
2、变量没有类型,但它们持有的值有类型。类型定义了值的行为特征。
3、很多开发人员将 undefined 和 undeclared 混为一谈,但在 JavaScript 中它们是两码事。undefined 是值的一种。undeclared 则表 示变量还没有被声明过。 遗憾的是,JavaScript 却将它们混为一谈,在我们试图访问 “undeclared” 变量时这样报错:ReferenceError: a is not defined, 并且 typeof 对 undefined 和 undeclared 变量都返回 “undefined” 。
然而,通过 typeof 的安全防范机制(阻止报错)来检查 undeclared 变量,有时是个不错的办法。
值
1、JavaScript 中的数组是通过数字索引的一组任意类型的值。字符串和数组类似,但是它们的行为特征不同,在将字符作为数组
来处理时需要特别小心。JavaScript 中的数字包括“整数”和“浮点型”。
2、基本类型中定义了几个特殊的值。
null 类型只有一个值 null ,undefined 类型也只有一个值 undefined 。所有变量在赋值之前默认值都是 undefined 。void 运算符返回 undefined 。
数字类型有几个特殊值,包括 NaN (意指“not a number”,更确切地说是“invalid number”)、+Infinity 、-Infinity 和 -0 。
3、简单标量基本类型值(字符串和数字等)通过值复制来赋值 / 传递,而复合值(对象等)通过引用复制来赋值 / 传递。 JavaScript 中的引用和其他语言中的引用 / 指针不同,它们不能指向别的变量 / 引用,只能指向值
原生函数
1、JavaScript 为基本数据类型值提供了封装对象,称为原生函数(如 String 、Number 、Boolean 等)。它们为基本数据类 型值提供了该子类型所特有的方法和属性(如:String#trim() 和 Array#concat(..) )。
- 内部属性 [[Class]]
Object.prototype.toString.call( [1,2,3] );
// "[object Array]"
Object.prototype.toString.call( /regex-literal/i );
// "[object RegExp]"
Object.prototype.toString.call( null );
// "[object Null]"
Object.prototype.toString.call( undefined );
// "[object Undefined]"
- 如果想要自行封装基本类型值,可以使用 Object(..) 函数(不带 new 关键字):
var a = "abc";
var b=newString(a);
var c=Object(a);
typeof a; // "string"
typeof b; // "object"
typeof c; // "object"
b instanceof String; // true
c instanceof String; // true
Object.prototype.toString.call( b ); // "[object String]" Object.prototype.toString.call( c ); // "[object String]"
- 拆封:如果想要得到封装对象中的基本类型值,可以使用 valueOf() 函数:
var a = new String( "abc" );
var b = new Number( 42 );
var c = new Boolean( true );
a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true
- 原生函数作为构造函数
vara=newArray(3);
var b = [ undefined, undefined, undefined ];
var c=[];
c.length = 3;
a; b; c;
a.join( "-" ); // "--" b.join( "-" ); // "--"
a.map(function(v,i){ return i; }); // [ undefined x 3 ] b.map(function(v,i){ return i; }); // [ 0, 1, 2 ]
- symbol
var mysym = Symbol( "my own symbol" );
mysym; // Symbol(my own symbol)
mysym.toString(); //"Symbol(myownsymbol)"
typeof mysym; // "symbol"
var a={};
a[mysym] = "foobar";
Object.getOwnPropertySymbols( a );
// [ Symbol(my own symbol) ]
虽然符号实际上并非私有属性(通过 Object.getOwnPropertySymbols(..) 便可以公开获得对象中的所有符号),但 它却主要用于私有或特殊属性。很多开发人员喜欢用它来替代有下划线(_ )前缀的属性,而下划线前缀通常用于命名私有或 特殊属性。
- 原生原型
原生构造函数有自己的 .prototype 对象,如 Array.prototype 、String.prototype 等。 这些对象包含其对应子类型所特有的行为特征。
String.prototype.XYZ 简写为 String#XYZ ,对其他 .prototypes 也同样如此。 String#indexOf(..)
在字符串中找到指定子字符串的位置。
String#charAt(..)
获得字符串指定位置上的字符。
String#substr(..) 、String#substring(..) 和 String#slice(..) 获得字符串的指定部分。
String#toUpperCase() 和 String#toLowerCase() 将字符串转换为大写或小写。
String#trim()
去掉字符串前后的空格,返回新的字符串。
以上方法并不改变原字符串的值,而是返回一个新字符串。
typeof Function.prototype; // "function"
Function.prototype();// 空函数!
RegExp.prototype.toString(); // "/(?:)/"——空正则表达式
"abc".match( RegExp.prototype ); // [""]
更糟糕的是,我们甚至可以修改它们(而不仅仅是添加属性):
Array.isArray(Array.prototype); //true
Array.prototype.push( 1, 2, 3 ); // 3
Array.prototype; // [1,2,3]
// 需要将Array.prototype设置回空,否则会导致问题!
Array.prototype.length = 0;
这里,Function.prototype 是一个函数,RegExp.prototype 是一个正则表达式,而 Array.prototype 是一个数 组。是不是很有意思?
- 将原型作为默认值
Function.prototype 是一个空函数,RegExp.prototype 是一个“空”的正则表达式(无任何匹配),而 Array.prototype 是一个空数组。对未赋值的变量来说,它们是很好的默认值
function isThisCool(vals,fn,rx) {
vals = vals || Array.prototype;
rx = rx || RegExp.prototype;
return rx.test(
vals.map( fn ).join( "" )
);
}
isThisCool(); // true
isThisCool(
["a","b","c"],
function(v){ return v.toUpperCase(); }, /D/
);// false
2、对于简单标量基本类型值,比如 “abc” ,如果要访问它的 length 属性或 String.prototype 方法,JavaScript 引擎会自动对该值进行封装(即用相应类型的封装对象来包装它)来实现对这些属性和方法的访问。
强制类型转换
一、值类型转换
- 显示隐式示例
var a=42;
var b = a + ""; // 隐式强制类型转换
var c = String( a ); // 显式强制类型转换
- toString() 可以被显式调用,或者在需要字符串化时自动调用。所有安全的 JSON 值 (JSON-safe)都可以使用 JSON.stringify(..) 字符串化。安全的 JSON 值是指能够呈现为有效 JSON 格式的值。
JSON.stringify(undefined); // undefined
JSON.stringify(function(){}); //undefined
JSON.stringify([1, undefined, function () {}, 4]);
// "[1,null,null,4]"
JSON.stringify({
a: 2,
b: function () {}
});// "{"a":2}"
- toNumber
var a = {
valueOf: function () {
return "42";
}
};
var b = {
toString: function () {
return "42";
}
};
var c = [4, 2];
c.toString = function () {
return this.join(""); // "42" };
Number(a); // 42
Number(b); // 42
Number(c); // 42
Number(""); // 0
Number([]); // 0
Number(["abc"]); // NaN
- ToBoolean
以下这些是假值:
undefined
null
false
+0、-0和NaN “”
假值的布尔强制类型转换结果为 false 。
二、 显式强制类型转换
- 显式解析数字字符串
var a = "42"
var b = "42px";
Number(a); // 42 parseInt( a ); // 42
Number(b); // NaN parseInt( b ); // 42
- 解析非字符串
parseInt( 1/0, 19 ); // 18
parseInt(1/0, 19) 实际上是 parseInt(“Infinity”, 19) 。第一个字符是 “I” ,以 19 为基数时值为 18 。第二个 字符 “n” 不是一个有效的数字字符,解析到此为止,和 “42px” 中的 “p” 一样。 最后的结果是 18 ,而非 Infinity 或者报错。所以理解其中的工作原理对于我们学习 JavaScript 是非常重要的
parseInt(0.000008);//0 ("0"来自于"0.000008")
parseInt(0.0000008); // 8 ("8" 来自于 "8e-7")
parseInt(false, 16); // 250 ("fa" 来自于 "false")
parseInt(parseInt, 16); //15("f"来自于"function..")
parseInt("0x10"); // 16
parseInt("103", 2); // 2
- 显式转换为布尔值
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
Boolean(a); // true
Boolean(b); // true
Boolean(c); // true
Boolean(d); // false
Boolean(e); // false
Boolean(f); // false
Boolean(g); // false
三、隐式强制类型转换
隐式强制类型转换 指的是那些隐蔽的强制类型转换,副作用也不是很明显。换句话说,你自己觉得不够明显的强制类型转换 都可以算作隐式强制类型转换。
- 字符串和数字之间的隐式强制类型转换
var a = [1,2];
var b = [3,4];
a + b; // "1,23,4"
原因:+ 将进行拼接操作。如果其中一 个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用
var a = {
valueOf: function () {
return 42;
},
toString: function () {
return 4;
}
};
a + ""; // "42" String( a ); // "4"
var a=[3];
var b=[1];
a-b;//2
- 布尔值到数字的隐式强制类型转换
function onlyOne(a, b, c) {
return !!((a && !b && !c) ||
(!a && b && !c) || (!a && !b && c));
}
var a = true;
var b = false;
onlyOne(a, b, b); // true onlyOne( b, a, b ); // true
onlyOne(a, b, a); // false
- 隐式强制类型转换为布尔值
下面的情况会发生布尔值隐式强制类型转换。
(1)if (..)语句中的条件判断表达式。
(3)while (..)和do..while(..)循环中的条件判断表达式。
(4)? :中的条件判断表达式。
(5) 逻辑运算符 || (逻辑或)和 && (逻辑与)左边的操作数(作为条件判断表达式)。
以上情况中,非布尔值会被隐式强制类型转换为布尔值,遵循前面介绍过的ToBoolean 抽象操作规则
var a = 42;
var b = "abc";
var c;
var d = null;
if (a) {
console.log("yep");
}
// yep
while (c) {
console.log("nope, never runs");
}
c = d ? a : b;
c;
if ((a && d) || c) {
console.log("yep");
}
// "abc"
// yep
|| 和 &&
a||b; // 大致相当于(roughly equivalent to): a?a:b;
a&&b; // 大致相当于(roughly equivalent to): a?b:a;符号的强制类型转换
var s1 = Symbol( "cool" );
String( s1 ); // "Symbol(cool)"
var s2 = Symbol( "not cool" );
s2 + ""; // TypeError
四、宽松相等和严格相等
- null undefined false “”
var a = null;
var b;
a == b;// true
a == null;// true
b == null;// true
a == false; // false
b == false; // false
a == ""; // false
b == ""; // false
a == 0; // false
b == 0; // false
var a=42;
var b=[42];
a==b;//true
[ 42 ] 首先调用 ToPromitive 抽象操作(参见 4.2 节),返回 “42” ,变成 “42” == 42 ,然后又变成 42 == 42 , 最后二者相等。
var a =null
var b=Object(a);// 和Object()一样
a==b;// false
var c=undefined;
var d=Object(c);// 和Object()一样
c==d;// false
var e=NaN;
var f=Object(e);// 和new Number( e )一样
e==f;// false
因为没有对应的封装对象,所以 null 和 undefined 不能够被封装(boxed),Object(null) 和 Object() 均返回一个 常规对象。
NaN 能够被封装为数字封装对象,但拆封之后 NaN == NaN 返回 false ,因为 NaN 不等于 NaN (参见第 2 章)
- 比较少见的情况
"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 晕! // false
"0" == NaN; // true
"0" == 0; // false
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // true -- 晕!
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // false
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; //true--晕!
"" == 0; // true -- 晕!
"" == []; // false
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; //true--晕!
0 == {}; // false
2 == [2]; // true
""==[null]; //true
[]==![] //true
0 == "\n"; // true
五、抽象关系比较
var a=["42"];
var b=["043"];
a<b; //false
var a=[4,2];
var b=[0,4,3];
a<b; //false
var a={b:42};
var b={b:43};
a<b; //false
a==b;//false
a>b; //false
a<=b;//true
a>=b;//true
因为根据规范a <= b被处理为b < a,然后将结果反转。因为b < a的结果是false,所以a <= b的结果是true
var a=[42];
var b = "043";
a < b; // false -- 字符串比较!
Number( a ) < Number( b ); // true -- 数字比较!
语法
语句和表达式在英语中都能找到类比——语句就像英语中的句子,而表达式就像短语。表达式可以是简单独立的,否则可能会 产生副作用
1、代码块
[] + {}; // "[object Object]"
{}+[];//0
第一行代码中,{} 出现在 + 运算符表达式中,因此它被当作一个值(空对象)来处理。第 4 章讲过 [] 会被强制类型转换为
“” ,而 {} 会被强制类型转换为 “[object Object]” 。但在第二行代码中,{} 被当作一个独立的空代码块(不执行任何操作)。代码块结尾不需要分号,所以这里不存在语法上的 问题。最后 + [] 将 [] 显式强制类型转换 (参见第 4 章)为 0 。
2、运算符的优先级
下表按从最高到最低的优先级列出JavaScript运算符。具有相同优先级的运算符按从左至右的顺序求值。
3、对 ES6 中的参数默认值而言,参数被省略或被赋值为 undefined 效果都一样,都是取该参数的默认值。然而某些情况下, 它们之间还是有区别的:
function foo(a = 42, b = a + 1) {
console.log(
arguments.length, a, b,
arguments[0], arguments[1]
);
}
foo(); // 0 42 43 undefined undefined
foo( 10 ); // 1 10 11 10 undefined
foo(10,undefined); //2101110undefined
foo(10, null); // 2 10 null 10 null
4、try..finally
function foo() {
try {
throw 42;
} finally {
console.log("Hello");
}
console.log("never runs");
}
console.log(foo());
// Hello
// Uncaught Exception: 42
function foo() {
try {
return 42;
} finally {
throw "Oops!";
}
console.log("never runs");
console.log(foo());
// Uncaught Exception: Oops!
finally 中的 return 会覆盖 try 和 catch 中 return 的返回值:
function foo() {
try {
return 42;
} finally {
// 没有返回语句,所以没有覆盖
}
}
function bar() {
try {
return 42;
} finally {
// 覆盖前面的 return 42 return;
}
}
function baz() {
try {
return 42;
} finally {
// 覆盖前面的 return 42 return "Hello";
}
}
foo(); // 42
bar(); // undefined
baz(); // Hello