javascript---数据类型
JavaScript---数据类型
- number---数值---整数和小数(1,3.14)
-
string ----字符串---文本(Hello world)
1、转义
需要用反斜杠转义的特殊字符,主要有下面这些。
\0
:null(\u0000
)\b
:后退键(\u0008
)\f
:换页符(\u000C
)\n
:换行符(\u000A
)\r
:回车键(\u000D
)\t
:制表符(\u0009
)\v
:垂直制表符(\u000B
)\'
:单引号(\u0027
)\"
:双引号(\u0022
)\\
:反斜杠(\u005C
)2、字符串与数组
var s = 'hello'; s[0] // "h" s[1] // "e" s[4] // "o"3、length属性
length
属性返回字符串的长度,该属性也是无法改变的。var s = 'hello'; s.length // 5 s.length = 3; s.length // 5上面代码表示字符串的
length
属性无法改变,但是不会报错。4、字符集
JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。
JavaScript 不仅以 Unicode 储存字符,还允许直接在程序中使用 Unicode 码点表示字符
var s = '\u00A9'; s // "©"5、Base64转码
所谓 Base64 就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、
+
和/
这64个字符组成的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。JavaScript 原生提供两个 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') // "你好"
-
boolean---布尔值---表示真伪的两个特殊值(true,false)
除了下面六个值被转为
false
,其他值都视为true
undefined
null
false
0
NaN
""
或''
(空字符串)布尔值往往用于程序流程的控制
-
undefined--未定义---表示未定义,或不存在,即由于目前没有定义, 所以此处暂时没有任何值
-
null ------空值------表示空值,即此处的值为空
-
object----对象----各种值组成的集合
- 对象是最复杂的数据类型,又可以分成三个子类型。
-
-
- 对象
- 函数
- 数组
-
对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型。
一、基本
1、什么是对象?
简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。
var obj = { 'foo': 'Hello', 'bar': 'World' };2、键值
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。
var obj = { p: function (x) { return 2 * x; } }; obj.p(1) // 2上面代码中,对象
obj
的属性p
,就指向一个函数。3、链式引用
如果属性的值还是一个对象,就形成了链式引用。
var o1 = {}; var o2 = { bar: 'hello' }; o1.foo = o2; o1.foo.bar // "hello"上面代码中,对象
o1
的属性foo
指向对象o2
,就可以链式引用o2
的属性。4、对象的引用
不同变量名指向同一个对象,则这两个变量都是对一个对象的引用,就是指向同一个内存地址。
修改其中一个变量,会影响到其他所有变量。
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 // {}上面代码中,
o1
和o2
指向同一个对象,然后o1
的值变为1,这时不会对o2
产生影响,o2
还是指向原来的那个对象。但是,这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
var x = 1; var y = x; x = 2; y // 1二、属性
1、属性的读取
- 点运算符
- 方括号运算符
var obj = { p: 'Hello World' }; obj.p // "Hello World" obj['p'] // "Hello World"2、属性的赋值
点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。
var obj = {}; obj.foo = 'Hello'; obj['bar'] = 'World';3、属性的查看
查看一个对象本身的所有属性,可以使用
Object.keys
方法。var obj = { key1: 1, key2: 2 }; Object.keys(obj); // ['key1', 'key2']4、属性的删除 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只有一种情况,
delete
命令会返回false
,那就是该属性存在,且不得删除。var obj = Object.defineProperty({}, 'p', { value: 123, configurable: false });另外,需要注意的是,
delete
命令只能删除对象本身的属性,无法删除继承的属性上面代码中,var obj = {}; delete obj.toString // true obj.toString // function toString() { [native code] }toString
是对象obj
继承的属性,虽然delete
命令返回true
,但该属性并没有被删除,依然存在。这个例子还说明,即使delete
返回true
,该属性依然可能读取到值。5、属性是否存在 in
- in
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true
,否则返回false
。var obj = { p: 1 }; 'p' in obj // true 'toString' in obj // true
in
运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。就像上面代码中,对象obj
本身并没有toString
属性,但是in
运算符会返回true
,因为这个属性是继承的。
- hasOwnProperty
可以使用对象的
hasOwnProperty
方法判断一下,是否为对象自身的属性。var obj = {}; if ('toString' in obj) { console.log(obj.hasOwnProperty('toString')) // false }6、属性的遍历 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)的属性,会跳过不可遍历的属性。
- 它不仅遍历对象自身的属性,还遍历继承的属性。
举例:
举例来说,对象都继承了
toString
属性,但是for...in
循环不会遍历到这个属性。上面代码中,对象var obj = {}; // toString 属性是存在的 obj.toString // toString() { [native code] } for (var p in obj) { console.log(p); } // 没有任何输出obj
继承了toString
属性,该属性不会被for...in
循环遍历到,因为它默认是“不可遍历”的。如果继承的属性是可遍历的,那么就会被
for...in
循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用for...in
的时候,应该结合使用hasOwnProperty
方法,在循环内部判断一下,某个属性是否为对象自身的属性。
var person = { name: '老张' }; for (var key in person) { if (person.hasOwnProperty(key)) { console.log(key); } } // name三、with语句
with (对象) { 语句; }
它的作用是操作同一个对象的多个属性时,提供一些书写的方便。
// 例一 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
区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。
上面代码中,对象var obj = {}; with (obj) { p1 = 4; p2 = 5; } obj.p1 // undefined p1 // 4obj
并没有p1
属性,对p1
赋值等于创造了一个全局变量p1
。正确的写法应该是,先定义对象obj
的属性p1
,然后在with
区块内操作它。这是因为
with
区块没有改变作用域,它的内部依然是当前作用域。这造成了with
语句的一个很大的弊病,就是绑定对象不明确。with (obj) { console.log(x); }
单纯从上面的代码块,根本无法判断
x
到底是全局变量,还是对象obj
的一个属性。这非常不利于代码的除错和模块化,编译器也无法对这段代码进行优化,只能留到运行时判断,这就拖慢了运行速度。因此,建议不要使用with
语句,可以考虑用一个临时变量代替with
。with(obj1.obj2.obj3) { console.log(p1 + p2); } // 可以写成 var temp = obj1.obj2.obj3; console.log(temp.p1 + temp.p2);
函数
一、基本
1、函数的声明(三种声明函数的方法)
- function命令
- 函数表达式
- function构造函数
(1)function命令--常用
function
命令声明的代码区块,就是一个函数。function print(s) { console.log(s); }
(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上面代码在函数表达式中,加入了函数名
x
。这个x
只在函数体内部可用,指代函数表达式本身,其他地方都不可用。这种写法的用处有两个,一是可以在函数体内部调用自身,二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。因此,下面的形式声明函数也非常常见。var f = function f() {};
需要注意的是,函数的表达式需要在语句的结尾加上分号,表示语句结束。而函数的声明在结尾的大括号后面不用加分号。总的来说,这两种声明函数的方式,差别很细微,可以近似认为是等价的。
(3)Function构造函数
var add = new Function( 'x', 'y', 'return x + y' ); // 等同于 function add(x, y) { return x + y; }
Function
构造函数可以不使用new
命令,返回结果完全一样。总的来说,这种声明函数的方式非常不直观,几乎无人使用。
2、第一等公民
JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
function add(x, y) { return x + y; } // 将函数赋值给一个变量 var operator = add; // 将函数作为参数和返回值 function a(op){ return op; } a(add)(1, 1) // 23、函数名的提升
JavaScript 引擎将函数名视同变量名,所以采用
function
命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。f(); function f() {}
表面上,上面代码好像在声明之前就调用了函数
f
。但是实际上,由于“变量提升”,函数f
被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。f(); var f = function (){}; // TypeError: undefined is not a function上面的代码等同于下面的形式。
var f; f(); f = function () {};上面代码第二行,调用
f
的时候,f
只是被声明了,还没有被赋值,等于undefined
,所以会报错。注意,如果像下面例子那样,采用
function
命令和var
赋值语句声明同一个函数,由于存在函数提升,最后会采用var
赋值语句的定义。var f = function () { console.log('1'); } function f() { console.log('2'); } f() // 1上面例子中,表面上后面声明的函数
f
,应该覆盖前面的var
赋值语句,但是由于存在函数提升,实际上正好反过来。二、函数的属性和方法
1、name属性
name
属性的一个用处,就是获取参数函数的名字。var myFunc = function () {}; function test(f) { console.log(f.name); } test(myFunc) // myFunc上面代码中,函数
test
内部通过name
属性,就可以知道传入的参数是什么函数。2、length属性
返回函数预期传入的参数个数,即函数定义之中的参数个数。
function f(a, b) {} f.length // 2上面代码定义了空函数
f
,它的length
属性就是定义时的参数个数。不管调用时输入了多少个参数,length
属性始终等于2。
length
属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的“方法重载”(overload)。3、toString()
函数的
toString()
方法返回一个字符串,内容是函数的源码。function f() { a(); b(); c(); } f.toString() // function f() { // a(); // b(); // c(); // }对于那些原生的函数,
toString()
方法返回function (){[native code]}
。Math.sqrt.toString() // "function sqrt() { [native code] }"上面代码中,
Math.sqrt()
是 JavaScript 引擎提供的原生函数,toString()
方法就返回原生代码的提示。函数内部的注释也可以返回。
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); // " 这是一个 // 多行注释"三、函数的作用域
1、定义:作用域(scope)指的是变量存在的范围
- 全局作用域:变量在整个程序中一直存在,所有地方都可以读取
- 函数作用域:变量只在函数内部存在
- 块级作用域:(ES6新增,JavaScript只用前两种)
全局作用域
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上面代码中,变量
v
同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量v
覆盖了全局变量v
。注意,对于
var
命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。if (true) { var x = 5; } console.log(x); // 5上面代码中,变量
x
在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。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上面代码中,函数
foo
内部声明了一个函数bar
,bar
的作用域绑定foo
。当我们在foo
外部取出bar
执行时,变量x
指向的是foo
内部的x
,而不是foo
外部的x
。正是这种机制,构成了下文要讲解的“闭包”现象。四、参数
1、概述
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
function square(x) { return x * x; } square(2) // 4 square(3) // 9上式的
x
就是square
函数的参数。每次运行的时候,需要提供这个值,否则得不到结果。2、传递方式
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
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 // 23、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
对象可以在运行时修改。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
对象,不会影响到真实参数a
和b
。通过
arguments
对象的length
属性,可以判断函数调用时到底带几个参数。function f() { return arguments.length; } f(1, 2, 3) // 3 f(1) // 1 f() // 0五、闭包和立即调用的函数表达式
1、闭包
闭包(closure)是 JavaScript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
闭包:定义在一个函数内部的函数
特点:是它可以”记住“诞生的环境,
闭包的本质:就是将函数内部和函数外部连接起来的一座桥梁
闭包的用处:
- 读取函数内部的变量
- 让哲别变量始终保持在内存中,即闭包可以使得它诞生环境一直存在
- 封装对象的私有属性和私有方法
理解闭包,首先必须理解变量作用域。
前面提到,JavaScript 有两种作用域:全局作用域和函数作用域。
- 函数内部可以直接读取全局变量,但是,正常情况下,函数外部无法读取函数内部声明的变量。
- 如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
function f1() { var n = 999; function f2() { console.log(n); // 999 } }上面代码中,函数
f2
就在函数f1
内部,这时f1
内部的所有局部变量,对f2
都是可见的。但是反过来就不行,f2
内部的局部变量,对f1
就是不可见的。这就是 JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。3.既然
f2
可以读取f1
的局部变量,那么只要把f2
作为返回值,我们不就可以在f1
外部读取它的内部变量了吗!function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999闭包就是函数
f2
,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2
记住了它诞生的环境f1
,所以从f2
可以得到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
的私有变量。注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
六、eval命令
数组
-
Symbol(ES6 新增)---表示独一无二的值