You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript OOP滚动监听异常:bind(this)后仍无法正确触发对象方法

JavaScript OOP滚动监听问题:bind后仍无法正确触发每个对象的方法

我是JavaScript OOP新手,最近在做一个需求:管理一批对象,当它们对应的DOM元素进入视口时执行特定操作。但现在遇到了两个棘手的问题:

  1. 滚动监听只有当Albert元素进入视口时才会触发
  2. 触发时所有对象的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:09:32