[js]变量与数据类型篇
一、变量
在JavaScript中就用一个变量名表示变量,变量名是大小写英文、数字、$
和_
的组合,不能用数字开头。变量名也不能是JavaScript的关键字;
1、变量的声明
(1)var:
申明一个变量,用=
对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用var
申明一次;
ES6中新增let和const
(2)let:所声明的变量,只在let
命令所在的代码块内有效;必须先声明,不存在变量提升;不允许在相同作用域内,重复声明同一个变量;
(3)const:
声明一个只读的常量。一旦声明,常量的值就不能改变;只声明不赋值,就会报错。
2、变量作用域
(1)全局变量:声明在函数外部的变量;浏览器环境中全局变量为window属性;
(2)函数作用域:针对局部变量来说的,在函数中定义的变量在函数外不能获取;
(3)块级作用域(ES6):
二、数据类型
6种基本类型
- 数值(number):整数和小数(比如
1
和3.14
) - 字符串(string):文本(比如
Hello World
)。 - 布尔值(boolean):表示真伪的两个特殊值,即
true
(真)和false
(假) undefined
:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值null
:表示空值,即此处的值为空。- 对象(object):各种值组成的集合。
<原始类型>
1、字符串(string):
1.1 字符串
- 字符串是以单引号'或双引号"括起来的任意文本
- ASCII字符可以以
\x##
形式的十六进制表示 - JavaScript 允许采用
\uxxxx
形式表示一个字符,其中xxxx
表示字符的 Unicode 码点。(常用于字符图标)
1.2 字符串常用方法及属性
(1)获取字符长度:.length
(2)获取第n位字符 .charAt(n)
(3)获取第n位字符编码 .charCodeAt(n)
(4)拼接字符串 a.concat('123','abc')
(5)获取、搜索字符串位置方法 str.indexOf('a') str.lastIndexOf('a') 找不到时返回-1
(6)删除字符串前后的空格 str.trim()
(7)截取字符串操作方法
-
- str.slice(start,end); 两个参数可正可负,负值代表从右截取,返回值:[start,end] 也就是说返回从start到end-1的字符
- str.substring(start,end); 两个参数都为正数,返回值:[start,end) 也就是说返回从start到end-1的字符
- str.substr(start,length); 第一个参数指定子字符串开始位置,第二个参数表示返回的字符个数
(8)字符串大小写转换方法
-
str.toLowerCase();
-
str.toUpperCase();
(9)字符串分割成字符串数组 str.split(separator,limit); separator指定字符串或正则,limit指定数组的最大长度 ;str.split(""); 每个字符都被分割 ;数组变成字符串arr.join('-')
(10)字符串模式匹配方法
-
- str.match(rgExp);
- str.test(rgExp);
(11)替换第一个子字符串 str.replace(
"at"
,
"one")
1.3字符串模板语法
反引号(``)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量;
模板字符串中嵌入变量,需要将变量名写在${}
之中
2、数值(Number)
2.1 数值
- JavaScript不区分整数和浮点数,统一用Number表示 ;
- 在JavaScript的内部采用IEEE754格式来表示数字,所以不区分整数和浮点数,都是用64位浮点数的形式储存。就是说,在JavaScript内部,就根本没有小数。但是有些运算必须得需要整数完成,所以JavaScript有时会把64位的浮点数转换成32位的整数,再进行运算。
123; // 整数123
0.456; // 浮点数0.456
1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
-99; // 负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
2.2 表示方法
2.2.1 整数
- 二进制:有前缀0b的数值,出现0,1以外的数字会报错
- 八进制:有前缀0o的数值,或者是以0后面再跟一个数字(0-7)。如果超出了前面所述的数值范围,则会忽略第一个数字0,视为十进制数
- 注意:八进制字面量在严格模式下是无效的,会导致支持该模式的JavaScript引擎抛出错误
- 十六进制:有前缀0x,后跟任何十六进制数字(0~9及A~F),字母大小写都可以,超出范围会报错
- 十进制
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b
(或0B
)和0o
(或0O
)表示
0b111110111 === 503 // true
0o767 === 503 // true
2.2.2 浮点数
所谓浮点数,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。与整数不同,浮点数只能用十进制来表示
浮点数的精度远远不如整数,所以设计浮点数的运算好比较要小心
console.log(0.1 + 0.2 == 0.3); //false 0.30000000000000004
console.log(0.6/0.2); //2.9999999999999996
2.2.3科学计数法
对于那些极大极小的数值,可以用e表示法(即科学计数法)表示的浮点数值表示。用e表示法表示的数值等于e前面的数值乘以10的指数次幂
以下两种情况,JavaScript会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。
- 小数点前的数字多余21位
console.log(1234567890123456789012);// 1.2345678901234568e+21
console.log(123456789012365648787); //123456789012365660000
- 小数点后面的0多余5位
console.log(0.0000006);//6e-7
console.log(0.000006); //0.000006
2.3 范围
- 由于内存的限制,ECMAScript并不能保存世界上所有的数值,所以就有了最大值和最小值
- 最小值保存在Number.MIN_VALUE中,这个值是5e-324
- 最大值保存在Number.MAX_VALUE,这个值是1.7976931348623157e+308
console.log(Number.MIN_VALUE) //5e-324
console.log(Number.MAX_VALUE); //1.7976931348623157e+308
- 如果数字超过最大值,javascript会返回Infinity,这称为正向溢出(overflow);
- 如果等于或超过最小负值-1023(即非常接近0),javascript会直接把这个数转为0,这称为负向溢出(underflow)
- 如果要想确定一个数值是不是有穷的,可以使用isFinite()函数。这个函数在参数位于最小与最大数值之间时会返回true
var result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); //false
2.4 常见问题
typeof NaN // 'number' ---
NaN
不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number
NaN === NaN // false ---
NaN
不等于任何值,包括它本身(1 / +0) === (1 / -0) // false ---除以正零得到
+Infinity
,除以负零得到-Infinity
,这两者是不相等的
2.4 数值常用方法
parseInt('123',10) 用于将字符串转为整数,方法还可以接受第二个参数(2到36之间),表示被解析的值的进制
parseFloat(
'314e-2'
)
用于将一个字符串转为浮点数。
isNaN(
123
)
用于方法可以用来判断一个值是否为NaN
3、布尔值(boolean)
(1)布尔值代表“真”和“假”两个状态。“真”用关键字true
表示,“假”用关键字false
表示。布尔值只有这两个值。
(2)以下运算返回布尔值:
-
- 两元逻辑运算符:
&&
(And),||
(Or) - 前置逻辑运算符:
!
(Not) - 相等运算符:
===
,!==
,==
,!=
- 比较运算符:
>
,>=
,<
,<=
- 两元逻辑运算符:
(3)下面六个值被转为false
,其他值都视为true
-
undefined
null
false
0
NaN
""
或''
(空字符串)
<合成类型>
4、对象(object)
4.1对象基本概念
- 合成类型(complex type)
- 对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型;
- 对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合;
- JavaScript 规定,如果行首是大括号,一律解释为语句(即代码块)。如果要解释为表达式(即对象),必须在大括号前加上圆括号。
4.2键名
- 对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键值),所以加不加引号都可以;
- 如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错;
- 对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加;
4.3对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
4.4对象的方法
- Object.keys查看一个对象本身的所有属性
delete
命令用于删除对象的属性,删除成功后返回true;只有一种情况,
delete
命令会返回false
,那就是该属性存在,且不得删除(Object.defineProperty);delete
命令只能删除对象本身的属性,无法删除继承的属性in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true
,否则返回false;
in
运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。for...in
循环用来遍历一个对象的全部属性- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
- 它不仅遍历对象自身的属性,还遍历继承的属性。
hasOwnProperty
方法,在循环内部判断一下,某个属性是否为对象自身的属性。
with
语句用于操作同一个对象的多个属性时,提供一些书写的方便
var obj = {p1: 1, p2: 2,}; with (obj) { p1 = 4; p2 = 5; }
5、数组(object)
5.1 数组的基本概念
- 数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示;
- 本质上,数组属于一种特殊的对象。
typeof
运算符会返回数组的类型是object;
- 当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole) ;
- 数组的空位是可以读取的,返回
undefined;
数组的某个位置是空位,与某个位置是
undefined
,是不一样的。如果是空位,使用数组的forEach
方法、for...in
结构、以及Object.keys
方法进行遍历,空位都会被跳过
5.2 数组的构造方法
Array
是 JavaScript 的原生对象,同时也是一个构造函数,可以用它生成新的数组。
var arr = new Array(2);
Array
构造函数对于不同的参数,返回不一样的结果
// 无参数时,返回一个空数组 new Array() // [] // 单个正整数参数,表示返回的新数组的长度 new Array(1) // [ empty ] // 非正整数的数值作为参数,会报错 new Array(3.2) // RangeError: Invalid array length new Array(-3) // RangeError: Invalid array length // 单个非数值(比如字符串、布尔值、对象等)作为参数, // 则该参数是返回的新数组的成员 new Array('abc') // ['abc'] new Array([1]) // [Array[1]] // 多参数时,所有参数都是返回的新数组的成员 new Array(1, 2) // [1, 2] new Array('a', 'b', 'c') // ['a', 'b', 'c']
5.3 数组的length属性
(1)arr.length 数组的length
属性,返回数组的成员数量
-
- JavaScript 使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有 4294967295 个(232 - 1)个,也就是说
length
属性的最大值就是 4294967295
- JavaScript 使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有 4294967295 个(232 - 1)个,也就是说
-
- 如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的
delete
命令删除一个数组成员,会形成空位,并且不会影响length
属性
(2)length
属性是可写的
-
- 如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到
length
设置的值 ;清空数组的一个有效方法,就是将length
属性设为0 - 如果人为设置
length
大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位 ;length
属性设为大于数组个数时,读取新增的位置都会返回undefined
- 如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到
(3)可使用检查某个键名是否存在的运算符in
。
5.4 数组静态方法Array.isArray()
Array.isArray
方法返回一个布尔值,表示参数是否为数组。它可以弥补typeof
运算符的不足
5.5数组常见方法
(1)valueOf
方法是一个所有对象都拥有的方法,表示对该对象求值;alueOf
方法返回数组本身;
(2)toString
方法也是对象的通用方法,数组的toString
方法返回数组的字符串形式。
(3)push
方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。
(4)pop
方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组。对空数组使用pop
方法,不会报错,而是返回undefined
(5)shift
方法用于删除数组的第一个元素,并返回该元素。shift
方法可以遍历并清空一个数组。
(6)unshift
方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。
(7)join
方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。如果数组成员是undefined
或null
或空位,会被转成空字符串。
var a = [1, 2, 3, 4]; a.join(' ') // '1 2 3 4' a.join(' | ') // "1 | 2 | 3 | 4" a.join() // "1,2,3,4"
(8)concat
方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变。
['hello'].concat(['world']) // ["hello", "world"] ['hello'].concat(['world'], ['!']) // ["hello", "world", "!"] [].concat({a: 1}, {b: 2}) // [{ a: 1 }, { b: 2 }] [2].concat({a: 1}) // [2, {a: 1}]
(9)reverse
方法用于颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组。
(10)slice
方法用于提取目标数组的一部分,返回一个新数组,原数组不变。arr.slice(start, end);
slice
方法的一个重要应用,是将类似数组的对象转为真正的数组。
Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 }) // ['a', 'b'] Array.prototype.slice.call(document.querySelectorAll("div")); Array.prototype.slice.call(arguments);
(11)splice
方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。
arr.splice(start, count, addElement1, addElement2, ...);
var a = ['a', 'b', 'c', 'd', 'e', 'f']; a.splice(4, 2) // ["e", "f"] a // ["a", "b", "c", "d"]
var a = ['a', 'b', 'c', 'd', 'e', 'f']; a.splice(4, 2, 1, 2) // ["e", "f"] a // ["a", "b", "c", "d", 1, 2]
var a = [1, 1, 1]; a.splice(1, 0, 2) // [] a // [1, 2, 1, 1]
var a = [1, 2, 3, 4]; a.splice(2) // [3, 4] a // [1, 2] 如果只提供第一个参数,等同于将原数组在指定位置拆分成两个数组
(12)sort
方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。如果想让sort
方法按照自定义方式排序,可以传入一个函数作为参数。
[ { name: "张三", age: 30 },{ name: "李四", age: 24 },{ name: "王五", age: 28 }].sort(function (o1, o2) {return o1.age - o2.age;})
(13)map
方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回;
var numbers = [1, 2, 3]; numbers.map(function (n) { return n + 1; });// [2, 3, 4] numbers// [1, 2, 3]
map
方法的回调函数有三个参数,elem
为当前成员的值,index
为当前成员的位置,arr
为原数组([1, 2, 3]
)。map
方法还可以接受第二个参数,用来绑定回调函数内部的this
变量
(14)forEach
方法与map
方法很相似,也是对数组的所有成员依次执行参数函数。但是,forEach
方法不返回值,只用来操作数据。这就是说,如果数组遍历的目的是为了得到返回值,那么使用map
方法,否则使用forEach
方法。
- 注意,
forEach
方法无法中断执行,总是会将所有成员遍历完。如果希望符合某种条件时,就中断遍历,要使用for
循环 ; forEach
方法不会跳过undefined
和null
,但会跳过空位。;
var out = []; [1, 2, 3].forEach(function(elem) { this.push(elem * elem); }, out); out // [1, 4, 9]
(15)filter
方法用于过滤数组成员,满足条件的成员组成一个新数组返回。
它的参数是一个函数,所有数组成员依次执行该函数,返回结果为true
的成员组成一个新数组返回。该方法不会改变原数组
[1, 2, 3, 4, 5].filter(function (elem) { return (elem > 3); }) // [4, 5]
(16)some
方法是只要一个成员的返回值是true
,则整个some
方法的返回值就是true
,否则返回false
。
var arr = [1, 2, 3, 4, 5]; arr.some(function (elem, index, arr) { return elem >= 3; }); // true
(17)every
方法是所有成员的返回值都是true
,整个every
方法才返回true
,否则返回false
。
var arr = [1, 2, 3, 4, 5]; arr.every(function (elem, index, arr) { return elem >= 3; }); // false
(18)reduce
方法和reduceRight
方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,reduce
是从左到右处理(从第一个成员到最后一个成员),reduceRight
则是从右到左(从最后一个成员到第一个成员),其他完全一样。
[1, 2, 3, 4, 5].reduce(function (a, b) { console.log(a, b); return a + b; }) // 1 2 // 3 3 // 6 4 // 10 5 //最后结果:15
(19)indexOf
方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1
。
indexOf
方法还可以接受第二个参数,表示搜索的开始位置。
['a', 'b', 'c'].indexOf('a', 1) // -1
(20)lastIndexOf
方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1
。
6、函数(Function)
- 函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。
- JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
6.1函数的声明
(1)function
命令声明的代码区块,就是一个函数。function
命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。
function print(s) { console.log(s); }
(2)采用变量赋值的写法
var print = function(s) { console.log(s); };
(3)Function
构造函数
Function
构造函数接受三个参数,除了最后一个参数是add
函数的“函数体”,其他参数都是add
函数的参数
var add = new Function( 'x', 'y', 'return x + y' );
6.2函数的使用
(1)调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数。
(2)函数体内部的return
语句,表示返回。JavaScript 引擎遇到return
语句,就直接返回return
后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return
语句所带的那个表达式,就是函数的返回值。return
语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined
(3)函数可以调用自身,这就是递归(recursion)。下面就是通过递归,计算斐波那契数列的代码。
function fib(num) { if (num === 0) return 0; if (num === 1) return 1; return fib(num - 2) + fib(num - 1); } fib(6) // 8
6.3函数与数据类型
- JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
- 由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
function add(x, y) { return x + y; } // 将函数赋值给一个变量 var operator = add; // 将函数作为参数和返回值 function a(op){ return op; } a(add)(1, 1) // 函数柯里化
// 2
6.4函数名的提升
JavaScript 引擎将函数名视同变量名,所以采用function
命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错
f(); function f() {}
表面上,上面代码好像在声明之前就调用了函数f
。但是实际上,由于“变量提升”,函数f
被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。
f(); var f = function (){}; // TypeError: undefined is not a function
6.5不得在非函数的代码块中声明函数,最常见的情况就是if
和try
语句
if (foo) { function x() {} } try { function x() {} } catch(e) { console.log(e); }
6.6函数的属性和方法
(1)f.name 函数的name
属性返回函数的名字
(2)f.length
属性返回函数预期传入的参数个数,即函数定义之中的参数个数
(3)f.toString
方法返回一个字符串,内容是函数的源码
6.7函数的作用域
(1)作用域(scope)指的是变量存在的范围。在 ES5 的规范中,Javascript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
- 函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。
- 在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)
- 函数内部定义的变量,会在该作用域内覆盖同名全局变量。
(2)与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var
命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部
function foo(x) { if (x > 100) { var tmp = x - 100; } } // 等同于 function foo(x) { var tmp; if (x > 100) { tmp = x - 100; }; }
(3)函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1; var x = function () { console.log(a); }; function f() { var a = 2; x(); } f() // 1
函数x
是在函数f
的外部声明的,所以它的作用域绑定外层,内部变量a
不会到函数f
体内取值,所以输出1
,而不是2
。
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
同样的,函数体内部声明的函数,作用域绑定函数体内部
function foo() { var x = 1; function bar() { console.log(x); } return bar; } var x = 2; var f = foo(); f() // 1
6.8参数
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
(1)函数参数不是必需的,Javascript 允许省略参数。
(2)如果有同名的参数,则取最后出现的那个值
(3)arguments 对象
-
由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是
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
对象可以在运行时修改。
严格模式下,arguments
对象是一个只读对象,修改它是无效的,但不会报错。
通过arguments
对象的length
属性,可以判断函数调用时到底带几个参数。
虽然arguments
很像数组,但它是一个对象。数组专有的方法(比如slice
和forEach
),不能在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
6.9闭包
变量作用域。前面提到,JavaScript 有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。
var n = 999; function f1() { console.log(n); } f1() // 999
上面代码中,函数f1
可以读取全局变量n
。但是,函数外部无法读取函数内部声明的变量。
function f1() { var n = 999; } console.log(n)
上面代码中,函数f1
内部声明的变量n
,函数外是无法读取的
如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
function f1() { var n = 999; function f2() { console.log(n); // 999 } }
上面代码中,函数f2
就在函数f1
内部,这时f1
内部的所有局部变量,对f2
都是可见的。但是反过来就不行,f2
内部的局部变量,对f1
就是不可见的。这就是 JavaScript 语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2
可以读取f1
的局部变量,那么只要把f2
作为返回值,我们不就可以在f1
外部读取它的内部变量了吗
function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999
上面代码中,函数f1
的返回值就是函数f2
,由于f2
可以读取f1
的内部变量,所以就可以在外部获得f1
的内部变量了
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。
function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7
上面代码中,start
是函数createIncrementor
的内部变量。通过闭包,start
的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc
使得函数createIncrementor
的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。为什么会这样呢?原因就在于inc
始终在内存中,而inc
的存在依赖于createIncrementor
,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。
闭包的另一个用处,是封装对象的私有属性和私有方法
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
的私有变量。
注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
6.10立即调用的函数表达式
在 Javascript 中,圆括号()
是一种运算符,跟在函数名之后,表示调用该函数。比如,print()
就表示调用print
函数。
(function(){ /* code */ }()); // 或者 (function(){ /* code */ })();
上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE。
注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能就会报错。
!function () { /* code */ }(); ~function () { /* code */ }(); -function () { /* code */ }(); +function () { /* code */ }();
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
// 写法一 var tmp = newData; processData(tmp); storeData(tmp); // 写法二 (function () { var tmp = newData; processData(tmp); storeData(tmp); }());
7、null 和 undefined
7.1 undefined
是一个表示”此处无定义”的原始值,转为数值时为NaN
7.2 null
是一个表示“空”的对象,转为数值时为0
;
if (!undefined) { console.log('undefined is false'); } // undefined is false if (!null) { console.log('null is false'); } // null is false undefined == null // true Number(null) // 0 5 + null // 5 Number(undefined) // NaN 5 + undefined // NaN
三、数据类型判断
1、typeof
typeof
运算符可以返回一个值的数据类型
- 数值、字符串、布尔值分别返回
number
、string
、boolean
- 函数返回
function
undefined
返回undefined
- 对象返回
object
typeof 123 // "number" typeof '123' // "string" typeof false // "boolean" function f() {} typeof f // "function" typeof undefined // "undefined" typeof window // "object" typeof {} // "object" typeof [] // "object"
2、instanceof
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例
(1)instanceof
运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。
var v = new Vehicle(); v instanceof Vehicle // true
(2)由于instanceof
检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true
var d = new Date(); d instanceof Date // true d instanceof Object // true
(3)instanceof
的原理是检查右边构造函数的prototype
属性,是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null
对象。这时,instanceof
判断会失真。
var obj = Object.create(null); typeof obj // "object" Object.create(null) instanceof Object // false
(4)区别array和obj
var x = [1, 2, 3]; var y = {}; x instanceof Array // true y instanceof Object // true
(5)instanceof
运算符只能用于对象,不适用原始类型的值。
var s = 'hello'; s instanceof String // false
(6)对于undefined
和null
,instanceOf
运算符总是返回false
undefined instanceof Object // false null instanceof Object // false
3、Object.prototype.toString
(1)基本类型判断
console.log(Object.prototype.toString.call("jerry"));//[object String] console.log(Object.prototype.toString.call(12));//[object Number] console.log(Object.prototype.toString.call(true));//[object Boolean] console.log(Object.prototype.toString.call(undefined));//[object Undefined] console.log(Object.prototype.toString.call(null));//[object Null]
(2)判断原生引用类型
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object] console.log(Object.prototype.toString.call(function(){}));//[object Function] console.log(Object.prototype.toString.call([]));//[object Array] console.log(Object.prototype.toString.call(new Date));//[object Date] console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
(3)自定义类型
这种方法不能准确判断person
是Person
类的实例,而只能用instanceof
操作符来进行判断
function Person(name, age) { this.name = name; this.age = age; } var person = new Person("Rose", 18); Object.prototype.toString.call(arr); // "[object Object]"
注:
部分内容引用和参考:JavaScript 标准参考教程