JavaScript 函数高级技巧:作用域安全、惰性载入与函数绑定
本文深入解析 JavaScript 函数的三个高级技巧。首先介绍作用域安全的构造函数,防止忘记使用 new 操作符导致的全局变量污染;其次讲解惰性载入函数,通过运行时替换优化浏览器兼容性检查的性能;最后详解函数绑定机制,利用闭包和原生 bind 方法解决 this 指向问题,提升代码的可维护性与执行效率。
前面的话
函数是任何编程语言的核心概念,在 JavaScript 中更是如此。此前已通过“深入理解函数”系列介绍了函数的基础知识,本文将进一步深入,探讨函数使用的三个高级技巧。
作用域安全的构造函数
技巧一:作用域安全的构造函数
构造函数本质上是一个通过 new 操作符调用的函数。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
}
var person=new Person('match',28,'Software Engineer');
console.log(person.name);//match
若未使用 new 操作符,原本应添加到 Person 对象的三个属性会被错误地添加到 window 对象上。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
}
var person=Person('match',28,'Software Engineer');
console.log(person);//undefined
console.log(window.name);//match
window 对象的 name 属性通常用于标识链接目标和框架,此处对该属性的意外覆盖可能引发页面上的其他错误。解决此问题的方法是创建一个作用域安全的构造函数。
function Person(name,age,job){
if(this instanceof Person){
this.name=name;
this.age=age;
this.job=job;
}else{
return new Person(name,age,job);
}
}
var person=Person('match',28,'Software Engineer');
console.log(window.name); // ""
console.log(person.name); //'match'
var person= new Person('match',28,'Software Engineer');
console.log(window.name); // ""
console.log(person.name); //'match'
然而,采用构造函数窃取模式进行继承会引发副作用。在以下代码中,由于 this 对象并非 Polygon 对象的实例,Polygon() 构造函数会创建并返回一个新的实例。
function Polygon(sides){
if(this instanceof Polygon){
this.sides=sides;
this.getArea=function(){
return 0;
}
}else{
return new Polygon(sides);
}
}
function Rectangle(wifth,height){
Polygon.call(this,2);
this.width=this.width;
this.height=height;
this.getArea=function(){
return this.width * this.height;
};
}
var rect= new Rectangle(5,10);
console.log(rect.sides); //undefined
若要在构造函数窃取模式中应用作用域安全的构造函数,需结合原型链继承,重写 Rectangle 的 prototype 属性,使其实例也成为 Polygon 的实例。
function Polygon(sides){
if(this instanceof Polygon){
this.sides=sides;
this.getArea=function(){
return 0;
}
}else{
return new Polygon(sides);
}
}
function Rectangle(wifth,height){
Polygon.call(this,2);
this.width=this.width;
this.height=height;
this.getArea=function(){
return this.width * this.height;
};
}
Rectangle.prototype= new Polygon();
var rect= new Rectangle(5,10);
console.log(rect.sides); //2
惰性载入函数
技巧二:惰性载入函数
由于各浏览器行为存在差异,我们常在函数中包含大量 if 语句以检查浏览器特性,从而解决不同浏览器的兼容性问题。例如,最常见的为 DOM 节点添加事件的函数。
function addEvent(type, element, fun) {
if (element.addEventListener) {
element.addEventListener(type, fun, false);
}
else if(element.attachEvent){
element.attachEvent('on' + type, fun);
}
else{
element['on' + type] = fun;
}
}
每次调用 addEvent 函数时,它都会检查浏览器支持的能力:首先检查是否支持 addEventListener 方法,如果不支持,再检查是否支持 attachEvent 方法;若仍不支持,则使用 DOM0 级方法添加事件。这个过程在每次调用 addEvent 时都会执行一遍。实际上,如果浏览器支持其中一种方法,它将一直支持,因此无需再进行其他分支的检测。这意味着 if 语句不必每次都执行,代码可以运行得更快。
解决方案是惰性载入。所谓惰性载入,是指函数执行的分支只发生一次。
有两种实现惰性载入的方式。
【1】第一种是在函数被调用时再处理函数。函数在第一次调用时,该函数会被覆盖为另一个按合适方式执行的函数,这样任何对原函数的后续调用都无需再经过执行分支。
我们可以使用以下方式重写 addEvent() 来实现惰性载入。
function addEvent(type, element, fun) {
if (element.addEventListener) {
addEvent = function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if(element.attachEvent){
addEvent = function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
}
else{
addEvent = function (type, element, fun) {
element['on' + type] = fun;
}
}
return addEvent(type, element, fun);
}
在这个惰性载入的 addEvent() 中,if 语句的每个分支都会为 addEvent 变量赋值,从而有效覆盖原函数。最后一步是调用新赋值的函数。下次调用 addEvent() 时,将直接调用新赋值的函数,从而无需再执行 if 语句。
但是,这种方法有一个缺点:如果函数名称发生变化,修改起来会比较麻烦。
【2】第二种方式是在声明函数时就指定适当的函数。这样在第一次调用函数时不会损失性能,仅在代码加载时会损失少量性能。
以下是按照这一思路重写的 addEvent()。以下代码创建了一个匿名的自执行函数,通过不同的分支来确定应该使用哪个函数实现。
var addEvent = (function () {
if (document.addEventListener) {
return function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if (document.attachEvent) {
return function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
}
else {
return function (type, element, fun) {
element['on' + type] = fun;
}
}
})();
函数绑定
技巧三:函数绑定
在 JavaScript 与 DOM 交互中,经常需要使用函数绑定。定义一个函数并将其绑定到特定 DOM 元素或集合的某个事件触发程序上,绑定函数常与回调函数及事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。
<button id="btn">按钮</button>
<script>
var handler={
message:"Event handled.",
handlerFun:function(){
alert(this.message);
}
};
btn.onclick = handler.handlerFun;
</script>
上面的代码创建了一个名为 handler 的对象。handler.handlerFun() 方法被分配为 DOM 按钮的事件处理程序。当按下该按钮时,调用该函数并显示一个警告框。虽然看似警告框应显示“Event handled”,但实际上显示的是 undefined。这个问题在于没有保存 handler.handleClick() 的执行环境,因此 this 对象最终指向了 DOM 按钮而非 handler。
可以使用闭包来修正此问题。
<button id="btn">按钮</button>
<script>
var handler={
message:"Event handled.",
handlerFun:function(){
alert(this.message);
}
};
btn.onclick = function(){
handler.handlerFun();
}
</script>
当然,这是特定于该场景的解决方案,创建多个闭包可能会使代码难以理解和调试。更好的办法是使用函数绑定。一个简单的绑定函数 bind() 接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,同时将所有参数原封不动地传递过去。
function bind(fn,context){
return function(){
return fn.apply(context,arguments);
}
}
这个函数看似简单,但其功能非常强大。在 bind() 中创建了一个闭包,闭包使用 apply() 调用传入的函数,并向 apply() 传递 context 对象和参数。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。
<button id="btn">按钮</button>
<script>
function bind(fn,context){
return function(){
return fn.apply(context,arguments);
}
}
var handler={
message:"Event handled.",
handlerFun:function(){
alert(this.message);
}
};
btn.onclick = bind(handler.handlerFun,handler);
</script>
ECMAScript 5 为所有函数定义了一个原生的 bind() 方法,进一步简化了操作。
只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及 setTimeout() 和 setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
JS 鼠标滚轮事件频繁触发导致卡顿怎么优化?
2026-11-05 17:08:07
优化JS回到顶部性能:防抖节流与requestAnimationFrame应用
2026-11-05 16:33:51
JS中Array、Set与Map的区别及应用场景选择
2026-11-05 16:03:02
PixiJS 常见报错排查:纹理跨域、黑屏与内存泄漏解决
2026-11-05 10:05:21
JS数组find与filter取值区别及性能对比
2026-11-05 08:39:21
