JS基础--类型

137 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

类型

1. 八种内建值类型

  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol
  • bigint

注意: 除了 object 所有这些类型都被称为“基本类型(primitives)”。

变量没有类型,但是值有类型

2. typeof

typeof 操作符可以检测给定值的类型,而且总是返回八种字符串值中的一种 (没有null,但是有function)

特别typeof null === "object"; // true

function:对象(object)的“子类型”。一个函数(function)被称为“可调用对象” —— 一个拥有 [[Call]] 内部属性、允许被调用的对象。class也是function

判断类型: MDN

function type(value) {
  // null单独处理
  if (value === null) {
    return "null";
  }
  const baseType = typeof value;
  // 基本类型,直接返回
  if (!["object", "function"].includes(baseType)) {
    return baseType;
  }

  // 对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 
  // Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里。
  const tag = value[Symbol.toStringTag];
  if (typeof tag === "string") {
    return tag;
  }

  // 判断是否为class
  if (
    baseType === "function" &&
    Function.prototype.toString.call(value).startsWith("class")
  ) {
    return "class";
  }

  // 构造函数名,例如`Array`, `GeneratorFunction`,
  // `Number`, `String`, `Boolean` or `MyCustomClass`(自定义的class)
  const className = value.constructor.name;
  if (typeof className === "string" && className !== "") {
    return className;
  }

  // 兜底
  return baseType;
}

3. undefined vs "undeclared"

var a;
a; // undefined
b; // ReferenceError: b is not defined
var a;
typeof a; // "undefined"
typeof b; // "undefined"

安全检查

// 噢,这将抛出一个错误!
if (DEBUG) {
	console.log( "Debugging is starting" );
}

// 这是一个安全的存在性检查
if (typeof DEBUG !== "undefined") {
	console.log( "Debugging is starting" );
}

4. Array

4.1 “稀散”的 array (坑,尽量避免)

留下或创建空的/丢失的值槽

// 1:只指定长度,不赋值
var arr1 = new Array(5) 
arr1.length == 5 // true

// 2:指定下标赋值,未赋值的为空值槽
var arr2 = []
arr2[4] = undefined
arr2.length == 5 // true
console.log(arr2) // [empty × 4, undefined]

// 3: 通过delete删除,(不会改变length)
var arr3 = [0,1,2,3,4]
delete arr3[4]
arr3.length == 5 // true
console.log(arr3) // [0, 1, 2, 3, empty]

// 4: 直接改变length
var arr4 = []
arr4.length = 5
console.log(arr4) // [empty × 5]
var a = new Array(5)
a.join('-').length == 4 // true

4.2 字符串属性 (坑,尽量避免)

  • 添加字符串属性不会改变Array的length
  • 如果是10进制数字字符串,会被转成数字,会被当做number引索
var arr = []
arr['a'] = 1
arr.length == 0
arr['0'] = 0
arr.length == 1
// 只打印0,字符串属性 遍历不了
arr.map(item => {
  console.log(item)
})

5. string

5.1 string与Array

var str = '012'
str[0] == '0' //(老板ie不支持)
str.charAt(0) == '0' // 推荐

var str = '𠮷' // 两个码点
str.length == 2
str[0] == str.charAt(0) // \uD842
str.split('') // ['\uD842', '\uDFB7']
Array.from(str) // ['𠮷']

6. number

6.1 精度损失

image-20220628100923963.png

IEEE 754 标准,使用64位固定长度来表示,也就是标准的 double 双精度浮点数
符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
指数位E:中间的 11 位存储指数(exponent),用来表示次方数
尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零

0.1+0.2过程

十进制的0.1和0.2会被转换成二进制的,0.1和0.2二进制小数是无限不循环的,截取53?位

相加后再转成10进制,就有了精度损失

