JavaScript Notes
基础语法
-
标签label:通常与break和continue搭配使用
// 与break:跳出双层循环 top: for (var i = 0; i < 3; i++){ for (var j = 0; j < 3; j++){ if (i === 1 && j === 1) break top; console.log('i=' + i + ', j=' + j); } } // i=0, j=0 // i=0, j=1 // i=0, j=2 // i=1, j=0 // 与break:跳出代码块 foo: { console.log(1); break foo; console.log('本行不会输出'); } console.log(2); // 1 // 2 // 与continue:会跳过当前循环,直接进入下一轮外层循环 top: for (var i = 0; i < 3; i++){ for (var j = 0; j < 3; j++){ if (i === 1 && j === 1) continue top; console.log('i=' + i + ', j=' + j); } }
数据类型
-
七种:数值、字符串、布尔值、
null
、undefined
、object
、Symbol
-
typeof
运算符:typeof 123 // "number" typeof '123' // "string" typeof false // "boolean" typeof undefined // "undefined" typeof null // "object" function f() {} typeof f // "function" typeof window // "object" typeof {} // "object" typeof [] // "object"
typeof
可以用来检查一个没有声明的变量,而不报错。// 错误的写法 if (v) { // ... } // ReferenceError: v is not defined // 正确的写法 if (typeof v === "undefined") { // ... }
-
null
与undefined
都可以表示“没有”,含义非常相似:if (!undefined) { console.log('undefined is false'); } // undefined is false if (!null) { console.log('null is false'); } // null is false undefined == null // true
区别是这样的:
null
是一个表示“空”的对象,转为数值时为0
;undefined
是一个表示"此处无定义"的原始值,转为数值时为NaN
。Number(null) // 0 5 + null // 5 Number(undefined) // NaN 5 + undefined // NaN
-
布尔值转换规则是除了下面六个值被转为
false
,其他值都视为true
。注意:空数组([]
)和空对象({}
)对应的布尔值,都是true
。undefined
null
false
0
NaN
""
或''
(空字符串)
数值
-
JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,
1
与1.0
是相同的,是同一个数。1 === 1.0 // true
某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算。
浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即 -2^53 到 2^53,都可以精确表示。
-
JavaScript 能够表示的数值范围为2^1024 到 2^-1023(开区间)。超出范围发溢出,但是不同于c语言,正向溢出返回Infinity,负向溢出返回0。
Math.pow(2, 1024) // Infinity Math.pow(2, -1023) // 0
-
八进制:有前缀
0o
或00
的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。通常来说,有前导0的数值会被视为八进制,但是如果前导0后面有数字8
和9
,则该数值被视为十进制。0888 // 888 0777 // 511
-
特殊值:
-
+0
和-0
:唯一有区别的场合是,+0
或-0
当作分母,返回的值是不相等的。 -
NaN
:不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number。5 - 'x' // NaN Math.acos(2) // NaN Math.log(-1) // NaN Math.sqrt(-1) // NaN 0 / 0 // NaN
-
NaN
不等于任何值,包括它本身。 -
NaN
在布尔运算时被当作false
。 -
NaN
与任何数(包括它自己)的运算,得到的都是NaN
。
-
-
Infinity
:表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,得到Infinity
。// 场景一 Math.pow(2, 1024) // Infinity // 场景二 0 / 0 // NaN 1 / 0 // Infinity
-
Infinity
有正负之分,Infinity
表示正的无穷,-Infinity
表示负的无穷。Infinity === -Infinity // false 1 / -0 // -Infinity -1 / -0 // Infinity
-
Infinity
与NaN
比较,总是返回false
。 -
Infinity
的四则运算,符合无穷的数学计算规则。5 * Infinity // Infinity 5 - Infinity // -Infinity Infinity / 5 // Infinity 5 / Infinity // 0
0乘以
Infinity
,返回NaN
;0除以Infinity
,返回0
;Infinity
除以0,返回Infinity
。0 * Infinity // NaN 0 / Infinity // 0 Infinity / 0 // Infinity
Infinity
加上或乘以Infinity
,返回的还是Infinity
。Infinity
减去或除以Infinity
,得到NaN
。Infinity
与null
计算时,null
会转成0,等同于与0
的计算。Infinity
与undefined
计算,返回的都是NaN
。
-
-
-
与数值相关的全局方法:
-
parseInt():用于将字符串转为整数。
字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符(特殊地,头部的空格是直接去除),就不再进行下去,返回已经转好的部分。
parseInt('123') // 123 // 字符串头部有空格,空格会被自动去除 parseInt(' 81') // 81 // 参数不是字符串,则会先转为字符串再转换 parseInt(1.23) // 1 parseInt('12.34') // 12 parseInt('15e2') // 15 // 如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN。 parseInt('abc') // NaN parseInt('.3') // NaN parseInt('+1') // 1 parseInt('-2') // -2 // 如果字符串以0x或0X开头,parseInt会将其按照十六进制数解析。 parseInt('0x10') // 16 // 对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。 parseInt(1000000000000000000000.5) // 1 // 等同于 parseInt('1e+21') // 1 parseInt(0.0000008) // 8 // 等同于 parseInt('8e-7') // 8
parseInt
方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。parseInt('1000', 2) // 8 parseInt('1000', 6) // 216 parseInt('1000', 8) // 512 parseInt('10', 37) // NaN parseInt('10', 1) // NaN // 如果第二个参数是0、undefined和null,则直接忽略。 parseInt('10', 0) // 10 parseInt('10', null) // 10 parseInt('10', undefined) // 10 // 如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。 // 如果最高位无法转换,则直接返回NaN。 parseInt('1546', 2) // 1 parseInt('546', 2) // NaN // 如果parseInt的第一个参数不是字符串,会被先转为字符串。这会导致一些令人意外的结果。 parseInt(0x11, 36) // 43 parseInt(0x11, 2) // 1 // 等同于 parseInt('17', 36) parseInt('17', 2)
-
parseFloat
:用于将一个字符串转为浮点数。// 如果字符串符合科学计数法,则会进行相应的转换。 parseFloat('314e-2') // 3.14 parseFloat('0.0314E+2') // 3.14 // 如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分。 parseFloat('3.14more non-digit characters') // 3.14 // parseFloat方法会自动过滤字符串前导的空格。 parseFloat('\t\v\r12.34\n ') // 12.34 // 如果参数不是字符串,则会先转为字符串再转换。 parseFloat([1.23]) // 1.23 // 如果字符串的第一个字符不能转化为浮点数,则返回NaN。 parseFloat([]) // NaN parseFloat('FF2') // NaN parseFloat('') // NaN
-
isNaN
:用来判断一个值是否为NaN。但是,isNaN
只对数值有效,如果传入其他值,会被先转成数值(Number函数)。比如,传入字符串的时候,字符串会被先转成NaN
,所以最后返回true
,这一点要特别引起注意。也就是说,isNaN
为true
的值,有可能不是NaN
,而是一个字符串。isNaN(NaN) // true isNaN(123) // false isNaN('Hello') // true // 相当于 isNaN(Number('Hello')) // true isNaN(['xzy']) // true // 等同于 isNaN(Number(['xzy'])) // true // 对于空数组和只有一个数值成员的数组,isNaN返回false,原因是这些数组能被Number函数转成数值。 isNaN([]) // false isNaN([123]) // false isNaN(['123']) // false // 因此,使用isNaN之前,最好判断一下数据类型。 function myIsNaN(value) { return typeof value === 'number' && isNaN(value); }
判断
NaN
更可靠的方法是,利用NaN
为唯一不等于自身的值的这个特点,进行判断。function myIsNaN(value) { return value !== value; }
-
isFinite
:返回一个布尔值,表示某个值是否为正常的数值。isFinite(Infinity) // false isFinite(-Infinity) // false isFinite(NaN) // false isFinite(undefined) // false isFinite(null) // true isFinite(-1) // true
除了
Infinity
、-Infinity
、NaN
和undefined
这几个值会返回false
,isFinite
对于其他的数值都会返回true
。
-
字符串
-
由于 HTML 语言的属性值使用双引号,所以很多项目约定 JavaScript 语言的字符串只使用单引号。
-
字符串分成多行书写:采用
\
或者+
。 -
如果想输出多行字符串,有一种利用多行注释的变通方法。
(function () { /* line 1 line 2 line 3 */}).toString().split('\n').slice(1, -1).join('\n') // "line 1 // line 2 // line 3"
-
反斜杠还有三种特殊用法。
(1)
\HHH
:反斜杠后面紧跟三个八进制数(000
到377
),代表一个字符。(2)
\xHH
:\x
后面紧跟两个十六进制数(00
到FF
),代表一个字符。(3)
\uXXXX
:\u
后面紧跟四个十六进制数(0000
到FFFF
),代表一个字符。 -
字符串可以被视为字符数组。如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回
undefined
。字符串内部的单个字符无法改变和增删。
length
属性返回字符串的长度,该属性也是无法改变的。 -
Base64 是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、
+
和/
这64个字符组成的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。JavaScript 原生提供两个 Base64 相关的方法。
btoa()
:任意值转为 Base64 编码atob()
:Base64 编码转为原来的值
var string = 'Hello World!'; btoa(string) // "SGVsbG8gV29ybGQh" atob('SGVsbG8gV29ybGQh') // "Hello World!" // 注意,这两个方法不适合非 ASCII 码的字符,会报错。 btoa('你好') // 报错 // 要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。 function b64Encode(str) { return btoa(encodeURIComponent(str)); } function b64Decode(str) { return decodeURIComponent(atob(str)); } b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE" b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
对象
-
对象的生成:
var obj = { 'a': 100, "f": function (x) { return x*x; }, };
-
对象的引用和Java类似。
-
如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。
({ foo: 123 }) // 正确 ({ console.log(123) }) // 报错
这种差异在
eval
语句(作用是对字符串求值)中反映得最明显。eval('{foo: 123}') // 123 eval('({foo: 123})') // {foo: 123}
-
属性的操作
-
属性的读取:数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。
-
属性的产看:
Object.keys(obj);
。 -
属性的删除:
delete
命令用于删除对象的属性,删除成功后返回true
。需要注意的是,delete
命令只能删除对象本身的属性,无法删除继承的属性(如toString)。delete obj.p // true // 删除一个不存在的属性,delete不报错,而且返回true。 var obj = {}; delete obj.p // true //只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。 Object.defineProperty
-
属性是否存在:
in
运算符用于检查对象是否包含某个属性(检查的是键名),如果包含就返回true
,否则返回false
。它的左边是一个字符串,表示属性名,右边是一个对象。var obj = { p: 1 }; 'p' in obj // true 'toString' in obj // true
in
运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。就像上面代码中,对象obj
本身并没有toString
属性,但是in
运算符会返回true
,因为这个属性是继承的。这时,可以使用对象的
hasOwnProperty
方法判断一下,是否为对象自身的属性。var obj = {}; if ('toString' in obj) { console.log(obj.hasOwnProperty('toString')) // false }
-
属性的遍历:
for...in
循环用来遍历一个对象的全部属性。for...in
循环有两个使用注意点:- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性(如
toString
属性)。 - 它不仅遍历对象自身的属性,还遍历继承的属性。
一般情况下,都是只想遍历对象自身的属性,所以使用
for...in
的时候,应该结合使用hasOwnProperty
方法,在循环内部判断一下,某个属性是否为对象自身的属性。var person = { name: '老张' }; for (var key in person) { if (person.hasOwnProperty(key)) { console.log(key); } }
- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性(如
-
-
with
语句,它的作用是操作同一个对象的多个属性时,提供一些书写的方便。var obj = { p1: 1, p2: 2, }; with (obj) { p1 = 4; p2 = 5; } // 等同于 obj.p1 = 4; obj.p2 = 5;
函数
-
函数的声明:
function f(...) {} var f = function(...) {}; // 下面这种形式可以在函数体内部调用自身,也方便除错 var f = function f() {}; // Function 构造函数基本无人使用
-
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。
function f() { console.log(1); } f() // 2 此处调用的是后文的f函数 function f() { console.log(2); } f() // 2
由于函数名的提升,前一次声明在任何时候都是无效的,这一点要特别注意。
JavaScript 引擎将函数名视同变量名,所以采用
function
命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。f(); function f() {}
表面上,上面代码好像在声明之前就调用了函数
f
。但是实际上,由于“变量提升”,函数f
被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。f(); var f = function (){}; // TypeError: undefined is not a function
上面的代码等同于下面的形式。
var f; f(); f = function () {};
上面代码第二行,调用
f
的时候,f
只是被声明了,还没有被赋值,等于undefined
,所以会报错。注意,如果像下面例子那样,采用
function
命令和var
赋值语句声明同一个函数,由于存在函数提升,最后会采用var
赋值语句的定义。var f = function () { console.log('1'); } function f() { console.log('2'); } f() // 1
上面例子中,表面上后面声明的函数
f
,应该覆盖前面的var
赋值语句,但是由于存在函数提升,实际上正好反过来。
参考:网道