类型和语法
类型
内置类型
- null
- undefined
- boolean
- number
- string
- object
- symbol
typeof undefined === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true
// object 子类型
typeof function a () {} === "function" // true
typeof [1, 2, 3] === "object" // true
// ES6中新加入的类型
typeof Symbol() === "symbol"; // true
// 以上六中均有同名字符串与之对应
// null 特殊
typeof null === "object" // true
值和类型
变量是没有类型的, 使用typeof 对变量操作时是针对变量所持有值的操作。
undefined&undeclared
undefined 是值的一种,undeclared则表示变量还没有被声明过。
var a
a // undefined
b // Uncaught ReferenceError: b is not defined
typeof a // undefined
typeof b // undefined
typeof 防御机制
尤其是多个脚本文件共享全局命名空间加载变量
-
判断未申明变量
// 会报错 // Uncaught ReferenceError: DEBUG is not defined if (DEBUG) { console.log("DEBUG is starting") }- typeof
if (typeof DEBUG !== "undefined") { console.log("DEBUG is starting") }- window
if (window.DEBUG) { console.log("DEBUG is starting") }
值
数组
tips
- delete 可以删除数组,但并不会改变length
- 创建"稀疏数组"(含有空白或空缺单元的数组),空缺单元的undefined与显示赋值的undefined不同
- 数组通过数字索引,索引也是对象,所以可以包含字符串键值和属性。如果字符串键值能够被强制转换为十进制数组的话,也会被当作数字索引来处理。
var a = []
a[0] =1
a['footer'] = 2
console.log(a.length) // 1
console.log(a['footer']) // 2
console.log(a.footer) // 2
a['13'] = 14
console.log(a.length) // 14
类数组
一组可以挺过数字索引的值如: DOM查询操作返回的DOM元素列表、通过arguments对象将函数的参数当作列表访问
- 类数组转换为数组
function foo () {
console.log(arguments) // {"0":"bar","1":"baz","2":"bam"}
let arr = Array.prototype.slice.call(arguments)
console.log(arr) // ["bar","baz","bam"]
}
let obj = {}
foo.apply(obj, ['bar','baz', 'bam'])
- ES6 Array.from
字符串
字符串在某种程度上可以说是类数组,都有length、indexof、concat。但是JS中字符串是不可变的,数组是可变的。因此字符串无法“借用”数组的可变成员函数如:reverse
数字
整数
"整数"就是没有小数的十进制数
console.log(42.0 === 42) // true
Number 方法
var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"
// 无效语法:
42.toFixed( 3 ); // SyntaxError 42.被视为一部分
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
支持格式
二进制、八进制、十六进制
0xf3; // 243的十六进制
0Xf3; // 同上
0o363; // 243的八进制
0O363; // 同上
0b11110011; // 243的二进制
0B11110011; // 同上
0.1+0.2 === 0.3
JS的"机器精度":2^-52,在ES6中该值定义在Number.EPSILON中,可以拿来使用,可以用来比较两个数字是否相等。
function numberCloseToEqual (a, b) {
return Math.abs(a - b) < Number.EPSILON
}
let a = 0.1 + 0.2
let b = 0.3
console.log(numberCloseToEqual(a, b)) // true
console.log(numberCloseToEqual(0.000000001, 0.000000000002)) // false
整数的安全范围
数字呈现方式决定了“整数”的安全范围需要远远小于Number.Max_value(1.7976931348623157e+308)。能够被"安全"呈现的最大整数是2^53-1, 在ES6中被定义为Macth.MAX_SAFE_INTEGER,相对应的最小整数位Number.MIN_SAFE_INTEGER。
整数检测
-
ES6中的Number.isInteger()
function print(val) { console.log(val) } print(Number.isInteger( 42 )) // true print(Number.isInteger( 42.000 )) // true print(Number.isInteger( 42.3 )) // false为ES6之前的版本实现Number.isInteger()
if (!Number.isInteger) { Number.isInteger = function (number) { return typeof number === 'number' && number % 1 == 0 } } -
检查是否为安全整数 Number.isSafeInteger(...),比Number.isInteger多一个不大于最大安全值的判断
32位有符号整数
所有的按位操作符都只适用于32位数字,这种操作下数字的安全范围就变成了Math.pow(-2, 31)~Math.pow(2, 31);如:|、&、<<、>>
按位操作符详细说明
特殊数值
不是值的值
undefined 类型只有一个值,即 undefined。null 类型也只有一个 值,即 null。它们的名称既是类型也是值。
undefined 和 null 常被用来表示“空的”值或“不是值”的值。二者之间有 一些细微的差别。例如:
- null 指空值(empty value)
- undefined 指没有值(missing value) 或者:
- undefined 指从未赋值
- null 指曾赋过值,但是目前没有值
null 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋 值。然而 undefined 却是一个标识符,可以被当作变量来使用和赋值。
void
通常情况下可以通过void 0来获取undefined
特殊数字
-
NaN 可以理解为'无效数值'、'失败数值'。
typeof NaN === 'number'为true;NaN == NaN为false.- 可以使用全局isNaN()检查是否为NaN,但事实上检查结果并不准确,实际上isNaN()是检查参数是否不是NaN, 也不是数字。
window.isNaN(NaN) // true window.isNaN('foo') // true window.isNaN(2) // false- ES6可以使用Number.isNaN()
Number.isNaN(NaN) // true Number.isNaN('foo') // false Number.isNaN(2) // false内置实现方法
if (!Number.isNaN) { Number.isNaN = function (n) { return n !== n } } -
无穷数
Number.POSITIVE_INFINITYNumber.NEGATIVE_INFINITY
console.log(1 / 0, -1 / 0) // Infinity,-Infinity
console.log(Infinity / Infinity) // NaN
- 零值
简单标量基本类型值(字符串和数字等)通过值复制来赋值 / 传递,而复合 值(对象等)通过引用复制来赋值 / 传递。JavaScript 中的引用和其他语言 中的引用 / 指针不同,它们不能指向别的变量 / 引用,只能指向值。
原生函数
- 常用原生函数: String() Number() Boolean() Array() Object() Function() RegExp() Date() Error() Symbol()
let a = new String('abc') // 创建字符串'abc'的封装对象 而非基本类型值'abc'
console.log(a)
// String {"abc"}
0: "a"
1: "b"
2: "c"
length: 3
__proto__: String
[[PrimitiveValue]]: "abc"
console.log(typeof a) // object
console.log(a instanceof String) // true
console.log(Object.prototype.toString.call(a)) // [object String]
内部属性[[class]]
所有typeof返回值为"object"的对象(如数组)都包含一个内部属性[[Class]],但是这个属性无法直接访问,一般通过Object.prototype.toString()来查看。
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.prototype.toString.call( "abc" ); // "[object String]"
Object.prototype.toString.call( 42 ); // "[object Number]"
Object.prototype.toString.call( true ); // "[object Boolean]"
数组内部[[Class]]属性值是Array, 正则表达式的值是RegExp。
虽然 Null() 和 Undefined() 这样的原生构造函数并不存在,但是内部 [[Class]] 属性值仍然是 "Null" 和 "Undefined"。
封装对象包装
JS 会自动为基本类型值包装一个封装对象
封装对象释疑
console.log(new Boolean(false))
if (new Boolean(false)) { // 一定会执行
console.log(1)
}
拆封
如果想要得到封装对象中的基本类型值,可以使用 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
隐式拆分(强制类型转换):
var a = new String( "abc" );
var b = a + ""; // b的值为"abc"
typeof a; // "object"
typeof b; // "string"
强制类型转换
可以这样来区分:类型转换发生在静态类型语言的编译阶段,而强制类 型转换则发生在动态类型语言的运行时(runtime)。
抽象值操作
ToString
-
对普通对象来说,除非自行定义,否则 toString() (Object.prototype.toString())返回内部属性 [[Class]] 的值 ,如 "[object Object]"。 如果对象有自己的 toString() 方法,字符串化时 就会调用该方法并使用其返回值。
-
数组调用默认toString()经过了重新定义,将所有单元字符串化以后再用","连接起来,如:
var a = [1, 2, 3]
console.log(a) // "1, 2, 3"
Tips: JSON 字符串化
- JSON.stringify() 与 ToString 相同点
- 字符串、数字、布尔值和 null 的 JSON.stringify(..) 规则与 ToString 基本相同。
- 如果传递给 JSON.stringify(..) 的对象中定义了 toJSON() 方 法,那么该方法会在字符串化前调用,以便将对象转换为安全的 JSON 值。
所有安全的 JSON 值(JSON-safe)都可以使用 JSON.stringify(..) 字 符串化。安全的 JSON 值是指能够呈现为有效 JSON 格式的值。
- 不安全JSON值
undefined、 function、symbol(ES6+)和包含循环引用(对象之间相互引用,形成 一个无限循环)的对象。JSON.stringify(..) 在对象中遇到 undefined、function 和 symbol 时会自动将其忽略,在数组中则会返回 null(以保证单元位置不 变)。
- 对象中定义了toJSON方法,JSON 字符串化时会首先调用该方 法,然后用它的返回值来进行序列化。
ToNumber
处理失败时返回NaN,同时针对十六进制数据按照十进制处理。
二进制: 0b/0B; 八进制:0o/0O; 十六进制: 0x/0X
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
let num = '0x123'
console.log(Number(num)) // 291
ToBoolean
- 假值
- undefined
- null
- false
- +0、-0和NaN
- ''
ToPrimitive
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive(参见 ES5 规范 9.1 节)会首先(通过内部操作 DefaultValue,参见 ES5 规范 8.12.8 节)检查该值是否有 valueOf() 方法。如果有并且返回基本类型 值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回 值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
显示强制类型转换
- Number、String
- toString
- 一元操作符+ 、-
可常用操作
- 日期显示转换为数字
var timestamp = +new Date() var timestamp = new Date().getTime(); // var timestamp = (new Date()).getTime(); // var timestamp = (new Date).getTime(); // Best Es5 var timestamp = Date.now() - ~运算符
上面提到过二进制运算符,
也为其中一个。遇到时首先将值强制转换为32位数字,然后执行字位操作“非”(对每个字符进行翻转)
~X 大致等同于-(x+1)
~42 // -(42+1) ===> -43
接下来先介绍下 indexOf 的返回值,失败为-1。在C语言中-1为"哨位值,被赋予了特殊的意义, -1代表执行失败,大于-1为成功。在JS中遵循了这一惯例。
优化indexOf判断
let a = "Hello World";
console.log(~a.indexOf( "lo" )); // -4 <-- 真值!
console.log(~a.indexOf( "ol" )); // 0 <-- 假值!
- 学位截除 ~~ 因为进行了两次反转,所以相当于执行ToInt32后的结果
~~ X 相当于能将值截取为一个32位整数
显示解析数字字符串
解析数字字符串是允许字符串中含有非数字字符,解析从左到右,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回NaN。
a = "42"
b = "42px"
console.log(Number(a), Number(b)) // 42 NaN
console.log(parseInt(a), parseInt(b)) // 42 42
parseInt(..) 先将参数强制类型转换为字符串再进行解析
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
显示转换为Boolean
Boolean(X) 或者 !!X
隐私类型转换
Tips
当一个操作数为对象时,首先会对其调用ToPrimitive操作,其次会再调用[[DefaultValue]],以数字作为上下文, 详细解释见ToPrimitive。
ToNumber抽象操作处理对象方式类似。例如:数组的valueOf()操作无法的得到简单基本类型值,就会转而调用toString()
字符串和数字之间的隐式强制类型转换
- 操作符的其中一个操作数是字符串或者可以得到字符串,则执行字符串拼接;否则执行数字加法。
隐式强制类型转换布尔值
- if (..) 语句中的条件判断表达式。
- for ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)。
- while (..) 和 do..while(..) 循环中的条件判断表达式。
- ? : 中的条件判断表达式。
- 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判 断表达式)。
|| 和 &&
|| 和 && 并不是真正意义上的“逻辑运算符”,确切的更贴近与“选择器操作符”或“操作数选择器运算符”。因为他们返回的并不是布尔值,返回值是两个操作数中的一个。
var a = 42;
var b = "abc";
var c = null;
console.log(a || b, a && b, c || b, c && b) // 42,abc,abc,null
对于 || 来说,如果条件判断结果为 true 就返回第一个操作数(a 和 c) 的值,如果为 false 就返回第二个操作数(b)的值。
&& 则相反,如果条件判断结果为 true 就返回第二个操作数(b)的值, 如果为 false 就返回第一个操作数(a 和 c)的值。
符号的强制类型转换
ES6 允许从符号到字符串的显式强制类型转换,然而隐式强制类型 转换会产生错误。
符号不能够被强制类型转换为数字(显式和隐式都会产生错误),但可以 被强制类型转换为布尔值(显式和隐式结果都是 true)。
=== 和 ==
“== 允许在相等比较中进行强制类型转换,而 === 不允 许。”
性能
=== 和 == 都会检查操作数的类型,== 类型不同时会进行强制类型转换,性能差别微秒级可忽略。实际上比较两个对象时,两者工作原理一样;两个对象指向同一个值时即视为相等,并不发生强制类型转换。
相等比较
-
数字和字符串
a = 42 b = "42" console.log(a === b) // false console.log(a == b) // true-
a==b 具体是如何转换?
ES5规范
- 如果 a 是数字,b是字符串,则返回 a == ToNumber(b)
- 如果 a 是字符串, b是数字, 则返回 ToNumber(a) = b
-
-
其他类型和布尔类型
b = "42" console.log(b == true) // falseES5规范
- 如果a 是Boolean, 则返回 ToNumber(a) == b
- 如果b 是Boolean,则返回 a == ToNumber(b)
-
null 和 undefined
在 == 中 null 和 undefined 相等(它们也与其自身相等),除此之外其 他值都不存在这种情况。
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 -
对象和非对象
ES5规定: == 操作符两边是对象的一侧进行 ToPrimitive操作(仅限于字符、数组),布尔类型会先被强制转换为数字
a = 42
b = [42]
a == b // true
c = "obc"
d = Object(c)
c == d
- 其他
-
更改内置原生原型
Number.prototype.valueOf = function () { return 3 } console.log(new Number(2) == 3) // true -
面试题 如何实现
a == 2 && a == 3// 使用valueOf let i = 2 Number.prototype.valueOf = function () { return i++ } var a = new Nummber(2) if (a == 2 && a == 3) { // true } // 使用defineProperty劫持 let obj = { a: 1 } Object.defineProperty(obj, 'a', { get () { return i++ } }) -
常见 == 判断
"0" == null; // false "0" == undefined; // false "0" == false; // true -- 晕! "0" == NaN; // false "0" == 0; // true "0" == ""; // false false == null; // false false == undefined; // false false == NaN; // false false == 0; // true -- 晕! false == ""; // true -- 晕! false == []; // true -- 晕! false == {}; // false "" == null; // false "" == undefined; // false "" == NaN; // false "" == 0; // true -- 晕! "" == []; // true -- 晕! "" == {}; // false 0 == null; // false 0 == undefined; // false 0 == NaN; // false 0 == []; // true -- 晕! -
极端情况
2 == [2] // true [] == ![] // true "" == [null] // true 0 == "\n" // true
-
抽象关系比较
a <= b 会被处理为 !(b < a) 来判断
- 比较双方均为字符串:按照字母顺序进行比较
- 其他:首先调用ToPrimivite,如果出现非字符串,就根据ToNumber强制类型转换为数字进行比较
语法
语句和表达式
语句相当于能完整表达意思的句子,并有一组词或者N个表达式组成,它们由运算符连接。其中表达式相当于短语,表达式可以由更小的表达式组成,可以有完整的意思也可以没有。
语句的结果值
语句均有一个结果值,其中规定定义 var 的结果值是 undefined。
语法中不允许我们将语句结果只赋值给另一个变量, 如:
var a, b
a = if (true) {
b = 4+38
} // if 代码块在控制台输出42,但是赋值给另一个变量是不被允许的
但是在实际过程中我们可以通过其他方式得到语句的执行结果:eval() 强烈不推荐、ES7中的do{...}表达式执行代码块
表达式副作用
可以赋值语句的结果值合并判断条件
上下文规则
- {...}
由此引出JS一个不太为认知的特性(不建议使用),标签语句
带标签循环跳转更大的用处在于:和break一起使用实现从内层跳转到外层foo: for(let i = 0; i < 4; i++) { // foo 标签 for (let j =0; j < 4; j++) { if (i == j) { console.log('跳过的', i, j) // 跳转到foo 的下一个循环 i+1 continue foo } if ((i * j) % 2 == 1) { console.log('跳过奇数', i, j) // 跳过本次循环,j+1 continue } console.log('未跳过' ,i, j) } }foo: for(let i = 0; i < 4; i++) { for (let j =0; j < 4; j++) { if ((i * j) % 2 == 1) { console.log('跳过奇数', i, j) break foo // 跳出标签foo 所在的循环和代码块 } console.log('未跳过' ,i, j) } }
Tips
[] + {} // "[object Object]"
{} + [] // 0
第一行代码中,{} 出现在 + 运算符表达式中,因此它被当作一个值(空对 象)来处理。第 4 章讲过 [] 会被强制类型转换为 "",而 {} 会被强制类型 转换为 "[object Object]"。
但在第二行代码中,{} 被当作一个独立的空代码块(不执行任何操作)。 代码块结尾不需要分号,所以这里不存在语法上的问题。最后 + [] 将 [] 显式强制类型转换(参见第 4 章)为 0。
冷知识
- 事实上JS中是没有 else if的,但是 if 和 else 在只包含单条语句的时候可以省略代码块{},实际我们经常用到的else if如下:
if (a) {
} else {
if (b) {} else {}
}