/**
 * 单精度 32位
 * 十进制转二进制: 123.456
 * 整数部分:不断除2,取余数,直到商为0
 * 小数部分:不断乘2,取整数,直到积为0
 * 
 * 计算过程
 * 整数
 *  123/2 = 61(1);
 *  61/2 = 30(1);
 *  30/2 = 15(0);
 *  15/2 = 7(1);
 *  7/2 = 3(1);
 *  3/2 = 1(1);
 *  1/2 = 0(1);
 * 逆向取值,所以整数转为二进制:1111011
 * 
 * 小数
 *  0.456*2 = 0.912(0);  
 *  0.912*2 = 1.824(1);
 *  0.824*2 = 1.648(1);
 *  0.648*2 = 1.296(1);
 * 
 *  0.296*2 = 0.592(0);
 *  0.592*2 = 1.184(1);
 *  0.184*2 = 0.368(0);
 *  0.368*2 = 0.736(0);
 *  ... 无限不循环
 * 
 * 正向取值,所以小数转化为二进制:0111 0100 1011 1100 0110 1010 0111 1110 1111 1001 1101 1011
 * 
 * 故该十进制对应的二进制数为:1111011.011101001011110001101010011111101111100111011011
 * 转成"尾数+阶码"的格式为:1.111011011101001011110001101010011111101111100111011011 * 2^6,所以阶码E应该为 6 + 127 = 133
 * 精度损失之后保留23位:1.11101101110100101111000 * 2^6
 * 
 * 符号为正,故是0
 * 阶码为6,而存储阶码时,32位偏移量为127(64位为1023),所以实际应该存 127+6 = 133,转成二进制为 1000 0101
 * 尾数直接取二进制小数 11101101110100101111000
 * 最后转成单精度的值为 0 10000101 11101101110100101111000
 * 参考:https://juejin.cn/post/6875257293257048078
 */

ES6 中,容差值 Number.EPSILON

function numbersCloseEnoughToEqual(n1,n2) {
	return Math.abs( n1 - n2 ) < Number.EPSILON;
}

var a = 0.1 + 0.2;
var b = 0.3;

numbersCloseEnoughToEqual( a, b );					// true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 );	// false

6.2 精确计算

类库:Math.js、decimal.js、big.js

实现:

  1. 乘10的n次方在计算,35.41 * 100 = 3540.9999999999995
  2. 记录小数点位置,替换掉小数点,算完后添加小数点

6.3 安全整数范围

Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER

判断:Number.isSafeInteger()

6.4 NaN

var a = NaN;
var b = "foo";

window.isNaN( a ); // true
window.isNaN( b ); // true -- 噢!

Number.isNaN( a ) // true
Number.isNaN( b ) // false

Object.is( a, NaN );	// true

6.5 +0 -0

-0 == 0;	// true
-0 === 0;	// true

function isNegZero(n) {
	n = Number( n );
	return (n === 0) && (1 / n === -Infinity);
}

isNegZero( -0 ); // true

Object.is( -0, -0 );		// true

7. instance of

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

基础用法

const auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car); // true
console.log(auto instanceof Object); // true

class继承

class A {}
class B extends A {}
const o2 = new B();
// true, because Object.getPrototypeOf(Object.getPrototypeOf(o2)) === A.prototype
o2 instanceof A;
// true, because Object.getPrototypeOf(o2) === B.prototype
o2 instanceof B;

Symbol.hasInstance 重写


class Forgeable {
  static isInstanceFlag = Symbol("isInstanceFlag");
  static [Symbol.hasInstance](obj) {
    return Forgeable.isInstanceFlag in obj;
  }
}
const obj = { [Forgeable.isInstanceFlag]: true };
console.log(obj instanceof Forgeable); // true

bind

class Base {}
const BoundBase = Base.bind(null, 1, 2);
console.log(new Base() instanceof BoundBase); // true

多frame/windows问题

// 会返回 `false,因为 Array.prototype !== window.frames[0].Array.prototype
[] instanceof window.frames[0].Array

Object.create

function Shape() {}
function Rectangle() {
  Shape.call(this); // call super constructor.
}

Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

const rect = new Rectangle();
rect instanceof Object; // true
rect instanceof Shape; // true
rect instanceof Rectangle; // true

优先级

!mycar instanceof Car  // false, !优先级大于 instanceof