JS:class 构造函数中的 addEventListener

JS: addEventListener in class constructor

我正在编写一个评论系统。我在构建 Like Button.

时遇到问题
  1. 点赞按钮是一种很棒的字体<i> 标签
  2. 作者不能喜欢自己的post
  3. 一个用户只能点赞一个post一次
  4. 点击喜欢按钮,通过切换 class 更改其纯色填充。

我完成的代码非常多,请不要费心阅读下面的代码片段,我提供它只是为了让您看看发生了什么。


// querySelector -> element
function proxy(el){
  el.oneEventListener = (event, func) => {
    if(el.lastEventListener == null){
      el.lastEventListener = {};
    }
    if(el.lastEventListener[event] != null){
      el.removeEventListener(event, el.lastEventListener[event]);
    }
    el.addEventListener(event, func);
    el.lastEventListener[event] = func;
  }
  return el;
}

// querySelectorAll -> NodeList
function proxyAll(el){
  el.forEach(ele=>{
    ele = proxy(ele);
  });
  return el;
}

/*
once proxy is implemented, by oneEventListener, 
only one event listener of particular type will exist on that element,
if you call addEventListener, proxy doesn't interfer it.
which the eventlistener added will work normally.
*/

// proxy query selector
const pqs = function(str){
  return proxy(document.querySelector(str));
}

// proxy query selector all
const pqsa = function(str){
  return proxyAll(document.querySelectorAll(str));
}
// end import

// optimization
// separate host comment and reply
// comment should have liked

class Author{
    constructor(name, avatar_link, profile_link){
        // avatar and profile are urls
        this.name = name;
        this.avatar = avatar_link;  // url to image
        this.profile = profile_link; // url to profile
    }
}

/* like system
1. like button is an <i> tag 
2. author cannot like his own post
3. one user can only like one post once
4. click means to switch state which trigger comment-checked class
*/

class LikeButton{
    // element, number element, likes, liked by this visitor, host (author object)
    constructor(el, num_el, likes, liked, host_author){
        this.el = proxy(el);
        this.num_el = num_el;
        this.likes = likes;
        this.liked = liked;
        this.host_author = host_author;

        // this.el.oneEventListener('click', function(){
        //  console.log('here');
        //  if(this.liked){
        //      this.unlike();
        //  }else{
        //      if(!this.like()){
        //          return
        //      }
        //  }
        //  this.liked = !this.liked;
        // });

        // this.like();
    }

    like(){
        if(this.host_author === visitor){
            window.alert("You cannot like your own post");
            return false;
        }else{
            this.likes++;
            console.log(this.el);
            this.el.classList.add('comment-checked');
            this.num_el.innerHTML = this.likes;
            return true;
        }
    }

    unlike(){
        this.likes--;
        this.num_el.innerHTML = this.likes;
        this.el.classList.remove('comment-checked');
        return true;
    }
}

class Reply{
    constructor(author, ago, content, by_author, likes, author_liked){
        this.author = author;
        this.ago = ago;
        this.content = content;
        this.by_author = by_author;
        this.likes = likes;
        this.author_liked = author_liked;
    }
}

// a comment is a reply
// act as host of replys parsed in dom
class Comment extends Reply{
    constructor(author, ago, content, by_author, likes, author_liked, comment_container){
        super(author, ago, content, by_author, likes, author_liked);

        this.comment_container = comment_container;
        this.replys = [];
    }

    add_reply(author, ago, content, by_author, likes, author_liked){
        this.replys.push(new Reply(author, ago, content, by_author, likes, author_liked));
        return this;
    }

