《你不知道的JavaScript》-- 精读(六)

知识点

1.为何要用this

function identify(){
    return this.name.toUpperCase();
}
function speak(){
    var greeting = "Hello, I'm " + identify.call(this);
    console.log(greeting);
}

var me = {
    name: "Kyle"
}
var you = {
    name: "Reader"
}

identify.call(me); // KYLE
identify.call(you); // READER

speak.call(me); // Hello,我是KYLE
speak.call(you); // Hello,我是READER
复制代码

这段代码能够在不一样的上下文对象(me和you)中重复使用函数identify()和speak(),不用针对每一个对象编写不一样版本的函数。javascript

若是不使用this,那就须要给identify()和speak()显式传入一个上下文对象。java

function identify(context){
    return context.name.toUppperCase();
}
function speak(context){
    var greeting = "Hello, I'm " + identify(context);
    console.log(greeting);
}
identify(you); // READER
speak(me); // hello,I'm + identify(context)
复制代码

然而,this提供了一种更优雅的方式来隐式“传递”一个对象引用,所以能够将API设计得更加简洁而且易于复用。ide

随着你的使用模式愈来愈复杂,显式传递上下文对象会让代码变得愈来愈混乱,使用this则不会这样。函数

2.关于this的误解

2.1 指向自身

function foo(num){
    console.log("foo:" + num);
    // 记录foo被调用的次数
    this.count++;
}
foo.count = 0;
var i;
for(i = 0; i < 10; i++){
    if (i > 5){
        foo(i);
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo被调用了多少次?
console.log(foo.count); // 0 -- 什么?!
复制代码

执行foo.count = 0时,的确向函数对象foo添加了一个属性count。可是函数内部代码this.count中的this并非指向那个函数对象,因此虽然属性名相同,根对象却并不相同,困惑随之产生。ui

实际上,若是深刻探索,就会发现这段代码无心中建立了一个全局变量count,其值为NaN。this

遇到这样的问题,大多数人会选择下面的解决方案,建立另外一个带有count属性的对象。spa

function foo(num){
    console.log("foo: " + num);
    // 记录foo被调用的次数
    data.count++
}
var data = {
    count: 0
}

var i;
for (i = 0; i < 10; i++){
   if (i > 5){
   foo(i);
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo被调用了多少次?
console.log(data.count);
复制代码

上面使用词法做用域“解决”了问题,可是忽视了真正的问题--没法理解this的含义和工做原理。设计

若是要从函数对象内部引用它自身,那只使用this是不够的。通常来讲你须要经过一个指向函数对象的词法标识符(变量)来引用它。code

思考下面的这两个函数对象

function foo(){
    foo.count = 4; // foo指向它自身
}

setTimeout(function (){
    // 匿名(没有名字的)函数没法指向自身
},10)
复制代码

第一个函数被称为具名函数,在它内部可使用foo来引用自身。

第二个例子中,传入setTimeout(...)的回调函数没有名称标识符(这种函数被称为匿名函数),所以没法从函数内部引用自身。

因此,对于咱们的例子来讲,另外一种解决方法是使用foo标识符替代this来引用函数对象:

function foo(num){
    console.log("foo:" + num);
    // 记录foo被调用的次数
    foo.count++;
}
foo.count = 0;
var i;
for(i = 0; i < 10; i++){
    if (i > 5){
        foo(i);
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo被调用了多少次?
console.log(foo.count); // 4
复制代码

然而,这种方法一样回避了this的问题,而且彻底依赖于变量foo的词法做用域。

另外一种方法是强制this指向foo函数对象:

function foo(num){
    console.log("foo:" + num);
    // 记录foo被调用的次数
    // 注意,在当前的调用方式下,this确实指向foo
    this.count++;
}
foo.count = 0;
var i;
for(i = 0; i < 10; i++){
    if (i > 5){
        foo.call(foo,i);
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo被调用了多少次?
console.log(foo.count); // 4
复制代码

2.2 它的做用域

第二种常见的误解是,this指向函数的做用域。这个问题有点复杂,由于在某种状况下它是正确的,可是在其余状况下它倒是错误的。

this在任何状况下都不指向函数的词法做用域。在JavaScript内部,做用域确实和对象相似,可见的标识符都是它的属性。可是做用域“对象”没法经过JavaScript代码访问,它存在于引擎内部。

function foo(){
    var a = 2;
    this.bar();
}
function bar(){
    console.log(this.a);
}

foo(); // ReferenceError: a is not defined
复制代码

首先,这段代码试图经过this.bar()来引用bar()函数。这样调用能成功纯属意外,咱们以后会解释缘由。调用bar()最天然的方法是省略前面的this,直接使用词法引用标识符。

此外,编写这段代码的开发者还试图使用this联通foo()和bar()的词法做用域,从而让bar()能够访问foo()做用域里的变量a。这是不可能实现的,使用this不可能在词法做用域中查到什么。

每当你想要把this和词法做用域的查找混合使用时,必定要提醒本身,这是没法实现的。

3 this究竟是什么

this是在运行时进行绑定的,并非在编写时绑定,它的上下文取决于函数调用时的各类条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会建立一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程当中用到。

总结

this既不指向自身也不指向函数的词法做用域。

this其实是在函数被调用时发生的绑定,它指向什么彻底取决于函数在哪里被调用。

巴拉巴拉

关于懒

我原本想写的是关于习惯,后来想一想仍是不要美化本身了,其实就是懒,也不必找借口,这个的原由是提交代码时不作确认,其实不少次我都作了,可是一遇到忙,或者提交的文件确实多的时候,我就懒得去检查了,这样就天然而然会致使不少问题,好比,以前就犯过API文档的接口名和实际的接口名不一致而被怼的状况,被教育一番,本身也非常羞愧,写了纸条提醒必定不要再犯,可是吧,仍是那句话,忙起来就忘了,这也是我想把它归到习惯的缘由,此次又是一样的问题,直接把代码粘贴过来改了一下,也没有检查,就提交了,天然又被批评了一番,心中的懊悔自没必要说,意识到了本身须要改正的两个地方,1.提交的东西要严谨,2.尽可能不粘贴代码,此次应该确定不会由于什么就忘了,由于教训是惨痛的!

相关文章
相关标签/搜索