JavaScript基础教程之JS函数中对this的疑惑与不解

Write By Monkeyfly

以下内容均为原创,如需转载请注明出处。

前提

最近渐渐的对JavaScript产生了学习的兴趣,遇到不会的就想把问题搞明白,即使打破砂锅我也要问到底。近期在JavaScript重新学习的过程中发现,我几乎每天都能碰到一些难以理解的问题,说真的,主要还是自己太菜了,逻辑思维能力也不好,理解起来感觉非常的吃力,在询问的过程中也有人说我笨,讲了半天都不理解。其实我自己也心知肚明,该虚心请教的还是要虚心请教,毕竟自己的能力在这里放着,不努力提高等什么呢,对吧。

今天呢,主要学习了JavaScript的委托事件,在自己手动敲写委托事件案例的过程中,运行的时候发现了一个错误:

错误代码如下所示:

Uncaught TypeError: Cannot read property 'style' of undefined
现场还原

HTML代码如下:

<div id="div3">
    <input id="btn" type="button" value="添加">
    <ul id="list2">
        <li>111</li>
        <li>222</li>
        <li>333</li>
        <li>444</li>
    </ul>
</div>

JS代码如下:

//获取到ul元素
var oUl = document.getElementById("list2");
//获取到li元素的集合,是一个伪数组
var oLi = document.querySelectorAll("#list2 li");
//获取到input元素
var oBtn = document.getElementById("btn");

/*现在要实现这么一个效果:鼠标移入li,li变黄;鼠标移出li,li变绿*/

for (var i = 0; i < oLi.length; i++) {
    /*给每一个li元素绑定鼠标移入事件,并设置背景颜色*/
    oLi[i].onmouseover = function(){
        oLi[i].style.backgroundColor = "yellow";
    };
    /*给每一个li元素绑定鼠标移出事件,并设置背景颜色*/
    oLi[i].onmouseout = function(){
        oLi[i].style.backgroundColor = "green";
    };
}
错误代码图如下所示:

错图1:

错误图片1

错图2

错误图片2

问题分析:

发现问题时,我首先想到的是“背景色的属性名是不是写错了?”,因为在写的时候我就不太确定,也没有去查一下,就凭借记忆中的印象直接写上去了。而且我记得背景颜色好像是没有缩写的,但是还不确定,毕竟平时js用的太少,没感觉。先不管了,就这样写了:backgroundColor。后来百度查了一下还真的没记错。

  • 在CSS中,属性名一般是这样写的background-color,自称“中划线写法“或“分割线写法”;
  • 在js中,设置属性用的是驼峰写法:形如backgroundColorfontSize等。

但是百度的过程中,发现也有形如这样的错误写法,如:bgColorbgcolor。为了弄清楚到底有没有这两个写法,我又去查了一下。

1.bgcolor 是HTML的用法,这个属性规定了文档(body)的背景颜色。table元素也能用。如下所示:

<!--bgcolor这个属性已经废弃了,以后就别用了。-->
<body bgcolor="#E6E6FA">
<table border="1" bgcolor="#00FF00">

但是官方呢,不赞成使用 body 元素的 bgcolor 属性;而且HTML5 也明确规定不支持 <table>标签的bgcolor 属性。都让使用 CSS 代替。自己打脸了吧。

2.bgColor是JScript的用法,那就更不用去管了。直接跳过。

经过一阵查询,发现不是属性名的拼写错误,于是就直接在浏览器的开发人员工具打断点分析,定位一下到底是哪个地方出错了,然后对oLi[i]添加了监听,即 这里写图片描述,发现oLi[i](for循环中的一个li元素对象)提示“undefined”。如下图所示:

这里写图片描述

奇怪,怎么会“undefined”呢?不应该啊,oLi[i].onmouseover都没问题,oLi[i].style.backgroundColor就不能用了,什么鬼?然后我就去看了作者的源码。因为我是按照人家的案例自己敲的代码,写之前只看了要实现什么功能就自己开始写了,想在写完之后再与作者的代码进行比对,看看自己写的代码到底和人家写的有什么区别或者有什么问题。

这不,问题就来了。

