[TOC]

1.1. javascript进阶

1.1.1. 高阶函数&函数抽象

js的一些本质

js中引用类型的值是按引用访问

js始终是单线程的,不同的语句块是以队列的形式排列在EventLoop中按顺序执行的

内存管理

管理内存的意义
  • 减少浏览器负担

内存过大会让浏览器压力过大,导致浏览器卡顿

  • node端

内存如果不够,服务就会中断,而nodejs开启的服务,如果不管理内存,就会中断

堆和栈

普通类型是通过栈储存;引用类型是通过堆储存,再由栈存储引用

v8引擎
  • 64位下是1.4g,32位下是700mb

事实上现行的浏览器会有些扩容,而node会自动使用一些c++内存扩容

  • v8引擎事实上分为新生代和老生代两个部分

新生代:短时间存活的变量会存在新生代中,内存量极小,64位下大概是32mb

老生代:生存时间比较长的变量,会转存到老生代,老生代占据了几乎所有的内存,64位下大概是1400mb

  • 回收算法

新生代:可以简称为复制-清空

算法过程:把存活着的变量复制到to空间,然后把from空间清空,然后对调from和to。这样可以提升回收速度,典型的牺牲空间换时间

老生代:标记已死变量 =》清除已死变量 =》整理磁盘

  • 什么时候触发回收?

1⃣️执行完一次代码,清空栈的时候

2⃣️内存不够的时候

全局变量会直到程序执行完毕,才会回收

普通变量是失去引用的时候,会被回收

只要变量被全局引用就不会被回收

  • 新生代与老生代的转换

☝️新生代发现本次复制后,会占用25%的to空间

☝️这个对象已经经历过一次回收

  • 如何检测内存

node运行时需要限制内存--max-old-space-size=1000

  //在浏览器端
  window.perfomance.memory
    {
    usedJSHeapSize: 1000 // 已使用内存
  }
    //Node端
  process.memoryUsage()
    {
    rss:123 // 总占用的内存 包括v8和c++
    heapTotal:233 // v8引擎的总内存
    heapUsed:244 // v8引擎已使用内存
    external:555 // v8调用的额外内存
    arrayBuffers:111
  }
优化内存
  1. 尽量不要定义全局,定义了要及时手动释放
  2. 注意闭包(闭包其实是局部引用)
  3. 限制数组长度
node的特殊点

node可以手动触发垃圾回收 global.gc

node可以设置内存限制--max-old-space-size=1000--max-new-space-size=1000

为什么v8为1.4
  1. 1.4g对于浏览器脚本是够用的
  2. 垃圾回收的时候是阻塞式的,在回收的时候会中断代码的执行;如果内存越大,垃圾回收耗时也会更久。

代码性能指标

健壮性

健壮性就是代码抗击风险的能力

目的:

  • 避免和程序无关的因素,导致代码报错

    比如不传参数

    function a(type){
    var data = {
      1: [1,2,3],
      2: [4,5,6]
    }
    return data[type][1]
  }
    // 改进 增添默认值
    type = type || 1
    // 改进
  • 快速给出问题反馈,找出bug产生的地方

    比如传错参数

    function b(n1, n2){
    return n1 + n2
  }
    // 如果n1或n2不是Number类型,就会返回NaN
    // 改进 增添检测
    if(typeof n1 != 'number' || typeof n2 != 'number'){
    throw new Error('n1 or n2 must be a number')
  }
  • 不会因为非必要、不可控的bug而导致程序执行中断

    比如不稳定的第三方接口

    // 解决方法
    try{
    throw new Error // 第三方借口报错
  }catch(error){
    log(error)
  }
  • 深数据处理(读取深数据)
    • 给默认值
    • 利用&&检测数据是否存在
