Scope和Context: 咱俩不是一个东西
我是Scope(作用域),那是隔壁邻居Context(执行上下文)。总有人以为咱俩是一个东西,但是真的不是啊!记住啊,他是隔壁老C!我先介绍我自己吧。
Scope(作用域)借锤子
对于我Scope, 我先给自己贴个标签——和变量的可见性相关。
(╯﹏╰)这么文绉绉的话是不是很不好理解,不好理解就对啦。要不然也不用举个栗子了:
function community() {
var hammer = 'The community owns a hammer';
function myHome() {
var wifi = 'This is my wifi, you cannot connect!'
alert(hammer);
}
myHome();
alert(wifi);
}
community();
这个栗子会报错,不用慌,让我给你解释解释:
首先呢,社区(community)里有个锤子(hammer),我家(myHome)呢,开着wifi。今天啊,我起了个早,寻思着找个锤子把我家门给钉死咯,以防隔壁家那谁谁谁来串门。在我家找了一圈,发现我家没有锤子,那咋办呢,我就顺着作用域链(Scope chain)爬啊爬,爬到社区。社区老大哥,你家有没有锤子啊,有的话借我使使啊。社区老大哥果然有锤子,而且社区老大哥的锤子的作用域(Scope)覆盖面积贼大,我赶紧拿着就溜回家把门给钉死咯。所以alert(hammer);
成功的得到:
这我刚把门给钉死,社区老大哥就来
alert(wifi);
他自己又没有wifi,以为能把我内部定义的wifi给alert出来。门都没有!你忘了我把门给钉死了啊?我的wifi的作用域根本出不去!社区老大哥灰头土脸的走了,还在控制台里留下一抹伤心的报错:
不知道我这么一说,你是不是稍微理解了我给自己加的标签了——和变量的可见性相关。
1.wifi呢,是我(myHome)这个函数内部定义的,对于我自己来说那肯定是可见的,也就是说wifi的作用域是在myHome函数内部的,我如果想用wifi那是随便用。
2.锤子呢,虽然是社区老大哥里定义的,但是因为他把我包起来了,那我就可以通过作用域链爬上去借他的锤子使使,所以锤子对于我(myHome)来说,也是可以用的。
3.我的wifi对于社区老大哥来说,那是不可见的,我可以顺着作用域链向上爬拿社区老大哥里的变量,但是他不能往下爬拿我的,这是规则。
你可能要问了,要是你自己也有锤子,社区老大哥那里也有锤子,你拿哪一个呢?
那当然是用我自己的了啊, 吃饱了撑得还爬绳子去拿别人的啊?是想累死我是么!
Context(执行上下文)拿this
这隔壁老C,看名字就贼难理解,还执行上下文?你当这是上语文课呢。算了算了,不调侃他那奇奇怪怪的名字了,等你理解了就会发现这名字取得还挺棒棒棒。接下来让他自己介绍一下吧。
我是Context, 我也给自己打个标签——我和我媳妇this形影不离。
为啥这么说呢,因为好多人一开始并不知道我,是因为想搞懂我媳妇this, 然后听到好多人说要搞懂this, 先要搞懂Context,所以才开始了解我。不是有好多人面试的时候看到各种关于this的面试题吗?看的头都大了,其实搞懂我Context之后,这些就全明白了。
一句话介绍自己Context——函数在被调用时的上下文环境,被调用函数中的this指向该上下文环境。先记住两个字调用!
那么怎么理解函数在被调用时的上下文环境呢?接下来又到了举栗子的时候了,真是百文不如十行代码啊:
var myName = '温浪';
function sayName (){
alert(this.myName);
}
sayName();
函数sayName被调用了,那么你想过函数是在哪个环境下被调用吗?在这里函数是在全局环境下被调用的,因此函数中的this指向全局对象——window。因此alert(this.myName);
可以输出:
上面这个栗子实在来太简单啦,那咱们来点难的加深一下理解:
var name = 'window';
var me = {
name: '温浪',
sayName: function() {
alert(this.name);
}
}
var myFunction = me.sayName;
myFunction();
输出的不是温浪
而是window
!
由此我们得出结论:
函数被调用时,this的指向与函数定义无关,而只与函数被调用时的上下文环境有关!再次强调!调用!
结合DOM事件的Context
通过上面的描述,你理解了函数调用时,this的指向由函数被调用时的我Context决定。那咱们再加点难度,结合DOM事件!
假设有以下代码:
<!DOCTYPE html>
<html>
<head>
<title>easyTest</title>
</head>
<body>
<button id="myButton" onClick="testContext()" name="dom">click me</button>
<script>
var name = 'window';
var me = {
name: '温浪',
sayName: function() {
alert(this.name);
}
}
var testContext = me.sayName;
</script>
</body>
</html>
代码中分别在dom节点对象,window对象和me对象中都定义了name属性,我们的目标是输出me对象中的温浪
,那么点击这个button到底会输出哪个name呢?答案是:
如果事件是直接通过inline的方式写在html中,那么this指向window对象。所以输出为window.
如果将代码改成下面这样:
<html>
<head>
<title>easyTest</title>
</head>
<body>
<button id="myButton" name="dom">click me</button>
<script>
var name = 'window';
var me = {
name: '温浪',
sayName: function() {
alert(this.name);
}
}
var testContext = me.sayName;
document.getElementById('myButton').addEventListener('click', testContext);
</script>
</body>
</html>
即通过js添加事件处理,那么会输出什么呢?答案是:
如果通过js添加事件处理,this指向该dom节点,在本例中也就是button节点。所以在点击的时候会alert出button节点上的name。
绕了一圈还是没有解决问题,现在我们的目标是显示me对象中的name。该怎么办呢, 其实很简单啊,要是你理解了我Context,就知道下面的代码可以解决这个问题:
<!DOCTYPE html>
<html>
<head>
<title>easyTest</title>
</head>
<body>
<button id="myButton" name="dom" onclick="me.sayName()">click me</button>
<script>
var name = 'window';
var me = {
name: '温浪',
sayName: function() {
alert(this.name);
}
}
</script>
</body>
</html>
哦!点击button的时候,让me去执行sayName,那么sayName的this就指向了me!成功解决问题!(虽然看上去这个例子很傻,但是希望能加深对Context的理解啦(╯﹏╰))
最后的最后
Scope和Context这两个家伙真是让人头大的不行,键盘都砸烂了!最后的最后,介绍另外两个兄弟,call和apply, 他俩就厉害了,可以手动指定方法被调用时this的指向!戳我去观光call和apply