js黄皮书笔记(三)|类型与语法

265 阅读8分钟

何为类型?-(并没有标准答案)类型是值的内部特征,他定义了值的行为,并区别各类值

类型

  • 内置类型|七种
    • 空值(null)
    • 未定义(undefined)
    • 布尔值(boolean)
    • 数字(number)
    • 字符串(string)
    • 对象(object)
    • 符号(symbol,ES6新增)
  • 除对象外,其他类型统称为“基本类型”。

typeof与null

  • typeof 运算符可以返回值的类型,一个字符串
  • 但是这对于 null 并不适用。
    • typeof null === “object”; // true
    • 这很有可能会是一个永远无法修复的 js bug,因为牵扯太多 web 系统,修复它会产生更多 bug 而使得很多系统无法正常过工作。
    • !a && typeof === "object"; //由此复合检测 null 值
  • function 是 object 的一个子类型,但是 typeof 对于对象会返回 “function”
    • 其中的 call 属性让他可以被调用,函数可以被理解为“可调用对象”
    • 函数的 length 是其声明的参数个数
  • array 是 object 的一个子类型,用数字顺序来进行属性索引的对象

类型与值

类型是“值”的定义,而变量是没有类型的,变量可以随时持有任何类型的值。

  • undefined 与 undeclared
    • 当变量不持有值为 undefined
    • 变量不存在,没有声明过的变量是 undeclared,不过浏览器会报错 x is not defined。但不意味着这个报错与 undefined 有关。
    • typeof 对于 undeclared 的未声明的变量返回 undefined,由于 typeof 的独特安全防范机制。这使得 typeof 可以用来判断变量是否被定义且没有报空指针错误的风险。

数组

  • tip:delete 运算符可以在数组中删除一个单元,但是数组的 length 属性并不会减一。
  • 数组通过数字进行索引,但是仍然可以像普通对象一样加入正常的键值,但是不会被 length 算入。

字符串

  • 字符串与字符数组有很多相似之处,但并不意味着字符串的实现就是一种数组。
  • 字符串作为一种基本数据类型是不可变的,而数组作为引用数据类型是可变的。
  • 字符串的成员函数不会改变原始值,而是创建并返回一个新的字符串,而数组的成员函数则直接在原始值上操作。
  • 数组的函数用来处理字符串很方便,比如 join map,但是字符串并没有这些函数,可以使用 call 来实现。
  • 但是对于改变原始数组的成员函数,如果字符串本身没有则无法借用,比如反转函数 reverse();
    • 对于这种情况可以吧字符串先用 split 转为字符数组,操作后在使用 join 拼接回字符串。
    • 这并不适用于含有复杂字符的字符串。比如 unicode 或其他多字节字符。

