说到this,就不得不提到function,相信看过其它相似文章的同窗也知道,正是因为调用function的对象不一样,才致使了this的指向不一样。因此之前总是去记忆每种调用function的状况所对应的this,由于状况有限并且不多,因此这固然是可行的——对于聪明人来讲。因此我不得不思考另一些方式来让我记住。javascript
那么首先咱们须要明确的一个事情是:___function___也是对象java
同时咱们还须要明确的一个事情是: function执行时是在某个特定的上下文中执行的。浏览器
全局环境是最外围的一个执行环境。全局执行环境被认为是window对象。所以全部全局变量和函数都是做为window对象的属性和方法建立的。代码载入浏览器时,全局执行环境被建立(当咱们关闭网页或者浏览器时全局执行环境才被销毁)。好比在一个页面中,第一次载入JS代码时建立一个全局执行环境。这也是为何闭包有一个内存泄露的缺点。由于闭包中外部函数被当成了全局环境。因此不会被销毁,一直保存在内存中。markdown
每一个函数都有本身的执行环境。当执行流进入一个函数时,函数环境就会被推入一个环境栈中。当函数执行完以后,栈将其环境弹出,把控制权返回给以前的执行环境。函数执行环境的变量对象是该函数的活动对象(activation object)。这就是上下文,函数执行时它也须要一些额外的信息来支撑它的运行。那么既然function是对象的话,就会有方法。而function中最核心的方法是call方法。所以咱们就从这儿入手。闭包
先来看一下如何使用call方法:app
function say(content) {
console.log("From " + this + ": Hello "+ content);
}
say.call("Bob", "World"); //==> From Bob: Hello World
复制代码
上面例子中,咱们经过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就指向新的对象 总结来讲就是下面两个等价变形:
只要理解以上两个变形,this就再也不是问题啦!!