拉勾教育大前端高薪训练营的学习笔记-函数式编程和javascript性能优化

文章内容输出来源:拉勾教育Java高薪训练营

函数式编程

  1. 学习函数式编程的理由

    • react和vue3.0都使用函数式编程
    • 函数式编程可以抛弃this
    • 打包时可以更好的利用tree-shaking过滤无用的代码
    • 方便测试和并行处理
    • 有许多第三方库帮助我们进行函数式开发,如lodash,underscore, ramda
  2. 高阶函数的意义

    • 抽象通用问题
    • 代码更加简洁
  3. 闭包:

    • 函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包
    • 可以在另一个作用域中调用一个函数的内部函数并访问到该函数作用域的成员

    闭包的本质:函数执行时会放到执行栈中,执行完毕后就从执行栈移除,但堆上的作用域成员即变量因为被外部引用即返回function中被引用,所以不能被释放,因此内部函数即返回的function依然可以访问外部函数的成员

  4. 纯函数:相同的输入,输出相同,即不改变原数组

    const arr = [1,2,3,5,7,8,6]
    cosnt {log}=console
    // 输出都一样,为纯函数
    log(arr.slice(0,3))
    log(arr.slice(0,3))
    // 输出不一样,为不纯函数
    log(arr.splice(0,1))
    log(arr.splice(0,1))
    
  5. lodash纯函数的使用

  6. 纯函数的好处:

    • 可缓存,将结果缓存起来(lodash.memoize)
    • 可以并行处理
    • 可测试
  7. web worker可以开启js的多线程

  8. 函数柯里化:当一个函数有多个参数时,可以先传递一部分参数,然后返回一个新函数接受剩余参数,返回结果

     const checkAge = min => (age=>age>=min)
     cosnt {log}=console
     let checkAge18 = checkAge(18)
     let checkAge20 = checkAge(20)
     
     checkAge18(24) // false
     checkAge18(15) // true
     checkAge20(20) // true
     checkAge20(24) // false
    
    // lodash柯里化
    const _ = require('lodash')
    const {log}= console
    
    function getSum(a,b,c){
        return a+b+c
    }
    
    const curried = _.curry(getSum)
    
    log(curried(1,2,3))
    log(curried(1)(2,3))
    log(curried(1,2)(3))
    
  9. 柯里化的优点

    • 给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
    • 这是一种对函数参数的缓存
    • 让函数更加灵活,粒度更小
    • 可以把多元函数转换成一元函数,可以使用函数组合
  10. 函数组合: 一个函数经过多个函数(数据管道)得到最终值,可以将中间的函数合并成一个函数。

    注意:合并的函数为纯函数

    // lodash.flowRight() 实现函数组合
    const _ = require('lodash')
    const {log}= console
    
    const reverse = arr=>arr.reverse()
    const first = arr=>arr[0]
    const toUpper = s=>s.toUpperCase()
    
    const f = _.flowRight(toUpper,first,reverse)
    log(f(['sss','ddd','ewew']))
    
  11. 函数组合-结合律

  12. 函数组合-调试

    // 调试
    const _ = require('lodash')
    const {log}= console
    const trace = _.curry((msg,v)=>{
      log(`${msg}: ${v}`)
      return v
    })
    const m = _.flowRight(_.toUpper,trace('first'),_.first,trace('reverse'),_.reverse)
    
  13. lodash/fp模块

    • lodash和lodash/fp模块map有区别;区别在于lodash.map(arr,(value,index,arr)=>{}),fp.map((value)=>{},arr)
  14. point free:合成函数,函数组合

  15. Functor(函子)

    • 为了将副作用控制在可控范围内
    • 函子:是一种特殊的容器(函数),通过一个普通的对象来实现,该对象有map方法,map方法可以运行一个函数对值进行处理(变形关系)
    // Functor
    // 封装_value值,防止副作用
    // _value就是副作用的外部变量
    // class作为一个盒子
    class Container{
        static of(v){
            return new Container(v)
        }
        constructor(v){
            this._value = v
        }
        // 通过map返回一个纯函数,用来处理_value(盒子内部的值)
        map(fn){
            return Container.of(fn(this._value))
        }
    }
    
    let r =  Container.of(5)
            .map(x=>x+2)
            .map(x=>x*x)
    console.log(r)   
    
  16. 函数副作用:让一个函数变成不纯函数,函数内依赖外部变量(状态),会导致输入相同时,外部变量变化时,输出不相同的副作用

  17. maybe函子为了解决函子传入null引发的副作用(Container.of(null))

  18. Either函子:两者中的任何一个,类似if…else…的处理;也可以用来处理异常

  19. IO函子

  20. Folktale和task异步执行

    const fs = require('fs')
    const {task} = require('folktale/concurrency/task')
    const {split, find} = require('lodash/fp')
    const {log} = console
    
    function readFile(filename){
        return task(resolver=>{
            fs.readFile(filename, 'utf-8', (err,data)=>{
                if(err) resolver.reject(err)
                resolver.resolve(data)
            })
        })
    }
    
    // task是一个函子
    readFile('package.json')
        .map(split('\n'))
        .map(find(x=>x.includes('version')))
        .run()
        .listen({
            onRejected:err=>log(err),
            onResolved: value=>log(value)
        })
    
  21. Pointed 函子就是有static of(){}的函子

  22. Monad函子同时具有join和of两个方法并遵守一些定律

  23. Functor, Pointed MayBe, Either, IO, Task,Monad

    • Functor函子就是带有map方法的class
    • Pointed 函子就是有static of(){}的函子
    • MayBe函子多了一个对null和undefined的判断。用于解决传值为空的问题
    • Either函子多了对的和错的两种函子,正确的使用对的函子,报错用错的函子。用于解决异常报错问题
    • Task函子是folktale的task。用于异步执行
    • IO函子this._value=fn;new IO(function(){return value})。可以延迟执行一个函数,控制副作用
    • Monad函子同时具有join和of两个方法并遵守一些定律;join()返回this._value();也可以说带有join方法的IO函子。用于解决函子嵌套问题

