zoukankan      html  css  js  c++  java
  • [技术博客] 敏捷软工——JavaScript踩坑记

    [技术博客] 敏捷软工——JavaScript踩坑记

    一、一个令人影响深刻的坑

    1.脚本语言的面向对象

    面向对象特性是现代编程语言的基本特性,JavaScript中当然集成了面向对象特性。但是JavaScript作为脚本语言并没有严格地遵守面向对象的规则,而是兼容了多种语言特性。

    • 面向对象编程
    • 命令式编程
    • 函数式编程

    可想而知,在兼容这些特性之后JavaScript会有很多奇特的行为,而对面向对象的固有认识会使我们在编写JavaScript时犯下无数错误。

    2. 忘记过去,从新学习

    C++和Java是我首先接触到的现代编程语言,它们已将面向对象范式固定到我心中。所以我在学习JavaScript时自然受到C++和Java的影响,将这两种语言的很多编程习惯迁入到JavaScript中。相应地无数的bug接踵而至,耗费了我大量的时间调试修复这些bug。这里将我踩过的坑记录下来,一是提醒自己注意JavaScript的语言特性,二是告诉自己不能因为熟悉就粗心、防止“淹死的都是会水的”发生在自己身上。

    在用JavaScript实现前端与后端通讯的功能时,遇到了一个奇怪的bug。下面先将有关地源码展现出来。

    checkThePath: function() {
        // console.log('http://' + window.location.host + '/classrooms/auto_test_projects/validate_runner');
        axios.get('http://' + window.location.host + '/classrooms/auto_test_projects/validate_runner', {
            params: {
                path: this.path
            }
        }).then(function(res) {
            let data = res.data;
            // console.log(data);
            if (data.key === 'agile soft engineering') {
              console.log('arrive init runner state');
              this.runner_state.state = 1;
              this.runner_state.id = data.id;
              this.runner_state.os = data.os;
              this.$emit('available', {
                  uid: this.runner_state.id,
                  os: this.runner_state.os,
                  path: this.path
              });
            }
          }
        ).catch((err) => {
            console.log('Error: happen in checking runner.');
            console.log(err);
        });
        this.show_check = true;
      }
    }
    

    当这段代码被执行的时候,游览器控制台报出了错误:undefined not have property runner_state。在一阵调试输出之后,我发现问题出在匿名函数中,匿名函数中的this指向的对象是undefined

    这个bug是如何产生的呢?原因在于匿名函数。JavaScript中有两种编译模式,一种为sloppy 模式,也即马虎模式;而另一种为strict模式,也即严格模式。JavaScript在这两种模式下有不同的行为,对于this的解释同样如此。

    • 在一般函数中,马虎模式下this被解释为global object,而在严格模式下this被解释为undefined
    • 在构造函数中,this被解释为新创建的对象。
    • 在对象方法中,this被解释为方法调用消息的接收者。

    原来在匿名函数之中this会被重新解释,即使该匿名函数在对象方法中被定义。可能Vue框架默认使用严格模式,this被解释为了undefined

    找到了问题的原因,解决办法在哪里呢?我们的目标是在传入.then方法的代码块中同样能够访问到调用对象的属性,那么就在一下几种可能的解决方法。

    • 将调用对象或调用对象的属性作为参数传入匿名函数中。
    • 改变传入代码块的方式,放弃使用匿名函数,从而使在代码块中this被解释为调用对象。

    第一种解决方法很容易实现,但会使得代码变得雍仲;第二种方法则更加符合简洁的编码原则。JavaScript提供了一种方便的机制使得(实际上是另一种函数声明方法),箭头函数下的的this解释等与匿名函数不尽相同。这个机制的名称为“箭头函数”,顾名思义,该函数的定义语句中箭头发挥了很大的作用。箭头函数主要有两大作用:更简短的函数并且不绑定this

    箭头函数中的this与上一层作用域中的this相同,这一点完美地符合我们的目标。那么,我们现在可以将代码修改为如下形式。

    checkThePath: function() {
        // console.log('http://' + window.location.host + '/classrooms/auto_test_projects/validate_runner');
        axios.get('http://' + window.location.host + '/classrooms/auto_test_projects/validate_runner', {
            params: {
                path: this.path
            }
        }).then((res) => {
            let data = res.data;
            // console.log(data);
            if (data.key === 'agile soft engineering') {
              console.log('arrive init runner state');
              this.runner_state.state = 1;
              this.runner_state.id = data.id;
              this.runner_state.os = data.os;
              this.$emit('available', {
                  uid: this.runner_state.id,
                  os: this.runner_state.os,
                  path: this.path
              });
            }
          }
        ).catch((err) => {
            console.log('Error: happen in checking runner.');
            console.log(err);
        });
        this.show_check = true;
      }
    }
    

    编译、运行,可以看到我们期待的现象成功出现:runner_state被配置,available事件被抛向父级组件。借助解决这个坑,我们认识到了JavaScript中解释this的机制,同样学习到了JavaScript中两大定义函数的方式。

    二、JavaScript中this的全貌

    那么,JavaScript中this都会被如何解释?实际上有这几个场景:this在函数中、this在顶层作用域、this在传入eval的字符串中。

    1.this在函数中

    第一部分我们已经对这部分进行了详细的介绍,所以在只简单的复述一下this在函数中的解释情况。

    • 在一般函数中,马虎模式下this被解释为global object,而在严格模式下this被解释为undefined
    • 在构造函数中,this被解释为新创建的对象。
    • 在对象方法中,this被解释为方法调用消息的接收者。

    2.this在顶层作用域中

    JavaScript作为脚本语言允许在顶层作用域执行代码,那么自然的this关键字也可以在顶层作用域被使用。

    • 在游览器中,顶层作用域中的this被解释为global object
    • Node.js框架中,顶层作用域中的this被解释为module.scope。这是因为我们一般在module中执行代码,所以“顶层作用域”在里应该是module的作用域。

    3.this在传入eval的字符串中

    • 如果eval被直接调用,eval中的thiseval方法所属作用域中的this相同。
    • 如果eval被间接调用,eval中的this被设置为global object

    三、另一个令人影响深刻的坑

    switch (this.option) {
        case 'Issues':
            contribution_data = this.members.map(i => {
                return 
                {
                    name: i.name,
                    value: i.issues_count
                }
            });
            contribution_data.push({name: 'To Do', value: this.todo});
            break;
        case 'Weight':
            contribution_data = this.members.map(i => {
                return 
                {
                    name: i.name,
                    value: i.issues_weight
                }
            });
            contribution_data.push({name: 'To Do', value: this.todoWeight});
            break;
        default:
            contribution_data = this.members.map(i => {
                return 
                {
                    name: i.name,
                    value: i.commits_count
                }
            });
    }
    

    作为一个花括号换行党,我在学习JavaScript时保留了最后的尊严:构造Object时将花括号换行,以工整的显示出Object的结构。这个倔强坚持了300行代码就被JavaScript无情的击碎了:我遇到了JavaScript的经典bug——封号自动补全与return。

    写完示例代码,访问网页!欸欸欸?我的网页去哪了?怎么什么都没有显示?

    不怕!赶紧打开chrome的控制台。

    ???怎么这么多报错,我看看。

    好多undifined啊,我裂开。

    折腾了两小时≧ ﹏ ≦,发现死于JavaScript的封号自动补全机制。

    将示例代码这样修改就OK了!

    switch (this.option) {
        case 'Issues':
            contribution_data = this.members.map(i => {
                return {
                    name: i.name,
                    value: i.issues_count
                }
            });
            contribution_data.push({name: 'To Do', value: this.todo});
            break;
        case 'Weight':
            contribution_data = this.members.map(i => {
                return {
                    name: i.name,
                    value: i.issues_weight
                }
            });
            contribution_data.push({name: 'To Do', value: this.todoWeight});
            break;
        default:
            contribution_data = this.members.map(i => {
                return {
                    name: i.name,
                    value: i.commits_count
                }
            });
    }
    

    集由这个坑,我打开了新世界的大门:JavaScript的封号自动补全机制,顺便还在知乎上围观了JavaScript编程要不要在行尾的论战。

    总而言之,JavaScript会给诸如break, continue, return等关键字以“特殊待遇”。如果它们之后紧接着换行符,那么JavaScript会在行尾自动补上一个封号。我可爱的工整的对象定义就是被这样抛弃的  ̄へ ̄。

  • 相关阅读:
    POJ3709 K-Anonymous Sequence 斜率优化DP
    POJ3233 Matrix Power Series
    第六周 Leetcode 446. Arithmetic Slices II
    POJ1743 Musical Theme 最长重复子串 利用后缀数组
    Ural 1517. Freedom of Choice 后缀数组
    iOS跳转到另一个程序
    上传源码到github
    NSTimer用法,暂停,继续,初始化
    iOS中多线程原理与runloop介绍
    NSRunLoop 概述和原理
  • 原文地址:https://www.cnblogs.com/starmiku/p/12983949.html
Copyright © 2011-2022 走看看