Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

《你不知道的javascript(中)》1.2 值 #27

Open
qunzi0214 opened this issue Feb 9, 2021 · 0 comments
Open

《你不知道的javascript(中)》1.2 值 #27

qunzi0214 opened this issue Feb 9, 2021 · 0 comments
Labels
read book 读书笔记

Comments

@qunzi0214
Copy link
Owner

qunzi0214 commented Feb 9, 2021

数组

需要注意,空白单元可能会导致出人意料的结果,和将数组元素显式赋值为 undefined 是有所区别的(在不同浏览器上表现也不一致)

var a = []

a[0] = 1
a[2] = 3

a[1] // undefined
a.length // 3

由于数组也是对象,所以也可以包含字符串键,但是不会被计算在数组长度内。如果字符串键能被强制类型转换为十进制数字的话,它会被当做数字键处理

var a = []

a[0] = 1
a['foo'] = 'bar'

a.length // 1
a['foo'] // bar
a.foo // bar

a['1'] = 3
a.length // 2
a[1] // 3

一些DOM查询操作,或是 arguments 对象得到的都是类数组,可以将他们转化为真正的数组。

function foo(a, b) {
  var arr = Array.prototype.slice.call(arguments)
  // ES6: var arr = Array.from(arguments)
  arr.push('bam')
  console.log(arr)
}

foo('bar', 'baz') // ['bar', 'baz', 'bam']

字符串

javascript中字符串是不可变更的,即字符串的成员函数不会改变其原始值,而总是创建并返回一个新的字符串。同时字符串也是类数组,可以借用数组的非变更方法来处理字符串

var str = 'abc'
var arr = Array.prototype.slice.call(str)

arr // ['a', 'b', 'c']

var newStr = Array.prototype.join.call(str, '-')

newStr // 'a-b-c'

如果借用数组的变更方法来处理字符串(和书中结论不一致,推测是最新浏览器js引擎对字符串对象的键 setter 做了限制)

var str = Array.prototype.reverse.call('abc')
// Uncaught TypeError: Cannot assign to read only property '0' of object '[object String]'

数字

javascript只有一种数值类型:number,没有真正意义上的整数。javascript中的整数即没有小数的十进制数,所以 42.0 === 42

特别大和特别小的的数字默认用指数格式显示,与 toExponential() 函数输出的结果相同。

var a = 5e10
a // 50000000000
a.toExponential() // '5e+10'

toFixed() 方法可以指定小数部分的显示位数,但是需要注意,返回的是给定数字处理后的字符串

var a = 42.59

a.toFixed(0) // '42'
a.toFixed(1) // '42.6'
a.toFixed(2) // '42.59'
a.toFixed(3) // '42.590'
a.toFixed(4) // '42.5900'

toPrecision() 方法用来指定有效数位的显示位数,同样返回的是字符串

var a = 42.59

a.toPrecision(1) // '4e+1'
a.toPrecision(2) // '43'
a.toPrecision(3) // '42.6'
a.toPrecision(4) // '42.59'
a.toPrecision(5) // '42.590'
a.toPrecision(6) // '42.5900'

数字字面量也可以使用相应的方法(运算时字面量会被Number对象封装),但是需要注意 . 运算符,因为它是一个有效的数字字符

42.toFixed(3) // SyntaxError
(42).toFixed(3) // '42.000'
0.42.toFixed(3) // '0.420'
42..toFixed(3) // '42.000'
42 .toFixed(3) // '42.000'

数字字面量同样可以表示二进制、八进制、十六进制,推荐表示进制的字符统一采用小写

0xf3 // 243 十六进制
0o363 // 243 八进制
0b11110011 // 243 二进制
0363 // 243 八进制,但是严格模式下已经不支持,不建议使用

二进制浮点数(IEEE 754规范)最大的问题是,小数并不精确

0.1 + 0.2 === 0.3 // false

可以通过设置一个误差值的方式来处理小数,通常称为机器精度(machine epsilon)。在javascript中,这个值是 2^-52(2.220446049250313e-16) ,在ES6中,这个值被定义在 Number.EPSILON 中。

if(!Number.EPSILON) {
  Number.EPSILON = Math.pow(2, -52)
}

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

numberEqual(0.1 + 0.2, 0.3) // true

