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];