可读性
  • 优化丑陋的结构

    1. if-else分支过长,可以使用策略模式、状态模式优化
    2. 分支套分支,通过条件&&条件
    3. 回调地狱,参考promise、asnyc
  • 遵循命名规范

    1. 常量 ,全大写
    2. 类名,首字母大写的驼峰
    3. 普通的变量和方法,驼峰
    4. 局部变量,下划线开头_num
    5. 语义化
  • 良好的注释

可复用性
  • 重复的操作不用写第二遍

  • 减少代码体积

做法(具体参考设计模式)

  • 桥接模式、享元模式等设计模式改进可复用性

  • 函数式编程

  • 低耦合,高内聚,模块遵循如此原则

可扩展性

增加新需求,不用变动现有的结构

增加新功能,只需要写新功能就好

做法 (具体参考设计模式)

留出扩展接口

模块低耦合,高内聚

良好的模块组织,只有消息交流

良好的扩展方案


函数式编程

常见的编程规范有:面向过程、面向对象、函数式编程

什么时候使用函数式编程

写一些工具库,或者第三方库

要是纯函数,不与实际业务耦合

工具库:(underscore)

  • 一些windows操作
  • 一些常见的数据操作
  • 一些业务面常见操作(比如请求后端api)
为什么要用函数式编程

能提高复用性和可扩展性

每一个函数就是一块积木,我们能随时拼入新的积木,随时能够去除积木去复用

可以被Tree-shaking

什么是Tree-shaking?

在打包时,(通过查找文档流)把没有用到的方法给去掉

函数式编程的缺点

逻辑性差,过多的函数难以管理,在处理业务逻辑的时候还是面向对象更好一点。

如何写好函数式编程

函数要求

  • 保证纯函数

    一个函数的返回结果只依赖于他的参数,同样的输入必定有同样的输出

  • 减少函数副作用

    函数副作用就是影响外部的数据,如全局变量

引用类型消除函数副作用(通过浅拷贝)

对象通过Object.create()

数组通过[...arr]

工程化下的函数式编程

(为了更好的配合Tree-shaking)

    //es6
    // 导出
    export function f1(){}
    export function f2(){}
    // 引入
    import {f1} from "./model.js"
    import * as all from "./model.js"

    //commonJS
    // 导出
    function f1(){}
    function f2(){}
    exports.f1 = f1
  exports.f2 = f2
    // 引入
    const all = require("./model.js")
  const f1 = require("./model.js").f1

compose和pipe

为什么需要compose和pipe

  • 值的传递写起来不方便

    我们连续执行一系列函数,传递某个值很难受

  • 执行起来不方便

    需要连续执行

    // compose 从右向左执行
    function compose(...funcs){
    /* 一种写法
     return function (...args){
       return func.reduceRight((res,cb)=> cb(res), ...args)
    }
    */
    // 第二种
    //return funcs.reduce((a, b) => (...args) =>  a(b(...args)))
    // 第三种
    return funcs.reduceRight((a, b) => (...args) =>  b(a(...args)))
  }
// pipe 从左向右执行
function pipe(...funcs){
  /* 一种写法
  return function(...args){
    return func.reduce((res,cb) => ,cb(res) ...args)
  }
  */
  return funcs.reduce((a, b) => (...args) =>  b(a(...args)))
}

通过promise组成链式调用

    Promise.resolve(10).then(multi1).then(add2).then(other).then((res)=> console.log(res))

高阶函数

如果一个函数接受另一个函数作为参数, 那么我们称该函数为高阶函数

常见的高阶函数
  • forEach和Maps
Array.prototype.myForEach = function(cb){
  // this为调用函数的数组
  // this[i]为当前遍历的value
  // i为当前遍历的索引index
  var len = this.length
  if(typeof callback != 'function'){
        throw new Error('must be function')
  }
  for(let i = 0; i < len; i++){
    cb.call(this, this[i], i)
  }
}
// Map
Array.prototype.myMap = function(cb){
  var len = this.length
  let arr = [] //这里不同
  if(typeof callback != 'function'){
        throw new Error('must be function')
  }
  for(let i = 0; i < len; i++){
    arr.push(cb.call(this, this[i], i))// 这里不同
  }
  return arr
}
  • reduce
