「大前端」JS Type


  • 为什么有的编程规范要求用 void 0 代替 undefined?
  • 字符串有最大长度吗?
  • 0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
  • ES6 新加入的 Symbol 是个什么东西?
  • 为什么给对象添加的方法能用在基本类型上?

运行时类型是代码实际执行过程中我们用到的类型。所有的类型数据都会属于 7 个类型之一。从变量、参数、返回值到表达式中间结果,任何 JavaScript 代码运行过程中产生的数据,都具有运行时类型。

类型

  • Undefined
  • Null
  • Boolean
  • String
  • Number
  • Symbol
  • Object

q1: 为什么有的编程规范要求用 void 0 代替 undefined?

void 运算符通常只用于获取undefined的原始值,一般使用void(0)(等同于void 0),因为 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设计失误之一,所以为了避免无意中被篡改,建议使用void 0来获取 undefined 值。

q2: 字符串有最大长度吗?

String 的最长长度是2^53 - 1,String 的意义并非“字符串”,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。
JavaScript 中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

let aa = "string";
aa[0] = "S";
aa; // string

q3: 0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?

根据双精度浮点数的定义,非整数 Number 无法使用==/===进行比较,正确的比较方法是使用 JS 提供的最小精度值:

Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON;

q4: ES6 新加入的 Symbol 是个什么东西?

ES6 内引入的新类型,是一切非字符串的对象 key 的集合。

symbol 是一种基本数据类型primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:”new Symbol()“。

var mySymbol = Symbol("my symbol");

可以使用 Symbol 的某些属性实现一些功能,比如可以使用Symbol.iterator(迭代器)来自定义for...of在对象上的行为:

var o = new Object();

o[Symbol.iterator] = function () {
  var v = 0;
  return {
    next: function () {
      return { value: v++, done: v > 10 };
    },
  };
};

for (var v of o) console.log(v); // 0 1 2 3 ... 9

q5: 为什么给对象添加的方法能用在基本类型上?

对象的定义是“属性的集合”。属性分为数据属性和访问器属性,二者都是 key-value 结构,key 可以是字符串或者 Symbol 类型。
JavaScript 中的“类”仅仅是运行时对象的一个私有属性,而 JavaScript 中是无法自定义类型的(TypeScript 解决了 JavaScript 的类型问题)。

3 与 new Number(3) 是完全不同的值,它们一个是 Number 类型, 一个是对象类型。

3 === new Number(3); // false,产生新的对象
3 === Number(3); // true,强制进行类型转换

Number、String 和 Boolean,三个构造器是两用的,当跟 new 搭配时,它们产生对象,当直接调用时,它们表示强制类型转换。

运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。

Symbol.prototype.hello = () => console.log("hello");
var a = Symbol("a");
console.log(typeof a); //symbol,a并非对象
a.hello(); //hello,有效

类型转换

StringToNumber

在不传入第二个参数的情况下,parseInt 只支持 16 进制前缀“0x”,而且会忽略非数字字符,也不支持科学计数法。在一些古老的浏览器环境中,parseInt 还支持 0 开头的数字作为 8 进制前缀,这是很多错误的来源。所以在任何环境下,都建议传入 parseInt 的第二个参数,而 parseFloat 则直接把原字符串作为十进制来解析,它不会引入任何的其他进制。

简易的字符串转数字:

function convertStringToNumber2(string, x = 10) {
  let charts = string.split("");
  let number = 0,
    i = 0;
  while (i < charts.length && charts[i] !== ".") {
    number = number * x;
    number += charts[i].codePointAt(0) - "0".codePointAt(0);
    i++;
  }
  if (charts[i] === ".") i++;
  let fraction = 1; // 小数
  while (i < charts.length) {
    fraction = fraction / x;
    number += (charts[i].codePointAt(0) - "0".codePointAt(0)) * fraction;
    i++;
  }
  return number;
}

在进行字符串到数字的类型转换时尽量使用 Number()进行转换。

NumberToString

当 Number 绝对值较大或者较小时,字符串表示则是使用科学计数法表示的。

装箱转换

每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象。

全局的 Symbol 函数无法使用 new 来调用,但我们仍可以利用装箱机制来得到一个 Symbol 对象,我们可以利用一个函数的 call 方法来强迫产生装箱。

var symbolObject = function () {
  return this;
}.call(Symbol("a"));

console.log(typeof symbolObject); //object
console.log(symbolObject instanceof Symbol); //true
console.log(symbolObject.constructor == Symbol); //true
var symbolObject = Object(Symbol("a"));

每一类装箱对象皆有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取:

console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]

拆箱转换

在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换。

对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则,拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。

先执行 valueOf 还是 toSting 取决于你的后续操作。

var o = {
  valueOf: () => {
    console.log("valueOf");
    return {};
  },
  toString: () => {
    console.log("toString");
    return {};
  },
};
o * 2; // valueOf toSring TypeError

String(o); // toString valueOf TypeError

var o = {
  valueOf: () => {
    console.log("valueOf");
    return {};
  },
  toString: () => {
    console.log("toString");
    return {};
  },
};

o[Symbol.toPrimitive] = () => {
  console.log("toPrimitive");
  return "hello";
};
o + ""; // toPrimitive hello

其他规范类型

  • List 和 Record: 用于描述函数传参过程。
  • Set:主要用于解释字符集等。
  • Completion Record:用于描述异常、跳出等语句执行过程。
  • Reference:用于描述对象属性访问、delete 等。
  • Property Descriptor:用于描述对象的属性。
  • Lexical Environment 和 Environment Record:用于描述变量和作用域。
  • Data Block:用于描述二进制数据。

实践

不用原生的 Number 和 parseInt,用 JavaScript 代码实现 String 到 Number 的转换,该怎么做呢?

let num = "12345";
+num;

文章作者: 阿汪同学
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 阿汪同学 !
评论
  目录