文章目录
一 基本数据类型、数组
1.1 变量
变量名可以由字母、数字、下划线及美元符号组合而成。但不能以数字开头。
变量名区分大小写。
变量声明未初始化,默认初始化为undefined。
1.2 基本数据类型
- 数字 —— 包括浮点数与整数
- 字符串 —— 包括由任意数量字符组成的序列
- 布尔值 —— 包括true和false
- undefined —— 当我们试图访问一个不存在的变量时,就会得到一个特殊值:undefined。除此之外,使用已声明却未赋值的变量也会如此。因为JavaScript会自动将变量在初始化之前的值设定为undefined。而undefined类型的值只有一个——undefined。
- null —— 这是另一种只包含一个值的特殊数据类型。所谓的null值,通常是指没有值或空值,不代表任何东西。
null与undefined最大的不同在于,被赋予null的变量通常被认为是已经定义了的,只不过它不代表任何东西。
任何不属于上述五种基本类型的值都会被认为是一个对象。甚至有时候我们也会将null视为对象——一个不代表任何东西的对象(东西)。
JavaScript中的数据类型主要分为以下两个部分:
- 基本类型(上述列出的五种类型)
- 非基本类型(即对象)
1.3 查看类型操作符——typeof
该操作符返回一个代表数据类型的字符串,返回结果可能如下:
- “number”
- “string”
- “boolean”
- “undefined”
- “object”
- “function”
当一个数字以0开头时,表示一个八进制数
当一个数字以0x开关时,表示一个十六进制值
当一个数字可以表示成1e1(或者1e+1、1E1、1E+1)这样的指数形式,意思是在数字1后面加1个0,也就是10。
1.3.1 Infinity
特殊值,代表超出了JavaScript处理范围的数值。但Infinity依然是一个数字。
javaScript所能处理的最大值是1.7976931348623157e+308,而最小值为5e-324。
任何数除以0结果也为Infinity。
正负Infinity相加,不会得到0,而是会得到一个叫做NaN(Not A Number的缩写,即不是数字)的东西。
Infinity与其他任何操作数执行任何算术运算的结果也都等于Infinity。
1.3.2 NaN
不是数字,但属于数字类型,为特殊的数字。
算术运算中使用了不恰当的操作数,导致运算失败,该运算就会返回NaN。
var a = 10 * “f”;
a;
NaN
而且NaN是有传染性的,只要我们的算术运算中存在一个NaN,整个运算就会失败。
1 + 2 + NaN;
NaN
1.3.3 字符串
当一个数字字符串用于算术运算中的操作数时,该字符串会在运算中被当做数字类型来使用。***(由于加法操作符的歧义性,这条规则不适用于加法运算。)***
字符串转换操作失败则返回一个NaN值。
1.3.4 逻辑运算符
JavaScript中有三种逻辑运算符,它们都属于布尔运算。
- ! —— 逻辑非(取反)
- && —— 逻辑与
- || —— 逻辑或
除了下面所列出特定值以外(它们将被转换为false),其余大部分值在转换为布尔值时都为true。
- 空字符串""
- null
- undefined
- 数字0
- 数字NaN
- 布尔值false
这6个值有时也会被称为falsy值,而其他值则被称为truthy值(包括字符串"0"、" "、"false"等)。
1.3.5 操作符优先级
逻辑运算符优先级:! > && > ||
1.3.6 特殊的
- NaN不等于任何东西,包括它自己。
NaN == NaN
false
- 使用一个不存在的变量,控制台返回错误信息
foo;
ReferenceError: foo is not defined
- 对不存在的变量使用typeof操作符,不报错,返回字符串"undefined"
typeof foo;
undefined
- 对已声明但未赋值的变量使用typeof操作符,返回字符串"undefined"
var somevar;
somevar;
typeof somevar;
undefined
- null不能由JavaScript自动赋值
var somevar = null;
null
somevar;
null
typeof somevar;
object
- undefined和null区别
var i = 1 + undefined;
i;
NaN
var i = 1 + null;
i;
1
转换成数字
1 * undefined;
NaN
1 * null;
0
转换成布尔值
!! undefined;
false
!! null;
false
转换成字符串
"value: " + undefined;
value: undefined
"value: " + null;
value: null
二 函数
函数通常都会有返回值,如果某个函数没有显式的返回值,我们就会默认它的返回值为undefined。
2.1 预定义函数
2.1.1 parseInt()
将收到的任何值(通常是字符串)转换成整数类型输出。转换失败返回NaN。
函数还有一个可选的第二参数:基数(radix),它负责设定函数所期望的数字类型——十进制、十六进制、二进制等。
未指定第二参数时默认为十进制,除以下两个情况:
- 首参以0x开头,二参默认指定为16
- 首参以0开头,二参默认指定为8
明确指定radix值问题比较安全的,若未指定,偶尔可能会存在错误情况,比如读取日期08这样的数据。
遇到第一个异常字符时放弃。
parseInt(‘123abc’);
123
2.1.2 parseFloat()
parseFloat()的功能与parseInt()基本相同,不过仅支持输入值转换为十进制数。函数只有一个参数。
与parseInt()不同的,parseFloat()还可以接受指数形式的数据。
parseFloat(‘1e10’);
10000000000
parseInt(‘1e10’);
1
2.1.3 isNaN()
确定某个输入值是否是一个可以参与算术运算的数字。可以用来检测parseInt()和parseFloat()的调用成功与否。
isNaN(NaN);
true
isNaN(123);
false
isNaN(parseInt(‘abc123’));
true
函数会试图将所接收的输入转换为数字
isNaN(‘1.23’);
false
2.1.3 isFinite()
用于检查输入是否是一个既非Infinity也非NaN的数字。
isFinite(Infinity);
false
isFinite(1e308);
true
isFinite(1e309);
false
2.1.4 URI的编码与反编码
转义URL或URI中特殊含义的字符:
编码:
encodeURI()
返回一个可用的URL
encodeURIComponent()
认为传递的仅仅是URL的一部分
反编码:
decodeURI()
decodeURLComponent()
2.1.5 eval()
将输入的字符串当做JavaScript代码来执行。
eval(‘var ii = 2;’)
ii;
2
应尽量避免使用eval():
- 安全性方面:不确定性太大
- 性能方面:它是一种由函数执行的“动态代码”,所以要比直接执行脚本要慢。
2.1.6 alert()
非JavaScript核心的一部分,由浏览器提供。利于调试,但这样会阻塞当前的浏览器线程,对于ajax应用程序不友好。
2.2 变量的作用域
var a = 123;
function f() {
alert(a); // undefined
var a = 1;
alert(a); // 1
}
f();
重点:函数域始终优先于全局域。所以局部变量a会覆盖掉所有与它同名的全局变量,尽管在alert()第一次被调用时,a还没有被正式定义(即该值为undefined),但该变量本身已经存在于本地空间了。这种特殊的现象叫做提升(hoisting)。即当JavaScript执行过程进入新的函数时,这个函数内被声明的所有变量都会被移动(或者说提升)到函数最开始的地方。且被提升的只有变量的声明。只有函数体内声明的这些变量在该函数执行开始时就存在,而与之相关的赋值操作并不会被提升,
var a = 123;
function f() {
var a; // same as: var a = undefined;
alert(a); // undefined
a = 1;
alert(a); // 1
}
f();
2.3 函数也是数据
函数也是赋值给变量的一种数据,所以函数的命名规则与一般变量相同——函数名不能以数字开头,并且可以由任意的字母、数字、下划线和美元符号组合而成。
2.3.1 匿名函数
var f = function(a) {
return a;
}
如通过上面这种方式定义的函数常被称为匿名函数(即没有名字的函数),特别是当它不被赋值给变量单独使用的时候。在这种情况下,此函数有两种优雅的用法:
-
将匿名函数作为参数传递给其他函数,接收函数就能利用我们所传递的函数来完成某些事情。
-
定义某个函数来执行某些一次性任务。
2.3.2 回调函数
既然函数与任何可以被赋值给变量的数据是相同的,那么它当然可以像其他数据那样被定义、删除、拷贝,以及当成参数传递给其他函数。
使用回调函数的优势:
-
它可以让我们在不做命名的情况下传递函数(节省变量名的使用)
-
我们可以将一个函数调用操作委托给另一函数(节省代码编写工作)
-
它们也有助于提升性能
未使用匿名函数:
function multiplyByTwo (a, b, c, callback) {
var i, ar = [];
for (i = 0; i < 3; i++) {
ar[i] = callback(arguments[i] * 2);
}
return ar;
}
function addOne (a) {
return a + 1;
}
var myarr = [];
myarr = multiplyByTwo(1, 2, 3, addOne);
使用匿名函数:
function multiplyByTwo (a, b, c, callback) {
var i, ar = [];
for (i = 0; i < 3; i++) {
ar[i] = callback(arguments[i] * 2);
}
return ar;
}
var myarr = [];
myarr = multiplyByTwo(1, 2, 3, function (a) {
return a + 1;
});
从上面可以看出,使用匿名函数代替addOne(),这样做可以节省一个额外的全局变量,且更易于随时根据需求调整代码。
2.3.3 即时函数
匿名函数的另一种应用示例——这种函数可以在定义后立即调用。比如:
(
function() {
alert('boo');
}
)();
闭合方式有以下两种:
- 1
(function() {
// ...
}());
- 2
(function() {
// ...
})();
使用即时(自调)匿名函数的好处是不会产生任何全局变量。当然,缺点在于这样的函数无法重复执行的(除非将它放在某个循环或其他函数中)。这使得即时函数非常适合于执行一些一次性的或初始化的任务。即时函数也可以有返回值。
2.3.4 内部(私有)函数
在一个函数内部定义另一个函数。
好处:
-
有助于我们确保全局名字空间的纯净性(命名冲突机会很小)。
-
确保私有性——这使得我们可以选择只将一些必要的函数暴露给“外部世界”,而保留属于自己的函数,使它们不为该应用程序 的其他部分所用。
2.3.5 返回函数的函数
function a() {
alert('A!');
return function() {
alert('B!');
}
}
// 两种调用返回的函数方法
// 1
> var newFunc = a();
> newFunc();
// 2
> a()();
2.3.6 能重写自己的函数
由于一个函数可以返回另一个函数,因此我们可以用新的函数来覆盖旧的。
a = a(); // 执行 alert('A!');
a(); // 执行alert('B!');
这对于要执行某些一次性初始化工作的函数来说会非常有用。这样一来,该函数可以在第一次被调用后重写自己,从而避免了每次调用时重复一些不必要的操作。
可以从外面来重定义该函数,也可以从内部重写。
function a() {
alert('A!');
a = function() {
alert('B!');
};
}
2.4 闭包
2.4.1 作用域链
在某函数内定义的所有变量在该函数外是不可见的,但如果该变量是在某代码块中被定义的(如在某个if或for语句中),那它在代码块外是可见的。
var a = 1;
function f() {
var b = 1;
return a;
}
f(); // 1
b; // ReferenceError: b is not defined
如果在函数outer()中定义了另一个函数inner(),那么,在inner()中可以访问的变量既来自它自身的作用域,也可以来自其“父级”作用域。这就形成了一条作用域链接(scope chain),该链的长度(或深度)则取决于我们的需要。
2.4.2 利用闭包突破作用域链
- 闭包#1
var a = "global variable";
var F = function () {
var b = "local variable";
var N = function () {
var c = "inner local";
return b;
};
return N;
};
> b;
ReferenceError: b is not defined
> var inner = F();
> inner();
"local variable"
-
闭包#2
将F()中定义的一个新函数N()赋值给全局变量inner。由于N()是在F()内部定义的,它可以访问F()的作用域,所以即使该函数后来升级成了全局函数,但它依然可以保留对F()作用域的访问权。
var inner; // placeholder
var F = function () {
var b = "local variable";
var N = function () {
return b;
};
return N;
};
> F();
undefined
> inner();
"local variable"
-
使用函数参数做闭包。
该参数与函数的局部变量没什么不同,但它们是隐式创建的(即不需要使用var声明)。即创建一个函数,该函数将返回一个子函数,而这个子函数返回的则是其父函数的参数。
function F(param) {
var N = function() {
return param;
};
param++;
return N;
}
> var inner = F(123);
> inner();
124
函数所绑定的是作用域本身,而不是在函数定义时该作用域中的变量或变量当前所返回的值。
- 循环中的闭包
闭包典型错误:
function F() {
var arr = [], i;
for (i = 0; i < 3; i++) {
arr[i] = function () {
return i;
};
}
return arr;
}
> var arr = F();
> arr[0](); // 3
> arr[1](); // 3
> arr[2](); // 3
要知道这里为什么结果都是3
这不是我们要的结果,此处创建了三个闭包,而它们都指向了一个共同的局部变量i。但是,闭包并不会记录它们的值,它们所拥有的只是相关域在创建时的一个连接(即引用)。
纠正错误:
- 方法1:不再直接创建一个返回i的函数了,而是将i传递给了另一个即时函数。在该函数中,i就被赋值给了局部变量x,这样一来,每次迭代中的x就会拥有各自不同的值。
function F() {
var arr = [], i;
for (i = 0; i < 3; i++) {
arr[i] = (function (x) {
return function() {
return x;
};
}(i));
}
return arr;
}
> var arr = F();
> arr[0](); // 0
> arr[1](); // 1
> arr[2](); // 2
- 方法2:定义一个内部函数(不使用即时函数),在每次迭代操作中,在中间函数内将i的值“本地化”。
function F() {
function binder(x) {
return function() {
return x;
};
}
var arr = [], i;
for (i = 0; i < 3; i++) {
arr[i] = binder(i);
}
return arr;
}
> var arr = F();
> arr[0](); // 0
> arr[1](); // 1
> arr[2](); // 2
2.4.3 关于闭包的应用示例:
2.4.3.1 getter与setter
假设一个变量表示某类特定值或某特定区间内的值。不想该变量暴露给外部,需要保护在相关函数内部。
所有一切通过一个即时函数来实现,我们在其中定义了全局函数,setValue()和getValue(),并以此来确保局部变量secret的不可直接访问性。
var getValue, setValue;
(function() {
var secret = 0;
getValue = function() {
return secret;
};
setValue = function(v) {
if (typeof v === "number") {
secret = v;
}
};
}());
> getValue(); // 0
> setValue(123);
> getValue(); // 123
> setValue(false);
> getValue(); // 123
2.4.3.2 迭代器
闭包实现迭代器。将“谁是下一个”的复杂逻辑封装成易于使用的next()函数,然后只需调用它就能实现相关的遍历操作了。
// 接受数组输入的初始化函数,私有指针i始终指向数组中的下一个元素
function setup(x) {
var i = 0;
return function() {
return x[i++];
};
}
> var next = setup(['a', 'b', 'c']);
> next(); // "a"
> next(); // "b"
> next(); // "c"