    parse(container){
        let ul1 = document.createElement('ul');
        ul1.setAttribute('id', 'comments-list');
        ul1.setAttribute('class', 'comments-list');

        let li1 = document.createElement('li');

        let div1 = document.createElement('div');
        div1.setAttribute('class', 'comment-main-level'); 

        let div2 = document.createElement('div');
        div2.setAttribute('class', 'comment-avatar');

        let img1 = document.createElement('img');
        img1.setAttribute('src', this.author.avatar);

        let div3 = document.createElement('div');
        div3.setAttribute('class', 'comment-box');

        let div4 = document.createElement('div');
        div4.setAttribute('class', 'comment-head');

        let h6 = document.createElement('h6');
        h6.setAttribute('class', 'comment-name');

        let a1 = document.createElement('a');
        a1.setAttribute('href', this.author.profile);
        a1.innerHTML = this.author.name;

        let span1 = document.createElement('span');
        span1.innerHTML = this.ago + ' ago';

        let i1 = document.createElement('i');
        i1.setAttribute('class', 'fa fa-reply');

        let i2 = document.createElement('i');
        i2.setAttribute('class', 'fa fa-heart');

        let i3 = document.createElement('i');
        i3.setAttribute('class', 'comment-likes');
        i3.innerHTML = this.likes;

        new LikeButton(
            i2,
            i3,
            this.likes,
            this.author_liked,
            this.author,
        );

        let div5 = document.createElement('div');
        div5.setAttribute('class', 'comment-content');
        div5.innerHTML = this.content;

        // reply list
        let ul2 = document.createElement('ul');
        ul2.setAttribute('class', 'comments-list reply-list');

        // construct
        this.comment_container.appendChild(ul1);
        ul1.appendChild(li1);
        li1.appendChild(div1);
        div1.appendChild(div2);
        div2.appendChild(img1);
        div1.appendChild(div3);
        div3.appendChild(div4);
        div4.appendChild(h6);
        h6.appendChild(a1);
        div4.appendChild(span1);
        div4.appendChild(i1);
        div4.appendChild(i2);
        div4.appendChild(i3);
        div3.appendChild(div5);

        li1.appendChild(ul2);

        this.replys.forEach(rep=>{
            let li2 = document.createElement('li');
            
            let div6 = document.createElement('div');
            div6.setAttribute('class', 'comment-avatar');

            let img2 = document.createElement('img');
            img2.setAttribute('src', rep.author.avatar);

            let div7 = document.createElement('div');
            div7.setAttribute('class', 'comment-box');

            let div8 = document.createElement('div');
            div8.setAttribute('class', 'comment-head');

            let h61 = document.createElement('h6');
            h61.setAttribute('class', 'comment-name');

            let a2 = document.createElement('a');
            a2.setAttribute('href', rep.author.profile);
            a2.innerHTML = rep.author.name;

            let span2 = document.createElement('span');
            span2.innerHTML = rep.ago + ' ago';

            let i4 = document.createElement('i');
            i4.setAttribute('class', 'fa fa-reply');

            let i5 = document.createElement('i');
            i5.setAttribute('class', 'fa fa-heart');

            let i6 = document.createElement('i');
            i6.setAttribute('class', 'comment-likes');
            i6.innerHTML = rep.likes;

            new LikeButton(
                i5,
                i6,
                rep.likes,
                rep.author_liked,
                rep.author,
            );
            
            let div9 = document.createElement('div');
            div9.setAttribute('class', 'comment-content');
            div9.innerHTML = rep.content;

            ul2.appendChild(li2);
            li2.appendChild(div6);
            div6.appendChild(img2);
            li2.appendChild(div7);
            div7.appendChild(div8);
            div8.appendChild(h61);
            h61.appendChild(a2);
            div8.appendChild(span2);
            div8.appendChild(i4);
            div8.appendChild(i5);
            div8.appendChild(i6);
            div7.appendChild(div9);
        });
    }
}

var con = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit omnis animi et iure laudantium vitae, praesentium optio, sapiente distinctio illo?";

const comment_wrapper = pqs('.comments-container');

const user1 = new Author(
    'Agustin Ortiz', 
    "http://i9.photobucket.com/albums/a88/creaticode/avatar_1_zps8e1c80cd.jpg",
    '#'
);

const user2 = new Author(
    'Lorena Rojero', 
    "http://i9.photobucket.com/albums/a88/creaticode/avatar_2_zps7de12f8b.jpg",
    '#'
);

// current visiting user, an author onject
const visitor = user2;

// var comment1 = 
// (new Comment(
//  user1,  
//  "20 minutes",
//  con,
//  true,
//  1,
//  false,
//  comment_wrapper
// ))
// .add_reply(
//  user2,
//  "10 minutes",
//  con, 
//  false,
//  2,
//  true,
// )
// .add_reply(
//  user1, 
//  "10 minutes",
//  con,
//  true,
//  3,
//  false, 
// );

var comment2 = 
(new Comment(
    user1,
    "10 minutes",
    con,
    true,
    4,
    false, 
    comment_wrapper
));

// comment1.parse();

comment2.parse();
/*body{
    position: fixed;
    width: 100vw;
    height: 100vh;
    background: darkgrey;
    display: flex;
    justify-content: center;
    align-items: center;
}

#shelf-image-comments{
    width: 500px;
    height: 300px;
    background: wheat;
}

.comment{
    width: 100%;
    height: 100px;
    background: grey;
}*/