能够呈现的最大浮点数大约是 1.798e+308 ,它定义在 Number.MAX_VALUE 中。最小浮点数大约是 5e-324 ,它定义在 Number.MIN_VALUE 中。能够被安全呈现的最大整数是 2^53 - 1 ,在ES6中,被定义在 Number.MAX_SAFE_INTEGER ,同理最小整数是 Number.MIN_SAFE_INTEGER 。可以通过 Number.isIntegerNumber.isSafeInteger 来检测一个数是否是整数/安全整数。

需要注意的是,有些数字操作(如数位操作)只适用于32位数字,此时数字的安全范围为 Math.pow(-2, 31) 到 Math.pow(2, 31) - 1

特殊数值

在非严格模式下,可以为全局标识符 undefined 赋值(千万不要这么做!)

function foo() {
  undefined = 2
}

function bar() {
  'use strict'
  undefined = 2 // TypeError
}

运算符 void 并不改变表达式的结果,只是让表达式不返回值

var a = 42
console.log(void a, a) // undefined 42

void 使用场景:

function doSomething() {
  if(!APP.ready) {
    return void setTimeout(doSomething, 100) // 和下方注释代码等价
    // setTimeout(doSomething, 100)
    // return
  }
  
  // 返回处理完成后的结果
  var result
  return result
}

if(doSomething()) {
  // 继续做其他事情
}

NaN(not a number) 是唯一一个非自反的值,即 NaN !== NaN 。检查一个数值是否是 NaN 有两种方式,其中全局 isNaN 函数有个严重的缺陷,应该使用ES6提供的 Number.isNaN ,或者通过唯一一个非自反的值这个特性来判断

var a = 2 / 'foo'
var b = 'bar'

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

在JavaScript中,存在无穷数 Infinity(Number.POSITIVE_INFINITY) 和 -Infinity(Number.NEGATIVE_INFINITY) ,当运算结果溢出时,会在无穷数和最大数之间做一个“就近取整”。同时 Infinity / Infinity 是一个未定义操作,结果为 NaN

var a = Number.MAX_VALUE // 1.7976931348623157e+308
a + Math.pow(2, 970) // Infinity (距离Infinity更近)
a + Math.pow(2, 969) // 1.7976931348623157e+308 (距离Number.MAX_VALUE更近)

0 在JavaScript中是区分正负的。这在某些场景下是必要的,例如“动画帧的移动速度”,数字的符号位可以用来记录移动方向,假设某个时刻速度值为 0 ,那么保留正负号不会丢失移动方向。

var a = 0 / -3 // -0
var b = 0 // 0

a.toString() // '0'
a + '' // '0'
String(a) // '0'
JSON.stringify(a) // '0'

// 有意思的是,反向操作是正确的
+ '-0' // -0
Number('-0') // -0
JSON.parse('-0') // -0

// 比较操作
a == b // true
a === b // true
b > a // false

// 区分0和-0
function isNegZero(n) {
  return (n === 0) && (1 / n === -Infinity)
}
isNegZero(a) // true
isNegZero(b) // false

ES6中,可以通过 Object.is() 来判断两个值是否绝对相等,可以借用这个方法来判断特殊数值(性能比运算符差)

var a = 2 / 'foo'
var b = 0 / -1

Object.is(a, NaN) // true
Object.is(b, -0) // true

值和引用

JavaScript中没有指针,变量不可能成为另一个变量的引用,引用指向的是某一个具体的值。赋值和引用完全根据值的类型来决定:null, undefined, string, number, boolean, symbol 总是通过值复制的方式赋值/传递,object(以及其各种子类型 function, array, regexp) 总是通过引用复制的方式来赋值/传递。

由于引用指向的是值而非变量,所以一个引用无法更改另一个引用的指向

var a = [1]
var b = a
a // [1]
b // [1]

b = [2]
a // [1]
b // [2]

对于函数参数也是同理

function foo(x) {
  x.push(4)
  x // [1, 2, 3, 4]
  
  x = [4, 5, 6]
  x.push(7)
  x // [4, 5, 6, 7]
}

var a = [1, 2, 3]
foo(a)
a // [1, 2, 3, 4]

需要注意的是,因为基本类型值是不可更改的,所以对应对象子类型在引用时,如果遇到值发生变化的情景,会解除引用并将变化的值转化为基本类型(而非基本类型对应的对象子类型)

var a = new Number(1)
b = a 
a // Number{1}
b // Number{1}
typeof a // 'object'

b = b + 1
b // 2
a // Number{1}
typeof b // 'number'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
read book 读书笔记
Projects
None yet
Development

No branches or pull requests

1 participant