ES5中令我头晕的this

说在前面

说到this,就不得不提到function,相信看过其它相似文章的同窗也知道,正是因为调用function的对象不一样,才致使了this的指向不一样。因此之前总是去记忆每种调用function的状况所对应的this,由于状况有限并且不多,因此这固然是可行的——对于聪明人来讲。因此我不得不思考另一些方式来让我记住。javascript

那么首先咱们须要明确的一个事情是:___function___也是对象java

几条纲领

  • 函数执行时,首先看函数名前是否存在‘.’,存在-> ‘.’前面是谁,this就指向谁;不存在-> this指向window。
  • 自执行函数中的this指向window。
  • 元素绑定事件后,事件执行时,回调函数中的this指向当前事件元素。
  • 构造函数模式中(new Function),类中(函数体中)this.xxx = xxx,this就是当前类的实例。
  • 经过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代码时建立一个全局执行环境。这也是为何闭包有一个内存泄露的缺点。由于闭包中外部函数被当成了全局环境。因此不会被销毁,一直保存在内存中。markdown

  • 函数执行环境

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

call方法

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

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执行函数时会默认完成以上的步骤,你能够把直接调用函数理解为一种语法糖 好比:oop

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有一点儿不一样ui

对于匿名函数来讲,上面的结论也是成立的this

(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就再也不是问题啦!!

学会JS的this这一篇就够了,根本不用记

相关文章
相关标签/搜索