上一篇讲了 JavaScript 中的类型和值,本篇主要关于 类型转换、变量。数组
须要注意包装对象、引用类型变动、对象到原始值的类型转换规则等。函数
运算符相关的隐式类型转换请查看:post
犀牛书这里提到了一个 包装对象 的概念(见 3.6 ),使用包装对象的概念解释了 为何字符串、数字等原始值也能够拥有各自的属性。学习
当 JavaScript 要读取字符串 s 的属性时,将会经过调用 new String(s) 来 将字符串转换为对象(也至关于调用 Object(s) ,对原始值使用 . 操做符读取属性会将原始值进行隐式类型转换,转换为对象),而后引用这个对象的属性,而使用结束后这个对象就会被 销毁(实现不必定要建立和销毁,可是能够用来类比这个过程)。测试
经过包装对象的概念能够很容易理解字符串和数字等为何有属性,为何没法给它们的属性赋值。ui
var s = 'string';
// 0
console.log(s.indexOf('s'));
// true
console.log(s.__proto__ === String.prototype);
复制代码
上述代码能够看作为this
var s = 'string';
console.log(new String(s).indexOf('s'));
复制代码
var s = 'string';
// 可想象为赋值在包装对象上,然而这段语句执行完成包装对象就被销毁了,因此 s 上找不到 test
s.test = 'test';
// undefined
console.log(s.test);
复制代码
原始值都是不可变动的,当为原始值的变量从新赋值时,修改的是新的值。spa
能够理解为每一个原始值都是一个独立的地址,把一个原始值赋值给一个变量会在内存中从新开辟一个空间赋值原始值并把新的地址赋值给这个变量。prototype
var a = 1; // 开辟栈空间 0001 -> 把空间地址赋给变量 a -> 存值 1 到 0001
var b = a; // 开辟栈空间 0002 -> 把空间地址赋给变量 b -> 复制 a 的值 1 -> 0002
// 1 1
console.log(b, a); // b 和 a 分别去 0002 和 0001 查询存储值 得出 1 1
b = 2; // 取出 b 的地址 0002 -> 存值 2 到 0002
// 2 1
console.log(b, a); // b 和 a 分别去 0002 和 0001 查询存储值 得出 2 1
复制代码
对象类型是可变的,将一个对象类型赋值给一个变量时,实际上 传递的是它的引用(指针)。指针
var a = {}; // 开辟栈空间 0001 -> 将空间地址赋给变量 a -> 开辟堆空间存储 X0001 -> 存值 {} -> 存储 X0001 地址到 0001
var b = a; // 开辟栈空间 0002 -> 将空间地址赋给变量 b -> 赋值 0001 中的地址,也就是 X0001 到 0002
// {} {}
console.log(b, a); // b 和 a 分别去 0002 和 0001 查询存储值,查出是堆地址时会去堆中取值,得出 {} {}
b.c = 2; // 读取 b 从堆中取值,修改堆中值为 {c: 2}
// {c:2} {c:2}
console.log(b, a); // b 和 a 分别去 0002 和 0001 查询存储值,查处是堆地址时会去堆中取值,得出 {c: 2} {c: 2}
复制代码
== 运算符在判断时会进行 类型转换,=== 运算符 不会作任何转换。
一般可使用全局函数 Object、Number、Boolean、String 来进行显式类型转换。
然而咱们也能够 借助隐式类型转换作显示类型转换。(隐式类型转换见上一篇)
console.log(+'0'); // 0 经过 + 运算符的隐式转换将字符串显式转换为数字
复制代码
注意,使用 Object 对 null、undefined 进行显示类型转换时,能够正常输出一个空对象而不报错,至关于没传入值。可是当 JavaScript 尝试对它们转换为对象类型时,将会抛出类型错误(好比 null.toString 至关于将 null 转为对象而后调用他的 toString,然而这将会抛出类型错误)。
对象到原始值的转换相对而言比较复杂。
全部的对象转换为布尔型 都为 true。
对象转换为字符串时,将会通过几个步骤:
使用代码测试一下
const a = {
toString: function() {
console.log('toString is called');
return null;
}
};
console.log(String(a));
// 'toString is called'
// 'null'
const b = {
toString: function() {
console.log('toString is called');
return {};
},
valueOf: function() {
console.log('valueOf is called');
return false;
}
};
console.log(String(b));
// 'toString is called'
// 'valueOf is called'
// 'false'
const c = {
toString: function() {
console.log('toString is called');
return {};
},
valueOf: function() {
console.log('valueOf is called');
return {};
}
};
console.log(String(c));
// 'toString is called'
// 'valueOf is called'
// TypeError: Cannot convert object to primitive value
复制代码
使用代码模拟一下
const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
const obj2Str = obj => {
let primitiveValue;
if ('toString' in obj) {
primitiveValue = obj.toString();
} else if(!('valueOf' in obj)) {
throw new TypeError("Can't find toString and valueOf");
}
if ('valueOf' in obj && (!('toString' in obj) || !isPrimitive(primitiveValue))) {
primitiveValue = obj.valueOf();
}
if (isPrimitive(primitiveValue)) {
return String(primitiveValue);
} else {
throw new TypeError("Can't convert to primitive");
}
};
复制代码
能够拿上面的测试代码验证一下是否是同样的输出
对象转换为数字时,将会通过几个步骤:(相似字符串的转换,可是有所差异)。
一样使用代码测试一下
const a = {
valueOf: function() {
console.log('valueOf is called');
return null;
}
};
console.log(Number(a));
// 'valueOf is called'
// 0
const b = {
valueOf: function() {
console.log('valueOf is called');
return {};
},
toString: function() {
console.log('toString is called');
return true;
}
};
console.log(Number(b));
// 'valueOf is called'
// 'toString is called'
// 1
const c = {
valueOf: function() {
console.log('valueOf is called');
return {};
},
toString: function() {
console.log('toString is called');
return {};
}
};
console.log(Number(c));
// 'valueOf is called'
// 'toString is called'
// TypeError: Cannot convert object to primitive value
复制代码
一样使用代码模拟一下
const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
const obj2Num = obj => {
let primitiveValue;
if ('valueOf' in obj) {
primitiveValue = obj.valueOf();
} else if(!('toString' in obj)) {
throw new TypeError("Can't find toString and valueOf");
}
if ('toString' in obj && (!('valueOf' in obj) || !isPrimitive(primitiveValue))) {
primitiveValue = obj.toString();
}
if (isPrimitive(primitiveValue)) {
return Number(primitiveValue);
} else {
throw new TypeError("Can't convert to primitive");
}
};
复制代码
在使用 +、==、关系运算符(>、< 等)操做对象时,将会将对象转换为原始值,对于 非日期对象,转换套用上述的数字的转换方式(先 valueOf 后 toString),而对于 日期对象,转换套用上述的字符串的转换方式(先 toString 后 valueOf),可是 不会执行最后的变为数字或字符串的类型转换部分。
使用代码模拟一下非日期对象的转换
const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
const obj2Primitive = obj => {
let primitiveValue;
if ('valueOf' in obj) {
primitiveValue = obj.valueOf();
} else if(!('toString' in obj)) {
throw new TypeError("Can't find toString and valueOf");
}
if ('toString' in obj && (!('valueOf' in obj) || !isPrimitive(primitiveValue))) {
primitiveValue = obj.toString();
}
if (isPrimitive(primitiveValue)) {
return primitiveValue;
} else {
throw new TypeError("Can't convert to primitive");
}
};
复制代码
+、== 会在进行上述转换后再次根据操做数进行二次判断,并再次将获得的原始值进行转换。
null 和 undefined 在进行 == 运算时将不会进行类型转换,JavaScript 将会直接判断另外一个操做数是否为 null 或者 undefined 而后直接返回结果
剩余的其它操做符转换类型比较明确,如 - 会将两个操做数都转换为数字(套用到转换为数字的转换规则)
使用 var 对变量进行 重复声明是无害的,由于全部的变量声明都会被提高到代码执行的顶端(声明实质上会在代码编译时执行),因此声明 1 ~ n 次的效果都是同样的。
函数在 定义时 就会绑定一个做用域链(词法做用域、静态做用域相关),如:函数定义区域的做用域 -> 上层函数定义区域的做用域 -> ... -> 全局做用域。
函数在执行时做用域链的顶端将会加入它自身的函数做用域(参数、局部变量)。变量的查找将会随着做用域链一直查找直到找到或者到全局做用域仍未找到报错为止。
以前有道题目是什么状况下 a == 1 && a == 2 为 true,看到上面的对象到原始值的转换,应该就能理解了,其实很简单,借助 == 操做符的隐式类型转换就能够作到。
const a = {
value: 1,
valueOf: function() {
return this.value++;
}
};
// true
console.log(a == 1 && a == 2);
复制代码
注意对象到原始值的转换指的是没有对对象的 toString 和 valueOf 作过变动时的结果。
认真看了上面的转换规则的确定能看出来,为啥 [] 转换为数字会变成 0 呢,由于先调用 valueOf 然而数组默认的 valueOf 就是它自身,不是原始值,因此只能调用 toString,获得空字符串,而后再转成数字,就变成 0 了。
值 | 字符串 | 数字 | 布尔 | 对象 |
---|---|---|---|---|
undefined | 'undefined' | NaN | false | throws TypeError |
null | 'null' | 0 | false | throws TypeError |
true | 'true' | 1 | new Boolean(true) | |
false | 'false' | 0 | new Boolean(false) | |
'' | 0 | false | new String('') | |
' ' | 0 | true | new String(' ') | |
'1.2' | 1.2 | true | new String('1.2') | |
'one' | NaN | true | new String('one') | |
0 | '0' | false | new Number(0) | |
-0 | '0' | false | new Number(-0) | |
NaN | 'NaN' | false | new Number(NaN) | |
Infinity | 'Infinity' | true | new Number(Infinity) | |
-Infinity | '-Infinity' | true | new Number(-Infinity) | |
1.2 | '1.2' | true | new Number(1.2) | |
{} | '[object Object]' | NaN | true | |
[] | '' | 0 | true |