number

  • js 只有 number 一种数字类型,并没有真正意义上的整数。42.0 与 42 在 js 看来是完全相等的。
  • js 的数字基于 IEEE 754 标准实现,双精度格式。(64位)
  • 语法
    • 42.0 = 42
    • 0.42 = .42
    • 5E10 = 50000000000
  • Number.prototype
    • 数字值可以使用 Number 对象封装,所以数字值可以调用 Number.prototype 的方法。
    • toFixed 返回指定小数位数的字符串表示,toPrecision 指定有效数位的显示位数。
    • .运算的点号是一个有效的数字常量字符,他被认为是值的一部分优先级高于认为是运算符
      • (42).toFixed.| 0.42.toFixed | 42..toFixed 是正确的
      • 42.toFixed 错误
  • 机器精度
    • 由于 IEEE 754 规范对于小数的处理精度问题,这个误差值已经被存入 Number.EPSILON。
    • 使用 Math.abs(a,b) < Number.EPSILON 来判断两个小数是否相等。
    • 对于整数,有安全范围 2^53-1,最大整数与最小整数被定义为 Number.MAX_SAFE_INTEGER。
    • 有些特殊操作只能操作32位,如与或运算之关心前32位,后面的部分会被忽略。
  • NaN | not a number
    • 理解为失败的数值或坏数值
    • NaN 是唯一一非自反值(NaN === NaN // false),使用 Number.isNaN 来确定一个值是否是NaN
  • Infinity | 无穷数
    • 除以0
    • 数字变成无穷数是不可逆的
    • 自除不为0,是一个未定义操作,得NaN
  • Object.is(a,b) | 用这个做比较可以处理上述所有情况!

值与引用

  • js 对值传递或引用传递在语法上没有区别,完全由语言自身根据值的类型来决定。
    • 对象和函数是引用传递,其他为值传递。
  • 更多见往期文档,数据类型相关。

原生函数

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol()——ES6 中新加入的!

他们可以使用 new 操作符来当作构造函数使用,且构造结构为 object 而非对应的基本数据类型。

内部属性class

通过 Object.prototype.tostring 访问到的 [object class],其中第二个字段即为对象内部的 class 属性值。

封装对象包装

  • 基本类型并没有 .length 等等属性和方法,所以在调用时 js 会自动的创建一个基本类型的包装对象并调用对应的方法。但是能无需提前封装来“做优化”,js 引擎已经做过了相关优化,自己封装反而会降低性能。
  • 一般不推荐使用封装对象,例如 new Boolean(false) 在 if 中为 true,但某些特定时候也能派上用场。
  • 使用 ValueOf() 来得到封装对象中的基本类型值。

强制类型转换

值类型转换

  • 将值的类型转换为另一种类型被称为显式类型转换。隐式类型转换又称为强制类型转换
  • 强制类型转换总是返回标量的基本类型值,而不会返回封装对象或函数
  • 另一种区分方式,静态类型语言的编译阶段的转换称为显式类型转换,运行时发生的类型转换称为隐式类型转换。

抽象值操作

ToPrimitive,抽象操作,toXXX 都属于抽象操作 - 先检查值是否有 valueOf() 方法,如果有则调用其返回基本类型值 - 如果没有则使用 toString() 返回 string ,再来进行强制类型转换为目标基本类型值 - 如果两个方法都没有返回基本类型值,则返回 typeError 错误 - Object.create(null) 创建的对象没有原型,也没有上述的两个方法,所以无法进行强制转换

转字符串

  • toString
    • 对于普通对象来说,toString 返回内部属性 class 的值。eg:[object object]
    • 数组的 toString 已经被改写了,将所有的单元转为字符串再用逗号链接
  • JSON.stringify
    • 对大多数简单值来说,与 toString 基本相同
    • 所有安全的 JSON 值都能用它进行字符串化
      • 不安全的 JSON 值有 undefined symbol function 或循环引用等
    • 定义一个 toJSON 方法来返回一个安全的 JSON 值然后再调用 sttingify 来字符串化
    • stringify 的第二个参数很有用
      • 如果是数组,则必须是字符串数组,序列化时只会关注这些字符的属性名
      • 如果是函数,则参数为键和值,return 为处理后的值,序列化前会通过函数处理一次值
    • 第三个参数为缩进格式
      • 如果是数字则一次缩进为多少个空格
      • 如果是字符则用这个字符代替一个缩进

转数字

  • ES5规范:true-1,false/null-0,undefined-NaN

  • ToNumber

    • 对字符串处理为数字,如果字符串不符合数字规范则返回 NaN
    • 但是对于0开头的字符串并不按16进制处理
  • 对象会先抽象转换成基本类型值,如果基本类型不是数字在进行一次转换

转布尔

  • 假值
    • undefiend
    • null
    • false
    • +0,-0,NaN
    • “”
  • 假值转换结果为 false,除此之外都是假值,包括封装了假值的对象也都是真值
  • 假值对象
    • 并非封装了假值的对象,而是一种 js 语法之外的外来值
    • 例如 doucument.all,则是假值,因为它已经被废止

显式转换

  • 运算符
    • 一元运算符 + 可以作为 Number 的转换,例如 +“42” 会被转换为 42
      • 5+ +‘3.14’ = 8.14
    • 有很多运算符可以进行显示转换,但是这不利于代码可读性,比如 ~
  • 解析数字字符串 parseInt
    • 从左到右将字符转换为数字,遇到非数字字符则停止
    • parseInt('42px') = 42
    • 不要尝试传入非字符串,这是在自找麻烦
  • 转布尔
    • 可以用 !!a 来将 a 转换为 布尔。
    • 其他的和常用方法差距不大

隐式转换

扬长避短,取其精华,弃其糟粕

字符串与数字

  • + 加法运算
    • [1,2] + [3,4] = "1,23,4"
      • 抽象操作没有得到基本类型而被 toString 后拼接字符串
    • ` 42 + '' = '42'
  • - 减法运算
    • '3.14' - 0 = 3.14

|| 和 && 与其他语言不同的是,在 js 中与或运算并不返回布尔,而是两个值中的一个

  • 对第一个操作数执行布尔判断
    • || 第一个数布尔转换后为 true,则返回第一个值
    • && 第一个数为true,则返回第二个值

else
对于 symbol,支持显式转换为 string,隐式转换则报 typeError

宽松相等== 与 严格相等===

过往的认知形容前者是比较值,后者是类型和值,可惜这是错误的。
正确的解释是前者允许在比较时进行强制类型转换,后者不允许

  • 性能
    • 根据错误的解释似乎严格相等工作量更大,因为进行了类型的比较
    • 实际上宽松相等更大,因为进行了强制类型转换,但是这个事情的性能消耗仅仅是微秒级,可忽略不计。
    • 他们都会比较类型,只不过对于类型不同的情况下采取了不同的处理
  • 抽象相等|这种宽松相等算法正是隐式强制类型转换被人诟病的原因
    • 如果两个值同等类型,则仅比较他们是否相等
      • NaN 不等于自己
      • +0 等于 -0
      • 对象则比较是否指向同一个对象而非值比较
    • 布尔与其他类型:先将布尔转为数字01,变成数字与其他类型的规范
    • 字符串与数字:将字符串转为数字
    • null 与 undefined 在 == 中相等,可用 == null 来代替 === null || === undefined
    • 对象与非对象:将对象抽象操作后比较
      • 对于null,undefiend,NaN,a == Object(a)为 false。因为他们没有封装对象,object 返回一个常规的空对象。
    • 重写 valueOf 来改变抽象操作结果,仅仅用于面试就行,开发中用出来属于是脑淤血
  • 扬长避短中的短
    • 避免 == false,以下比较都是 true
      • "0" == false
      • false == 0
      • false == ""
      • false == []
    • 以下也是 true,涉及 [],"",0 的尽量避免的情况
      • "" == 0; //true
      • "" == [];
      • 0 == []

抽象关系比较

  • 比较双方先进行抽象操作
  • 如果量变都是字符串则按照字母顺序依次比较,知道第一个不想等的字母出现
  • 如果不符合两边都是字符串,则两边都转为 number 进行比较

语法

  • 语句有结果值,仅供了解目前没啥用;
  • 语句可以带标签
  • {} + []; //0 代码块的坑
  • js其实没有 else if,实际上后面的 if 作为一个单独的语句跟在了 else 后面。

运算符优先级

  • 例如 && 优先级高于 ||
    优先级参考链接
  • 运算符多了依然建议通过()来确定,需要保证代码可读性

TDZ

typeof 对于未声明变量返回 undeifend 不会报错,但是在 TDZ 暂时性死区中会报错。

try .. catch .. finally

如果 try 中发生了终止,比如 return 或 throw,则 finally 会在终止语句之前触发。
如果 finally 发生了终止,则原来 try 中的终止语句会被抛弃。