本文是我看阮一峰前辈的博客时自己随手搞得摘要,就是给自己看的,所以没什么质量可言
查看一个对象本身的所有属性,可以使用
Object.keys
方法。
for...in循环有两个使用注意点。
- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性
- 它不仅遍历对象自身的属性,还遍历继承的属性。
对象有两种读取成员的方法:“点”结构(
object.key)和方括号结构(
object[key])。但是,对于数值的键名,不能使用点结构。
数组
由于数组本质上是对象的一种,所以我们可以为数组添加属性,但是这不影响
length属性的值。
数组的
slice方法将类似数组的对象,变成真正的数组。
var arr = Array.prototype.slice.call(arrayLike);var arr = [];arr[100] = 'a';100 in arr // true1 in arr // false
检查某个键名是否存在的运算符
in,适用于对象,也适用于数组。
var arr = [];arr[100] = 'a';100 in arr // true1 in arr // false
for...in循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。但是,
for...in不仅会遍历数组所有的数字键,还会遍历非数字键。
使用
delete命令删除一个数组成员,会形成空位,并且不会影响
length属性。
数组的某个位置是空位,与某个位置是
undefined,是不一样的。如果是空位,使用数组的
forEach方法、
for...in结构、以及
Object.keys方法进行遍历,空位都会被跳过。
函数
(1)function命令
function命令声明的代码区块,就是一个函数。
function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。
function print(s) { console.log(s);}
(2)函数表达式
除了用
function命令声明函数,还可以采用变量赋值的写法。
var print = function(s) { console.log(s);};
采用函数表达式声明函数时,
function
命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
需要注意的是,函数的表达式需要在语句的结尾加上分号,表示语句结束。而函数的声明在结尾的大括号后面不用加分号。
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。由于函数名的提升(参见下文),前一次声明在任何时候都是无效的,这一点要特别注意。
由于“变量提升”,函数
f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript就会报错。如果同时采用
function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。
根据ECMAScript的规范,不得在非函数的代码块中声明函数,最常见的情况就是if和try语句
name属性返回紧跟在
function关键字之后的那个函数名。
length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。
函数的
toString方法返回函数的源码。
作用域(scope)指的是变量存在的范围。Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。
var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
很容易犯错的一点是,如果函数
A调用函数
B,却没考虑到函数
B不会引用函数
A的内部变量。
函数参数不是必需的,Javascript允许省略参数。
通过下面的方法,可以为函数的参数设置默认值
function f(a) { (a !== undefined && a !== null) ? a = a : a = 1; return a;}f() // 1f('') // ""f(0) // 0
如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var obj = [1, 2, 3];function f(o){ o = [2, 3, 4];}f(obj);obj // [1, 2, 3]
如果有同名的参数,则取最后出现的那个值。
function f(a, a) { console.log(a);}f(1, 2) // 2
上面的函数
f
有两个参数,且参数名都是
a
。取值的时候,以后面的
a
为准。即使后面的
a
没有值或被省略,也是以其为准。
需要注意的是,虽然
arguments很像数组,但它是一个对象。数组专有的方法(比如
slice和
forEach),不能在
arguments对象上直接使用。
但是,可以通过
apply方法,把
arguments作为参数传进去,这样就可以让
arguments使用数组方法了。
// 用于apply方法myfunction.apply(obj, arguments).// 使用与另一个数组合并Array.prototype.concat.apply([1,2,3], arguments)
要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。
var args = Array.prototype.slice.call(arguments);// orvar args = [];for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]);}
arguments
对象带有一个
callee
属性,返回它所对应的原函数。
如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
闭包最大的特点,就是它可以“记住”诞生的环境,比如
f2记住了它诞生的环境
f1,所以从
f2可以得到
f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。
外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
解决方法就是不要让
function出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。
(function(){ /* code */ }());// 或者(function(){ /* code */ })();
上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称IIFE。
注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个IIFE,可能就会报错。
eval命令的作用是,将字符串当作语句执行。
放在
eval中的字符串,应该有独自存在的意义,不能用来与
eval以外的命令配合使用。
JavaScript规定,如果使用严格模式,
eval内部声明的变量,不会影响到外部作用域。
运算符
+
- 如果运算子是对象,先自动转成原始类型的值(即先执行该对象的valueOf方法,如果结果还不是原始类型的值,再执行toString方法;如果对象是Date实例,则先执行toString方法)。
- 两个运算子都是原始类型的值以后,只要有一个运算子是字符串,则两个运算子都转为字符串,执行字符串连接运算。
- 否则,两个运算子都转为数值,执行加法运算。
加法运算符以外的其他算术运算符(比如减法、除法和乘法),都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。
数值运算符(
+)同样使用加号,但是加法运算符是二元运算符(需要两个操作数),它是一元运算符(只需要一个操作数)。
数值运算符的作用在于可以将任何值转为数值(与
Number函数的作用相同)。
+true // 1+[] // 0+{} // NaN数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值。
字符串按照字典顺序进行比较。
undefined和
null与其他类型的值比较时,结果都为
false,它们互相比较时结果为
true
绝大多数情况下,对象与
undefined与
null比较,都返回
false。只有在对象转为原始值得到
undefined时,才会返回
true,这种情况是非常罕见的。
对于非布尔值的数据,取反运算符会自动将其转为布尔值。规则是,以下六个值取反后为
true,其他值取反后都为
false。
- undefined
- null
- false
- 0(包括+0和-0)
- NaN
- 空字符串('')
如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与
Boolean函数的作用相同。这是一种常用的类型转换的写法。
有一点需要特别注意,位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。另外,虽然在JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。
所有的位运算都只对整数有效。否运算遇到小数时,也会将小数部分舍去,只保留整数部分。所以,对一个小数连续进行两次否运算,能达到取整效果。
使用否运算取整,是所有取整方法中最快的一种。
对字符串进行否运算,JavaScript引擎会先调用Number函数,将字符串转为数值。
Number函数将字符串转为数值的规则,参见《数据的类型转换》一节。否运算对特殊数值的处理是:超出32位的整数将会被截去超出的位数,NaN和Infinity转为0。
对于其他类型的参数,否运算也是先用
Number转为数值,然后再进行处理。
“异或运算”有一个特殊运用,连续对两个数a和b进行三次异或运算,aˆ=b, bˆ=a, aˆ=b,可以互换它们的值(详见
维基百科)。这意味着,使用“异或运算”可以在不引入临时变量的前提下,互换两个变量的值。
这是互换两个变量的值的最快方法。
函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数。
数据类型转换
原始类型的值主要是字符串、布尔值、
undefined和
null,它们都能被
Number转成数值或
NaN。
// 数值:转换后还是原来的值Number(324) // 324// 字符串:如果可以被解析为数值,则转换为相应的数值Number('324') // 324// 字符串:如果不可以被解析为数值,返回NaNNumber('324abc') // NaN// 空字符串转为0Number('') // 0// 布尔值:true 转成1,false 转成0Number(true) // 1Number(false) // 0// undefined:转成 NaNNumber(undefined) // NaN// null:转成0Number(null) // 0
Number
函数将字符串转为数值,要比
parseInt
函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为
NaN
。
parseInt('42 cats') // 42Number('42 cats') // NaN
上面代码中,
parseInt
逐个解析字符,而
Number
函数整体转换字符串的类型。
另外,
Number函数会自动过滤一个字符串前导和后缀的空格。
Number('\t\v\r12.34\n') // 12.34
实际上,
Number背后的真正规则复杂得多,内部处理步骤如下。
- 调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
- 如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
- 如果toString方法返回的是对象,就报错。
使用
String函数,可以将任意类型的值转化成字符串。转换规则如下。
(1)原始类型值的转换规则
- 数值:转为相应的字符串。
- 字符串:转换后还是原来的值。
- 布尔值:true转为"true",false转为"false"。
- undefined:转为"undefined"。
- null:转为"null"。
String方法背后的转换规则,与
Number方法基本相同,只是互换了
valueOf方法和
toString方法的执行顺序。
- 先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
- 如果toString方法返回的是对象,再调用valueOf方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
- 如果valueOf方法返回的是对象,就报错。
使用
Boolean函数,可以将任意类型的变量转为布尔值。
它的转换规则相对简单:除了以下六个值的转换结果为
false,其他的值全部为
true。
- undefined
- null
- -0
- 0或+0
- NaN
- ''(空字符串)
注意,所有对象(包括空对象)的转换结果都是
true,甚至连
false对应的布尔对象
new Boolean(false)也是
true。
遇到以下三种情况时,JavaScript会自动转换数据类型,即转换是自动完成的,对用户不可见。
// 1. 不同类型的数据互相运算123 + 'abc' // "123abc"// 2. 对非布尔值类型的数据求布尔值if ('abc') { console.log('hello')} // "hello"// 3. 对非数值类型的数据使用一元运算符(即“+”和“-”)+ {foo: 'bar'} // NaN- [1, 2, 3] // NaN
自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用
String
函数进行转换。如果该位置即可以是字符串,也可能是数值,那么默认转为数值。
由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用
Boolean、
Number和
String函数进行显式转换。
JavaScript 编程风格
- 表示函数调用时,函数名与左括号之间没有空格。
- 表示函数定义时,函数名与左括号之间没有空格。
- 其他情况时,前面位置的语法元素与左括号之间,都有一个空格。
JavaScript最大的语法缺点,可能就是全局变量对于任何一个代码块,都是可读可写。这对代码的模块化和重复使用,非常不利。
因此,避免使用全局变量。如果不得不使用,用大写字母表示变量名,比如
UPPER_CASE。
为了避免可能出现的问题,最好把变量声明都放在代码块的头部。
建议使用
Object.create()命令,替代
new命令。如果不得不使用
new,为了防止出错,最好在视觉上把构造函数与其他函数区分开来。比如,构造函数的函数名,采用首字母大写(InitialCap),其他函数名一律首字母小写。
因此,不要使用
with语句。
建议避免使用
switch...case结构,用对象结构代替
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]();}
Object对象
Object作为构造函数使用时,可以接受一个参数。如果该参数是一个对象,则直接返回这个对象;如果是一个原始类型的值,则返回该值对应的包装对象。
注意,通过
new Object()的写法生成新对象,与字面量的写法
o = {}是等价的。
上面代码表示
Object函数可以将各种值转为对应的构造函数生成的对象。
如果
Object方法的参数是一个对象,它总是返回原对象
利用这一点,可以写一个判断变量是否为对象的函数。
function isObject(value) { return value === Object(value);}isObject([]) // trueisObject(true) // false
Object.keys
方法和
Object.getOwnPropertyNames
方法很相似,一般用来遍历对象的属性。它们的参数都是一个对象,都返回一个数组,该数组的成员都是对象自身的(而不是继承的)所有属性名。它们的区别在于,
Object.keys
方法只返回可枚举的属性(关于可枚举性的详细解释见后文),
Object.getOwnPropertyNames
方法还返回不可枚举的属性名。
Object.prototype.valueOf()
valueOf方法的作用是返回一个对象的“值”,默认情况下返回对象本身。
Object.prototype.toString()
toString方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。
不同数据类型的
Object.prototype.toString方法返回值如下。
- 数值:返回[object Number]。
- 字符串:返回[object String]。
- 布尔值:返回[object Boolean]。
- undefined:返回[object Undefined]。
- null:返回[object Null]。
- 数组:返回[object Array]。
- arguments对象:返回[object Arguments]。
- 函数:返回[object Function]。
- Error对象:返回[object Error]。
- Date对象:返回[object Date]。
- RegExp对象:返回[object RegExp]。
- 其他对象:返回[object " + 构造函数的名称 + "]