1. 七种数据类型
数值(number):整数和小数(比如1和3.14)
字符串(string):文本(比如Hello World)
布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
null:表示空值,即此处的值为空
对象(object):各种值组成的集合
symbol
- 基本数据类型:number、string、bookean、symbol
- 空类型:undefinged、null
- 容器数据类型:object
- 狭义的对象(object)
- 数组(array)
- 函数(function)
查看值的类型
// typeof 用法
typeof 123 // "number"
typeof '123' // "string"
typeof false // "boolean"
function f() {}
typeof f // "function"
typeof undefined // "undefined"
typeof window // "object"
typeof {} // "object"
typeof [] // "object"
typeof null // "object" // null的类型是object,这是由于历史原因造成的
// typeof 在判断语句下的使用
if (typeof v === "undefined") {
// ...
}
// instanceof 用法
var o = {};
var a = [];
o instanceof Array // false
a instanceof Array // true
2. null,undefined 和 布尔值
null与undefined都可以表示“没有”,含义非常相似
// 语法效果几乎没区别
// 同时设置两个这样的值与历史原因有关
var a = undefined;
var a = null;
// null 转数字
Number(null) // 0
5 + null // 5
// undefined 转数字
Number(undefined) // NaN
5 + undefined // NaN
// 这样理解 null 和 defined 用法和含义
// null表示空值,即该处的值现在为空
// undefined表示“未定义”,下面是返回undefined的典型场景
var i; // 变量声明了,但没有赋值
i // undefined
function f(x) { // 调用函数时,应该提供的参数没有提供,该参数等于 undefined
return x;
}
f() // undefined
var o = new Object(); // // 对象没有赋值的属性
o.p // 函数没有返回值时,默认返回 undefined
function f() {}
f() // undefined
布尔值
- “真”用关键字true表示,“假”用关键字false表示
- 如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值
// 5 个会被转为 false 的值
undefined
null
false
0
NaN
'' 或 ""
// 注意,空数组([])和空对象({})对应的布尔值,都是true
3. 数值
- JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此
- 由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心
- 与数值相关的全局方法
- parseInt()
- parseFloat()
- isNaN()
- isFinite()
Math.pow(2, 53) + 2 // 9007199254740994
Math.pow(2, 53) + 3 // 9007199254740996
Math.pow(2, 53) + 4 // 9007199254740996
// 大于2的53次方以后,多出来的有效数字(最后三位的111)都会无法保存,变成0
// 所以简单的法则就是,JavaScript 对15位的十进制数都可以精确处理
Math.pow(2, 1024) // Infinity
// 如果一个数大于等于2的1024次方,那么就会发生“正向溢出”
Math.pow(2, -1075) // 0
// 如果一个数小于等于2的-1075次方,那么就会发生为“负向溢出”
// JavaScript 提供Number对象的MAX_VALUE和MIN_VALUE属性,返回可以表示的具体的最大值和最小值。
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
数值的进制
// 默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制
0xff // 255
0o377 // 255
0b11 // 3
0xzz // 报错
0o88 // 报错
0b22 // 报错
正零和负零
-0 === +0 // true
0 === -0 // true
0 === +0 // true
+0 // 0
-0 // 0
(-0).toString() // '0'
(+0).toString() // '0'
(1 / +0) === (1 / -0) // false
// 因为除以正零得到+Infinity,除以负零得到-Infinity
NaN
- 主要出现在将字符串解析成数字出错的场合
5 - 'x' // NaN
Math.acos(2) // NaN
Math.log(-1) // NaN
Math.sqrt(-1) // NaN
0 / 0 // NaN
typeof NaN // 'number'
NaN === NaN // false
Boolean(NaN) // false
NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN
parseInt()
parseInt方法用于将字符串转为整数
parseInt('123') // 123
// 如果字符串头部有空格,空格会被自动去除
parseInt(' 81') // 81
// 如果parseInt的参数不是字符串,则会先转为字符串再转换
parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
// parseInt的参数都是字符串,结果只返回字符串头部可以转为数字的部分
parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
// 如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN
parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1
// 如果字符串以0x或0X开头,parseInt会将其按照十六进制数解析
// 如果字符串以0开头,将其按照10进制解析
parseInt('0x10') // 16
// 进制转换
parseInt('1000') // 1000
parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
// 第二个参数表示被解析的值的进制
// 第二个参数只有在2到36之间,超出这个范围,则返回NaN
// 如果第二个参数是0、undefined和null,则直接忽略
parseInt('10', 37) // NaN
parseInt('10', 1) // NaN
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10
parseFloat()
parseFloat('3.14') // 3.14
parseFloat('314e-2') // 3.14
parseFloat('0.0314E+2') // 3.14
parseFloat('3.14more non-digit characters') // 3.14
parseFloat([]) // NaN
parseFloat('FF2') // NaN
parseFloat('') // NaN
// 尤其值得注意,parseFloat会将空字符串转为NaN
isNaN()
- 使用isNaN之前,最好判断一下数据类型
// isNaN只对数值有效,如果传入其他值,会被先转成数值
isNaN(NaN) // true
isNaN(123) // false
isNaN('Hello') // true
isNaN({}) // true
isNaN(['xzy']) // true
// 对于空数组和只有一个数值成员的数组,isNaN返回false
// 原因是这些数组能被Number函数转成数值
isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false
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。
4. 字符串
// 合法字符串
'abc'
"abc"
'key = "value"'
"It's a long journey"
'Did she say \'Hello\'?'
"Did she say \"Hello\"?"
// 分多行会报错
'a
b
c'
// // SyntaxError: Unexpected token ILLEGAL
// 若要分行,每一行尾部加 \
var longString = 'Long \
long \
long \
string';
longString
// "Long long long string"
// 连接运算符(+)可以连接多个单行字符串
var longString = 'Long '
+ 'long '
+ 'long '
+ 'string';
// 转义
\0 :null(\u0000)
\b :后退键(\u0008)
\f :换页符(\u000C)
\n :换行符(\u000A)
\r :回车键(\u000D)
\t :制表符(\u0009)
\v :垂直制表符(\u000B)
\' :单引号(\u0027)
\" :双引号(\u0022)
\\ :反斜杠(\u005C)
字符串可以被视为字符数组,因此可以使用数组的方括号运算符
var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"
length 属性
// length 属性无法改变
var s = 'hello';
s.length // 5
s.length = 3;
s.length // 5
s.length = 7;
s.length // 5
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') // "你好"
5. 对象
- 一组“键值对”(key-value)的集合
- 对象的所有键名都是字符串,如果键名是数值,会被自动转为字符串
var obj = {
foo: 'Hello',
bar: 'World'
};
// 如果属性的值还是一个对象,就形成了链式引用
var o1 = {};
var o2 = { bar: 'hello' };
o1.foo = o2;
o1.foo.bar // "hello"
表达式还是语句?
// 歧义的代码
// 如果是一个表达式,表示一个包含foo属性的对象
// 如果是一个语句,表示一个代码区块,里面有一个标签foo,指向表达式123
{ foo: 123 }
// JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块
// 如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式
({ foo: 123 }) // 正确
({ console.log(123) }) // 报错
属性的读取
// 点运算符和方括号运算符
var obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World"
// 方括号里用变量
var foo = 'bar';
var obj = {
foo: 1,
bar: 2
};
obj.foo // 1
obj[foo] // 2
// 方括号运算符内部还可以使用表达式
obj['hello' + ' world']
obj[3 + 3]
// 数字键可以不加引号,因为会自动转成字符串
var obj = {
0.7: 'Hello World'
};
obj['0.7'] // "Hello World"
obj[0.7] // "Hello World"
属性的赋值
var obj = {};
obj.foo = 'Hello';
obj['bar'] = 'World';
var obj = { p: 1 };
// 等价于
var obj = {};
obj.p = 1;
属性的查看
// 返回一个数组
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
属性的删除:delete 命令
// delete命令用于删除对象的属性,删除成功后返回 true
var obj = { p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
// 注意,删除一个不存在的属性,delete不报错,而且返回true
var obj = {};
delete obj.p // true
属性是否存在:in 运算符
// in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
// 对象的hasOwnProperty方法判断一下,是否为对象自身的属性
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')) // false
}
属性的遍历:for…in 循环
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log('键名:', i);
console.log('键值:', obj[i]);
}
// 使用for...in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性
var person = { name: '老张' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
with 语句
- 操作同一个对象的多个属性时,提供一些书写的方便 ```javascript // 例一 var obj = { p1: 1, p2: 2, }; with (obj) { p1 = 4; p2 = 5; } // 等同于 obj.p1 = 4; obj.p2 = 5;
// 例二 with (document.links[0]){ console.log(href); console.log(title); console.log(style); } // 等同于 console.log(document.links[0].href); console.log(document.links[0].title); console.log(document.links[0].style);
// 注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性 // 否则会创造一个当前作用域的全局变量
<a name="1FdR9"></a>
### 6. 函数
函数声明
```javascript
// function 命令
function print(s) {
console.log(s);
}
// 函数表达式
var print = function(s) {
console.log(s);
};
// Function 构造函数
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
// 以传递任意数量的参数给Function构造函数,只有最后一个参数会被当做函数体
// 如果只有一个参数,该参数就是函数体
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明
function f() {
console.log(1);
}
f() // 2
function f() {
console.log(2);
}
f() // 2
圆括号运算符,return 语句和递归
- 函数体内部的return语句,表示返回
- JavaScript 引擎遇到return语句,就直接返回return后面的那个表达式的值
- 后面即使还有语句,也不会得到执行
- 也就是说,return语句所带的那个表达式,就是函数的返回值
- return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined
function add(x, y) {
return x + y;
}
add(1, 1) // 2
// 函数可以调用自身,这就是递归(recursion)。下面就是通过递归,计算斐波那契数列的代码
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
fib(6) // 8
第一等公民
- JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同
函数名的提升
- JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时
- 整个函数会像变量声明一样,被提升到代码头部
f();
function f() {}
// 采用赋值语句定义函数,JavaScript 就会报错
f();
var f = function (){};
// TypeError: undefined is not a function
// 用function命令和var赋值语句声明同一个函数,由于存在函数提升,最后会采用var赋值语句的定义
var f = function () {
console.log('1');
}
function f() {
console.log('2');
}
f() // 1
函数的属性和方法
// name 属性
function f1() {}
f1.name // "f1"
length 属性,函数预期传入的参数个数
function f(a, b) {}
f.length // 2
// toString()方法返回一个字符串,内容是函数的源码
function f() {
a();
b();
c();
}
f.toString()
// function f() {
// a();
// b();
// c();
// }
// 对于那些原生的函数,toString()方法返回function (){[native code]}
Math.sqrt.toString() // "function sqrt() { [native code] }"
// 函数内部的注释也可以返回
function f() {/*
这是一个
多行注释
*/}
f.toString()
// "function f(){/*
// 这是一个
// 多行注释
// */}"
// 利用这一点,可以变相实现多行字符串
var multiline = function (fn) {
var arr = fn.toString().split('\n');
return arr.slice(1, arr.length - 1).join('\n');
};
function f() {/*
这是一个
多行注释
*/}
multiline(f);
// " 这是一个
// 多行注释"
函数作用域
- ES5 中,JavaScript 只有两种作用域,全局作用域和函数作用域
- ES6 新增了块级作用域
- 对于顶层函数来说,函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取
- 在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)
- 对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量
var v = 1;
function f() {
console.log(v);
}
f()
// 1
函数内部的变量提升
- 与全局作用域一样,函数作用域内部也会产生“变量提升”现象
- var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
// 等同于
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
函数本身的作用域
- 函数本身也是一个值,也有自己的作用域。它的作用域与变量一样
- 就是其声明时所在的作用域,与其运行时所在的作用域无关
// 函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
// 函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x)
// ReferenceError: a is not defined
// 同样地,函数体内部声明的函数,作用域绑定函数体内部
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
// 函数foo内部声明了一个函数bar,bar的作用域绑定foo
// 当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x
// 正是这种机制,构成了“闭包”现象
参数
- 函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数
// 参数允许省略
function f(a, b) {
return a;
}
f(1, 2, 3) // 1
f(1) // 1
f() // undefined
f.length // 2
// 函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数
参数传递
- 函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)
- 这意味着,在函数体内修改参数值,不会影响到函数外部
- 如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)
- 也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值
- 如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值
// 在函数内部,p的值是原始值的拷贝,无论怎么修改,都不会影响到原始值
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
// 在函数内部修改obj的属性p,会影响到原始值
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
// 在函数f()内部,参数对象obj被整个替换成另一个值。这时不会影响到原始值
// 这是因为,形式参数(o)的值实际是参数obj的地址
// 重新对o赋值导致o指向另一个地址,保存在原地址上的值当然不受影响
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
function f(a, a) {
console.log(a);
}
f(1) // undefined
function f(a, a) {
console.log(arguments[0]);
}
f(1) // 1
arguments 对象
- 可以在函数体内部读取所有参数
- arguments对象包含了函数运行时的所有参数
- arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推
- 这个对象只有在函数体内部,才可以使用
var f = function (one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f(1, 2, 3)
// 1
// 2
// 3
// 正常模式下,arguments对象可以在运行时修改
var f = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1) // 5
严格模式下,arguments对象与函数参数不具有联动关系
var f = function(a, b) {
'use strict'; // 开启严格模式
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1) // 2
arguments 与 数组的关系
- 虽然arguments很像数组,但它是一个对象
- 数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
闭包
- 闭包(closure)是 JavaScript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现
- 得到函数内的局部变量的方法,在函数的内部,再定义一个函数
- 闭包的最大用处有两个
- 一个是可以读取函数内部的变量
- 另一个就是让这些变量始终保持在内存中
- 闭包的另一个用处,是封装对象的私有属性和私有方法
- 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量
- 所以内存消耗很大,因此不能滥用闭包,否则会造成网页的性能问题 ```javascript // 闭包就是函数f2,即能够读取其他函数内部变量的函数 function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999
// 闭包使得内部变量记住上一次调用时的运算结果 function createIncrementor(start){ return function(){ return start++ } } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7 // start是函数createIncrementor的内部变量 // 通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算
function Person(name) { var _age; function setAge(n) { _age = n; } function getAge() { return _age; } return { name: name, getAge: getAge, setAge: setAge }; }
// 函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量 var p1 = Person(‘张三’); p1.setAge(25); p1.getAge() // 25
立即调用的函数表达式
- function 这个关键字即可以当作语句,也可以当作表达式
- 为了避免解析上的歧义,JavaScript 引擎规定,如果function关键字出现在行首,一律解释成语句
- 引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面
```javascript
function(){ /* code */ }();
// SyntaxError: Unexpected token (
// 语句
function f() {}
// 表达式
var f = function f() {}
// 引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
// 这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE
// 上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能就会报错
// 如果没有分号,JavaScript 会将它们连在一起解释,将第二行解释为第一行的参数
// 推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
// 通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:
// 一是不必为函数命名,避免了污染全局变量
// 二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
eval 命令
- eval命令接受一个字符串作为参数,并将这个字符串当作语句执行
- eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题
- 不推荐使用
eval('var a = 1;');
a // 1
eval('3x') // Uncaught SyntaxError: Invalid or unexpected token
// eval命令修改了外部变量a的值
var a = 1;
eval('a = 2');
a // 2
7. 数组
- 数组的本质属于一种特殊的对象。typeof运算符会返回数组的类型是object
- 数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)
length 属性是一个动态的值,等于键名中的最大整数加上1
['a', 'b', 'c'].length // 3
var arr = ['a', 'b'];
arr.length // 2
arr[2] = 'c';
arr.length // 3
arr[9] = 'd';
arr.length // 10
arr[1000] = 'e';
arr.length // 1001
// 数组的数字键不需要连续,length属性的值总是比最大的那个整数键大1
// 如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少到length设置的值
var arr = [ 'a', 'b', 'c' ];
arr.length // 3
arr.length = 2;
arr // ["a", "b"]
// 清空数组的一个有效方法,就是将length属性设为0
var arr = [ 'a', 'b', 'c' ];
arr.length = 0;
arr // []
var a = ['a'];
a.length = 3;
a[1] // undefined
in 运算符
var arr = [ 'a', 'b', 'c' ];
2 in arr // true
'2' in arr // true
4 in arr // false
var arr = [];
arr[100] = 'a';
100 in arr // true
1 in arr // false
for…in 循环和数组的遍历
// for..in
var a = [1, 2, 3];
for (var i in a) {
console.log(a[i]);
}
// for循环
for(var i = 0; i < a.length; i++) {
console.log(a[i]);
}
// while循环
var i = 0;
while (i < a.length) {
console.log(a[i]);
i++;
}
// while逆向遍历
var l = a.length;
while (l--) {
console.log(a[l]);
}
// forEach 方法遍历数组
var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
console.log(color);
});
// red
// green
// blue
数组的空位
var a = [1, , 1];
a.length // 3
var a = [1, 2, 3,];
a.length // 3
a // [1, 2, 3]
var a = [, , ,];
a[1] // undefined
var a = [1, 2, 3];
delete a[1];
a[1] // undefined
a.length // 3
// length属性不过滤空位
// 空位和undefined,是不一样的
// 数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过
var a = [, , ,];
a.forEach(function (x, i) {
console.log(i + '. ' + x);
}) // 不产生任何输出
for (var i in a) {
console.log(i);
} // 不产生任何输出
Object.keys(a) // []
// 如果某个位置是undefined,遍历的时候就不会被跳过
var a = [undefined, undefined, undefined];
a.forEach(function (x, i) {
console.log(i + '. ' + x);
});
// 0. undefined
// 1. undefined
// 2. undefined
for (var i in a) {
console.log(i);
}
// 0
// 1
// 2
Object.keys(a)
// ['0', '1', '2']
// 空位就是数组没有这个元素,所以不会被遍历到
// 而undefined则表示数组有这个元素,值是undefined,所以遍历不会跳过
类似数组的对象
- 键名都是正整数或零,并且有length属性
// 典型的“类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串
// arguments对象
function args() { return arguments }
var arrayLike = args('a', 'b');
arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false
// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false
// 字符串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false
// 调用数组的 slice 方法可以将“类似数组的对象”变成真正的数组
// 即调用它的截取片段
var arr = Array.prototype.slice.call(arrayLike);
// 调用数组的 forEach 方法
Array.prototype.forEach.call(arrayLike, print);
// 这种方法比直接使用数组原生的forEach要慢
// 所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法
「@浪里淘沙的小法师」