• 读书笔记-你不知道的js(中卷)


    类型

    值和类型

    js中的变量是没有类型的,只有值才有。
    undefined 和 undeclared 是不同的,前者是已经申明但没赋值,后者是未定义。

    var a  
    a // undefined
    b // ReferenceError: b is not defined 报错
    
    • 1
    • 2
    • 3
    var a
    typeof a // undefined
    typeof b // undefined 不会报错,typeof有安全机制
    
    • 1
    • 2
    • 3

    写polyfill有用

    数组

    delete 数组单元后,数组长度不会改变

    数组下标可以传字符串,数字字符串会被转为数字类型(不建议使用)

    字符串

    字符串成员函数不会改变其原始值,会返回一个新的字符串。

    let s = 'abc'
    ss = s.toUpperCase()
    ss === s // false
    ss // ABC
    // s.push('d') // 不能使用
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以使用数组形式取值,但不能使用数组形式修改对应的下标。

    let s = 'abc'
    s[1] // b
    s.chatAt(1) // b
    s[1] = d
    s // abc
    
    • 1
    • 2
    • 3
    • 4
    • 5

    字符串可以借用数组的不可变更函数

    let s = 'abc'
    // s.join 
    // s.map
    Array.prototype.join.call(a, '-')
    Array.prototype.map.call(a, function (v) {
      return v.toUpperCase() + '.'
    }).join('')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对于无法使用数组可变更成员函数的,可以将字符串转数组,处理完后再转回来。

    Array.prototype.reverse(s)
    let ss = s.split('').reverse().join('')
    ss
    
    • 1
    • 2
    • 3

    数字

    let a = 33.22
    // 小数位数
    a.toFixed(3) // 33.220
    // 有效位数
    a.toPrecision(3) // 33.2
    // 有效但不建议写法
    22..toFixed(2) // 22.00
    // 无效写法
    22.toFixed(2) // SyntaxError
    22 .toFixed(2) // 22.00
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    es6
    2 8 16 进制
    推荐字母小写

    误差

    // Number.EPSILON
    0.1 + 0.2
    function isNumberEqual(n1, n2) {
      return Math.abs(n1 - n2) < Number.EPSILON
    }
    let a = 0.1, 0.2
    let b = 0.3
    isNumberEqual(a, b) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最大值最小值,安全值

    Number.MAX_VALUE
    Number.MIN_VALUE // 无限接近0的值
    Number.MAX_SAFE_INTEGER
    Number.MIN_SAFE_INTEGER
    
    • 1
    • 2
    • 3
    • 4

    整数检测

    Number.isInteger(22) // true
    Number.isInteger(22.22) // true
    Number.isInteger(22.2) // false
    
    • 1
    • 2
    • 3

    安全整数

    Number.isSafeInteger(Math.pow(2, 53)) //false
    Number.isSafeInteger(Math.pow(2, 53) - 1) //true
    
    • 1
    • 2

    32位有符号整数,53改为31

    Math.pow(-2, 31)
    Math.pow(2, 31) - 1
    
    • 1
    • 2

    特殊数值

    undefined 可当作变量赋值
    null 不可赋值
    void 运算符
    返回 undefined用

    function getData() {
      if(!api.ready) {
        return void setTimeout(getData, 300);
      }
      return res
    }
    if (getData) {
      // coding...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    等价于

    if(!api.ready) {
      setTimeout(getData, 300);
      return
    }
    
    • 1
    • 2
    • 3
    • 4

    NaN

    a = 6/ 'a'
    typeof a  // number
    a === NaN // false NaN不等于自身
    b = 'a'
    
    isNaN(a) // 是否为非数字的值
    // true
    isNaN(b)
    // true
    
    Number.isNaN(a) // 判断的是数字的操作,操作中值里面带有非数字的部分则为false
    // true
    Number.isNaN(b)
    // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    无穷数
    有穷数可以去无穷数,反之不行
    计算移除结果为 Infinity 或者 -Infinity

    var a = 1 / 0 // Infinity
    Infinity/Infinity // NaN
    1/Infinity // Infinity
    
    
    • 1
    • 2
    • 3
    • 4

    零值

    var a = 0 / -3 //-0
    var b = 0 * -3 // -0
    a.toString() // '0'
    a + '' // '0'
    String(a) // '0'
    JSON.stringify(-0) // '0'
    
    +'-0' // -0
    Number('-0') // -0
    JSON.parse('-0') // -0
    
    a == 0 // true
    a === 0 // true
    -0 === 0 // true
    0 > -0 // false
    0 < -0 // false
    1/a === -Infinity // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    特殊等式

    var a = 2 / 'foo'
    var b = -3 * 0
    Object.is(a, NaN)
    Object.is(b, -0)
    
    • 1
    • 2
    • 3
    • 4

    能使用==、===就不要使用Object.is()。性能高些。

    值和引用
    标量基本类型值是值复制
    复合值是引用复制

    不能修改对方的引用,可以修改共同值

    var a = [1,2,3]
    var b = a
    a[3] = 4
    a // [1,2,3,4]
    b // [1,2,3,4]
    
    b = [5,6,7]
    a // [1,2,3,4]
    b // [5,6,7]
    function fn(a) {
      a.push(5)
      console.log(a) // [1,2,3,4,5]
      a = [1,2,3]
      console.log(a) // [1,2,3]
    }
    fn(a)
    a // [1,2,3,4,5]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    修改引用

    var a = [1,2,3,4]
    function fn(a) {
      a.push(5)
      console.log(a) // [1,2,3,4,5]
      a.length = 0 // 清空数组
      a.push(1,2,3)
      console.log(a) // [1,2,3]
    }
    fn(a)
    a // [1,2,3]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    fn(a.slice()) // 传递浅副本
    
    • 1

    封装标量值到复合值,从而修改变量值

    var obj = {
      a:1
    }
    function fn(wrapper) {
      wrapper.a = 2
    }
    fn(obj)
    obj.a // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    原生函数

    内部属性[[class]],内部的分类,基本类型值被称为被各自的封装对象自动包装

    Object.prototype.toString.call(/abc/i)
    // '[object RegExp]'
    Object.prototype.toString.call([1,2,3])
    // '[object Array]'
    Object.prototype.toString.call({a:1})
    // '[object Object]'
    Object.prototype.toString.call(123)
    // '[object Number]'
    Object.prototype.toString.call('123')
    // '[object String]'
    Object.prototype.toString.call(true)
    // '[object Boolean]'
    Object.prototype.toString.call(null)
    // '[object Null]'
    Object.prototype.toString.call(undefined) 
    // '[object Undefined]'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    封装对象包装

    基本类型无.length,.toString属性和方法,需要通过封装对象才能访问

    var a = 'abc'
    a.length // 3
    a.toUpperCase() // 'ABC'
    
    • 1
    • 2
    • 3

    不要提前自己封装,让引擎自己去操作,引擎会自己优化,自己去优化反而效率更低

    自行封装对象基本类型值

    var a = Object('abc')
    typeof a // 'object'
    a instanceof String // true
    
    • 1
    • 2
    • 3

    拆封

    a.valueOf() // abc
    
    • 1

    原生函数作为构造函数

    var a = new Array(3) // [empty*3]
    var b = [undefined, undefined, undefined] // [undefined, undefined, undefined]
    var c = [] // [empty*3]
    var d = [,,,] // [empty*3]
    var e = Array.apply(null, {length: 3}) // [undefined, undefined, undefined]
    c.length = 3
    a.join('-') // '--'
    b.join('-') // '--'
    a.map(function(v, i){return i}) // [0,1,2]
    b.map(function(v, i){return i}) // [empty*3]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    强制类型转换

    • 返回基本类型
    • 动态类型语言的进行时
    • 将对象强制转化为string是通过ToPrimitive抽象完成的

    ToString

    let ary = [1,2,3]
    ary.toString() // 1,2,3
    
    • 1
    • 2

    JSON.stringify 字符串化,非强制类型转换

    在对象中遇到 undefined, function, symbol 时会自动忽略,数组中则转为null

    不安全的JSON:
    类型:undefined,function,symbol和包含循环引用(对象之间互相引用,形成无限循环)
    返回值:undefined

    处理不安全JSON:
    定义toJSON方法来返回一个安全的JSON值

    var o = {}
    var a = {
      b:1,
      c:o,
      d: function(){},
      e: [1,2,3] 
    }
    o.f = a // 创建循环引用
    // JSON.stringify(a)
    a.toJSON = function() {
      return {
        b: this.b,
        e: this.e.slice(1)
      }
    }
    JSON.stringify(a)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ToNumber

    Object.create() 返回的对象,没有 valueof() 和 toString() 函数,无法进行强制类型转换

    对象转换:

    1. 转换为基本类型值
    2. 非基本类型值,则强制转换

    强制转换

    1. 抽象操作ToPrimitive,内部操作DefaultValue
    2. 检查是否有 valueOf 函数
    3. 检查是否有 toString 函数
    4. 产生 TypeError 错误
    console.log(+true) // 1
    console.log(+false) // 0
    console.log(+undefined) // NaN
    console.log(+null) // 0
    
    • 1
    • 2
    • 3
    • 4

    ToBoolean

    假值对象

    let a = new Boolean(false)
    let b = new Number(0)
    let c = new String('')
    d = a && b && c // d String{''}
    e = new Boolean(a && b && c) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5

    document.all的历史以及被设置为假值以此废弃旧的代码判断,不再执行ie兼容程序。

    字符串和数字互转

    let a = '3.14'
    let b = +a // 3.14
    let c = 1 + - + + - + + 1 // 2 负负得正
    // 转换日期 时间戳
    let d = new Date([2022, 10,1])
    +d // 不建议 
    let t = +new Date() // 不建议
    Date.now() // 建议
    let tt = new Date().getTime() // 建议
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ~

    ~:将对象转换为数字类型并且取反。返回2的补码。

    ~42 // -(42+1) -43
    
    • 1

    -1:哨位值
    抽象渗漏:保留了底层的细节

    ~~

    一般用来取代正数Math.floor()的处理
    只适合32位
    对负数处理与Math.floor()不同

    ~~12.1 // 12
    ~~-12.1 // 12
    Math.floor(12.1) // 12
    Math.floor(-12.1) // -13
    12.1 | 0 // 12
    -12.1 | 0 // 13
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    解析数字字符串

    转换和解析的不同

    • 解析从左到右,遇到非数字字符就停止。
    • 转换不允许出现非数字字符,否则会失败返回NaN.
    let a = '22'
    let b = '22px'
    Number(a) // 22
    parseInt(a) // 22
    Number(b) // NaN
    parseInt(b) // 22
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    parseInt的历史问题
    es5前,下面的场景会出现问题:

    let h = "08"
    let s = '09'
    let time = parseInt(h) + ':' + parseInt(s) // 0:0
    
    • 1
    • 2
    • 3

    parseInt(string, radix)string的第一个字符会被默认为radix的值,0开头即为8进制。
    所以es5前需要parseInt('08', 10)这样的写法。
    es5后默认radix为10,radix的有效值为 0-9,a-i(区分大小写) 19最高
    parseInt一般用来解析数字字符串,对于非数字字符串
    对于非数字字符串,parseInt会先将其强制转换为字符串。

    parseInt(1/0, 19) // 18
    // =>
    parseInt('Infinity', 19) // 解析了i,以19为基数时值为18.
    
    • 1
    • 2
    • 3

    !!

    建议用Boolean或!!来让代码更清晰易读

    // 强制转换JSON里面的数据

    let a = [
      1,
      function() {}
    ]
    JSON.stringify(a) // [1, null]
    JSON.stringify(a, function(k, v) {
      if(typeof v === 'function') {
        return !!v
      } else {
        return v
      }
    }) // '[1, true]'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    隐式强制类型转换的作用是减少冗余,让代码更简洁

    var a = [1]
    var b = [2,3]
    a + b // '12,3'
    
    • 1
    • 2
    • 3

    规则:

    1. 如果其中一个是对象,则调用ToPrimitive抽象操作,调用[[DefaultValue]],转为数字的上下文。
    2. 如果无法使用valueOf取得基本类型值,则转而调用toString()。
    3. 以上操作通过则最后执行拼接操作。

    所以以上执行数组相加,会先执行valueOf,由于无法获取基本类型值,所以将数组转为调用toString(),最后是字符串拼接。
    也就是会呈现出先执行数字运算,后执行字符串运算的效果。

    [] + {} // '' + '[object object]' = '[object object]' 
    {} + [] // 0
    
    • 1
    • 2

    a + ‘’ 和 String(a)的不同

    let a = {
      valueOf: function() {
        return 1
      },
      toString: function() {
        return 2
      }
    }
    a + '' // 1
    String(a) // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    a + '' 先调用valueOf
    String(a) 直接调用toString

    let a = [1]
    let b = [2]
    a - b // -1
    
    • 1
    • 2
    • 3

    布尔值

    查看传入几个布尔值

    隐式强制转换

    function fn() {
      var sum = 0
      for(let i = 0; i < arguments.length; i++) {
        if (arguments[i]) {
          sum += arguments[i]
        }
      }
      return sum === 1
    }
    fn(true, false, false) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    显式强制转换

    function fn() {
      var sum = 0
      for(let i = 0; i < arguments.length; i++) {
        sum += Number(!!arguments[i])
      }
      return sum === 1
    }
    fn(true, false, false) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    隐式类型转换为布尔值

    • if
    • while
    • for 第二个条件
    • ?:
    • || &&

    || &&

    选择器运算符

    let a = 11
    let b = 'abc'
    let c = null
    a || b // 11
    a && b // 'abc'
    c || b // 'abc'
    c && b // null
    function fn (a, b) {
      a = a || 'hello'
      b = b || 'world'
    }
    fn() // 'hello world'
    fn('this is j', '') // 'this is j world'
    function foo() {console.log(d)}
    let d = 1
    d && foo() // 1 短路
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    有和没有!!的差别

    let a = 11
    let b = null
    let c = 'abc'
    if(a && (b || c)) {
      console.log('ok') // ok
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里(b || c) -> 'abc'
    然后a && 'abc' -> 'abc'
    最后'abc' => true

    使用!!

    let a = 11
    let b = null
    let c = 'abc'
    if(!!a && (!!b || !!c)) {
      console.log('ok') // ok
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    避免隐式转换

    symbol属于特殊情况,暂时跳过

    抽象相等

    == 和 ===

    性能不需要在意
    区别:==允许在比较中隐式强制类型转换。===不允许。

    数字和字符串比较,转为数字进行比较

    var a = 11
    var b = '11'
    a == b // true
    a === b // false
    
    • 1
    • 2
    • 3
    • 4

    规则:

    1. type(x)是数字,type(y)是字符串,则x == ToNumber(y)
    2. type(x)是字符串,type(y)是数字,则ToNumber(x) == y

    字符串和布尔值比较, 布尔值转为数字,布尔值优先级最高。

    let a = '12'
    let b = true
    a == b // false
    
    • 1
    • 2
    • 3

    规则:

    1. type(x)是布尔类型,则ToNumber(x) == y
    2. type(y)是布尔类型,则x == ToNumber(y)

    建议不要使用 == true, == false 操作。

    undefined null

    undefined == null

    对象和非对象之间的相等比较

    var a = [11]
    var b = 11
    a == b // true
    
    • 1
    • 2
    • 3
    var a = 'abc'
    var b = Object(a)
    a == b // true
    a === b // false
    
    • 1
    • 2
    • 3
    • 4
    var a = null
    var b = undefined
    var c = NaN
    var x = Object(a)
    a == x // false
    var y = Object(b)
    b == y // false
    var z = Object(c)
    c == z // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    null,undefined不能够被拆封,所以调用Object.create(null)返回对象
    NaN被拆封为NaN,NaN == NaN => false

    [] == ![] // true 
    
    • 1

    如果在浏览器上输入[]则会是false,因为[]会先执行toString,转为’‘,最后才转为false。
    根据类型判断,右边是布尔值。所以根据布尔值和其他类型规则判断,左边会转换为布尔值来和右边比较。
    布尔值强制类型转换,即ToBoolean规则规定:undefined,null,false,’‘,+0,-0,NaN 是假值,其他为真值。
    先执行![], 执行ToBoolean规则操作,[]为true,![]为false。
    接着执行左边的[], []会调用内部的ToPrimiter去调用valueOf,valueOf因为[]是对象无法转为数字,所以交给toString去执行.
    []的toSting是’‘字符串,’'的强制类型转换为布尔值便是 false。

    2 == [2] // true
    '' == [null] // true
    
    • 1
    • 2

    强制类型转换中,数组valueOf返回数组本身,所以数组最后会被字符串化。

    0 == '\n' // true
    
    • 1

    '', ' ', '\n' 会被ToNumber转换为0

    比较 没有严格比较

    先调用ToPrimitive,是否是数字,否则则转为字符串,字符串按照字母顺序比较。

    var a = [11]
    var b = ['22']
    a < b // true
    var a = ['22']
    var b = ['012']
    a < b // false 字符串则按照字母顺序来比较
    var a = [1,2]
    var b = [0,3,4]
    a < b // false
    var a = {b:1}
    var b = {b:1}
    a < b // false,全部转为[object object]
    a == b // false
    a > b // false
    a <= b // true 被处理为 b < a,然后结果反转。<= 实质是 !(a > b),处理为!(b < a)
    a >= b // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    语法

    异步和性能

    将来与现在

    分块的程序

    事件循环

    并行线程

    并发

    任务

    语句顺序

    回调

    条件

    顺序的大脑

    信任问题

    省点回调

    promise

    then 鸭子类型

    promise 问题

    • 调用过早
    • 调用过晚
    • 未调用
    • 调用次数过少、过多
    • 未能传递参数
    • 吞掉异常
      无法使用try-catch捕获异常
    • 信任问题

    链式流

    错误处理

    • 绝望的陷阱
    • 未捕获的错误
    • 成功的坑

    promise模式

    • Promise.all([])
    • Promise.race()
    • 变体
    • 并发迭代

    promiseApi 概述

    • new Promise
    • Promise.resolve
    • then…catch…
    • promise.all()
    • promise.race()

    promise缺点

    • 顺序错误处理
    • 单一值(不算缺点
    • 单一决议
    • 惯性
    • 无法取消
    • 性能

    生成器

    打破完整运行

    • 输入和输出
    • 多个迭代器

    生成器生成值

    • 生产者与迭代器
    • iterable
    • 生成器迭代器

    异步迭代生成器

    生成器+Promise

    • 支持promise的Generator Runner
    • 生成器中的promise并发

    生成器的委托

    • 为什么
    • 消息委托
    • 异步委托
    • 递归委托

    生成器并发

    形实转换程序

    之前的生成器

    程序性能

    性能测试与调优

    高级异步模式

  • 相关阅读:
    Java笔记(五)
    前端对接阿里oss保姆级教程(第二章使用武器)
    如何从清空的回收站中恢复已删除的Word文档?
    【剑指Offer】13.机器人的运动范围
    SpringBoot(三) - Ribbon客户端负载均衡,Zuul网关,Config配置中心
    .NET周刊【10月第1期 2023-10-01】
    Java 并发编程面试题——BlockingQueue
    hadoop学习笔记-centos环境
    for for in for of forEach map reduce filter
    Flutter的Constructors for public widgets should have a named ‘key‘ parameter警告
  • 原文地址:https://blog.csdn.net/junjiahuang/article/details/126347100