/**
 * Oscuro: #283035
 * Azul: #03658c
 * Detalle: #c7cacb
 * Fondo: #dee1e3
 ----------------------------------*/
 * {
    margin: 0;
    padding: 0;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
 }

 a {
    color: #03658c;
    text-decoration: none;
 }

ul {
    list-style-type: none;
}

body {
    font-family: 'Roboto', Arial, Helvetica, Sans-serif, Verdana;
    background: #dee1e3;
}

/** ====================
 * Lista de Comentarios
 =======================*/
.comments-container {
    margin: 60px auto 15px;
    width: 768px;
}

.comments-container h1 {
    font-size: 36px;
    color: #283035;
    font-weight: 400;
}

.comments-container h1 a {
    font-size: 18px;
    font-weight: 700;
}

.comments-list {
    margin-top: 30px;
    position: relative;
}

/**
 * Lineas / Detalles
 -----------------------*/
.comments-list:before {
    content: '';
    width: 2px;
    height: 100%;
    background: #c7cacb;
    position: absolute;
    left: 32px;
    top: 0;
}

.comments-list:after {
    content: '';
    position: absolute;
    background: #c7cacb;
    bottom: 0;
    left: 27px;
    width: 7px;
    height: 7px;
    border: 3px solid #dee1e3;
    -webkit-border-radius: 50%;
    -moz-border-radius: 50%;
    border-radius: 50%;
}

.reply-list:before, .reply-list:after {display: none;}
.reply-list li:before {
    content: '';
    width: 60px;
    height: 2px;
    background: #c7cacb;
    position: absolute;
    top: 25px;
    left: -55px;
}


.comments-list li {
    margin-bottom: 15px;
    display: block;
    position: relative;
}

.comments-list li:after {
    content: '';
    display: block;
    clear: both;
    height: 0;
    width: 0;
}

.reply-list {
    padding-left: 88px;
    clear: both;
    margin-top: 15px;
}
/**
 * Avatar
 ---------------------------*/
.comments-list .comment-avatar {
    width: 65px;
    height: 65px;
    position: relative;
    z-index: 99;
    float: left;
    border: 3px solid #FFF;
    -webkit-border-radius: 4px;
    -moz-border-radius: 4px;
    border-radius: 4px;
    -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.2);
    -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.2);
    box-shadow: 0 1px 2px rgba(0,0,0,0.2);
    overflow: hidden;
}

.comments-list .comment-avatar img {
    width: 100%;
    height: 100%;
}

.reply-list .comment-avatar {
    width: 50px;
    height: 50px;
}

.comment-main-level:after {
    content: '';
    width: 0;
    height: 0;
    display: block;
    clear: both;
}
/**
 * Caja del Comentario
 ---------------------------*/
.comments-list .comment-box {
    width: 680px;
    float: right;
    position: relative;
    -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.15);
    -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.15);
    box-shadow: 0 1px 1px rgba(0,0,0,0.15);
}

.comments-list .comment-box:before, .comments-list .comment-box:after {
    content: '';
    height: 0;
    width: 0;
    position: absolute;
    display: block;
    border-width: 10px 12px 10px 0;
    border-style: solid;
    border-color: transparent #FCFCFC;
    top: 8px;
    left: -11px;
}

.comments-list .comment-box:before {
    border-width: 11px 13px 11px 0;
    border-color: transparent rgba(0,0,0,0.05);
    left: -12px;
}

.reply-list .comment-box {
    width: 610px;
}
.comment-box .comment-head {
    background: #FCFCFC;
    padding: 10px 12px;
    border-bottom: 1px solid #E5E5E5;
    overflow: hidden;
    -webkit-border-radius: 4px 4px 0 0;
    -moz-border-radius: 4px 4px 0 0;
    border-radius: 4px 4px 0 0;
}

.comment-box .comment-head i {
    float: right;
    margin-left: 14px;
    position: relative;
    top: 2px;
    color: #A6A6A6;
    cursor: pointer;
    -webkit-transition: color 0.3s ease;
    -o-transition: color 0.3s ease;
    transition: color 0.3s ease;
}

.comment-box .comment-head .comment-likes{
    font-style: normal;
    color: #A6A6A6;
}

.comment-box .comment-head i:hover {
    color: #03658c;
}

