【没人看系列】js 数据类型

本文为作者JavaScript学习笔记,只是加强个人理解和认识。大部分概念与代码参考了阮一峰 js 教程


基本

1. 语句

语句(statement)是为了完成某种任务而进行的操作

语句和表达式的区别在于,前者主要为了进行某种操作,一般情况下不需要返回值;后者则是为了得到返回值,一定会返回一个值。

2. 变量

变量是对“值”的具名引用。变量就是为“值”起名,然后引用这个名字,就等同于引用这个值。变量的名字就是变量名。

变量的声明和赋值,是分开的两个步骤 如果只是声明变量而没有赋值,则该变量的值是==undefined==。==undefined==是一个特殊的值,表示“无定义”。

变量提升

JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。

8.判断

switch语句后面的表达式,与case语句后面的表示式比较运行结果时,采用的是严格相等运算符(===),而不是相等运算符(==),这意味着比较时不会发生类型转换。

7.do...while 循环

do...while循环与while循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。

不管条件是否为真,do...while循环至少运行一次,这是这种结构最大的特点。另外,while语句后面的分号注意不要省略。

数据类型概述

1. 简介

js 有6种数据类型, es6 又新增了第7种symbol

布尔值

下列运算符会返回布尔值:

  • 前置逻辑运算符: ! (Not)

  • 相等运算符:===!====!=

  • 比较运算符:>>=<<=

    如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true

    • undefined
    • null
    • false
    • 0
    • NaN
    • ""''(空字符串)

注意,空数组([])和空对象({})对应的布尔值,都是true

数值

NaN不等于任何值,包括它本身。

与数值相关的全局方法

parseInt()

parseInt方法用于将字符串转为整数。

parseInt('123') // 123
复制代码

如果parseInt的参数不是字符串,则会先转为字符串再转换。

parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
复制代码

字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。

parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
复制代码

parseFloat()

parseFloat方法用于将一个字符串转为浮点数。

isNaN()

isNaN方法可以用来判断一个值是否为NaNisNaN只对数值有效,如果传入其他值,会被先转成数值.也就是说,isNaNtrue的值,有可能不是NaN,而是一个字符串。

isFinite()

isFinite方法返回一个布尔值,表示某个值是否为正常的数值。除了Infinity-InfinityNaNundefined这几个值会返回falseisFinite对于其他的数值都会返回true

字符串

如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此。

转义

\n表示换行,输出的时候就分成了两行。

字符串与数组

符串可以被视为字符数组,因此可以使用数组的方括号运算符,无法改变字符串之中的单个字符。

length属性返回字符串的长度,该属性也是无法改变的。

Base64 转码

JavaScript 原生提供两个 Base64 相关的方法。

  • btoa():任意值转为 Base64 编码
  • atob():Base64 编码转为原来的值

对象

简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。

对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。

对象的引用

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

如果取消某一个变量对于原对象的引用,不会影响到另一个变量。

这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。

属性的读取

读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。

var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
obj['p'] // "Hello World"
复制代码

请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。

var foo = 'bar';

var obj = {
  foo: 1,
  bar: 2
};

obj.foo  // 1
obj[foo]  // 2
复制代码

上面代码中,引用对象objfoo属性时,如果使用点运算符,foo就是字符串;如果使用方括号运算符,但是不使用引号,那么foo就是一个变量,指向字符串bar

属性的赋值

点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

JavaScript 允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。

属性的查看

查看一个对象本身的所有属性,可以使用Object.keys方法。

var obj = {
  key1: 1,
  key2: 2
};

Object.keys(obj);
// ['key1', 'key2']
复制代码

属性的删除:delete 命令

delete命令用于删除对象的属性,删除成功后返回true

注意,删除一个不存在的属性,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运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。就像上面代码中,对象obj本身并没有toString属性,但是in运算符会返回true,因为这个属性是继承的。

这时,可以使用对象的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]);
}
复制代码

如果继承的属性是可遍历的,那么就会被for...in循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用for...in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性。

var person = { name: '老张' };

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}
复制代码

with 语句

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区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。 建议不使用with语句

函数

函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。

1.概述

1.1 函数的声明

JavaScript 有三种声明函数的方法。

1)function 命令

function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。

function print(s) {
  console.log(s);
}
复制代码

上面的代码命名了一个print函数,以后使用print()这种形式,就可以调用相应的代码。这叫做函数的声明(Function Declaration)。

