[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
}
优化内存
- 尽量不要定义全局,定义了要及时手动释放
- 注意闭包(闭包其实是局部引用)
- 限制数组长度
node的特殊点
node可以手动触发垃圾回收 global.gc
node可以设置内存限制--max-old-space-size=1000和--max-new-space-size=1000
为什么v8为1.4
- 1.4g对于浏览器脚本是够用的
- 垃圾回收的时候是阻塞式的,在回收的时候会中断代码的执行;如果内存越大,垃圾回收耗时也会更久。
代码性能指标
健壮性
健壮性就是代码抗击风险的能力
目的:
避免和程序无关的因素,导致代码报错
比如不传参数
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)
}
- 深数据处理(读取深数据)
- 给默认值
- 利用
&&检测数据是否存在
可读性
优化丑陋的结构
if-else分支过长,可以使用策略模式、状态模式优化- 分支套分支,通过
条件&&条件 - 回调地狱,参考promise、asnyc
遵循命名规范
- 常量 ,全大写
- 类名,首字母大写的驼峰
- 普通的变量和方法,驼峰
- 局部变量,下划线开头
_num - 语义化
良好的注释
可复用性
重复的操作不用写第二遍
减少代码体积
做法(具体参考设计模式)
桥接模式、享元模式等设计模式改进可复用性
函数式编程
低耦合,高内聚,模块遵循如此原则
可扩展性
增加新需求,不用变动现有的结构
增加新功能,只需要写新功能就好
做法 (具体参考设计模式)
留出扩展接口
模块低耦合,高内聚
良好的模块组织,只有消息交流
良好的扩展方案
函数式编程
常见的编程规范有:面向过程、面向对象、函数式编程
什么时候使用函数式编程
写一些工具库,或者第三方库
要是纯函数,不与实际业务耦合
工具库:(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
}
}
}