JavaScript数据类型
1、概述
1、简介
JavaScript 语言的每一个值,都属于某一种数据类型.
JavaScript 的数据类型,共有六种(ES6增加了一个Symbol(符号)类型的值),总共7种:
数值(number):整数和小数
字符串(string):文本
布尔值(boolean):表示真伪的两个特殊值
undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
null:表示空值,即此处的值为空
对象(object):各种值组成的集合
1、(简单)原始类型(primitive type)的值:数值、字符串、布尔值 、undefined、null、symbol.
undefined和null属于特殊值
2、复杂的数据类型:对象是,又可以分成三个子类型。
狭义的对象(object)
数组(array)
函数(function)
2、typeof 运算符
确定值属于什么类型的值的确定方法:
typeof运算符
instanceof运算符
Object.prototype.toString方法
typeof运算符:如果变量没有声明就放在typeof后面,这时不会报错,会返回undefined,这个特点通常用在判断语句。例子:if(typeof v === "undefined"{//…… }
在typeof运算中。(空)数组[]和(空)对象{}都返回Object,说明js内部数组本质上是一种特殊对象;
而intanceof运算会详细分辨并返回数组和对象。
typeof(null)返回的是Object .
2、null undefined 和 boolean
1.1、概述
null与undefined都可以表示“没有”,含义非常相似,将一个变量赋值为undefined或null,老实说,语法效果几乎没区别。在if语句中,它们都会被自动转为false,相等运算符(==)甚至直接报告两者相等。
区别是这样的:null是一个表示“空”的对象,转为数值时为0;undefined是一个表示"此处无定义"的原始值,转为数值时为NaN。
1.2、用法和含义
null表示空值,即该处的值现在为空,调用函数时某个参数未设置任何值,就可以传入null,表示该参数为空。
undefined表示未定义。 未定义可以理解为:
变量声明了,但没有赋值。
调用函数时该提供的参数没有提供。
对象没有赋值的属性。
函数没有返回值时默认返回undefined.
常用举例:
1、如果一个变量没有赋值,那么(语法、必须)就是undefined;
2、如果有个对象,现在不想赋值,那么推荐使用null,undefined也不是不可以。(惯例)
3、如果有一个非对象,比如number\string\boolean,现在不想赋值,推荐现在初始化成undefined.(惯例)
2、布尔值
布尔值代表“真”和“假”两个状态。“真”用关键字true表示,“假”用关键字false表示。布尔值只有这两个值。
下列运算符会返回布尔值:
前置逻辑运算符:!(not)
相等运算符:===,!==,==,!=
比较运算符:>,>=,<,<=
如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个被转为false之外,其他都为ture. undefined\null \false \NaN\0\空字符串:‘’或"".
布尔值常常用于程序流程的控制。
空数组[]、空对象{}的布尔值都是ture.
3、数值number
1、概述
1.1、整数和浮点数
js内所有数字都是64位浮点数形式存储的,所以1和1.0是严格意义上也是相同的数。
1.2、数值精度
根据国际标准 IEEE 754,JavaScript 浮点数的64个二进制位,从最左边开始,是这样组成的。
第1位:符号位,0表示正数,1表示负数
第2位到第12位(共11位):指数部分,指数部分在0到2047之间
第13位到第64位(共52位):小数部分(即有效数字)
精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-253到253,都可以精确表示,大于2的53次方以后,多出来的有效数字都会无法保存,变成0。
1.3、数值范围
JavaScript 能够表示的数值范围为21024到2-1023(开区间),超出这个范围的数无法表示,如果一个数大于等于2的1024次方,那么就会发生“正向溢出”,即 JavaScript 无法表示这么大的数,这时就会返回Infinity,负向溢出时,即 JavaScript 无法表示这么小的数,这时会直接返回0。
JavaScript 提供Number对象的MAX_VALUE和MIN_VALUE属性,返回可以表示的具体的最大值和最小值。
2、数值的表示法
JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。
小数点前的数字多于21位
小数点后的零多于5个。
3、数值的进制
十进制:没有前导零的数值
八进制:有前缀0o或0O的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。
十六进制:有前缀0x或0X的数值
二进制:有前缀0b或0B的数值
默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制,举例:oxff // 255;
0o377 // 255;
0b11 // 3
通常来说,有前导0的数值会被视为八进制,但是如果前导0后面有数字8和9,则该数值被视为十进制。
4、特殊的数值
4.1、正零和负零
JavaScript 内部实际上存在2个0:一个是+0,一个是-0,区别就是64位浮点数表示法的符号位不同。它们是等价的。
几乎所有场合,正零和负零都会被当作正常的0
唯一有区别的场合是,+0或-0当作分母,返回的值是不相等的,之所以出现这样结果,是因为除以正零得到+Infinity,除以负零得到-Infinity
4.2、NaN
表示“非数字”(Not a Number)。举例:
主要出现在将字符串解析成数字出错的场合:5 - 'x' // NaN
另外,一些数学函数的运算结果会出现NaN:Math.log(-1) // NaN;
0除以0也会得到NaN。
NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number
运算中:NaN不等于任何值,包括它本身
NaN与任何数(包括它自己)的运算,得到的都是NaN
4.3、infinity含义
Infinity有正负之分,Infinity表示正的无穷,-Infinity表示负的无穷。Infinity === -Infinity // false
Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)
Infinity与NaN比较,总是返回false
Infinity的四则运算,符合无穷的数学计算规则.
一些特殊情况:
0乘以Infinity,返回NaN;0除以Infinity,返回0;Infinity除以0,返回Infinity。
Infinity加上或乘以Infinity,返回的还是Infinity
Infinity减去或除以Infinity,得到NaN
Infinity与null计算时,null会转成0,等同于与0的计算。
Infinity与undefined计算,返回的都是NaN
5、与数值相关的全局用法:
5.1.1、paraseInt基本用法
paraseInt()方法将字符串转换为整数
如果字符串头部有空格,会自动去除
如果paraseInt的参数不是字符串,会先将变成字符串再变数字
字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。
如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN
所以,parseInt的返回值只有两种可能,要么是一个十进制整数,要么是NaN
5.1.2、进制转换
parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。举例:parseInt('1000', 2) // 8
默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制:
举例:
parseInt('1000') // 1000
// 等同于
parseInt('1000', 10) // 1000
如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。如果第二个参数是0、undefined和null,则直接忽略。
如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。如果最高位无法转换,则直接返回NaN。 举例:
parseInt('1546', 2) // 1
parseInt('546', 2) // NaN
上面代码中,对于二进制来说,1是有意义的字符,5、4、6都是无意义的字符,所以第一行返回1,第二行返回NaN.
JavaScript 不再允许将带有前缀0的数字视为八进制数,而是要求忽略这个0。但是,为了保证兼容性,大部分浏览器并没有部署这一条规定。
5.2、paraseFlaot()
parseFloat方法用于将一个字符串转为浮点数:parseFloat('3.14') // 3.14
如果字符串符合科学计数法,则会进行相应的转换。
如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分。举例:parseFloat('3.14more non-digit characters') // 3.14
parseFloat方法会自动过滤字符串前导的空格。
如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回NaN
parseFloat会将空字符串转为NaN
5.3、isNaN()
isNaN方法可以用来判断一个值是否为NaN,返回结果为ture or false.
isNaN只对数值有效,如果传入其他值,会被先转成数值,比如,传入字符串的时候,字符串会被先转成NaN,所以最后返回true,这一点要特别引起注意。也就是说,isNaN为true的值,有可能不是NaN,而是一个字符串。出于同样的原因,对于对象和数组,isNaN也返回true。
但是,对于空数组和只有一个数值成员的数组,isNaN返回false
5.4、isFinite()
isFinite方法返回一个布尔值,表示某个值是否为正常的数值
除了Infinity、-Infinity、NaN和undefined这几个值会返回false,isFinite对于其他的数值都会返回true
4、字符串
1、概述
1.1、定义
字符串就是零个或多个排在一起的字符,放在引号或双引号中。
单双引号之间可以随便内外放置;
如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此
HTML 语言的属性值使用双引号,所以很多项目约定 JavaScript 语言的字符串只使用单引号,当然,只使用双引号也完全可以。
字符串默认只能写在一行内,分成多行将会报错。必须分成多行,可以在每一行的尾部使用反斜杠
连接运算符(+)可以连接多个单行字符串,将长字符串拆成多行书写,输出的时候也是单行
如果想输出多行字符串,有一种利用多行注释的变通方法:(function () { /*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
// "line 1
// line 2
// line 3"
1.2、转义
反斜杠(\)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符.
如果在非特殊字符前面使用反斜杠,则反斜杠会被省略.
\0 //null
\b //后退键
\f //换页符
\n //换行符
\r //回车键
\t //制表符
\v //垂直制表符
\’ //单引号
\" //双引号
\\ //反斜杠
多行字符串的写法:
在换行的位置写一个反斜杠;var s = '123\
345\
78';
分成几个部分:var s = '123'+'345'+'78';
(ES6)只用反引号开头结束,就可以随便换行:换行的话空格会被带上,所以length会加一个。
var s = `12345
23445`
\ 的其它三种用法:
\HHH 反斜杠后紧跟着三个八进制数(000 - 377),代表一个字符,HHH对应该字符的 Unicode 码点。
\xHH \x后面紧跟两个十六进制数(00到FF),代表一个字符,HH对应该字符的 Unicode 码点。
\uXXXX \u后面紧跟四个十六进制数(0000到FFFF),代表一个字符。XXXX对应该字符的 Unicode 码点.
1.3、字符串与数组
字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。
如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回undefined。
字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。字符串内部的单个字符无法改变和增删,
1.4、length属性:
length属性返回字符串的长度,该属性也是无法改变的。
2、字符集
JavaScript引擎不能自动识别编号大于0xFFFF(16位/bite)的Unicode字符,原因是js引擎创建之后才发表的Unicode字符集和UTf-8编码.
JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode字符集表示。
\u00A9表示版权符号:"?"。
f\y006F\u006F表示'foo'。
"".length 为 2 ,但不是因为 "" 是两个字符,而是一个字符,因为它是大于Unicode字符集的能表示的范围,但是js只能识别16位的Unicode 字符,所以js取它的前16个位,其长度为1个字符,但是JS强制规定长度为2.这是js上一个bug.
JavaScript 不仅以 Unicode 储存字符,还允许直接在程序中使用 Unicode 码点表示字符,即将字符写成\uxxxx的形式,其中xxxx代表该字符的 Unicode 码点。 解析代码的时候,JavaScript 会自动识别一个字符是字面形式表示,还是 Unicode 形式表示。输出给用户的时候,所有字符都会转成字面形式。
每个字符在js中都是以16位即两个字节的 UTF-16格式存储,也就是说js的单位字符长度固定为16位,即两个字节。
对于码点在U+10000到U+10FFFF之间的字符,长度为32位(即4个字节),而且前两个字节在0xD800到0xDBFF之间,后两个字节在0xDC00到0xDFFF之间。
总结一下,对于码点在U+10000到U+10FFFF之间的字符,JavaScript 总是认为它们是两个字符(length属性为2)。所以处理的时候,必须把这一点考虑在内,也就是说,JavaScript 返回的字符串长度可能是不正确的。这是历史原因导致的。
3、Base64转码
Base64就是一种编码方法:可以将任意值转换成0-9、A-Z、a-z、+ 和 / 这64个字符组成的可打印字符,即基于以上64个js引擎可打印的字符来表示二进制数据的表示方法。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。
有时,文本里面包含一些不可打印的符号,比如 ASCII 码0到31的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用 Base64 编码。
JavaScript 原生提供两个 Base64 相关的方法:
全局函数btoa() //任意值转为Base64编码
全局函数 atob() //Base64编码转为原来的值
注意,这两个方法不适合非 ASCII 码的字符,会报错。
要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法,举例:
function b64Encode(str) {
return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
return decodeURIComponent(atob(str));
}
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
5、对象
1、概述
1.1、对象生成方法:
object对象就是js中键值对(成员)的集合,是一种符合数据集合,就是hash table.
obj是js中最重要的数据类型,最核心概念。
obj = {foo:'hello',bar : 'world'}
1.2、键名
所有键名都是字符串,ES6又引入了symbol值也可以作为键名,键名加不加引号都一样,一律视为字符串。但是如果键名不加引号,则必须是符合标识符的规定。
如果键名是数值,会自动被转化为字符串。
对象里面的value值也可以是对象。
如果键名不符合标识符的规范,则必须写在引号中,否则会报错。
对象的每一个键名又称为属性,键值可以是任何数据,如果一个属性的值为函数,则 通常把这个属性称为‘方法’,它可以像函数那样调用。如果属性的值还是一个对象,就成了链式引用。
对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加
属性可以动态创建:不必在对象声明时就指定,举例:var obj = {};
obj.foo = 123;
obj.foo //123
1.3、对象的引用
如果用不同的变量指向同一个对象,那么他们都是这个对象的引用,也就是说不同变量名指向同一个内存地址,修改一个变量会影响到其他所有的变量。
对于引用的不同操作情况:(以下两种情况只限在对象,若不同变量指向的是不同的原始数据,变量这时都是值的拷贝。)
如果不同变量指向同一个对象,修改其中一个变量,会影响到其他变量:
var o1 = {};
var o2 = o1;
o1.a = 1;
o2.a //1
o2.b = 2;
o1.b //2
如果取消某一个变量对于原对象的引用,不会影响到另一个变量:
var o1 = {};
var o2 = o1;
o1 = 1;
o2.a //{}
1.4、表达式还是语句?
js引擎读到表达式和语句分不清时一律视为代码块,只有代码块才能被执行,表达式不能被执行。举例:{ console.log(123) } // 123
如果想把花括号里的分不清表达式还是语句的部分解释为对象就必须在花括号前加上圆括号,因为圆括号里面的只能是表达式。这种差异在eval语句(作用是对字符串求值)中反映得最明显
2、属性(对象中的键名)的操作
2.1、属性的读取:
读取对象的属性,有两种方法:
使用点运算符:var obj ={p:'hello world'};
obj.p //'hello world';里面的键名字符串符合标识符规则的时候才可以用。
使用方括号运算符:var obj ={p:'hello world'};
obj['p'] //'hello world';
如果键名是数字时可以不加引号,其他的都要加,不然以变量来使用。
2.2、属性的赋值:
点运算符和方括号运算符也可以用来赋值。
使用点运算符:var obj ={};
obj.foo = 'hello world'
使用方括号运算符:var obj ={};
obj['p'] ='hello world';
举例:var obj = {p:'1'} 等价于 var obj = {};
obj['p'] = '1'或obj.p = '1'
2.3、属性的查看:
Object.keys()方法:
举例:var obj = {key1:1,key2:2};
Object.keys(obj);
//['key1','key2']
2.4、属性的删除:delete命令
用delete命令删除成功后返回true.该方法属性被删除后他的对应的值也会被删除。
而obj['key'] = undefined;方法删除后键名会被删除,但是对应的键值不会被删。
var obj = {p:1};
delete obj.p //true
obj.p //undefined
Object.keys(obj) //[]
object.keys(obj)这个API能查出来obj有哪些key.
2.5、属性的删除:delete命令
删除一个不存在的属性,delete不报错,而且返回true,因此不能根据delete命令的结果,认定某个属性是存在的。
只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。举例: var obj = Object.defineProperty({}, 'p', {
value: 123,
configurable: false
});
obj.p // 123
delete obj.p // false
另外,需要注意的是,delete命令只能删除对象本身的属性,无法删除继承的属性.
2.6、属性是否存在: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
}
2.7、属性的遍历:for …… in循环
for in 循环用来遍历对象的全部属性,但是遍历顺序不确定。
var obj = {a: 1, b: 2, c: 3};
for(var i in obj)
console.log('键名:',i);
console.log('键值:',obj[i]);
它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性.
它不仅遍历对象自身的属性,还遍历继承的属性.但是一般情况下,都是只想遍历对象自身的属性,
所以使用for...in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性,举例:
var person = { name: '老张' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
对象都继承了toString属性,但是for...in循环不会遍历到这个属性.
toString属性默认不会被遍历。
2.8、几种常见的创建对象的方法:
1、var obj = {};
2、var obj = new Object();
3、var obj = Object.create(Object.prototype);
3、with语句
格式: with(对象){语句;}
作用:操作同一个对象的多个属性时提供书写的方便。
举例1:
var obj = {
p1: 1,
p2: 2,
};
with (obj) {
p1 = 4;
p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;
举例2:
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区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。这是因为with区块没有改变作用域, 它的内部依然是当前作用域。这造成了with语句的一个很大的弊病,就是绑定对象不明确。
因此,建议不要使用with语句,可以考虑用一个临时变量代替with:举例:
with(obj1.obj2.obj3) {
console.log(p1 + p2);
}
// 可以写成
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);
6、函数
1、概述
1.1、函数的声明
js有三种声明函数的方法:
function命令:function print(s){console.log(s);
}
采用变量赋值的写法:var print = function(s){console.log(s);
} //这种写法将一个匿名函数赋值给变量,这个匿名函数称为函数表达式,因为赋值语句的等号右侧只能放表达式。 采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。 下面的形式声明函数也非常常见。var f = function f() {};
需要注意的是,函数的表达式需要在语句的结尾加上分号,表示语句结束。而函数的声明在结尾的大括号后面不用加分号。
Function构造函数:你可以传递任意数量的参数给Function构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。总的来说,这种声明函数的方式非常不直观,几乎无人使用。
function add(x, y) {
return x + y;
}
1.2、函数的重复声明
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。
function f() {
console.log(1);
}
f() // 2
function f() {
console.log(2);
}
f() // 2
1.3、圆括号里运算符、return 语句和递归
函数调用时要使用圆括号运算符,圆括号中写函数的参数,函数名(参数)//调用函数
举例:
function add(x,y){
return x + y;
}
add(1,2) //3
函数体内部的return语句,表示返回。JavaScript 引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return语句所带的那个表达式,就是函数的返回值。return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined。
函数可以调用自身,就是递归:例子:
function fib(num){
if(num === 0) return 0;
if(num === 1) return 1;
return fib(num - 2) + fib(nun - 1)
}
fib(6) //8
1.4、第一等公民
JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。 凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。 函数只是一个可以执行的值,此外并无特殊之处。
由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
1.5、函数名的提升
js引擎将函数名视同变量名,采用function声明函数时整个函数跟声明变量一样,被提升到代码块头部。这就是函数名的提升。
但是如果采用赋值语句定义函数,必须最后写赋值语句,否则报错,返回undefined.
2、函数的属性和方法:
2.1、name属性
如果是function声明的函数:name属性返回函数的名字;举例:function f1(){};
fi,name //'f1'
如果是变量赋值的形式写的函数,只有变量的值是匿名函数时:name属性返回变量名。举例:var f1 = function(){};
f1.name //'f1'
如果是变量赋值的形式写的函数,只有变量的值是具名函数时:name属性返回function关键字之后那个函数名(变量名)。举例:var f1 = function myName(){};
f1.name //'myName'
而myName这个名字只能在函数体内部可用。
name属性的用处就是过去函数参数的名字;举例 :
var myFunc = function () {};
function test(f) {
console.log(f.name);
}
test(myFunc) // myFunc
2.2、length属性
函数的length属性返回函数预期传入的参数个数,即函数定义之时的参数个数;
只返回定义函数时传入的参数个数,不管调用时传入了多少个参数。
length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的“方法重载”(overload)。
2.3、toString()
函数的toString方法返回一个字符串,内容是函数的源码.
对于那些原生的函数(就是js自带的函数),toString()方法返回function (){[native code]}
函数内部的注释也可以返回。利用这一点,可以变相实现多行字符串。举例:
var multiline = function (fn) {
var arr = fn.toString().split('\n');
return arr.slice(1, arr.length - 1).join('\n');
};
function f() {/*
这是一个
多行注释
*/}
multiline(f);
// " 这是一个
// 多行注释"
3、函数作用域:
3.1、定义
作用域(scope)指的是变量存在的范围,ES5规定两种作用域、ES6又加了一个块级作用域:
全局作用域global variable:变量在整个程序中一直存在,所有地方都可以读取,
函数作用域local variable:变量只在函数内部存在,函数外部无法读取。
块级作用域:
对于顶层函数来说,函数外部声明的变量就是全局变量,他可以在函数内部读取。
函数内部定义的变量会在该作用域内覆盖同名全局变量。
想用var 命令声明局部变量,必须在函数内部声明,否则在其他块区中声明一律被视为全局变量。
3.2、函数内部的变量提升:
与全局作用域一样,函数作用域内部也会出现变量提升现象,函数内部var命令生命的变量都会被提升到函数体的头部。
3.3、函数本身作用域
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关.
var a = 1;
var x = function(){
console.log(a);
};
function f(){
var a = 2;
x();
}
f() //1
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域.
4、参数
4.1、概述
函数运行的时候需要提供外部数据,不同的外部数据得到不同的结果。这种外部数据叫做参数。
4.2、参数的省略
函数参数不是必须的,js允许省略函数参数。
函数f定义了若干个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript 都不会报错。省略的参数的值就变为undefined。
函数的length属性与实际传入的参数个数无关,只反映函数预期(声明时传入的参数个数)传入的参数个数。
没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。
4.3、参数的传递方式
函数参数如果是原始类型的值(数值、字符串、boolean),传递方式是传值传递(pass by value),这就意味着函数体内部修改参数值,不会影响到函数外部。
函数参数是复合型类型(对象、数组、null 、undefined)等,传递方式是传址传递(pass by refrence),因此在函数内部修改参数,将会影响到原始值. 如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
4.4、同名参数
如果有同名的参数,则取最后出现的那个值。即使后面的参数没有值或被省略,也是以其为准,就会返回undefined。
4.5、arguments对象
定义:
js允许有不定数目的参数,所以读取函数体内部所有参数,这就需要arguments.
arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推,这个对象只有在函数内部才可用。
正常模式下,arguments对象可以在函数运行时修改。
严格模式下,arguments对象与函数参数不具有联动关系,也就是说修改arguments对象不会影响到实际的函数参数。
通过arguments对象的length属性,可以判断函数调用时到底带几个参数。
arguments对象与数组的关系:
虽然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]);
}
callee属性:
arguments对象带有一个callee属性,返回它所对应的原函数。
【JavaScript数据类型】可以通过arguments.callee()方法返回到数组的原函数。这个属性在严格模式里面是禁用的,因此不建议使用。
5、函数的其他知识点
5.1、闭包closure
闭包(closure)是 JavaScript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.
函数内部可以直接读取全局变量,但是,函数外部无法读取函数内部声明的变量。如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
举例:
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的内部变量了。
闭包就是函数f2,即能够读取其他函数内部变量的函数.
由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
闭包可以看作是函数内部作用域的一个接口。
闭包的另一个用处,是封装对象的私有属性和私有方法。
注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
5.2、立即调用的函数表达式(IIFE)
在 JavaScript 中,圆括号()是一种运算符,跟在函数名之后,表示调用该函数。比如,print()就表示调用print函数。
有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误,产生这个错误的原因是,function这个关键字即可以当作语句,也可以当作表达式。
js语法规定,只要function出现在首行,一律解释成语句。
立即调用的函数表达式Immediately-Invoked Function Expression:简称 IIFE。让function关键字出现在首行,并且让js引擎认为是表达式,最简单的方法就是将其放在圆括号内,这就可以立即调用这个函数了。
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
6、eval命令:
6.1、基本用法
eval命令接受一个字符串为参数,并将参数作为语句来执行。如果参数字符串无法当作语句运行,那么就会报错。
放在eval中的字符串,应该有独自存在的意义,不能用来与eval以外的命令配合使用.
如果eval的参数不是字符串,那么会原样返回。
eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。
为了防止这种风险,JavaScript 规定,如果使用严格模式,eval内部声明的变量,不会影响到外部作用域。不过,即使在严格模式下,eval依然可以读写当前作用域的变量。
总之,eval的本质是在当前作用域之中,注入代码。由于安全风险和不利于 JavaScript 引擎优化执行速度,所以一般不推荐使用。通常情况下,eval最常见的场合是解析 JSON 数据的字符串,不过正确的做法应该是使用原生的JSON.parse方法
6.2、eval的别名调用
前面说过eval不利于引擎优化执行速度。更麻烦的是,还有下面这种情况,引擎在静态代码分析的阶段,根本无法分辨执行的是eval。举例:
var m = 'eval'
m(var x = 1;
)
x //1
为了保证eval 别名不影响代码优化,js标准规定,凡是使用eval 内部一律是全局作用域。
eval是别名调用,所以即使它是在函数中,它的作用域还是全局作用域,因此输出的a为全局变量。这样的话,引擎就能确认e()不会对当前的函数作用域产生影响,优化的时候就可以把这一行排除掉。
eval的别名调用的形式五花八门,只要不是直接调用,都属于别名调用,因为引擎只能分辨eval()这一种形式是直接调用。
7、数组
7.1、定义
数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。
给数组赋值方法:
定义时赋值:var arr = ['a','b','c'];
先定义后赋值:var arr = [];
arr[0] = 'a;
'arr[0] = 'b;
'arr[0] = 'c;
'
任何类型的数据都可以放入数组,
如果数组的元素还是数组,就形成了多维数组。
7.2、数组的本质
本质上数组是一种特殊的对象。typeof运算符返回的结果是object;
数组的本质是它的键名是顺序次序排列的一组整,从0开始。
Object.keys方法返回数组的键名,以数组形式返回;
由于数组成员的键名是固定的(默认总是0、1、2...),因此数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。
JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。注意,这点在赋值时也成立。一个值总是先转成字符串,再作为键名进行赋值。
上一章说过,对象有两种读取成员的方法:点结构(object.key)和方括号结构(object[key])。但是,对于数值的键名,不能使用点结构。arr.0的写法不合法,因为单独的数值不能作为标识符(identifier)。所以,数组成员只能用方括号arr[0]表示(方括号是运算符,可以接受数值)。
7.3、length属性
数组的length属性返回数组的成员数量。
js使用32位整数保存数组元素个数。数组成员最多只有 4294967295 个(2^32 - 1)个,也就是说length属性的最大值就是 4294967295。
只要是数组,就一定有length属性。该属性是一个动态的值,等于键名中的最大整数加上1。
数组的数字键不需要连续,length属性的值总是比最大的那个整数键大1。另外,这也表明数组是一种动态的数据结构,可以随时增减数组的成员。
length属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到length设置的值。
清空数组的一个方法:arr.length = 0;
arr //[]
如果人为设置length大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位。读取新增的位置都会返回undefined;
如果人为设置length为不合法的值,JavaScript 会报错。
值得注意的是,由于数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length属性的值.
如果数组的键名是添加超出范围的数值,该键名会自动转为字符串。
7.4、in运算符
检查某个键名是否存在的运算符。适用于数组和对象。
注意,如果数组的某个位置是空位,in运算符返回false。
7.5、for in 循环和数组的遍历
for...in循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。举例:
var arr = ['a','b','c'];
for(var i in arr){
console.log([a[i]);
}
//1
//2
//3
for in 不仅会遍历数组所有的数字键,还会遍历非数字键。举例:
var arr = [1,2,3];
arr.foo = true;
for(var keyin arr){
console.log(key);
}
//1
//2
//3
//foo
上面代码在遍历数组时,也遍历到了非整数键foo。所以,不推荐使用for...in遍历数组。
数组的遍历可以考虑使用for循环或while循环。
常用的数组的遍历方法:
1、
var a = [1, 2, 3];
// for循环
for(var i = 0;
i < a.length;
i++) {
console.log(a[i]);
};
2、
var a = [1, 2, 3];
// while循环
var i = 0;
while (i < a.length) {
console.log(a[i]);
i++;
};
3、
var a = [1, 2, 3];
var l = a.length;
while (l--) {
console.log(a[l]);
}
这是逆向遍历,即从最后一个元素向第一个元素遍历。
数组的forEach方法,也可以用来遍历数组。
7.6、数组的空位hole
当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位。
但是空位不影响数组的length属性。
需要注意的是,如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。
数组的空位是可以读取的,返回undefined;
使用delete属性删数组成员,会形成空位,但不影响length属性。
var arr = ['1','2','3'];
delete arr[1] //arr['1','undefined','3']
arr.length //3
也就是说,length属性不过滤空位。所以,使用length属性进行数组遍历,一定要非常小心。
数字的某个位置是空位与某个位置是undefind是不一样的,如果是空位使用数组的forEach方法、for in 结构、object.keys方法进行遍历,空位都会被跳过。
如果某个位置是undefined,遍历时不会被跳过。
7.7、类似数组的对象
如果一个对象的所有键名都是整数或零,并且有length属性,那么这个对象就很像数组,就称为array-like object.
类似数组的对象不是数组,因为它(对象)不支持数组的push方法。
类似数组的对象”的根本特征,就是具有length属性。只要有length属性,就可以认为这个对象类似于数组。但是有一个问题,这种length属性不是动态值,不会随着成员的变化而变化。
典型的类似数组的对象arrayLike:函数的arguments对象、大多数DOM元素、字符串。
1、数组的slice 方法可以将类似数组的对象变成真正的数组。举例:var arr = Array.prototype.slice.call(arrayLike);
2、除了让类似数组的对象转为真正的数组,还有就是通过call()让数组的方法放到类似数组的对象上,举例:var arr = Array.prototype.slice.call(arrayLike);
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- 事件代理
- 数组常用方法一
- 使用协程爬取网页,计算网页数据大小
- Java|Java基础——数组
- Python数据分析(一)(Matplotlib使用)
- Jsr303做前端数据校验
- Spark|Spark 数据倾斜及其解决方案
- 数据库设计与优化
- 爬虫数据处理HTML转义字符