JavaScript OOP滚动监听异常:bind(this)后仍无法正确触发对象方法
JavaScript OOP滚动监听问题:bind后仍无法正确触发每个对象的方法
我是JavaScript OOP新手,最近在做一个需求:管理一批对象,当它们对应的DOM元素进入视口时执行特定操作。但现在遇到了两个棘手的问题:
- 滚动监听只有当Albert元素进入视口时才会触发
- 触发时所有对象的
i_am_watching方法都会执行
我按照MDN中EventTarget.addEventListener()的说明,给监听函数加了.bind(this),但问题依然存在,想请教各位大佬哪里出错了?
我的代码如下:
<html> <head> <style> .content { background: #f8f8f8; padding: 20px; height: 1000px; } .person { padding: 20px; font-size: 20px; line-height: 20px; } .person.mark { background: pink; } .person.peter { background: lightblue; } .person.albert { background: lightgreen; } div + div { margin-top: 20px; } div { border: 1px solid black; } #console { position: fixed; top: 10px; right: 10px; width: 50%; height: 300px; overflow: auto; border: 3px solid black; background: white; } </style> </head> <body> <div id="console"></div> <div class="content">...some content...</div> <div id="mark_div" class="person mark">MARK</div> <div class="content">...some content...</div> <div id="peter_div" class="person peter">PETER</div> <div class="content">...some content...</div> <div id="albert_div" class="person albert">ALBERT</div> <div class="content">...some content...</div> <script> function Person( name, div_id ) { // properties this.label = name; this.div = document.getElementById( div_id ); // auxiliary function this.is_in_viewport = function( el ) { var rect = el.getBoundingClientRect(); return ( rect.top+el.offsetHeight >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } // i_am_watching this.i_am_watching = function() { if( this.is_in_viewport( this.div ) ) document.getElementById('console').innerHTML += "I'm watching <b>"+name+"<br>" ; } // listener window.addEventListener( 'scroll', this.i_am_watching, false ); } Person( "mark", "mark_div" ); Person( "peter", "peter_div" ); Person( "albert", "albert_div" ); </script> </body> </html>
问题分析与解决方案
嗨,兄弟,你的问题根源其实有两个,先来拆解一下:
1. 没有用new关键字调用构造函数
你现在是直接用Person("mark", "mark_div")这种普通函数调用方式,这会导致构造函数里的this指向全局对象(浏览器里就是window)。也就是说,三次调用Person时,每次都会覆盖window上的label、div、i_am_watching这些属性——最后一次调用是Albert,所以只有他的属性保留下来了,这就是为什么只有Albert的元素触发时才有反应。
2. 滚动监听绑定的问题
就算你加了.bind(this),如果没有用new,this依然是window,绑定根本没起到作用。而且每次调用Person都会给window加一个滚动监听,所以滚动时三个监听都会触发,导致所有对象的方法都执行(但因为前面的属性被覆盖了,所以实际输出的都是Albert的信息)。
修正后的代码
我给你调整了代码,解决了这些问题,还加了滚动防抖优化性能:
<html> <head> <style> .content { background: #f8f8f8; padding: 20px; height: 1000px; } .person { padding: 20px; font-size: 20px; line-height: 20px; } .person.mark { background: pink; } .person.peter { background: lightblue; } .person.albert { background: lightgreen; } div + div { margin-top: 20px; } div { border: 1px solid black; } #console { position: fixed; top: 10px; right: 10px; width: 50%; height: 300px; overflow: auto; border: 3px solid black; background: white; } </style> </head> <body> <div id="console"></div> <div class="content">...some content...</div> <div id="mark_div" class="person mark">MARK</div> <div class="content">...some content...</div> <div id="peter_div" class="person peter">PETER</div> <div class="content">...some content...</div> <div id="albert_div" class="person albert">ALBERT</div> <div class="content">...some content...</div> <script> // 防抖函数:减少滚动事件触发频率,避免性能浪费 function debounce(func, delay = 100) { let timer; return function() { clearTimeout(timer); timer = setTimeout(() => func.apply(this, arguments), delay); }; } function Person(name, div_id) { // 保险:如果忘记用new调用,自动修正 if (!(this instanceof Person)) { return new Person(name, div_id); } // 实例属性 this.label = name; this.div = document.getElementById(div_id); } // 把方法放到原型上,所有实例共享,节省内存 Person.prototype.is_in_viewport = function(el) { const rect = el.getBoundingClientRect(); const windowHeight = window.innerHeight || document.documentElement.clientHeight; const windowWidth = window.innerWidth || document.documentElement.clientWidth; return ( rect.top + el.offsetHeight >= 0 && rect.left >= 0 && rect.bottom <= windowHeight && rect.right <= windowWidth ); }; Person.prototype.i_am_watching = function() { if (this.is_in_viewport(this.div)) { document.getElementById('console').innerHTML += `I'm watching <b>${this.label}</b><br>`; } }; // 创建实例并绑定监听 const mark = new Person("mark", "mark_div"); window.addEventListener('scroll', debounce(mark.i_am_watching.bind(mark)), false); const peter = new Person("peter", "peter_div"); window.addEventListener('scroll', debounce(peter.i_am_watching.bind(peter)), false); const albert = new Person("albert", "albert_div"); window.addEventListener('scroll', debounce(albert.i_am_watching.bind(albert)), false); </script> </body> </html>
关键改动说明
- 必须用
new创建实例:这样每个Person实例都有自己独立的this上下文,属性不会被覆盖。我还加了一个判断,就算忘记写new也能正常工作。 - 正确绑定
this:现在给每个实例的i_am_watching方法绑定了实例本身的this,确保方法里能正确访问当前实例的label和div。 - 原型上定义方法:把
is_in_viewport和i_am_watching放到Person.prototype上,避免每个实例都创建一份相同的函数,节省内存。 - 滚动防抖:滚动事件触发非常频繁,用防抖函数可以让方法在滚动停止后再执行,减少不必要的计算和控制台输出。
这样调整后,每个元素进入视口时,只有对应的实例方法会执行,控制台也会正确输出对应的名字啦~
内容的提问来源于stack exchange,提问作者Marco Panichi
相关产品推荐
相关产品推荐