.comment-checked{
    color: #03658c !important;
}

.comment-box .comment-name {
    color: #283035;
    font-size: 14px;
    font-weight: 700;
    float: left;
    margin-right: 10px;
}

.comment-box .comment-name a {
    color: #283035;
}

.comment-box .comment-head span {
    float: left;
    color: #999;
    font-size: 13px;
    position: relative;
    top: 1px;
}

.comment-box .comment-content {
    background: #FFF;
    padding: 12px;
    font-size: 15px;
    color: #595959;
    -webkit-border-radius: 0 0 4px 4px;
    -moz-border-radius: 0 0 4px 4px;
    border-radius: 0 0 4px 4px;
}

.comment-box .comment-name.by-author, .comment-box .comment-name.by-author a {color: #03658c;}
.comment-box .comment-name.by-author:after {
    content: 'autor';
    background: #03658c;
    color: #FFF;
    font-size: 12px;
    padding: 3px 5px;
    font-weight: 700;
    margin-left: 10px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    border-radius: 3px;
}

/** =====================
 * Responsive
 ========================*/
@media only screen and (max-width: 766px) {
    .comments-container {
        width: 480px;
    }

    .comments-list .comment-box {
        width: 390px;
    }

    .reply-list .comment-box {
        width: 320px;
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <!-- Fuentes de Google -->
    <link href='https://fonts.googleapis.com/css?family=Roboto:400,700' rel='stylesheet' type='text/css'>
    <!-- Iconos -->
    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">

    <link rel="stylesheet" href="comment.css" />
</head>
<body>

<!-- <div id="shelf-image-comments">
    <div class="comment">
        <lable>Weilory</lable><p>Here</p>
    </div>
</div> -->

<!-- Contenedor Principal -->
    <div class="comments-container">
        <h1>Comentarios <a href="http://creaticode.com">creaticode.com</a></h1>

        <!-- <ul id="comments-list" class="comments-list">
            <li>
                <div class="comment-main-level">
                    <div class="comment-avatar"><img src="http://i9.photobucket.com/albums/a88/creaticode/avatar_1_zps8e1c80cd.jpg" alt=""></div>
                    <div class="comment-box">
                        <div class="comment-head">
                            <h6 class="comment-name by-author"><a href="http://creaticode.com/blog">Agustin Ortiz</a></h6>
                            <span>hace 20 minutos</span>
                            <i class="fa fa-reply"></i>
                            <i class="fa fa-heart"></i>
                            <i class="comment-likes">2</i>
                        </div>
                        <div class="comment-content">
                            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit omnis animi et iure laudantium vitae, praesentium optio, sapiente distinctio illo?
                        </div>
                    </div>
                </div>
                <ul class="comments-list reply-list">
                    <li>
                        <div class="comment-avatar"><img src="http://i9.photobucket.com/albums/a88/creaticode/avatar_2_zps7de12f8b.jpg" alt=""></div>
                        <div class="comment-box">
                            <div class="comment-head">
                                <h6 class="comment-name"><a href="http://creaticode.com/blog">Lorena Rojero</a></h6>
                                <span>hace 10 minutos</span>
                                <i class="fa fa-reply"></i>
                                <i class="fa fa-heart"></i>
                                <i class="comment-likes">2</i>
                            </div>
                            <div class="comment-content">
                                Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit omnis animi et iure laudantium vitae, praesentium optio, sapiente distinctio illo?
                            </div>
                        </div>
                    </li>

                    <li>
                        <div class="comment-avatar"><img src="http://i9.photobucket.com/albums/a88/creaticode/avatar_1_zps8e1c80cd.jpg" alt=""></div>
                        <div class="comment-box">
                            <div class="comment-head">
                                <h6 class="comment-name by-author"><a href="http://creaticode.com/blog">Agustin Ortiz</a></h6>
                                <span>hace 10 minutos</span>
                                <i class="fa fa-reply"></i>
                                <i class="fa fa-heart"></i>
                                <i class="comment-likes">2</i>
                            </div>
                            <div class="comment-content">
                                Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit omnis animi et iure laudantium vitae, praesentium optio, sapiente distinctio illo?
                            </div>
                        </div>
                    </li>
                </ul>
            </li>

            <li>
                <div class="comment-main-level">
                    <div class="comment-avatar"><img src="http://i9.photobucket.com/albums/a88/creaticode/avatar_2_zps7de12f8b.jpg" alt=""></div>
                    <div class="comment-box">
                        <div class="comment-head">
                            <h6 class="comment-name"><a href="http://creaticode.com/blog">Lorena Rojero</a></h6>
                            <span>hace 10 minutos</span>
                            <i class="fa fa-reply"></i>
                            <i class="fa fa-heart"></i>
                            <i class="comment-likes">2</i>
                        </div>
                        <div class="comment-content">
                            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit omnis animi et iure laudantium vitae, praesentium optio, sapiente distinctio illo?
                        </div>
                    </div>
                </div>
            </li>
        </ul> -->
        
    </div>

<script src="comment.js"></script>
</body>
</html>


预期输出,当我点击 like button 时,它应该变成蓝色,剩下的数字应该递增 1。

LikeButton 由以下构造:

class LikeButton{
    // element, number display element, likes, whether liked by this visitor, host (author object)
    constructor(el, num_el, likes, liked, host_author){
        this.el = proxy(el);
        this.num_el = num_el;
        this.likes = likes;
        this.liked = liked;
        this.host_author = host_author;

        this.el.oneEventListener('click', function(){
            console.log('here');
            if(this.liked){
                this.unlike();
            }else{
                if(!this.like()){
                    return
                }
            }
            this.liked = !this.liked;
        });
        this.like();
    }

    like(){
        if(this.host_author === visitor){
            window.alert("You cannot like your own post");
            return false;
        }else{
            this.likes++;
            console.log(this.el);
            this.el.classList.add('comment-checked');
            this.num_el.innerHTML = this.likes;
            return true;
        }
    }

    unlike(){
        this.likes--;
        this.num_el.innerHTML = this.likes;
        this.el.classList.remove('comment-checked');
        return true;
    }
}

你可能已经注意到我使用了奇怪的方法proxyoneEventListener,它们是单例事件类型监听器只允许在一个元素上发生一次点击事件,通过以下方式存档:

// querySelector -> element
function proxy(el){
  el.oneEventListener = (event, func) => {
    if(el.lastEventListener == null){
      el.lastEventListener = {};
    }
    if(el.lastEventListener[event] != null){
      el.removeEventListener(event, el.lastEventListener[event]);
    }
    el.addEventListener(event, func);
    el.lastEventListener[event] = func;
  }
  return el;
}

// querySelectorAll -> NodeList
function proxyAll(el){
  el.forEach(ele=>{
    ele = proxy(ele);
  });
  return el;
}

如果我把oneEventListener换成addEventListener也没有什么区别,例如下面的代码

class LikeButton{
    // element, number display element, likes, whether liked by this visitor, host (author object)
    constructor(el, num_el, likes, liked, host_author){
        this.el = el;
        this.num_el = num_el;
        this.likes = likes;
        this.liked = liked;
        this.host_author = host_author;

        this.el.addEventListener('click', function(){
            console.log('here');
            if(this.liked){
                this.unlike();
            }else{
                if(!this.like()){
                    return
                }
            }
            this.liked = !this.liked;
        });
        this.like();
    }

    like(){
        if(this.host_author === visitor){
            window.alert("You cannot like your own post");
            return false;
        }else{
            this.likes++;
            console.log(this.el);
            this.el.classList.add('comment-checked');
            this.num_el.innerHTML = this.likes;
            return true;
        }
    }

    unlike(){
        this.likes--;
        this.num_el.innerHTML = this.likes;
        this.el.classList.remove('comment-checked');
        return true;
    }
}

当点击 heart 赞按钮时,我预计至少 here 会登录到控制台,但什么也没发生,因此 addEventListener in LikeButton constructor 没有完全没有工作。

箭头函数和函数我都试过了。没锻炼。

非常感谢您阅读到最后。如果有人提供一些建议,我会很感动。

您遇到的问题实际上是引用的阴影。 “this”关键字不再适用于 class。它指的是事件被路由到的元素。 您可以将要绑定到“this”关键字的引用作为参数传递给 addEventListener 函数,如下所示:

this.el.addEventListener('click', function(){
            console.log('here');
            if(this.liked){
                this.unlike();
            }else{
                if(!this.like()){
                    return
                }
            }
            this.liked = !this.liked;
        }.bind(this));

再次说明一下:

您有一个 class 并且您有一个封装在其中的元素(例如,类似于坚果和坚果壳)。 构造函数中的“this”指的是您的 class,第二个“this”指的是您点击处理程序中的 HTML 元素。