内存结构
程序运行时,计算机内存会为程序开辟出一块专用内存,用于存储数据。在JavaScript中,变量名和变量值都是数据,但是它们存储的位置是不同的,变量名存储在内存的一个空间中,变量值是存储在另一个空间中。
每一个值都有一个内存地址,变量名实际上保存的就是值的内存地址,这样就建立了名字和值的联系。
值分为原始类型和对象类型,内存分为栈内存与堆内存。
- 栈内存用于保存变量名,内存地址和原始类型。
- 堆内存用于保存对象类型。
内存中不会创建重复的原始类型,可以理解为原始类型是不可变的。
let a = 'HelloWorld';
let b = 'HelloWorld';
但是内存中可以创建重复的对象类型,并且它们的内存地址不同。
- 给不同变量赋值同样的原始类型,它们是相等的,它们保存的是同一个内存地址。
- 给不同变量赋值同样的对象类型,它们是不相等的,因为它们比较的是内存地址。
let a = 1;
let b = 1;
console.log(a === b); // true
let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false
- 变量arr1赋值给变量arr2,实际上是把arr1保存的数组[1, 2, 3, 4]的内存地址赋给arr2。它们俩指向同一个地址的数组,所以arr1修改对arr2是有影响的。
var arr1 = [1, 2, 3, 4];
var arr2 = arr1;
arr1.push(5);
// arr1和arr2的地址是同一个
console.log(arr1 + '|' + arr2); // 1,2,3,4,5|1,2,3,4,5
- 重新赋值的过程是指向了新的内存地址。
var arr1 = [1, 2, 3, 4];
var arr2 = arr1;
arr1 = [1, 2];
console.log(arr1 + '|' + arr2);
// 1,2|1,2,3,4,5
类型判断
typeof
typeof(data)方法 返回一个字符串用于表示参数的数据类型,但是对于引用类型它只能区分是函数还是其它引用类型,比如以下代码中的对于数组的返回结果就是object。
console.log(typeof(123)); // "number"
console.log(typeof("hello")); // "string"
console.log(typeof(true)); // "boolean"
console.log(typeof(undefined)); // "undefined"
console.log(typeof(null)); // "object"
console.log(typeof({})); // "object"
console.log(typeof([])); // "object"
console.log(typeof(function(){})); // "function"
console.log(typeof(null)); // "object"
console.log(typeof(NaN)); // "number"
console.log(typeof(document.all)); // "undefined"
注意null是一个object类型,它最早是空对象的指针,目前来说是一个历史遗留问题。
console.log(typeof(null)); //object
如果一个变量a未定义,使用typeof()返回的是undefined,注意这个undefined是字符串格式的。
console.log(typeof(a)); // undefined
console.log(typeof(typeof(a))); // string
constructor
constructor 指向创建该实例对象的构造函数。
var arr = [];
console.log(arr.constructor); // ƒ Array() { [native code] }
instanceof
instanceOf()方法 判断一个对象是否是右边的实例。
function Car(){}
var car = new Car();
console.log(car instanceof Car); // true
并且只要该对象能通过原型链访问到右边,都会返回true。
- 第一条语句打印true,因为
Car构造函数是由Function构造函数创建的实例。 - 第二条语句打印fasle,因为
car对象和Function之间并不存在原型链上的联系。 - 第三条语句打印true,因为
car对象可以找到Car构造函数的原型,然后接着找到Object。
function Car(){}
var car = new Car();
console.log(Car instanceof Function); // true
console.log(car instanceof Function); // false
console.log(car instanceof Object); // true
Object.prototype.toString
toString()方法 不接收参数,返回表示调用它的对象的值的字符串。
- 注意的是它返回的字符串格式为
[object 对象类型]
var obj = {name: 'Li'};
console.log(obj.toString()); // [object Object]
所以虽然默认的toString()方法不会显示太多信息,但是可以用于判断数据的类型,所以可以结合call使用。
对于 Object.prototype.toString.call(arg),若参数arg为 null 或 undefined,直接返回结果。
console.log(Object.prototype.toString.call(undefined)) // "[object Udefined]"
console.log(Object.prototype.toString.call(null)) // "[object Null]"
对于其他数据类型,如果是原始数据类型,那么首先是进行包装类,如果是引用对象类型则直接使用。
console.log(Object.prototype.toString.call(123)) // "[object Number]"
console.log(Object.prototype.toString.call('HelloWorld')) // "[object String]"
console.log(Object.prototype.toString.call(true)) // "[object Boolean]"
console.log(Object.prototype.toString.call({name: 'Li'})) // "[object Object]"
console.log(Object.prototype.toString.call([1, 2, 3])) // "[object Array]"
console.log(Object.prototype.toString.call(function(){var a = 1;})) // "[object Function]"
- 在实际项目中会经常使用这个方式判断数据类型,使用时要注意变量缓存。
var arr = [];
var str = Object.prototype.toString,
trueTip = '[object Array]';
if(str.call(arr) === trueTip){
console.log('是数组');
}else{
console.log('不是数组');
}
由于这个默认方法不会显示太多有用的信息,所以很多类都会重新定义自己的toString()方法。比如,在把数组转换为字符串时,会得到数组元素的一个列表,每个元素都会转换为字符串。而把函数转换为字符串时,可以得到函数的源代码。
- 原始类型重新定义的
toString()方法,会返回当前值的字符串。 - 注意
undefined和null并没有自己的toString()方法,直接调用会报错。
var a = 'HelloWorld',
b = 123,
c = true,
d = undefined,
e = null,
f = [4, 5, 6],
g = function(){
var str = 'demo';
};
console.log(a.toString()); // 'HelloWorld'
console.log(b.toString()); // '123'
console.log(c.toString()); // 'true'
// console.log(d.toString()); // error
// console.log(e.toString()); // error
console.log(f.toString()); // '4, 5, 6'
console.log(g.toString()); // 'function(){ var str = 'demo'; }'
类型转换
显示类型转换
Number(data)方法 将目标转换为数字类型。
let a = '123';
console.log(Number(a) + '-' + typeof(Number(a))); // 123-number
let b = 'true';
console.log(Number(b) + '-' + typeof(Number(b))); // NaN-number
let c = false;
console.log(Number(c) + '-' + typeof(Number(c))); // 0-number
let d = null;
console.log(Number(d) + '-' + typeof(Number(d))); // 0-number
let e = undefined;
console.log(Number(e) + '-' + typeof(Number(e))); // NaN-number
parseInt(data, radix)方法 将目标转换为整型,参数data表示转换的元数据,radix参数表示基底。用于指定目标转换的进制,范围是2-36。
let a = 13.14;
console.log(parseInt(a)); // 13
let b = '10';
console.log(parseInt(b, 16)); // 十进制中10对应十六进制的16
- 如果目标是混合型的字符串,那么会从第一位开始看,如果第一位不是数字,那就直接转换为
NaN。如果是数字那就一直判定到非数为止。
let a = 'abc123';
console.log(parseInt(a)); // NaN
let b = '123a456';
console.log(parseInt(b)); // 123
- 注意它和Number()方法的区别,对于所有非数类型的值,它不会考虑转换为一个具体的数字,而是只需要转换为整型数字类型就行,所以以下几种的结果都是
NaN。
let a = true;
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-NaN
let b = null;
console.log(typeof(parseInt(b)) + '-' + parseInt(b)); // number-NaN
let c = undefined;
console.log(typeof(parseInt(c)) + '-' + parseInt(c)); // number-NaN
let d = NaN;
console.log(typeof(parseInt(d)) + '-' + parseInt(d)); // number-NaN
parseFloat()方法 将目标转换为小数。
toFixed(num)方法 设置要保留的小数位。
let num = parseFloat('3.1415926');
console.log(num.toFixed(2)); // 设置要保留的小数位
String()方法 将目标转换为字符串类型。
let num = 123456;
console.log(String(num)); // 123456
console.log(typeof(String(num))); // string
toString(radix)方法 将目标转换为的字符串类类型,它也有参数radix,用于设置转换的进制。注意null和undefined没有该方法。
let str = 100;
console.log(str.toString()); // 100
Boolean()方法 将目标转换为布尔值。
console.log(Boolean(1)); // true
console.log(Boolean(0)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(null)); // false
console.log(Boolean('abc')); // true
隐式类型转换
如果对一个字符串进行++操作,那就会对这个字符串先进行Number()隐式转换,再进行++操作。
let a = '123'; // 隐式进行了Number(a)
a++;
console.log(a); // 124
let b = 'abc';
b++;
console.log(b); // NaN
其它运算符比如减、乘除,取余和正负号也都是会隐式转换,将字符串先进行Number(),再运算。
let a = '3' * 2;
let b = '10' / 2;
let c = '10' % 3;
console.log(a); // 6
console.log(b); // 5
console.log(c); // 1
之前说到的任何数值和字符串相加+,都会变成字符串拼接,本质上也是存在String()隐式转换的。
let a = '123';
let b = 456; // 隐式进行了String(b);
console.log(a + b); // 123456
比较运算符也存在隐式转换,但要注意全等===是需要看数据类型的,所以它不会隐式转换。
let bool2 = 1 === '1';
console.log(bool2); // false
- 注意以下代码的过程是,
2 > 1 = true;->true == 1;所以b的输出结果是true。
let a = 2 > 1 > 3;
console.log(a); // false
let b = 2 > 1 == 1;
console.log(b); // true
undefined和null都不等于0,但是它们俩自身相等,相等的原因并不是因为两个值都可以转换为false,而是因为ECMA-262规定对它们的相等性测试要返回true。并且要注意它们不全等,压根就不是同一数据类型。
let a = undefined == 0;
console.log(a); // false
let b = null == 0;
console.log(b); // false
let c = undefined == null;
console.log(c); // true
isNaN()方法 判断目标是否是NaN,这个过程中也存在隐式转换,比如字符串'123'会转换为123。null会转换为数字0,undefined是转换为NaN。
console.log(isNaN(123)); // false
console.log(isNaN('123')); // false
console.log(isNaN('a')); // true
console.log(isNaN(null)); // false
console.log(isNaN(undefined)); // true