JavaScript 标准参考教程(alpha)笔记——语法
http://javascript.ruanyifeng.com/#introduction
2.1.4 n--: 先使用n再执行n=n-1; --n: 在使用n之前先执行n=n-1;
2.1.5 区块( { } )对于var
命令不构成单独的作用域,与不使用区块的情况没有任何区别。
2.1.6.3 若switch结构中case代码块内部没有break语句,否则就会接下去执行下一个case代码。
需要注意的是,switch
语句后面的表达式,与case
语句后面的表示式比较运行结果时,采用的是严格相等运算符(===
),而不是相等运算符(==
),这意味着比较时不会发生类型转换。
2.1.6.4 表达式一定返回一个值——(https://www.zhihu.com/question/39420977/answer/81250170)
2.1.7.4 break
语句用于跳出代码块或循环。continue
语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。
如果存在多重循环,不带参数的break
语句和continue
语句都只针对最内层循环。
break语句和continue语句怎么带参数:在 break / continue 后面添加一个 数字/标签 来表示跳出 几重循环/标签循环,eg:break 2; /* 跳出2重循环*/;(http://wanlimm.com/77201406202191.html)
(关于语句笔记的一个链接:https://www.jianshu.com/p/0ec3434f7781)
2.1.7.5 标签相当于定位符,可以是任意的标识符,通常配合break和continue使用。
对象文字是JavaScript的突出特点之一:它们允许您直接创建对象 - 不需要任何类。
- 对象:对象将数据存储在属性中。每个属性都有一个名称和一个值。
var obj = { propName1: 123, propName2: "abc" } obj.propName1 = 456; obj["propName1"] = 456; //与前面的语句相同
- 阵列
var arr = [true, "abc", 123]; console.log(arr[1]); // abc console.log(arr.length); // 3
注意:函数和数组都是对象。例如,他们可以有属性:
function foo() {} foo.bar = 123;
instanceof是一个二元操作符(运算符),它的作用是判断其左边对象是否为其右边类的实例,返回boolean类型的数据。
数据类型
2.2.2 JavaScript 有三种方法,可以确定一个值到底是什么类型。
typeof
运算符(注意null返回object)instanceof
运算符Object.prototype.toString
方法
2.2.3var 语句没有返回值。因为 var a = 1; 是声明语句,声明语句不返回任何东西,所以没有返回值。然后 a = 1; 是赋值语句,返回1。
2.2.4 如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false
,其他值都视为true
。
undefined
null
false
0
NaN
""
或''
(空字符串)
注意,空数组([]
)和空对象({}
)对应的布尔值,都是true
。
2.3.4.1
/*javaScript 内部实际上存在2个0:一个是+0,一个是-0,区别就是64位浮点数表示法的符号位不同。它们是等价的。*/ -0 === +0 // true 0 === -0 // true 0 === +0 // true /*唯一有区别的场合是,+0或-0当作分母,返回的值是不相等的。*/ (1 / +0) === (1 / -0) // false /*因为除以正零得到+Infinity,除以负零得到-Infinity,这两者是不相等的*/
2.3.4.2 NaN表示“非数字”,并不是独立的数据类型,依然属于Number,0
除以0
也会得到NaN
。
NaN的运算规则:
NaN
不等于任何值,包括它本身。NaN === NaN // false /*数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立。*/ [NaN].indexOf(NaN) // -1
NaN
在布尔运算时被当作false
。Boolean(NaN) // false
NaN
与任何数(包括它自己)的运算,得到的都是NaN
。NaN + 32 // NaN NaN - 32 // NaN NaN * 32 // NaN NaN / 32 // NaN
2.3.4.3 Infinity
表示“无穷”,有正负之分,正无穷(正的数值太大,eg:Math.pow(2, 1024)
)和负无穷(正的数值太大),另一种是非0数值除以0,也得到Infinity
。
Infinity === -Infinity // false 1 / -0 // -Infinity -1 / -0 // Infinity /*非零正数除以-0,会得到-Infinity,负数除以-0,会得到Infinity。*/
Infinity
大于一切数值(除了NaN
),-Infinity
小于一切数值(除了NaN
)。
Infinity
与NaN
比较,总是返回false
。
Infinity > 1000 // true -Infinity < -1000 // true Infinity > NaN // false -Infinity > NaN // false Infinity < NaN // false -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 // Infinity Infinity * Infinity // Infinity
Infinity
减去或除以Infinity
,得到NaN
。Infinity - Infinity // NaN Infinity / Infinity // NaN
Infinity
与null
计算时,null
会转成0,等同于与0
的计算。null * Infinity // NaN null / Infinity // 0 Infinity / null // Infinity
Infinity
与undefined
计算,返回的都是NaN
。undefined + Infinity // NaN undefined - Infinity // NaN undefined * Infinity // NaN undefined / Infinity // NaN Infinity / undefined // NaN
2.3.5.1 对于那些会自动转为科学计数法的数字,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
如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN
。如果第二个参数是0
、undefined
和null
,则直接忽略。
/*如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。如果最高位无法转换,则直接返回NaN。*/ parseInt('1546', 2) // 1 parseInt('546', 2) // NaN
如果parseInt
的第一个参数不是字符串,会被先转为字符串。这会导致一些令人意外的结果。
parseInt(0x11, 36) // 43 parseInt(0x11, 2) // 1 // 等同于 parseInt(String(0x11), 36) parseInt(String(0x11), 2) // 等同于 parseInt('17', 36) parseInt('17', 2)
这种处理方式,对于八进制的前缀0,尤其需要注意。
parseInt(011, 2) // NaN // 等同于 parseInt(String(011), 2) // 等同于 parseInt(String(9), 2)
2.3.5.2 parseFloat
会将空字符串转为NaN
。这些特点使得parseFloat
的转换结果不同于Number
函数。
parseFloat(true) // NaN Number(true) // 1 parseFloat(null) // NaN Number(null) // 0 parseFloat('') // NaN Number('') // 0 parseFloat('123.45#') // 123.45 Number('123.45#') // NaN
2.3.5.3 isNaN
方法可以用来判断一个值是否为NaN
。判断NaN
更可靠的方法是,利用NaN
为唯一不等于自身的值的这个特点,进行判断。
function myIsNaN(value) { return value !== value; }
2.3.5.4 isFinite
方法返回一个布尔值,表示某个值是否为正常的数值。除了Infinity
、-Infinity
和NaN
这三个值会返回false
,isFinite
对于其他的数值都会返回true
。
2.4.1.3 字符串可以被视为字符数组,但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。字符串内部的单个字符无法改变和增删。
2.4.1.4 length
属性返回字符串的长度,该属性也是无法改变的。
2.4.3 Base64 转码
btoa()
:任意值转为 Base64 编码atob()
:Base64 编码转为原来的值/*要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。*/ function b64Encode(str) { return btoa(encodeURIComponent(str)); } function b64Decode(str) { return decodeURIComponent(atob(str)); } b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE" b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
2.5.1.4 下面代码中,大括号就定义了一个对象,它被赋值给变量obj
,所以变量obj
就指向一个对象。该对象内部包含两个键值对(又称为两个“成员”),第一个键值对是foo: 'Hello'
,其中foo
是“键名”(成员的名称),字符串Hello
是“键值”(成员的值)。键名与键值之间用冒号分隔。第二个键值对是bar: 'World'
,bar
是键名,World
是键值。两个键值对之间用逗号分隔。
var obj = { foo: 'Hello', bar: 'World' };
2.5.1.2 对象的所有键名都是字符串,加不加引号都可以。如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。
var obj = { p: function (x) { return 2 * x; } }; obj.p(1) // 2
如果属性的值还是一个对象,就形成了链式引用。
var o1 = {}; var o2 = { bar: 'hello' }; o1.foo = o2; o1.foo.bar // "hello" /*对象o1的属性foo指向对象o2,就可以链式引用o2的属性。*/
属性可以动态创建,不必在对象声明时就指定。
var obj = {}; obj.foo = 123; obj.foo // 123
2.5.1.3 如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
var o1 = {};"hello world" var o2 = o1; o1.a = 1; o2.a // 1 o2.b = 2; o1.b // 2
/*o1
和o2
指向同一个对象,因此为其中任何一个变量添加属性,另一个变量都可以读写该属性。*/
此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。
var o1 = {}; var o2 = o1; o1 = 1; o2 // {} /*o1和o2指向同一个对象,然后o1的值变为1,这时不会对o2产生影响,o2还是指向原来的那个对象。*/
但是,这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
var x = 1; var y = x; x = 2; y // 1 /*当x的值发生变化后,y的值并不变,这就表示y和x并不是指向同一个内存地址。*/
2.5.1.4 如果行首是{ },即为语句/代码块,若要表示为表达式/对象,则要在大括号前加上圆括号。
/*这种差异在eval语句(作用是对字符串求值)中反映得最明显*/ eval('{foo: 123}') // 123 eval('({foo: 123})') // {foo: 123} /*如果没有圆括号,eval将其理解为一个代码块(foo理解为标签);加上圆括号以后,就理解成一个对象。*/
2.5.2.1 读取属性
var obj = { p: 'Hello World' }; obj.p // "Hello World" obj['p'] // "Hello World" /*一种是使用点运算符,还有一种是使用方括号运算符读取属性p。*/
请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。
var foo = 'bar'; var obj = { foo: 1, bar: 2 }; obj.foo // 1 obj[foo] // 2 /*引用对象obj的foo属性时,如果使用点运算符,foo就是字符串;如果使用方括号运算符,但是不使用引号,那么foo就是一个变量,指向字符串bar。*/
方括号[ ]运算符内部还可以使用表达式。数字键可以不加引号,因为会自动转成字符串。
注意,数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。
var obj = { 123: 'hello world' }; obj.123 // 报错 obj[123] // "hello world" /*对数值键名123使用点运算符,结果报错。*/
2.5.2.2 点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。
2.5.2.3 Object.keys查看一个对象本身的 所有 属性。
2.5.2.4 对象obj
并没有p
属性,但是delete
命令照样返回true
。需要注意的是,delete
命令只能删除对象本身的属性,无法删除继承的属性。
2.5.2.5 in
运算符用于检查对象是否包含某个属性。in
运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。(例如toString
方法不是对象obj
自身的属性,而是继承的属性。)
2.2.2.6 for...in
循环用来遍历一个对象的全部属性。
两个注意点:
- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
- 它不仅遍历对象自身的属性,还遍历继承的属性。
对象都继承了toString
属性,但是for...in
循环不会遍历到这个属性。
ar obj = {}; // toString 属性是存在的 obj.toString // toString() { [native code] } for (var p in obj) { console.log(p); } // 没有任何输出
对象obj
继承了toString
属性,该属性不会被for...in
循环遍历到,因为它默认是“不可遍历”的。
如果继承的属性是可遍历的,那么就会被for...in
循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用for...in
的时候,应该结合使用hasOwnProperty
方法,在循环内部判断一下,某个属性是否为对象自身的属性。
2.5.3 with
语句的作用是操作同一个对象的多个属性,注意,如果with
区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。所以必须先定义属性,再在with区块内操作它,因为with区块没有改变作用域,它的内部依然是当前作用域。这导致with语句绑定对象不明确。因此,建议不要使用with
语句,可以考虑用一个临时变量代替with
。
with (对象) { 语句; } var obj = {}; with (obj) { p1 = 4; //等同于obj.p1=4 p2 = 5; //等同于obj.p2=5 } obj.p1 // undefined p1 // 4 with(obj1.obj2.obj3) { console.log(p1 + p2); } // 可以写成 var temp = obj1.obj2.obj3; console.log(temp.p1 + temp.p2);
2.6.1 任何类型的数据,都可以放入数组。如果数组的元素还是数组,就形成了多维数组。
2.6.2 本质上,数组属于一种特殊的对象。typeof
运算符会返回数组的类型是object
。数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)。(Object.keys
方法返回数组的所有键名。)
由于数组成员的键名是固定的(默认总是0、1、2…),因此数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。
但是,对于数值的键名,不能使用点结构。
var arr = [1, 2, 3]; arr.0 // SyntaxError /*arr.0的写法不合法,因为单独的数值不能作为标识符(identifier)。所以,数组成员只能用方括号arr[0]表示(方括号是运算符,可以接受数值)。*/
2.6.3 length
属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到length
设置的值。当length
属性设为大于数组个数时,读取新增的位置都会返回undefined
。清空数组的一个有效方法,就是将length
属性设为0。值得注意的是,由于数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length
属性的值。
var a = []; a['p'] = 'abc'; a.length // 0 a[2.1] = 'abc'; a.length // 0 /*上面代码将数组的键分别设为字符串和小数,结果都不影响length属性。因为,length属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以length属性保持为0。*/
2.6.4 检查某个键名是否存在的运算符in
,适用于对象,也适用于数组。
2.6.5 for...in
循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。for...in
不仅会遍历数组所有的数字键,还会遍历非数字键。
var a = [1, 2, 3]; a.foo = true; for (var key in a) { console.log(key); } // 0 // 1 // 2 // foo /*在遍历数组时,也遍历到了非整数键foo。因此不推荐使用for...in遍历数组。*/
使用for
循环或while
循环遍历数组。数组的forEach
方法,也可以用来遍历数组。
var colors = ['red', 'green', 'blue']; colors.forEach(function (color) { console.log(color); }); // red // green // blue
2.6.6 数组的空位是可以读取的,返回undefined
,且其不影响length
属性。使用delete
命令删除一个数组成员,会形成空位,并且不会影响length
属性。
数组的某个位置是空位,与某个位置是undefined
,是不一样的。如果是空位,使用数组的forEach
方法、for...in
结构、以及Object.keys
方法进行遍历,空位都会被跳过。如果某个位置是undefined
,遍历的时候就不会被跳过。
这就是说,空位就是数组没有这个元素,所以不会被遍历到,而undefined
则表示数组有这个元素,值是undefined
,所以遍历不会跳过。
2.6.7 “类似数组的对象”的根本特征,就是具有length
属性。只要有length
属性,就可以认为这个对象类似于数组。但是有一个问题,这种length
属性不是动态值,不会随着成员的变化而变化。
1、数组的slice
方法可以将“类似数组的对象”变成真正的数组。
var arr = Array.prototype.slice.call(arrayLike);
2、通过call()
把数组的方法放到对象上面。
function print(value, index) { console.log(index + ' : ' + value); } Array.prototype.forEach.call(arrayLike, print); /*arrayLike代表一个类似数组的对象,本来是不可以使用数组的forEach()方法的,但是通过call(),可以把forEach()嫁接到arrayLike上面调用。*/
/*在arguments
对象上面调用forEach
方法。*/
// forEach 方法 function logArgs() { Array.prototype.forEach.call(arguments, function (elem, i) { console.log(i + '. ' + elem); }); } // 等同于 for 循环 function logArgs() { for (var i = 0; i < arguments.length; i++) { console.log(i + '. ' + arguments[i]); } }
字符串也是类似数组的对象,所以也可以用Array.prototype.forEach.call
遍历。
不过,最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach
方法。
var arr = Array.prototype.slice.call('abc'); arr.forEach(function (chr) { console.log(chr); }); // a // b // c
2.7.1.1 除了用function
命令声明函数,还可以采用变量赋值的写法。
var f = function f() {};
/*函数名f只在函数体内部可用,指代函数表达式本身
*/
//近似等价于 function f() {};
2.7.1.2 如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。
function f() { console.log(1); } f() // 2 function f() { console.log(2); } f() // 2 /*由于函数名的提升,前一次声明在任何时候都是无效的,这一点要特别注意。*/
2.7.1.3 函数可以调用自身,这就是递归(recursion)。
2.7.1.4 JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。
function add(x, y) { return x + y; } // 将函数赋值给一个变量 var operator = add; // 将函数作为参数和返回值 function a(op){ return op; } a(add)(1, 1) // 2
2.7.1.5 如果同时采用function
命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。函数f提升到头部只能用
function
命令声明。
f(); function f() {}
2.7.1.6 因为函数f的提升所以不能在条件语句中声明函数。要达到在条件语句中定义函数的目的,只有使用函数表达式。
if (false) { var f = function () {}; } f() // undefined
2.7.2.1 函数的name
属性返回函数的名字。如果是通过变量赋值定义的函数,那么name
属性返回变量名。如果变量的值是一个具名函数,那么name
属性返回function
关键字之后的那个函数名。
2.7.2.2 函数的length
属性返回函数定义之中的参数个数。
2.7.2.3 利用toString
方法变相实现多行字符串。
var multiline = function (fn) { var arr = fn.toString().split('\n'); return arr.slice(1, arr.length - 1).join('\n'); }; function f() {/* 这是一个 多行注释 */} multiline(f); // " 这是一个 // 多行注释"
2.7.3.1 函数外部声明的变量就是全局变量,在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。函数内部定义的变量,会在该作用域内覆盖同名全局变量。注意,对于var
命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
if (true) { var x = 5; } console.log(x); // 5
2.7.3.2 与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var
命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
2.7.3.3 函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1; var x = function () { console.log(a); }; function f() { var a = 2; x(); } f() // 1 /*函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。*/
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
2.7.4.2 Javascript 允许省略参数,省略的值变为undefined,并且函数和length属性与传入参数个数无关,只返回函数定义的参数,但是没法省略靠前的参数,若一定要省略,则传入undefined。
2.7.4.3 原始类型(数值、字符串、布尔值)的值传值传递,则在函数体内修改参数值不会影响到函数外部。复合类型的值(数组、对象、其他函数),传递方式是传址传递,则传入函数的原始值的地址会在内部修改,将会影响到原始值。注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var obj = [1, 2, 3]; function f(o) { o = [2, 3, 4]; } f(obj); obj // [1, 2, 3] /*在函数f内部,参数对象obj被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o)的值实际是参数obj的地址,重新对o赋值导致o指向另一个地址,保存在原地址上的值当然不受影响。*/
2.7.4.4 若函数有同名的参数,则取最后出现的那个值,调用函数时,没有提供第二个参数,则第二个参数的值默认为undefined,若要获得第一个的值,使用arguments
对象。
2.7.4.5 (1).arguments
对象包含了函数运行时的所有参数,在函数体内部使用,正常模式下,arguments对象可以在运行时修改,而在严格模式下('use strict'
),修改是无效的。可以通达arguments
对象的length属性判断函数调用时带了几个参数。(2).arguments
对象不是数组,要让arguments
对象使用数组方法,只有将其转变为真正的数组。
slice
方法和逐一填入新数组。var args = Array.prototype.slice.call(arguments); // 或者 var args = []; for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }
- callee 属性,通过arguments.callee来调用函数本身,但在严格模式里禁用,因此不建议使用。
2.7.5.1 JavaScript 语言特有的”链式作用域”结构,子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999
函数f2就是闭包,即能够读取其他函数内部变量的函数。 闭包最大的特点就是能够记住它的诞生环境,比如f2的诞生环境是f1。闭包可以读取函数内部变量并让这些变量始终保持在内存中,使内部变量记住上一次调用时的运算结果。
function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7
通过闭包,start
的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。因此可以看作闭包是函数内部作用域的一个接口。
闭包的另一个用处,是封装对象的私有属性和私有方法。
function Person(name) { var _age; function setAge(n) { _age = n; } function getAge() { return _age; } return { name: name, getAge: getAge, setAge: setAge }; } var p1 = Person('张三'); p1.setAge(25); p1.getAge() // 25
/*函数Person
的内部变量_age
,通过闭包getAge
和setAge
,变成了返回对象p1
的私有变量。*/
闭包内存消耗大,因此不能滥用,否则可能造成网页的性能问题。
2.7.5.2 在Javascript 中,圆括号()
是一种运算符,函数() 表示调用该函数。
匿名函数使用“立即执行的函数表达式”,它的目的是:一、不必为函数命名,避免了污染全局变量;二、 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
// 写法一 var tmp = newData; processData(tmp); storeData(tmp); // 写法二 (function () { var tmp = newData; processData(tmp); storeData(tmp); }()); /*写法二比写法一更好,因为完全避免了污染全局变量。*/
2.7.6 eval
命令可以将字符串当作语句执行。不建议使用。eval()调用这种方式叫直接调用,作用域为当前作用域,除此之外,其他的调用方式都叫间接调用,作用域为全局作用域。与eval
作用类似的还有Function
构造函数。
2.8.1.1 加法运算符存在重载(加法运算符是在运行时决定,到底是执行相加,还是执行连接。运算子的不同,导致了不同的语法行为,这种现象称为“重载”)。并且算术运算符只有加法运算符进行重载。除加法外它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。
2.8.1.2 加法运算符如果运算子是对象,必须先转成原始类型的值,然后再相加。
2.8.2.1 余数运算符(%
)运算结果的正负号由第一个运算子的正负号决定,所以为了得到负数的正确余数值,应先使用绝对值函数。
// 错误的写法 function isOdd(n) { return n % 2 === 1; } isOdd(-5) // false isOdd(-4) // false // 正确的写法 function isOdd(n) { return Math.abs(n % 2) === 1; } isOdd(-5) // true isOdd(-4) // false
2.8.2.4 指数运算符(**)。
2.8.3 与位运算符的结合。
// 等同于 x = x >> y x >>= y // 等同于 x = x << y x <<= y // 等同于 x = x >>> y x >>>= y // 等同于 x = x & y x &= y // 等同于 x = x | y x |= y // 等同于 x = x ^ y x ^= y
2.8.4 比较运算符返回布尔值,非相等的比较运算符先看两个运算子是否都是字符串,若是,则按Unicode 码(字典顺序)比较,若不是,转变为数值再进行比较。
2.8.4.1 字符串按照字典顺序比较。JavaScript 引擎内部首先比较首字符的 Unicode 码点。如果相等,再比较第二个字符的 Unicode 码点,以此类推。所有字符都有 Unicode 码点,因此汉字也可以比较。
'cat' > 'Cat' // true' /*小写的c的 Unicode 码点(99)大于大写的C的 Unicode 码点(67),所以返回true。*/
2.8.4.2 (1)两个原始类型的值的比较,除了相等运算符(==
)和严格相等运算符(===
),其他比较运算符都是先转成数值再比较。特殊情况:任何值包括NaN本身与NaN比较皆返回false。(2)如果运算子是对象,会转为原始类型的值,再进行比较。
[2] > [1] // true // 等同于 [2].valueOf().toString() > [1].valueOf().toString() // 即 '2' > '1' [2] > [11] // true // 等同于 [2].valueOf().toString() > [11].valueOf().toString() // 即 '2' > '11' {x: 2} >= {x: 1} // true // 等同于 {x: 2}.valueOf().toString() >= {x: 1}.valueOf().toString() // 即 '[object Object]' >= '[object Object]'
注意,Date 对象实例用于比较时,是先调用toString
方法。如果返回的不是原始类型的值,再接着对返回值调用valueOf
方法。
2.8.4.3 注意,NaN
与任何值都不相等(包括自身)。另外,正0
等于负0
。
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。
{} === {} // false [] === [] // false (function () {} === function () {}) // false /*上面代码分别比较两个空对象、两个空数组、两个空函数,结果都是不相等。原因是对于复合类型的值,严格相等运算比较的是,它们是否引用同一个内存地址,而运算符两边的空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是false。*/
注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值。
undefined
和null
与自身严格相等。“严格不相等运算符”(!==
),它的算法就是先求严格相等运算符的结果,然后返回相反值。
2.8.4.4 对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转化成原始类型的值,再进行比较。
[1] == 1 // true // 等同于 Number([1]) == 1 [1] == '1' // true // 等同于 String([1]) == Number('1') [1] == true // true // 等同于 Number([1]) == Number(true) /*数组[1]与数值进行比较,会先转成数值,再进行比较;与字符串进行比较,会先转成字符串,再进行比较;与布尔值进行比较,两个运算子都会先转成数值,然后再进行比较。*/
undefined
和null
与其他类型的值比较时,结果都为false
,它们互相比较时结果为true
。
相等运算符的缺点:
0 == '' // true 0 == '0' // true 2 == true // false 2 == false // false false == 'false' // false false == '0' // true false == undefined // false false == null // false null == undefined // true ' \t\r\n ' == 0 // true
/*上面这些表达式都很容易出错,因此不要使用相等运算符(==
),最好只使用严格相等运算符(===
)。*
2.8.5.1 不管什么类型的值,经过取反运算后都变成了布尔值。
!undefined // true !null // true !0 // true !NaN // true !"" // true !54 // false !'hello' // false ![] // false !{} // false
两次取反就是将一个值转为布尔值的简便写法。
2.8.5.3 只通过第一个表达式的值,控制是否运行第二个表达式的机制,就称为“短路”(short-cut)。
2.8.5.4 三元条件表达式与if...else
语句具有同样表达效果。但有一个重大差别是
if...else
是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式。
2.8.6 左移运算符(<<
)表示将一个数的二进制值向左移动指定的位数,尾部补0
,即乘以2
的指定次方(最高位即符号位不参与移动)。右移运算符(>>
)表示将一个数的二进制值向右移动指定的位数,头部补0
,即除以2
的指定次方(最高位即符号位不参与移动)。带符号位的右移运算符(>>>
)表示将一个数的二进制形式向右移动,包括符号位也参与移动,头部补0
。所以,该运算总是得到正值。对于正数,该运算的结果与右移运算符(>>
)完全一致,区别主要在于负数。
2.8.7.1 void
运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined
。
2.8.7.2 逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
2.8.8.2 圆括号内只能放置表达式,如果将产品放在圆括号内就会报错。
2.9.1 强制转换主要指使用Number
、String
和Boolean
三个函数,手动将各种类型的值,分布转换成数字、字符串或者布尔值。
- (1)
Number()
// 字符串:如果不可以被解析为数值,返回 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
parseInt
逐个解析字符,而Number
函数整体转换字符串的类型。parseInt
和Number
函数都会自动过滤一个字符串前导和后缀的空格。parseInt('42 cats') // 42 Number('42 cats') // NaN
Number
方法的参数是对象时,将返回NaN
,除非是包含单个数值的数组。 - (2) String()
- 数值:转为相应的字符串。
- 字符串:转换后还是原来的值。
- 布尔值:
true
转为字符串"true"
,false
转为字符串"false"
。 - undefined:转为字符串
"undefined"
。 - null:转为字符串
"null"
。
String方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
String({a: 1}) // "[object Object]" String([1, 2, 3]) // "1,2,3"
-
先调用对象自身的
toString
方法。如果返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 -
如果
toString
方法返回的是对象,再调用原对象的valueOf
方法。如果valueOf
方法返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 -
如果
valueOf
方法返回的是对象,就报错。
String
方法背后的转换规则,与Number
方法基本相同,只是互换了valueOf
方法和toString
方法的执行顺序。
- (3) Boolean()
除了以下五个值的转换结果为
false
,其他的值全部为true
。undefined
null
-0
或+0
NaN
''
(空字符串)
true
,甚至连false
对应的布尔对象new Boolean(false)
也是true。
2.10.2 Error
对象及其派生错误都是构造函数。开发者可以使用它们,手动造成错误对象的实例。
2.10.4 遇上throw
语句,就手动中断程序执行,抛出一个错误即任何类型的值。
2.10.5 不确定代码出错,则放入try...catch中,catch捕获错误后,不会中断程序,会按正常流程继续执行。且catch中还可以嵌套使用try...catch。
2.10.6 try...catch
结构允许在最后添加一个finally
代码块,表示不管是否出现错误,都必需在最后运行的语句。try...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
/*catch
代码块结束执行之前,会先执行finally
代码块。catch
代码块之中,触发转入finally
代码快的标志,不仅有return
语句,还有throw
语句。*/
/*进入catch
代码块之后,一遇到throw
语句,就会去执行finally
代码块*/