(2)函数表达式

除了用function命令声明函数,还可以采用变量赋值的写法。

var print = function(s) {
  console.log(s);
};
复制代码

这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(Function Expression),因为赋值语句的等号右侧只能放表达式。

采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。

(3)Function 构造函数

第三种声明函数的方式是Function构造函数。

总的来说,这种声明函数的方式非常不直观,几乎无人使用。

1.2 函数的重复声明

如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。

function f() {
  console.log(1);
}
f() // 2

function f() {
  console.log(2);
}
f() // 2
复制代码

上面代码中,后一次的函数声明覆盖了前面一次。而且,由于函数名的提升(参见下文),前一次声明在任何时候都是无效的,这一点要特别注意。

1.3 圆括号运算符,return 语句和递归

调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数。

函数体内部的return语句,表示返回。JavaScript 引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return语句所带的那个表达式,就是函数的返回值。return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined

函数可以调用自身,这就是递归(recursion)。下面就是通过递归,计算斐波那契数列的代码。

function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}

fib(6) // 8
复制代码

上面代码中,fib函数内部又调用了fib,计算得到斐波那契数列的第6个元素是8。

1.4函数名的提升

JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。

2.函数的属性

name 属性

函数的name属性返回函数的名字。

如果是通过变量赋值定义的函数,那么name属性返回变量名。

如果变量的值是一个具名函数,那么name属性返回function关键字之后的那个函数名。

name属性的一个用处,就是获取参数函数的名字。

length 属性

函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。

toString()

函数的toString方法返回一个字符串,内容是函数的源码。

3. 函数的作用域

在 ES5 的规范中,Javascript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。ES6 又新增了块级作用域

与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

函数本身的作用域

函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1
复制代码

上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2

总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

闭包就是创建局部变量, 使得函数外部可以访问到这个变量

4.参数

函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。

函数参数不是必需的,Javascript 允许省略参数。

但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined

传递方式

函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。

如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。

注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。

arguments 对象

由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。

严格模式下,arguments对象是一个只读对象,修改它是无效的,但不会报错。

与数组的关系

需要注意的是,虽然arguments很像数组,但它是一个对象。数组专有的方法(比如sliceforEach),不能在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]);
}
复制代码

5.函数的其他知识点

闭包

理解闭包,首先必须理解变量作用域。前面提到,JavaScript 有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。

由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境. 在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。

闭包的另一个用处,是封装对象的私有属性和私有方法。

立即调用的函数表达式

解决方法就是不要让function出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
复制代码

通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

// 写法一
var tmp = newData;
processData(tmp);
storeData(tmp);

// 写法二
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());
复制代码

上面代码中,写法二比写法一更好,因为完全避免了污染全局变量。

数组

1.定义

数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。

除了在定义时赋值,数组也可以先定义后赋值。

任何类型的数据,都可以放入数组。

2.数组的本质

本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object

3.length 属性

数组的length属性,返回数组的成员数量。

只要是数组,就一定有length属性。该属性是一个动态的值,等于键名中的最大整数加上1

length属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到length设置的值。

清空数组的一个有效方法,就是将length属性设为0。

如果人为设置length大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位。

4. in 运算符

检查某个键名是否存在的运算符in,适用于对象,也适用于数组。

5.for...in 循环和数组的遍历

for...in循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。但是,for...in不仅会遍历数组所有的数字键,还会遍历非数字键。所以,不推荐使用for...in遍历数组。

数组的遍历可以考虑使用for循环或while循环。

数组的forEach方法,也可以用来遍历数组,详见《标准库》的 Array 对象一章。

数组的空位

当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。需要注意的是,如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。

数组的空位是可以读取的,返回undefined

使用delete命令删除一个数组成员,会形成空位,并且不会影响length属性。

类似数组的对象

如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”(array-like object)。

典型的“类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串。

数组的slice方法可以将“类似数组的对象”变成真正的数组。

var arr = Array.prototype.slice.call(arrayLike);
复制代码

除了转为真正的数组,“类似数组的对象”还有一个办法可以使用数组的方法,就是通过call()把数组的方法放到对象上面。

function print(value, index) {
  console.log(index + ' : ' + value);
}

Array.prototype.forEach.call(arrayLike, print);
复制代码

注意,这种方法比直接使用数组原生的forEach要慢,所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值