ES5中令人头晕的this

说到this,就不得不提到function,相信看过其它类似文章的同学也知道,正是由于调用function的对象不同,才导致了this的指向不同。所以以前老是去记忆每种调用function的情况所对应的this,因为情况有限而且很少,所以这当然是可行的——对于聪明人来说。所以我不得不思考另外一些方式来让我记住。

那么首先我们需要明确的一个事情是:function也是对象

先来几条纲领:
1.函数执行时,首先看函数名前是否存在‘.’,存在-> ‘.’前面是谁,this就指向谁;不存在-> this指向window。
2.自执行函数中的this指向window。
3.元素绑定事件后,事件执行时,回调函数中的this指向当前事件元素。
4.构造函数模式中(new Function),类中(函数体中)this.xxx = xxx,this就是当前类的实例。
5.通过call、apply、bind方法中第一个参数强行改变this指向,call、apply、bind参数中第一个参数就是方法调用者的this指向。
当call、apply、bind参数中第一个参数是空、null、undefined时,this指向window

在原型模式中查找this方法步骤:
1)首先确定this的指向(上面5点)
2)把this替换成对应代码块
3)按照原型链机制,一步步查找结果

同时我们还需要明确的一个事情是:
function执行时是在某个特定的上下文中执行的。那什么是上下文呢?

  • 全局执行环境
      全局环境是最外围的一个执行环境。全局执行环境被认为是window对象。因此所有全局变量和函数都是作为window对象的属性和方法创建的。代码载入浏览器时,全局执行环境被创建(当我们关闭网页或者浏览器时全局执行环境才被销毁)。比如在一个页面中,第一次载入JS代码时创建一个全局执行环境。
      这也是为什么闭包有一个内存泄露的缺点。因为闭包中外部函数被当成了全局环境。所以不会被销毁,一直保存在内存中。

  • 函数执行环境
    ​  每个函数都有自己的执行环境。当执行流进入一个函数时,函数环境就会被推入一个环境栈中。当函数执行完之后,栈将其环境弹出,把控制权返回给之前的执行环境。函数执行环境的变量对象是该函数的活动对象(activation object)。
      这就是上下文,函数执行时它也需要一些额外的信息来支撑它的运行。那么既然function是对象的话,就会有方法。而function中最核心的方法是call方法。因此我们就从这儿入手。

  • call方法
    先来看一下如何使用call方法:

function say(content) {  
       console.log("From " + this + ": Hello "+ content);  
   }  
say.call("Bob", "World"); //==> From Bob: Hello World 

·Step1: 把第二个到最后一个参数作为函数执行时要传入的参数
·Step2: 把函数执行时的this指向第一个参数
·Step3: 在上面这个特殊的上下文中执行函数
上面例子中,我们通过call方法,让say函数执行时的this指向"Bob",然后把"World"作为参数传进去,所以输出结果是可以预见的。
js执行函数时会默认完成以上的步骤,你可以把直接调用函数理解为一种语法糖
比如

function say(word) {  
   console.log(world);  
}  
say("Hello world");  
  
say.call(window, "Hello world");  

以上可以把say("Hello world")看做是say.call(window,"Hello world")的语法糖。
这个结论非常关键
所以以后每次看见functionName(xxx)的时候,你需要马上在脑海中把它替换为functionName.call(window,xxxx),这对你理解this的指向非常重要。不过也有例外,在ES5的strict mode中call的第一个参数不是window而是undefined。之后的例子我假设总是不在strictmode下,但你需要记住strictmode有一点儿不同
对于匿名函数来说,上面的结论也是成立的

(function(name) {  
    //  
})("aa");  
//等价于  
(function(name) {  
    //  
}).call(window, "aa"); 

函数作为对象的方法被调用
直接来看代码:

var person = {  
    name : "caibirdme",  
    run : function(time) {  
        console.log(this.name + "has been running for over "+ time+ " minutes");  
    }  
};  
person.run(30); //==> caibirdme has been running for over 30 minutes  
//等价于  
person.run.call(person, 30); // the same  

你会发现这里call的第一个参数是person而不是window
当你明白了这两点,下意识地把函数调用翻译成foo.call()的形式,明确call的第一个参数,那基本上this的问题就难不住你了。
还是来举几个例子吧
例一:

function hello(thing) {    
  console.log(this + " says hello " + thing);  
}  
  
person = { name: "caibirdme" }    
person.hello = hello;  
  
person.hello("world") // 相当于执行 person.hello.call(person, "world")  
//caibirdme says hello world  
  
hello("world") // 相当于执行 hello.call(window, "world")   
//[object DOMWindow]world  

例二:

var obj = {  
    x: 20,  
    f: function(){ console.log(this.x); }  
};  
  
obj.f(); // obj.f.call(obj)  
//==> 20  
  
obj.innerobj = {  
    x: 30,  
    f: function(){ console.log(this.x); }  
}  
  
obj.innerobj.f(); // obj.innerobj.f.call(obj.innerobj)  
// ==> 30  

例三:

var x = 10;  
var obj = {  
    x: 20,  
    f: function(){  
        console.log(this.x); //this equals obj  
                // ==> 20  
        var foo = function(){ console.log(this.x); }  
        foo(); // foo.call(window)  
                //foo中this被指定为window,所以==> 10  
    }  
};  
  
obj.f();  // obj.f.call(obj)  
// ==> 20 10  

由例三引出一个非常common的问题,如果我想让foo输出20怎么办?这时候需要用到一点小技巧
例四:

var x = 10;  
var obj = {  
    x: 20,  
    f: function(){  
        console.log(this.x);  
        var that = this; //使用that保留当前函数执行上下文的this  
        var foo = function(){ console.log(that.x); } //此时foo函数中的this仍然指向window,但我们使用that取得obj  
        foo(); // foo.call(window)  
    }  
};  
  
obj.f(); obj.f.call(obj)  
// ==> 20 20  

再来一个稍微难一点点的(但其实用call替换法一点儿也不难)
例五:

var x = 10;  
var obj = {  
    x: 20,  
    f: function(){ console.log(this.x); }  
};  
  
obj.f(); // obj.f.call(obj)  
// ==> 20  
  
var fOut = obj.f;  
fOut(); // fOut.call(window)  
//==> 10  
  
var obj2 = {  
    x: 30,  
    f: obj.f  
}  
  
obj2.f(); // obj2.f.call(obj2)  
//==> 30  

例五如果没有明确:
es5中this是在执行是才会被确认的
是会出错的。
可能会认为说 obj.f 那个函数定义在obj里面,那 this 就该指向obj。
用于构造函数
先看一段代码:

func person(name) {  
    this.name = name;  
}  
var caibirdme = new person("deen");  
// caibirdme.name == deen  

函数在用作构造函数时同样可以用call方法去代替,那这里怎么代替呢?
这里又需要明确一点:
new constrcut()是一种创建对象的语法糖
它等价于

function person(name) {  
   this.name = name;  
}  
var foo = new person("deen");  
//通过new创建了一个对象  
//new是一种语法糖,new person等价于  
var bar = (function(name) {  
    var _newObj = {  
        constructor : person,  
        __proto__ : person.prototype,  
    };  
    _newObj.constructor(name); // _newObj.constructor.call(_newObj, name)  
    return _newObj;  
})();  

new的时候this就指向新的对象
总结来说就是下面两个等价变形:
· foo() ---> foo.call(window)
· obj.foo() --> obj.foo.call(obj)
只要理解以上两个变形,this就不再是问题啦!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280