一、null
null是只有一个值得数据类型,这个特殊值是null
null值表示一个空对象指针,如果保存对象的变量还没有真正的保存对象,就应该明确的让该变量保存null值
null与undefined都可以表示“没有”,含义非常相似。将一个变量赋值为undefined或null,在if语句中,它们都会被自动转为false,相等运算符(==)甚至直接报告两者相等。
if (!undefined) {
console.log('undefined is false');
}
// undefined is false
if (!null) {
console.log('null is false');
}
// null is false
undefined == null
// true
Number(undefined) // NaN
5 + undefined // NaN
二、undefind
1、定义一个变量,但是没有初始化,会得到undefined
2、变量未定义,会得到undefined
3、函数中return不带任何返回值时,函数停止执行会返回undefined
4、函数参数arguments,没有传递值的命名参数将自动被赋予undefined
5、对象没有赋值的属性var o = new Object(); o.p // undefined
三、布尔值
该类型有2个字面量值true和false,经常用在流程控制语句和选择判断语句,
常见false值,除了false值都基本都是true
转换规则是除了下面六个值被转为false,其他值都视为true
1.数字0、
2.NaN、
3.“ ”,空字符串
4.false
5.undefined
6.null
下列运算符会返回布尔值:
- 前置逻辑运算符: ! (Not)
- 相等运算符:===,!==,==,!=
- 比较运算符:>,>=,<,<=
注意:空数组([])和空对象({})对应的布尔值,都是true
if ([]) {
console.log('true');
}
// true
if ({}) {
console.log('true');
}
// true
四、数值
1、浮点数:所谓浮点数,就是该数值中必须包含一个小数点,且小数点后面至少有一位数字,由于保存浮点数的内存空间是保存整数的2倍,因此,如果小数点后面没有任何数字,或者本身就是一个整数(1.0),那么该值会被转化成整数,但是浮点数计算会产生四舍五入误差的问题。
2、数值精度:精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-253到253,都可以精确表示。
3、数值范围:则 JavaScript 能够表示的数值范围为21024到2-1023(开区间),超出这个范围的数无法表示。这时就会返回Infinity
Math.pow(2, 1024) // Infinity
4、数值的表示法:小数点前的数字多于21位 或者 小数点后的零多于5个,JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示
5、特殊数值:
正零和负零:几乎所有场合,正零和负零都会被当作正常的0
(1 / +0) === (1 / -0) // false 除以正零得到+Infinity,除以负零得到-Infinity,这两者是不相等的
NaN:NaN是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合
NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Numbertypeof NaN // 'number'
NaN不等于任何值,包括它本身。NaN === NaN // false
NaN与任何数(包括它自己)的运算,得到的都是NaN
Infinity:一个正的数值太大,或一个负的数值太小,或者另一种是非0数值除以0,得到Infinity
Infinity有正负之分,Infinity表示正的无穷,-Infinity表示负的无穷
由于数值正向溢出(overflow)、负向溢出(underflow)和被0除,JavaScript 都不报错,所以单纯的数学运算几乎没有可能抛出错误
Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)
Infinity与NaN比较,总是返回false6、与数值相关的全局方法
parseInt():将字符串转为整数。
// parseInt方法用于将字符串转为整数。
parseInt('123') // 123
// 如果字符串头部有空格,空格会被自动去除。
parseInt(' 81') // 81
// 如果parseInt的参数不是字符串,则会先转为字符串再转换。
parseInt(1.23) // 1 // 等同于 parseInt('1.23') // 1
// 字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分
parseInt('15e2') // 15
// 如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN
parseInt('abc') // NaN
parseInt('.3') // NaN
// parseInt的返回值只有两种可能,要么是一个十进制整数,要么是NaN
// parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制
parseInt('1000') // 1000
// 等同于
parseInt('1000', 10) // 1000
parseInt('1000', 2) // 8
parseFloat:将一个字符串转为浮点数
// 如果字符串符合科学计数法,则会进行相应的转换。
parseFloat('314e-2') // 3.14
// 如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分。
parseFloat('3.14more non-digit characters') // 3.14
// parseFloat方法会自动过滤字符串前导的空格。
parseFloat('\t\v\r12.34\n ') // 12.34
// 如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回NaN。
parseFloat([]) // NaN
parseFloat('FF2') // NaN
parseFloat('') // NaN
isNaN():用来判断一个值是否为NaN
// isNaN只对数值有效,如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成NaN,所以最后返回true,这一点要特别引起注意。也就是说,isNaN为true的值,有可能不是NaN,而是一个字符串。
isNaN('Hello') // true
// 相当于
isNaN(Number('Hello')) // true
// 使用isNaN之前,最好判断一下数据类型。
function myIsNaN(value) {
return typeof value === 'number' && isNaN(value);
}
// 判断NaN更可靠的方法是,利用NaN为唯一不等于自身的值的这个特点,进行判断。
function myIsNaN(value) {
return value !== value;
}
isFinite():isFinite方法返回一个布尔值,表示某个值是否为正常的数值。
// 除了Infinity、-Infinity、NaN和undefined这几个值会返回false,isFinite对于其他的数值都会返回true
isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true
五、字符串
字符串就是零个或多个排在一起的字符,放在单引号或双引号之中
'key = "value"'
"It's a long journey"
// 如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此
'Did she say \'Hello\'?'
// 字符串默认只能写在一行内,分成多行将会报错。
'a
b
c'
// SyntaxError: Unexpected token ILLEGAL
// 连接运算符(+)可以连接多个单行字符串,将长字符串拆成多行书写,输出的时候也是单行
var longString = 'Long '
+ 'long '
+ 'long '
+ 'string';
字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)
var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"
// 直接对字符串使用方括号运算符
'hello'[1] // "e"
// 如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回undefined。
'abc'[3] // undefined
'abc'[-1] // undefined
'abc'['x'] // undefined
// 字符串内部的单个字符无法改变和增删,这些操作会默默地失败
var s = 'hello';
delete s[0];
s // "hello"
s[1] = 'a';
s // "hello"
length属性返回字符串的长度,该属性也是无法改变的。
var s = 'hello';
s.length // 5
s.length = 3;
s.length // 5
六、对象
对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。
键名与键值之间用冒号分隔。
两个键值对之间用逗号分隔。
键名:对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以加不加引号都可以。
// 如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错
// 报错
var obj = {
1p: 'Hello World'
};
// 不报错
var obj = {
'1p': 'Hello World',
'h w': 'Hello World',
'p+q': 'Hello World'
};
对象的每一个键名又称为“属性”(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"
属性可以动态创建,不必在对象声明时就指定。
var obj = {};
obj.foo = 123;
obj.foo // 123
对象的引用:如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
var o1 = {};
var o2 = o1;
o1.a = 1;
o2.a // 1
o2.b = 2;
o1.b // 2
// 这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
var x = 1;
var y = x;
x = 2;
y // 1
对象属性操作
读取:有两种方法,一种是使用点运算符,还有一种是使用方括号运算符
var foo = 'bar';
var obj = {
foo: 1,
bar: 2
};
obj.foo // 1
obj[foo] // 2
// 引用对象obj的foo属性时,如果使用点运算符,foo就是字符串;如果使用方括号运算符,但是不使用引号,那么foo就是一个变量,指向字符串bar
// 数字键可以不加引号,因为会自动转成字符串
var obj = {
0.7: 'Hello World'
};
obj['0.7'] // "Hello World"
obj[0.7] // "Hello World"
赋值:点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值
var obj = {};
obj.foo = 'Hello';
obj['bar'] = 'World';
查看 :查看一个对象本身的所有属性,可以使用Object.keys方法
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
删除:delete命令用于删除对象的属性,删除成功后返回true
var obj = { p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
属性是否存在:in 运算符
// in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
// in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。
// 这时,可以使用对象的hasOwnProperty方法判断一下,是否为对象自身的属性
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')) // false
}
属性的遍历:for...in 循环
// for...in循环用来遍历一个对象的全部属性
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log('键名:', i);
console.log('键值:', obj[i]);
}
// 键名: a
// 键值: 1
// 键名: b
// 键值: 2
// 键名: c
// 键值: 3
// 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
// 它不仅遍历对象自身的属性,还遍历继承的属性。
// 如果只想遍历自身属性,应该结合使用hasOwnProperty方法
var person = { name: '老张' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
七、函数
7.1 函数的声明
1)function 命令
function 函数名(参数1,参数2) {
// 函数体
}
// 调用
函数名()
2)函数表达式
// 将一个匿名函数赋值给变量
var print = function(s) {
console.log(s);
};
// 采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效
var print = function x(){
console.log(typeof x);
};
x
// ReferenceError: x is not defined
print()
// function
3)Function 构造函数
// Function构造函数接受三个参数,除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数。
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
7.2 函数的重复声明
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明
function f() {
console.log(1);
}
f() // 2
function f() {
console.log(2);
}
f() // 2
7.3 return 语句
JavaScript 引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即使还有语句,也不会得到执行
return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined
7.4 函数名的提升
7.5 函数的属性和方法
1、name
name: 函数的name属性返回函数的名字
如果是通过变量赋值定义的匿名函数,那么name属性返回变量名。
var f2 = function () {};
f2.name // "f2"
如果变量的值是一个具名函数,那么name属性返回function关键字之后的那个函数名
var f3 = function myName() {};
f3.name // 'myName'
name属性的一个用处,就是获取参数函数的名字。
var myFunc = function () {};
function test(f) {
console.log(f.name);
}
test(myFunc) // myFunc
2、length
函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。
// 它的length属性就是定义时的参数个数。不管调用时输入了多少个参数,length属性始终等于2
function f(a, b) {}
f.length // 2
3、**toString() **
函数的toString方法返回一个字符串,内容是函数的源码
// 对于那些原生的函数,toString()方法返回function (){[native code]}。
function f() {
a();
b();
c();
}
f.toString()
// function f() {
// a();
// b();
// c();
// }
7.6 函数作用域
作用域(scope)指的是变量存在的范围。在 ES5 的规范中,JavaScript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
对于顶层函数来说,函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。
var v = 1;
function f() {
console.log(v);
}
f()
// 1
在函数内部定义的变量,外部无法读取,称为“局部变量”
function f(){
var v = 1;
}
v // ReferenceError: v is not defined
函数内部定义的变量,会在该作用域内覆盖同名全局变量。
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1
注意,对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量
if (true) {
var x = 5;
}
console.log(x); // 5
函数内部的变量提升
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
// 等同于
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
函数本身的作用域
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域
很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数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
7.7 arguments 对象
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
function square(x) {
return x * x;
}
square(2) // 4
square(3) // 9
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递.这意味着,在函数体内修改参数值,不会影响到函数外部。
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
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
在函数体内部读取所有参数,就是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对象与函数参数不具有联动关系。也就是说,修改arguments对象不会影响到实际的函数参数
var f = function(a, b) {
'use strict'; // 开启严格模式
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1) // 2
arguments对象的length属性,可以判断函数调用时到底带几个参数
function f() {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
f() // 0
需要注意的是,虽然arguments很像数组,但它是一个对象.
如果要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
arguments对象带有一个callee属性,返回它所对应的原函数。
var f = function () {
console.log(arguments.callee === f);
}
f() // true
可以通过arguments.callee,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用。
八、数组
数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。var arr = ['a', 'b', 'c'];
定义时赋值,数组也可以先定义后赋值
任何类型的数据,都可以放入数组
如果数组的元素还是数组,就形成了多维数组。
本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object typeof [1, 2, 3] // "object"
Object.keys方法返回数组的所有键名。可以看到数组的键名就是整数0、1、2
数组的length属性,返回数组的成员数量。
更详细见后续数组详细章节。
本文参考:JavaScript 教程