Skip to content

02-数据类型

第二章 数据类型

统计信息:字数 8043 阅读17分钟

2021-11-16 2023-04-03

2.1 基本类型

typeof null === 'object'
undefined == null

2.2 数值

isNaN

isNan 可以判断是否是 NaN,但是实际可能出现问题:如果传入的参数不是数值,需要首先转化成数值,之后进行判断NaN,这样可能造成不准确。

isNaN(NaN) => true
isNaN('test') == isNaN(Number('test')) => true
isNaN('') => false
isNaN([]) => false
isNaN([1,2]) => true

所以实际判断时,首先需要判断是否是数值

function isNumber(para) {
  return typeof(para) === 'number' && isNaN(para);
}

更好的办法:利用NaN不等于自己进行判断

function isNumber(para) {
  return para !== para;
}

isFinite

isFinite() 判断一个数值是否是正常的数值。

如果传入的是undefined NaN Infinity -Infinity 就返回false

传入数值或者null返回的是true

当然,如果传入的不是数值,首先转化成数值再判断。例如字符串123和字符串test分别对应真假。

2.3 字符串

字符串引号

字符串可以使用单引号或者双引号包裹,如果一个字符串内部有单引号和双引号,需要使用反斜杠进行转义(URL,sql 语句中尤其需要转义)。

由于HTML使用双引号,JS最好使用单引号表示字符串,避免混淆。

字符串和数组

字符串可以视为字符数组

可以直接通过下标获取具体位置的字符,但是无法改变字符串(只读属性)。如果传入的下标大于字符串的长度,或者下标不是数字,那么返回的就是 undefined。字符串可以获取长度 string.length 也是只读属性。

字符串不具有数组的其他属性方法。

字符串编码

字符串有多个编码形式,不同编码的转换(通常是 utf-8,某些表情 utf8md4 等需要特殊处理)

2.4 对象

对象由键值对组成。

对象的键默认是字符串。如果是数值,会转化成字符串。在ES6中,symbol 也可以做键。

对象的值数据类型比较多:可以是一个函数,那么这个键就是对象的方法;可以是一个对象,那么就是对象的链式引用(注意对象的深复制和浅复制)。

let obj = {
  b: function(para) {
    return para * 2;
  }
};

对象的属性

对象的属性可以动态创建;

如果不同的变量指向相同的对象,那么这些变量都是这些对象的引用(指针)。如果改变一个引用数值,其他引用也会改变。获取对象的属性时,如果属性是数字,不能使用点语法获取(会当做小数点报错),可以通过方括号获取对象的属性。

查看对象的属性:使用 Object.keys(obj) 可以查看一个对象的所有属性。通常使用 for in 遍历对象中的属性,这样可以获取设置不同的属性。

console.log(Object.keys(document)); //  ['location', 'search']
var obj = { name: "Mike", age: 18 };

// 避免使用 with 语句获取对象内部的属性
with (obj) {
  name = "Harry";
  age = 15;
}

// equal with 
obj.name = "Harry";
obj.name = 15;

好处:可以直接使用,或者改变对象的某个属性

注意:如果对象没有这个属性,with 大括号内赋值会形成当前作用域内部的全局变量,对象并不会新增属性,所以不要使用 with 改变对象的变量。

var obj = {};
with (obj) {
  name = "Mike";
}
console.log(obj.name); // undefined
console.log(name); // "Mike"

可以使用临时变量代替 with.

let _obj = obj.obj1.obj2;
_obj.name = "Mike";

2.5 函数

函数的声明

函数的重复声明:函数名的提升会造成后面的函数永远覆盖前面的函数。如果同时使用function命令和变量赋值声明一个函数,那么变量提升后,会保留赋值声明的函数。

var f = function() {
  console.log('test');
}
function f() {
  console.log('test-bug');
}
f(); // test

性能:直接运算 > for循环 > 函数递归 recursion

函数递归调用可能存在边界错误,造成内存泄漏

函数如果没有返回语句 return,那么返回值是undefined

函数是一等公民:函数和其他变量一样:可以把函数当做一个变量赋值给另一个值,那么函数类似于变量可以传入另一个函数,或者作为函数的结果返回。

函数的属性和方法

函数是一个特殊的对象,所以继承了对象的属性

Function.name 可以获取函数的名字(不会这样做)

Function.length 获取函数默认传入参数的个数(可以判断函数定义时和调用时的参数的差异)

Function.toString 返回一个字符串,就是函数的源码

函数作用域

在ES5中,只有函数作用域和全局作用域,在ES6中增加了块级作用域;

函数内部变量会提升到函数头部。

函数如果引用外部的参数,那么需要根据函数定义时的环境判断外部的变量。这样,如果在函数嵌套中出现,在一个函数外部可以访问函数内部的变量,那么构成了闭包结构。

function foo() {
  var str = 'test';
  function bar() {
    console.log(str);
  }
  return bar;
}
var f = foo();
f();
// 可以获取函数内部的变量

闭包可以让变量始终在内存中,可以保存上一次调用的参数等,闭包是内部函数作用域的一个对外接口。可以封装对象的私有方法。外层函数运行时,都会生成一个闭包,这个闭包会持续保存外部函数的内部变量,所以内存消耗较大。

函数立即执行

IIFE (Immediately Invoked Function Expression)

(
  function(){
    /* codes */
  }()
);
// 函数定义后立即执行

好处:减少全局变量(对于只运行一次的函数),函数名就是一个全局变量;同时可以形成一个单独的作用域,可以封装外部函数无法读取的私有变量。适合于只运行一次的函数。

2.6 数组

清空数组的一个方法:array.length = 0;

数组的本质是一个对象,如果人为添加数组的属性,那么不会影响到数组的长度(当然,不建议直接改变数组的属性,获取属性和数组的项就会不方便)。如果设置数组的下标不合法(负数或者字符串作为数组的某个下标)那么这个下标就会转化成数组的属性。

如果在 leetcode 中,可以使用数组具有对象的特点,直接存储值,使数组具有对象的索引关系,同时具有线性存储的特性,可以节省内存。实际工作中,为了代码可读性和可维护性,最好避免直接使用数组的这个方法。

let arr = [];
arr[-1] = '0';
arr['name'] = "Mike";
console.log(arr.length); //0
console.log(arr[-1]); // '0' 因为-1首先转化成字符串,然后获取的数组的属性

使用 for…in 可以遍历数组的属性,包括数字下标和数组的属性

使用 delete 会删除数组的某一个数组成员,形成一个空位,返回 undefined,但是数组的长度不会受到影响(所以避免使用这个方法,直接使用 pop shift splice 即可)

let arr = [1, 2, 3];
delete arr[2];
console.log(arr); // [1, 2, empty];
console.log(arr[2]); // undefined

如果数组用 delete 删除了一个元素造成空位,然后使用 for 和 length 会遍历到这个 undefined 值,可能造成错误;使用 for…in… 或者 forEach 遍历时,会跳过空位输出结果。所以,尽量避免使用 delete 方法,优先使用 forEach 遍历数组的值。

所以如果删除最后一个元素,可以直接设置长度,arr.length = arr.length - 1(这个方法可读性比较差,可以使用 arr.pop() 方法,表示删除最后一个元素)

伪数组

最常见的伪数组就是函数的 arguments 属性

伪数组转换成数组(ES5)

var arr = Array.prototype.slice.call(arrayLike);

ES6

let arr = [...arrayLike];

Last update: November 9, 2024