Skip to content

变量类型

变量类型包括

  • 六种原始类型,包括Boolean、String、Number、Null、Undefined、Symbol
  • 引用类型,Object对象

类型判断用到哪些方法?

  • typeof,可以得到undefined boolean number string object function、symbol等类型结果
    • 注意对于数组、null和object而言,typeof都会返回'object'
  • instanceof,用于实例和构造函数的对应,其工作机制是:检测右侧构造函数的prototype属性是否出现在左侧实例对象的原型链上。
  • Object.prototype.toString.call(xxx)将会得到一个包含变量类型的字符串

数据类型转换 == 和 ===

主要需要注意的是 == 操作符的比较规则,当两个数据的类型不一致时会发生类型转换,且可能同时发生类型转换,对应规则如下图所示

总结一下==比较的规则

  • null == undefined,反之亦然;
  • 字符串与数字比较,会将字符串转换成数字然后进行比较
  • 布尔值与任何类型比较,都先将布尔值转换成数字,然后再进行比较
  • 字符串与对象比较,先将对象传入toPrimitive,然后与字符串进行对比;数字同理

关于对象转原始类型ToPrimitive(input, PreferredType?),可以参考:

其中hint的取值可以为numberstringdefault,表示当前语境传入的值(或者说是表示这是一个什么类型的运算),常见的运算中

js
var a = {
    [Symbol.toPrimitive](hint) {
        console.log(hint)
    }
}
+a // hint为number
a + 1 // hint为default

// 下面情况hint为string
var o = {}
o[a] = 'xx' // string
alert(a) // string
parseInt(a) // string

ToPrimitive的具体流程如下:

  • 如果对象存在Symbol.toPrimitive,调用 obj[Symbol.toPrimitive](hint)
    • 其中hint为当前语境传入的值(或者说是表示这是一个什么类型的运算),取numberstringdefault; *如果不存在Symbol.toPrimitive,且 hint 取值是 "string":
    • 无论是否存在,先调用 obj.toString(),如果返回了原始值,则使用该值;否则调用obj.valueOf()
  • 否则(也就是 hint 取值是 "number" 或 "default" 的情况):
    • 无论是否存在,先调用 obj.valueOf(),如果返回了原始值,则使用该值;否则调用obj.toString()

需要注意

  • Symbol.toPrimitive方法或最后调用的obj.toString只能返回原始值,否则会抛出TypeError
  • 我们创建的字面量对象{}[]不包含Symbol.toPrimitive方法,且valueOf方法返回的是自身,仍旧为引用值
  • 但是Date对象实现了默认的Symbol.toPrimitive方法,且返回的返回的是date.toString的值
js
let user = {
    name: "John",
    money: 1000,

    [Symbol.toPrimitive](hint) {
        // 手动修改toPrimitive的值
        return false
    }
};
console.log(user == false) // true

// 先调用valueOf,返回[],需要再调用toString,返回'', 表达式变成'' == false,
// 然后将布尔值转换为数字, '' == 0, 
// 字符串与数字比较时将字符串转换为数字,表达式变为 0 == 0,返回true
console.log([] == false)

最后,记住上面的比较只会发生在原始值和对象之间,当二者均为引用对象时,比较的是内存地址而不是上面的运算规则

实战一下

js
console.log(null == undefined) // true
console.log(undefined == null) // true

console.log(undefined == false) // false NaN !== 0
console.log(undefined == true) // false NaN !== 1

console.log([] == '') // true, '' == '' => 0 == 0
console.log([] == true) // false, '' == true => 0 === 1
console.log([] == ![]) // true, Boolean([])为true, [] == false => 0 == 0
console.log([] == []) // false, 数组地址不同
console.log([0] == false) // true '0' == 0 => 0 == 0

console.log({} == {}) // false,对象地址不同
console.log({} == !{}) // false, {} == false => '[object Object]' == 0 => 1 == 0

null 和 undefined的区别

  • null 表示一个对象是“没有值”的值,也就是值为“空”;
  • undefined 表示一个变量声明了没有初始化(赋值);

新增数据类型Symbol

Symbol 是ES6新增的一种基础数据类型,代表的是 唯一且不与其他任何类型相同的值。

js
// 创建一个唯一的symbol
var sy1 = Symbol("foo") 
var sy2 = Symbol("foo")
console.log(sy1 === sy2) // false

// 创建一个可复用的symbol,其原理为:从全局symbol注册表中查找对应的symbol,如果没有则新建并返回;如果有则直接返回
var gSy =  Symbol.for("foo") 
var gSy2 =  Symbol.for("foo")

console.log(sy1 == gSy) // false
console.log(gSy2 == gSy) // true

console.log(Symbol.keyFor(gSy)) // foo

Symbol的一个作用是当做对象的属性名。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

但是需要注意的是:Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回,通过Object.getOwnPropertySymbols方法可以获取指定对应的所有Symbol属性名

Symbol的另外一个作用是:可以创建唯一的枚举值,如redux中的actionType等。

函数参数传递

JS中的变量类型可以分为基础类型和引用类型,需要注意的是:JS中的函数参数传递,都是按值进行的

  • 原始类型的处理方式是,将参数的值完全复制一份,按值传递,
    • 对于传递过来的变量进行修改,不会影响到原变量。
  • 引用类型的处理方式是,将参数的地址复制一份,按值传递地址(更准确的说是引用复制传递)
    • 对于变量的成员进行修改时,会直接影响原变量;
    • 而如果对传递过来的变量进行重新赋值,则不会影响原变量,并且此后再修改变量的成员,也不会影响原变量。
js
var obj = {
    x: 1
}
function foo(o){
    o.x = 100
}
function foo2(o) {
    o = 100
}

console.log(obj) // {x:1}
foo(obj)
console.log(obj) // {x:100}
foo2(obj)
console.log(obj) // // {x:100}

这种设计的意义在于:

  • 按值传递的类型,复制一份存入栈内存,这类类型一般不占用太多内存,而且按值传递保证了其访问速度。
  • 按共享传递的类型,是复制其引用,而不是整个复制其值(C 语言中的指针),保证过大的对象等不会因为不停复制内容而造成内存的浪费。

数字精度问题

0.1 + 0.2 不等于0.3,这是因为,计算机的二进制实现和位数限制有些数无法有限表示。 JS采用双精度存储

  • 1位用来表示符号位
  • 11位用来表示指数
  • 52位表示尾数

当表示浮点数时可能出现尾数无限循环,因此只能四舍五入,由于只存在0和1,这就会导致计算机浮点数运算出现误差。

解决办法

  • 现将浮点数转换成整数,计算之后在除以扩大的倍数,如(0.1*10+2*10)/10
  • 手动实现计算库,按位进行计算

判断数组的几种方式

instanceOf,主要是判断左侧对象的原型链上能不能找到右侧构造函数的原型

js
[] instanceOf Array // true
[] instanceOf Object // true

Object.prototype.toString,常用语判断浏览器内置对象

Object.prototype.toString.call([]) // [object Array]

Array.isArray主要用来检测对象是否为数组,他的优点在于可以检测来自iframe中的对象

js
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3]

// Correctly checking for Array
Array.isArray(arr);  // true
Object.prototype.toString.call(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false