贴上原作者的代码:

//鼠标移入变红,移出变白
for(var i=0; i<aLi.length;i++){
    aLi[i].onmouseover = function(){
        this.style.background = 'red';
    /*aLi[i].style.background = 'red';*/
    };
    aLi[i].onmouseout = function(){
        this.style.background = 'red';
    /*aLi[i].style.background = 'red';*/
    }
}

这区别很容易就看出来了,人家写的方法中用的是this对象,而我没有用(其实我根本就不会用),然后我就很纳闷,一直不理解为什么就非要用this,用原始的对象难道就不可以吗?我觉得我写的也没有错啊,不是都可以实现的吗?为什么偏偏就我的报错了,真奇怪。突然之间感觉我什么都不会,连this对象都不会用,而且别人用了还不知道为什么要这么用。瞬间感觉自己弱爆了,因为我觉得一般人都不会卡在这里,至少也知道this的用法,不像我这种初级的遇到了各种解决不了的问题。于是就去百度了this对象的用法,结果看了半天只知道了:

this是Javascript语言的一个关键字。它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。this不管在什么情况下都指的是,调用函数的那个对象。

我也知道this这样用没问题,我想知道的是原因。于是乎,我就又去群里找大神请教了,你猜怎么着?

第一个帮我的人:远程协助我解决问题,我非常感激。我们交流了半天,她最终放弃了,说了那么多,演示了那么久,我还是不明白,理解不了。因为我觉得她说的还是不够清晰透彻,没从根本上说明白到底为什么要用this。最后她实在没办法了,就直呼我:“太笨了,而且这个好难讲啊,我觉得我讲的已经够清楚了,你为什么就是不明白?剩下的只能靠你自己了。我帮不了你。”我只能说:“你说得对,我也很无奈,我就是难以理解。”

大概过程:

向她说明了问题,并且截图做了详细的错误标识,然后就进入了不停的答复中。下面看图说话。

图1:指明错误位置

问题

图2: 让我在控制台输出一下oLi[i]的值,很明显undefined

这里写图片描述

图3:

她:你可以理解为this属于自己。在循环里或者事件里,this就属于这个事件
我:它也是每次循环才会执行,当i=0时就是图片的那样

这里写图片描述

她:这个样只有第一个li元素才会执行,其他都不执行
我:所以把 0 换成 i 就不可以了?
她:对,你创建一个input,给这个输入框一个点击事件,然后打印this.value,我感觉这个是能让你最快理解的方法。

举了一个简单的例子给我讲,我就试了一下:

<input id="btn" type="button" value="添加">

oBtn.onclick = function(){
    //此时两条语句输出内容相同,且没有报错
    console.log(oBtn.value);
    console.log(this.value);  
}
//  我:这两个都可以实现打印出value。如果只有一个元素,this和它本身都能用.方法。
//  她:这里this指向的就是oBtn。oBtn创建了一个方法。所以this指向的就是这个方法,但是这个方法赋值给了oBtn,所以就指向oBtn。 
//  我:为什么元素一多就不能用了? oLi[i]在每一次的循环中也是指每一个,比如说第一次循环时,i=0;oLi[i]就代表oLi[0]
//  她:如果不用this,用oLi[i]指的就是全部啊。上面循环的是oLi,只能用this来指向自己。
//  我:刚开始i=0;oLi[i]此时指的也不是全部吧。而且oLi[i]一次循环只能代表一个值,怎么表示全部?  
//  她:你去百度看看this的指向吧。
//  我:好吧。如果只有一个元素,this和它本身都能用.方法。那我去好好学习一下
//  她:这个好难讲啊。你好笨啊!
//  我:我知道this指的是当前对象。
//  她:我觉得我讲的很清楚了。
//  我:当前对象就是当前的元素节点,即当前触发点击事件的li元素。
//  她:你如果oLi[i]的话不就指向全部了么?oLi[i]  就变成了全部啊。。只有靠[0.1.2.3] 去取值。。
//  我:但是,这是有条件的。从 for i=0 开始,每次循环的时候,i只有一个取值。
//  她:[]  这个是原生的一个下标 。
//  我:oLi[i].onmouseover  它都能找到是oLi[0]
//  她:你是先循环的。把所有的li都循环出来了。你又给所有的li加了一个事件。然后你有给所有事件添加了一个背景颜色。
//  我:在函数内部就无法识别oLi[i]是谁?
//  她:就是说[i] 就是所有。
//  她:当然了。你把所有的li都加了事件。而且你移入的时候哪个去变呢? 浏览器无法识别只有报错喽
//  我:不是这样走的吗? 循环一次,找到第一个li,注册事件;然后循环第二次,找到第二个li,再注册事件
//  她:不是。  只要在循环里 [i]  就是所有
//  我:[i]是代表每一个。只不过是因为循环指定了条件  所以它才是一个确定的值。
//  她:你去创建5个li在循环着5个li  给个点击事件。在事件里面打印 li[i]  你看看是啥