reduce((preOrTotal, current, index, arr)=>{}, init)

Array.prototype.myReduce = function(fn,init){
  let i = 0
  let len = this.length
  let preOrTotal = init
  if (init == undefined){
    preOrTotal = this[0]
    i = 1
  }
  for(;i < len; i++){
    preOrTotal = fn.call(this, preOrTotal, this[i], i, this)
  }
  return preOrTotal
}
  • filter
filter((currentValue, index, arr) => {}, thisValue)

Array.prototype.myFilter = function (fn, thisValue){
  let len = this.length
  let i = 0
  let _res = []
  for(i; i < len; i++){
    if(fn.call(this, this[i], i, this)) {
      let _body = this[i]// 保存一下value
      if(typeof this[i] === 'object'){
        // _body = Object.create(this[i]) 我觉得这样不行
        _body = Object.assign({},this[i]) // 浅拷贝
      }
      _res.push(_body)
    }
  }
  return res
}
自己的高阶函数
  • 保持纯函数,减少函数副作用
  • 调用选择call还是apply(确定参数使用call,不确定使用apply)
function findProperty(obj, fn){
  let _obj = Object.create(obj)
  for ( let item in _obj){
    // 要进行的判断、操作
  }
}

函数柯里化

定义:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受剩余参数而且返回结果的新函数的技术

使用场景:

不方便传入参数(回调、promise)

常用bind()函数做柯里化,手写bind

func.bind(this, args)

Function.prototype.myBind = function (that/*, arguments*/) {
  if(typeof this != 'function'){
    throw new Error('must be function')
  }
  let _self = this
  let partArgs = slice.call(arguments, 1) // 去除that
  return function (/*arguments*/) {
    partArgs = partArgs.concat(slice.call(arguments)) // 整合arguments
    return _self.apply(that, ...partArgs)
  }
}

防抖与节流

防抖

为了避免高频的事件触发,只选择最后一次触发作为真正的触发;比如输入框输入,用户在输入文本时,是快速的,不应该在每一次按键事件触发就去操作页面(比如联想),而是在用户输入停下时,去触发操作。

特点:防抖会等到用户停止触发,才会执行操作

事件触发 => 开启一个定时器 => 如果再次触发,则清除上一次的,就重开一个 => 定时事件到了,触发操作

function debounce(fn, delay){
  let timer = null
   let _self = this
  return function(){
    clearTimeout(timer)
    timer = setTimeout(()=>{
      fn.apply(_self, arguments)
    },delay)
  }
}
节流

在第一次触发的时候,就进行操作,而不论紧接着多少次的触发,都会只进行第一次的操作,直到第一次操作结束。

特点:某个操作希望上一次的完成后再进行下一次,或者说希望隔一定时间触发一次。规定时间内只触发一次。

事件触发 => 操作执行 => 关闭阀门 => 阀门关闭,后续触发无效 => 一定时间后,阀门打开 => 操作可再次触发

function throttle(fn, delay){
  let timer = null
  let _self = this
  return function(){
    if(timer){
      return
    }
    timer = setTimeout(()=> {
      fn.apply(_self, arguments)
    })
  }
}
function throttle1(fn, delay){
  let vaild = true // 阀门,默认开启
  let _self = this
  return function () {
    if(vaild){
      setTimeout(()=>{
        fn.apply(_self, arguments)
        vaild = true // 操作完成之后,打开阀门
      }, delay)
      vaild = false // 在开启了定时器之后,关闭阀门
    }else{
      return false
    }
  }
}
© Jasonk0 all right reserved,powered by Gitbook该文件修订时间: 2022-07-21 08:53:32

results matching ""

    No results matching ""