Javascript 当中的 this 与其余语言是彻底不一样的机制,颇有可能会让一些编写其余语言的工程师迷惑。javascript
根据 this 的英语语法,很容易将函数中出现的 this
理解为函数自身。在 javascript 当中函数做为一等公民,确实能够在调用的时候将属性值存储起来。可是若是使用方法不对,就会发生与实际预期不一致的状况。具体状况,请看下面代码java
function fn(num){ this.count++; } fn.count = 0; for(var i=0;i<3;i++){ fn(i); } console.log(fn.count); // 0
若是 fn 函数里面的 this 指向自身函数,那么 count 属性的属性值就应该产生变化,但实际上倒是纹丝不动。对于这个问题,有些人会利用做用域来解决,好比这么写数组
var data = { count:0 }; function fn(num){ data.count++; } for(var i=0;i<3;i++){ fn(i); } console.log(data.count); //3
又或者更直接的这么写浏览器
function fn(num){ fn.count++; } fn.count = 0; for(var i=0;i<3;i++){ fn(i); } console.log(fn.count);//3
虽然这两种方式都输出了正确的结果,可是却避开了 this 到底绑定在哪里的问题。若是对一个事物的工做原理不清晰,就每每会产生头痛治头,脚痛治脚的问题,从而致使代码变得的丑陋,并且维护性也会变得不好。安全
第一种是最多见的 this 的绑定,看一下下面的代码app
function fn(){ console.log(window === this); //浏览器环境 } fn(); //true
函数 fn 是直接在全局做用域下调用的,没有带其余任何修饰,这种状况下,函数调用的时候使用了 this 的默认绑定,指向了全局对象。函数
这样就清楚了第一个例子中的 this 指向, fn 函数中的 this 指向了全局变量,因此 this.count++ 至关于 window.count++(浏览器环境下),固然不会对 fn 函数的count属性产生影响。this
有一点要说明的是,上面种状况只能在非严格模式(strict mode)下才能发生,在严格模式下,会将 this 默认绑定为 undefined。以免全局变量的污染。spa
若是函数在以对象为上下文进行调用,那么 this 的绑定就会产生变化。this 会绑定到调用这个函数的对象,查看下面代码:prototype
var obj = { a:1, fn:function(){ console.log(this.a); } } obj.fn(); //1
即便函数声明不在对象当中,this 指向仍会产生变化
function fn(){ console.log(this.a); } var obj = { a:1, fn:fn } obj.fn(); //1
因而可知,this 的绑定,不与函数定义的位置有关,而是与调用者和调用方式有关。
在隐式的绑定规则下,有一些特殊的地方,须要注意。
function fn(){ console.log(this.a); } var obj3 = { a:3, fn:fn } var obj2 = { a:2, obj3:obj3 } var obj = { a:1, obj2:obj2 } obj.obj2.obj3.fn(); //3
在多层对象引用下,this 指向的是调用的函数的那个对象。
查看下面代码
function fn(){ console.log(this); } var obj = { fn:fn } var fun = obj.fn; fun(); //window
虽然 fn 引用了 obj.fun ,可是函数的调用方式,还是不带任何修饰的,因此 this 仍是绑定在了 window 上。
还有一种状况,容易让你们忽略,那就是传参的时候,其实会进行隐式赋值。
function fn(){ console.log(this); } function doFn(fn){ fn(); } var obj = { fn:fn } doFn(obj.fn); //window
隐式绑定 this 不是一种很推荐的方式,由于颇有可能就发生丢失的状况,若是业务当中对 this 的绑定有要求,建议仍是使用显示绑定的方式。
显示绑定就是利用函数原型上的 apply 与 call 方法来对 this 进行绑定。用法就是把想要绑定的对象做为第一个参数传进去。
function fn(){ console.log(this); } var obj = {}; fn.call(obj); //{}
有些时候会想将函数的 this 绑定在某个对象上,可是不须要当即调用,这样的话,直接利用 call 或者 apply 是没法作的。
function fn(){ console.log(this); } function bind(fn){ fn(); } var obj = { fn:fn } bind.call(obj,fn); //window
上面这个例子,看似好像能够,但其实是 bind 函数的 this 绑定到了 obj 这个对象,可是 fn 仍然是没有任何修饰的调用,因此 fn 仍然是默认的绑定方式。
function fn(){ console.log(this); } function bind(fn,obj){ return function(){ fn.apply(obj,arguments); } } var obj = { fn:fn } var fun = bind(fn,obj); fun(); //obj
这样调用,就能够将灵活多变的 this ,紧紧的控制住了,由于 fn 的调用方式为 apply 调用。因此,this 就被绑定在传入的 obj 对象上,在 ES5 当中,函数的原型方法上多了一个 bind。效果与上面的函数基本一致,具体用法限于篇幅就很少说了。
new 是一个被不少人误解的一个关键字,但实际上 javascript 的 new 与传统面向对象的语言彻底不一样。
我的把 new 理解为一种特殊的函数调用,当使用 new 关键字来调用函数的时候,会执行下面操做,
建立一个全新的对象
将空对象的 __proto__
指向构造函数的 prototype
将新对象的 this 绑定到调用的函数上
若是函数返回值为基本类型或者为 this又或者不返回任何值,那么将会返回这个建立的新对象,若是返回了一个对象,那么则会返回这个对象,而不会返回建立的新对象。
function fn(a){ this.a = a; } fn.prototype.hi = function(){ console.log('hi') } var obj = new fn(2); console.log(obj);
function fn(a){ this.a = a; return {}; } var obj = new fn(2); console.log(obj); //{}
null 和 undefined 也是能够做为 this 的绑定对象的,可是实际上应用的是默认的绑定。
可是这种传参的实际效用是什么呢?
常见的用法是将一个数组展开,做为参数传入参数。好比
function fn(a,b){ console.log('a:',a,'b:',b); } fn.apply(null,[1,2]); // a: 1 b: 2
可是这种用法会有一个坑,那就是若是函数存在了 this ,那么就会应用默认的绑定规则,将 this 绑定在全局对象上,发生于预期不一致的状况。为了代码更加稳健,可使建立一个比空对象更空的对象。
var obj = Object.create(null); console.log(obj.__proto__); //undefined var obj2 = {} console.log(obj2.__proto__); //Object {}
Object原型上有一个 create 方法,这个方法会建立一个对象,而后将对象的原型指向传入的参数,因此传入 null 的话,产生一个没有 prototype 的对象,因此会比空对象更加"空"。
因此传入这个对象,会比传入 null 更加安全。
var obj = Object.create(null); fn.apply(obj,[1,2]);
在 ES6 当中,出现了一个新的函数类型,箭头函数。
若是使用箭头函数,那么就不会使用上面提到的四种 this 绑定方式,而是根据做用域来决定
比较常见的是用于事件函数和定时器的状况。
下面是比较常见的传统 this 写法
function fn(){ var _this = this; setTimeout(function(){ console.log(_this.a); },100) } var obj = { a:2 } fn.call(obj); //2
若是使用箭头函数则能够这么写
function fn(){ setTimeout(()=>{ //this 来源于 fn 函数的做用域 console.log(this.a); },100) } var obj = { a:2 } fn.call(obj); //2
若是是在事件函数当中,this 的绑定是指向触发事件的 DOM 元素的,
$('body')[0].addEventListener('click',function(){ console.log(this); },false);
点击 body 元素以后,控制台则会显示 body 元素
若是想判断一个函数的 this 绑定在哪里,首先是找到函数的调用位置,以后是按照规则来判断。
若是函数调用时没有任何修饰条件,那么在严格模式下则会绑定到 undefined ,非严格模式下会绑定到全局。
若是是用对象作上下文,来对函数进行调用,那么则会绑定到调用的这个对象上。
若是是用 call 或者 apply 方法来进行调用的,则会绑定到第一个传入参数上。
若是是使用 new 关键字来调用函数的,则会绑定到新建立的那个对象上.
若是是在事件函数内,则会绑定到触发事件的那个DOM元素上。