深入理解JavaScript函数作为对象的高级特性
本文深入探讨JavaScript中函数作为第一类对象的核心特性,包括属性与方法的支持。通过函数缓存、记忆模式(Memoization)及上下文伪造数组等实际案例,展示如何利用函数特性优化性能、管理状态及提升代码灵活性,帮助开发者掌握高级JS编程技巧。
前言
JavaScript函数作为第一类对象
JavaScript赋予了函数非常多的特性,其中最重要的特性之一就是将函数作为第一类型的对象。这意味着在JavaScript中,函数不仅可以拥有属性和方法,还可以享有所有普通对象所具备的特性。此外,函数最核心的能力在于它可以被直接调用执行。
我们可以通过简单的实验来验证这一特性。
// 简单实验 函数作为对象的存在
let fn = function () {}
fn.prop = 'fnProp'
console.log(fn.prop) // fnProp
为函数添加属性的功能在日常开发中可能较少被主动尝试或使用,但在某些JavaScript库或事件回调管理场景中,它能发挥巨大的作用。接下来我们将通过几个具体示例来深入探讨。
利用函数属性进行缓存
函数缓存
在某些场景下,我们需要存储一组相关但相互独立的函数。这一需求看似简单,实现方式也不复杂。最直观的做法是使用数组来保存所有函数,但这并非最优解。下面我们将通过利用函数的属性来实现这一目标。
// 1:函数缓存示例
let store = {
nextId: 1, // id
cache: {}, // 缓存
add (fn) {
// 如果函数中没有id属性那么就缓存
if (!fn.id) {
console.log(`begin add func ${fn.name}`)
fn.id = store.nextId ++
// 设置完缓存之后返回true
return !!(store.cache[fn.id] = fn)
} else {
console.log(`${fn.name} is already in cache`)
}
}
}
function storeCache() {}
store.add(storeCache) // begin add func storeCache
store.add(storeCache) // storeCache is already in cache
上述代码逻辑清晰:`store`对象用于管理缓存,`cache`属性用于存储函数,`nextId`属性用于保存当前的缓存ID。`add()`方法用于设置存储,它首先判断当前函数是否已存在于缓存中,然后再进行缓存设置,从而限制函数的重复添加,最后返回布尔值true。
`!!`构造是一种将任意JavaScript表达式转换为等效布尔值的简洁方式。
缓存记忆模式(Memoization)
缓存记忆函数
这种函数能够记住之前计算过的结果,从而避免重复计算,这显然能够显著提升代码的执行性能。
在举例之前,我们先分析这种方式的优缺点。
优点
- 缓存了之前的结果,最终用户享有性能优势
- 实际上是发生在幕后,操作无感
缺点
- 内存的牺牲这是肯定的
- 打破了存粹性(一个函数或者方法应该只做好一件事)
- 如果方法中有算法,那么很难测量这个算法的性能
了解了优缺点我们来看一个简单的计算素数的例子(不是很严谨)
// 2: 缓存记忆函数
function isPrime (value) {
if (!isPrime.anwers) isPrime.anwers = {}
// 先从缓存里面取
if (isPrime.anwers[value] != null ) {
return isPrime.anwers[value]
}
// 开始进行判断和计算
let prime = value != 1
for (let index = 2; index < value; index++) {
if (value % index == 0) {
prime = false
break;
}
}
// 保存计算出来的值
return isPrime.anwers[value] = prime
}
console.log(isPrime(5))
console.log(`从函数记忆中直接读取${isPrime.anwers[5]}`)
这里的好处是特别明显的,我们再次的取用`isPrime.anwers[5]`的时候不需要经过任何的计算,但是大型的计算要主要内存的使用
缓存记忆DOM元素
缓存记忆DOM元素
通过元素的标签查询DOM的操作代价是昂贵的,各位前端开发者肯定都很清楚。我们下面使用缓存记忆的方式来优化这一操作。
// 3:缓存记忆DOM元素
function getElements (name) {
if (!getElements.cache) getElements.cache = {}
return getElements.cache[name] = getElements.cache[name] || document.getElementsByTagName(name);
}
console.log(getElements('div')) // HTMLCollection
console.log(getElements.cache['div']) // HTMLCollection
这个函数和上面的缓存使用了同一个手法,而且这简单的4句代码能为我们的性能带来大幅度的提升。这也算是一种超能力吧。函数的很多特性都和其上下文有关,接下来我们研究一个和上下文相关的例子。
利用上下文伪造数组方法
伪造数组方法(上下文相关)
在某些情况下,我们想创建一个包含一组数据的对象,但是这个数据包含很多的状态,比如和集合项有关的元数据,那么我们用数组来存就不太合适了。那么这里我们就用对象的方式来假扮数组。通过改变上下文来完成一些“不法的行为”。
// 4:伪造数组方法
// <input type="button" id="add" >
// <input type="button" id="remove" >
let elems = {
length: 0, //为了保存个数
add (elem) {
Array.prototype.push.call(this, elem)
},
gather (id) {
this.add(document.getElementById(id))
}
}
elems.gather('add')
elems.gather('remove')
console.log(elems[0]); // <input type="button" id="add" >
console.log(elems[1]); // <input type="button" id="remove" >
console.log(elems.length); // 2
console.log(elems);
/**
0: input#add
1: input#remove
add: ƒ add(elem)
gather: ƒ gather(id)
length: 2
*/
在我还对JS懵懵懂懂的时候看到这样的操作被秀了一脸,简直是刺激了我幼小的心灵。
我们在`add`函数中实现了把元素添加到了集合中,而且Array又正好提供push方法,不用白不用。这种操作也是直白的展示了函数上下文的超强特性。
总结
JavaScript强大的灵活性,也带来更多的可能性。路漫漫其修远兮,吾将上下而求索。
代码地址
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
crypto-js 处理 Base64 和 Hex 编码转换的正确姿势
2026-11-05 16:39:11
ammo.js 内存管理:避免 Wasm 环境下的内存泄漏技巧
2026-11-05 10:21:09
JS几种补零方法的性能对比与选择建议
2026-11-04 21:17:50
JS数组去重最佳实践:Set、filter与reduce实现方案
2026-11-04 10:57:15
基准元素选择策略对 JavaScript 快速排序性能的影响
2026-11-03 22:37:29