javascript性能优化

  1. js内存管理

    • 申请内存空间
    • 使用内存空间
    • 释放内存空间
  2. js的垃圾回收机制

    • 找不到的对象或不在引用的对象就是垃圾
    • 能够访问到的对象就是可达对象
    • 全局变量对象就是根
  3. GC算法:垃圾回收的机制

    • 引用计数:
      • 计数为0,就释放内存;
      • 即使回收垃圾对象;减少程序暂停,因为内存空出来了
      • 有变量循环引用,内存不释放的问题;一旦对象很多,修改计数消耗资源较大
    • 标记清除:
      • 遍历对象进行标记,没标记的释放内存;
      • 解决变量循环引用,内存不释放的问题;
      • 有内存碎片化的问题;不会立即回收垃圾对象
    • 标记整理
      • 遍历对象进行标记,在进行内存整理,没标记放到一块一起释放内存;
      • 减少碎片化空间
      • 不会立即回收垃圾对象
    • 分代回收
    1. V8

      • 采用的是即时编译,直接转成机器码
      • 内存设限,64位1.5G;32位800M
    2. V8垃圾回收策略

      • 分代回收
      • 内存分为新生代和老生代
      • 不同对象使用不同算法
    3. V8回收新生代

      • V8内存空间一分为二
      • 小空间存储新生代对象(32M|16M)
      • 新生代对象就是存活时间较短的对象
      • 新生代回收实现
        • 回收采用复制算法和标记整理
        • 将新生代内存分为两个等大的空间
        • 使用空间为From,空闲空间为To
        • 活动对象存储在From空间
        • 标记整理后将活动对象copy到To空间
        • From在与To交换空间后释放内存
        • 总结:每当有新生对象诞生,就会在 From 空间出现,当From存储到一定空间时会触发GC即标记整理,标记整理后将整理的活动对象(From中包含活动和不活动对象,这里只copy活动对象)copy到To,之后将From变成To释放内存,而To变成From,开始下一轮循环
      • 晋升:将新生代移动到老生代
        • 拷贝是可能会出现晋升
        • 一轮GC(垃圾回收)还存活的新生代需要晋升
        • To空间使用率超过25%,需要晋升
  4. 老生代回收

    • 老生代对象存放在右侧老生代区域
    • 64位1.4G空间,32位700M空间
    • 老生代对象就是存活时间较长的对象(比如闭包)
    • 老生代回收实现
      • 主要采用标记清除,标记整理,增量标记算法
      • 首先使用标记清除完成垃圾回收
      • 当新生代晋升时,老生代区放不下时,用标记整理进行空间优化
      • 增量标记进行效率优化
  5. Performance工具:监控内存

    • chrome开发者工具,性能界面
    • 开始录制,操作界面
    • 结束录制,选择内存可以看到有什有降,就是垃圾回收
  6. 内存问题

    • 页面出现延迟加载或经常暂停
    • 页面持续性出现糟糕的性能
    • 页面性能随时间越来越差
  7. 内存问题标准

    • 内存泄漏: 内存使用持续升高
    • 内存膨胀: 在多数设备上都存在性能问题,判断是否时程序问题,可以在多个设备上执行进行比较
    • 频繁垃圾回收: 通过内存变化图分析
  8. 监控内存的方法

    • 浏览器任务管理器
    • Timeline时序图记录(开发者工具的性能界面)
    • 堆快照查找分离DOM(开发者工具的内存界面)
    • 判断是否存在频繁的垃圾回收
  9. 增量标记:在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。

    并发标记:该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行

  10. js性能优化

    • 使用Benchmark.js的http://jsperf.com完成
    • ops/sec 值每秒执行次数越大越好
  11. js 代码性能优化方法

    • 少用全局变量
    • 缓存全局变量,缓存到局部变量
    • 通过原型对象添加附加方法,方法声明使用prototype来添加方法
    • 避免闭包,方法最好直接定义一个名字来调用
    • 避免属性访问方法使用,访问属性最好直接访问,不要使用方法返回
    • for循环优化,使用.length最好是在let i= n.length;i;i–,不然每次比较都要获取.length
    • 性能forEach>for>for in
    • 节点添加的优化,使用document.createDocumentFragment()
    • 克隆节点优化,使用使用newNode = document.getElementById(‘old’).cloneNode(flase)替换newNode = document.getElementById(‘old’)
    • 直接量替换new object,要使用arr=[1,2,3,4],最好不要arr = new Array(1,2,3,43)

你可能感兴趣的:(前端,JavaScript)