举例:

这里写图片描述

调试:

这里写图片描述

//  我:undefined
//  她:就是li[i]没有被。。
//  我:没找到li[i],我刚才试过了
//  她:这个只能你自己了。我帮不了你。
//  我:我再好好想想

此时,我就去百度搜了一下“为什么js函数里只能用this才能实现?”结果还真在网上遇到了同样的问题
,然后就认真看了下面的评论。

问题如下图所示:

问题

答案如下图所示:

这里写图片描述

刚开始看还没看懂,这时第二位帮助我的人出现了。他直接私聊我帮我回答并解释问题,真的是太感谢了。

解决问题的过程
//  他:把oLi[i]换成this
//  我:我不知道为什么oLi[i]不能用
//  我:不是这样走的吗? 循环一次,找到第一个li,注册事件;然后循环第二次,找到第二个li,再注册事件
//      在每次循环中,oLi[i]的值都是确定的。我觉得和this效果等价呀。

他说:

这里写图片描述

//  我:嗯,根据你说的我好好想想。
//  他:用jq写很快,$(this)
//  我:js从上往下执行的,for循环中的语句也是依次执行,比如说,当走到下图所示的箭头的地方时,按你的意思,for循环已经走完了?

这里写图片描述

/*  我:明白你的意思了,每次循环的时候,每个li元素点击事件已经被添加上了,但是每次循环的时候function里面的内容是不被执行的,对吧?*/
//  他:你不信吗,你在for循环下面加句console.log(i)
//  我:我不是不信,是我不理解
//  他:你看看会不会循环完?

我试了一下:

这里写图片描述

//  他:每次循环的时候,每个li元素点击事件已经被添加上了,这句话不对,是你鼠标移入的时候事件才触发。
//  他:你看吧,鼠标还没有移入,循环走完了已经。
//  我:事件已经被添加了,只是还没被触发。
//  我:鼠标移入事件是  在for循环完之后  li元素挨个注册的吗?注册完成以后,然后每个Li元素依次执行事件中的内容
//  他:不是,是你鼠标移入时,当前的这个li已经找不到了
//  我:添加事件和触发事件 是分开进行的
//  我:第一步,for循环走完;第二步,给每一个li元素注册鼠标移入事件;第三步,注册完之后,当鼠标移入的时候,每个li元素才会触发之前注册的事件
//  我:你看我这样写对不对?
//  他:对

我当时按照我的理解,用画板画了张图发给他:(如下)

这里写图片描述

//  他:for循环为什么要对号入座呢?
//  我:for循环之后console.log(i)  得到0;1;2;3
//  他:for走完了,现在的最后一个值是3
//  我:在for循环内部写,console.log(i),每个i都输出来了
//  他:是啊,没毛病,
//  我:for走完了,现在的最后一个值是4,因为最后i++
//  他:对,是4
//  我:for循环不是当i=0时,就进去循环了?转一圈,然后1+1;执行第二次循环
//  他:最后一个是4,然后你的操作是,鼠标移入,那么当前的这个oLi[i]存在吗?
//  我:执行第一次循环的时候只是给oLi[0]注册了鼠标移入事件,然后就自加+1,去执行第二次循环了
//  我:这个时候 i 根本进不去function里面?
//  我:因为此时并没有触发事件   【此时,貌似渐渐明白了】
//  他:对,事件并没有触发
//  我:如果function里面什么也不写,for依然会执行四次。oLi[i]的每一个都会注册鼠标移入事件,只是没有触发的条件。
//  他:嗯

我:描述了我的理解1

这里写图片描述

//  我:可以这样理解吗?

我:描述了我的理解2

这里写图片描述

//  他:恩,在移入事件内部,i 它不会被识别出来,从而导致这条代码无法实现功能
//  我:最后一次for执行完毕的时候,i=4
//  他:你可以这样理解(如下图所示)

这里写图片描述

//  我:这个时候才会去执行function,但是所有的i现在都已经是4了。
//  我:绑定事件的时候,i 还是刚开始的 i,0或者1或者2或者3,但是最后要执行function的时候,i 全部变成4了
//  他:我调用changeBackground()这个函数,这个函数内部的 oLi[i]并不知道是什么,那么就报错了
//  我:相当于绑定的所有事件都只对最后一个oLi[i]生效,但是并没有oLi[4]
//  我:oLi[i]只有三个,所以加断点调试的时候,oLi[4]监听后被认为是undefined
//  我:我明白了。其实,本质还是变量作用域的问题。 【此时,我终于明白了,不容易啊】
//  他:就是作用域的问题
//  我:此时用的oLi[i],就等于未定义

我:找的别人推荐的解决方法

这里写图片描述

//  他:函数自执行
//  我:怪不得别人说我笨,不适合学编程。我当时没选择java,而选了前端。就是因为我的逻辑思维能力很差,现在才发现原来js同样需要大量的逻辑思维
//  他:你喜欢扣问题,寻根究底的。可不是吗?那你html和css呢,这两个也要学好。
//  我:这两个还可以,就是js很弱。不理解为什么是这样,下次碰到还是不会用。
//  他:见多了就会用了。
//  我:但是绑定事件和触发事件不是都在for循环里面写的吗?按道理来说,两个里面都可以识别 i
//  我:for循环是一个作用域,绑定事件和触发事件在这个作用域里面
//  他:for包裹的没问题。还是上面我给你发的这个代码,你好好看看
//  我:这个function能不能写到for循环外面?写到外面this就用不了把。

我:根据上面说的,自己的理解,乱写的。

这里写图片描述

//  我:这样实现应该怎么改写?
//  他:不能这样写的,只是让你那样理解的。
//  我:只能写在里面吗?或者说只能写在一起对吗?就么有办法写在外面了?
//  他:不是哇,这个方法是帮助理解的,里外都不能这样写。
//  我:那就只有这一种写法了,连着写。
//  他:对
//  我:好的,我知道了。

最后调试的时候发现,此时oLi[i]的值确实是4。所以在这里只能用this对象了。

如下图所示:

这里写图片描述

其实,本质还是变量的作用域的问题,这才是核心。

oLi[i].onmouseover = function(){
    this.style.backgroundColor = "yellow";
};

就等价于:

oLi[i].onmouseover = changeBackground();
//很明显,这是两个作用域
function changeBackground(){
    oLi[i].style.background='#fff';//此时i肯定是undefined
}

结束语(知易行难)

今天写了这么多,而且也如实地模拟还原了情景,就是为了能够让大家看清所有的疑惑和问题,能够清晰地看到我艰难行走的每一步,从根源去发现问题,了解问题和解决问题;从没有见过this对象到了解this对象。现在大家应该知道为什么要用this对象了吧。当然这这是一个基础,你可能会说,就这么一个简单的问题,为什么非要长篇大论的说这么多,写这么多,值得吗?不管别人怎么说,我只坚持做好我自己的就好了。

最后,希望这篇文章能够对大家有所帮助。关于如何让它们分开来写,还没有找到解决办法,现在学艺不精,等以后再说吧。其实,现在只是认识this的起跑线,后面的路还很漫长,当然也有很长的一段路要走。毕竟我现在还处于基础阶段,存在很多疑惑和不解,只能说,继续加油吧!有什么问题大家可以在下方评论或者留言,我们可以一起学习交流,共同进步!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值