别再为了this发愁了------JS中的this机制编程
题记:JavaScript中有不少使人困惑的地方,或者叫作机制。可是,就是这些东西让JavaScript显得那么美好而不同凡响。比方说函数也是对象、闭包、原型链继承等等,而这其中就包括颇让人费解的this机制。不论是新手仍是老手,不仔细深抠一下还真闹不明白this倒地咋回事捏。今天,咱们就一块儿看一下this倒地咋回事,别再为了this发愁了。闭包
一、this是啥?app
简言之,this是JavaScript语言中定义的众多关键字之一,它的特殊在于它自动定义于每个函数域内,可是this倒地指引啥东西却让不少人张二摸不着头脑。这里咱们留个小悬念,但愿看完这篇文章了你能回答出来this到底指引个甚。编程语言
二、this有啥用?ide
那边观众又该问了,既然this这么难以理解,那么为个甚还要用它呢?咱们来看个例子:函数
1 function identify() { 2 return this.name.toUpperCase(); 3 } 4 function sayHello() { 5 var greeting = "Hello, I'm " + identify.call( this ); 6 console.log( greeting ); 7 } 8 var person1= { 9 name: "Kyle" 10 }; 11 var person2= { 12 name: "Reader" 13 }; 14 identify.call( person1); // KYLE 15 identify.call( person2); // READER 16 sayHello.call( person1); // Hello, I'm KYLE 17 sayHello.call( person2); // Hello, I'm READER
这段代码很简单,咱们定义了两个函数,分别为identify和sayHello。而且在不一样的对象环境下执行了它们,达到了复用的效果,而不用为了在不一样的对象环境下执行而必须针对不一样的对象环境写对应的函数了。简言之,this给函数带来了复用。那边客官又问了,我不用this同样能够实现,如:this
1 function identify(context) { 2 return context.name.toUpperCase(); 3 } 4 function sayHello(context) { 5 var greeting = "Hello, I'm " + identify( context); 6 console.log( greeting ); 7 } 8 var person1= { 9 name: "Kyle" 10 }; 11 var person2= { 12 name: "Reader" 13 }; 14 identify( person1); // KYLE 15 identify( person2); // READER 16 sayHello( person1); // Hello, I'm KYLE 17 sayHello( person2); // Hello, I'm READER
仔细一看,这位客官给出的解决方法的确也达到了相似的效果。赞一个!我想说的是,随着代码的增长,函数嵌套、各级调用等变得愈来愈复杂,那么传递一个对象的引用将变得愈来愈不明智,它会把你的代码弄得很是乱,甚至你本身都没法理解清楚。而this机制提供了一个更加优雅而灵便的方案,传递一个隐式的对象引用让代码变得更加简洁和复用。好了,知道了this的用处,那么再看看咱们对它的误解。spa
三、关于this的误解code
相信不少童鞋是学过其它语言的,在不少编程语言中都有this的机制,惯性思惟把其它语言里对它的理解带到了JavaScript中。同时,因为this这个单词的理解致使了咱们产生了对它各类各样的误解。因此,开始前,咱们先澄清下对它的误解。对象
3.1 误解一:this引用function自己
咱们都知道,在函数里引用函数能够达到递归和给函数属性赋值的效果。而这在不少应用场景下显得很是有用。因此,不少人都误觉得this就是指引function自己。例如:
1 function fn(num) { 2 console.log( "fn: " + num ); 3 // count用于记录fn的被调用次数 4 this.count++; 5 } 6 fn.count = 0; 7 var i; 8 for (i=0; i<10; i++) { 9 if (i > 5) { 10 fn( i ); 11 } 12 } 13 // fn: 6 14 // fn: 7 15 // fn: 8 16 // fn: 9 17 18 console.log( fn.count ); // 0 -- 耶?咋不是4捏?
上面咱们想要记录fn被调用的次数,但是明显fn被调用了四次但count仍然为0。咋回事捏?这里简单解释下,fn里第4行的自增隐式的建立了一个全局变量count,因为初始值为undefined,因此每一次自增其实依然不是一个数字,你在全局环境下打印count(window.count)输出的应该是NaN。而第6行定义的函数熟悉变量count依然没变,仍是0。若是对这个执行结果不清楚的,欢迎去看我前些天的那篇博文(聊一下JS中的做用域scope和闭包closure scope和closure),在这里你只须要知道,this引用的是function这种理解是错误的就行。
这边就会又有人问了,既然this不是引用function,那么我要实现递归函数,该咋引用呢?这里简单回答下介个问题,两种方法:①函数体内用函数名来引用函数自己②函数体内使用arguments.callee来引用函数(不推荐)。那么既然第二种方法不推荐,匿名函数咋引用呢?用第一种,而且给匿名函数一个函数名便可(推荐)。
3.2 误解二:this引用的是function的词法做用域
这种误解欺骗的人可能更多一些。首先,澄清一下,this并无引用function的词法做用域。的确JS的引擎内对词法做用域的实现的确像是一个对象,拥有属性和函数,可是这仅仅是JS引擎的一种实现,对代码来讲是不可见的,也就是说词法做用域“对象”在JS代码中取不到。(关于词法做用域,若是不理解,能够参考以前的一篇博文《聊一下JS中的做用域scope和闭包closure scope和closure》)。看个错误的例子:
1 function fn1() { 2 var a = 2; 3 this.fn2();//觉得this引用的是fn1的词法做用域 4 } 5 function fn2() { 6 console.log( this.a ); 7 } 8 fn1(); //ReferenceError
上面的代码明显没有执行出想要的结果,从而能够看到this并无引用函数的词法做用域。甚至,能够确定的说,这个例子里fn2能够在fn1里正确执行都是偶然的(理解了词法做用域你就知道为何这里执行不报错了)。
四、this到底跟啥有关?
好了,扯了那么多都没上干货,有的观众都开始关闭当前页开始离席了。这里,咱们郑重声明:this跟函数在哪里定义没有半毛钱关系,函数在哪里调用才决定了this到底引用的是啥。也就是说this跟函数的定义不要紧,跟函数的执行有大大的关系。因此,记住,“函数在哪里调用才决定了this到底引用的是啥”。
五、this机制的四种规则
this到底绑定或者引用的是哪一个对象环境决定于函数被调用的地方。而函数的调用有不一样的方式,在不一样的方式中调用决定this引用的是哪一个对象是由四种规则肯定的。咱们一个个来看。
5.1 默认绑定全局变量
这条规则是最多见的,也是默认的。当函数被单独定义和调用的时候,应用的规则就是绑定全局变量。以下:
1 function fn() { 2 console.log( this.a ); 3 } 4 var a = 2; 5 fn(); // 2 -- fn单独调用,this引用window
5.2 隐式绑定
隐式调用的意思是,函数调用时拥有一个上下文对象,就好像这个函数是属于该对象的同样。例如:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj = { 5 a: 2, 6 fn: fn 7 }; 8 obj.fn(); // 2 -- this引用obj。
须要说明的一点是,最后一个调用该函数的对象是传到函数的上下文对象(绕懵了)。如:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj2 = { 5 a: 42, 6 fn: fn 7 }; 8 var obj1 = { 9 a: 2, 10 obj2: obj2 11 }; 12 obj1.obj2.fn(); // 42 -- this引用的是obj2.
还有一点要说明的是,失去隐式绑定的状况,以下:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj = { 5 a: 2, 6 fn: fn 7 }; 8 var bar = obj.fn; // 函数引用传递 9 var a = "全局"; // 定义全局变量 10 bar(); // "全局"
如上,第8行虽然有隐式绑定,可是它执行的效果明显是把fn赋给bar。这样bar执行的时候,依然是默认绑定全局变量,因此输出结果如上。
5.3 显示绑定
学过bind()\apply()\call()函数的都应该知道,它接收的第一个参数便是上下文对象并将其赋给this。看下面的例子:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj = { 5 a: 2 6 }; 7 fn.call( obj ); // 2
若是咱们传递第一个值为简单值,那么后台会自动转换为对应的封装对象。若是传递为null,那么结果就是在绑定默认全局变量,如:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj = { 5 a: 2 6 }; 7 var a = 10; 8 fn.call( null); // 10
5.4 new新对象绑定
若是是一个构造函数,那么用new来调用,那么绑定的将是新建立的对象。如:
1 function fn(a) { 2 this.a = a; 3 } 4 var bar = new fn( 2 ); 5 console.log( bar.a );// 2
注意,通常构造函数名首字母大写,这里没有大写的缘由是想提醒读者,构造函数也是通常的函数而已。
六、结束语
读到如今,1中问的问题你该本身能回答上来了。上面介绍的四种关于this绑定的4中状况和规则,现实写代码的过程当中确定比这要多和复杂,可是不管多复杂多乱,它们都是混合应用上面的几个规则和状况而已。只要你的思路和理解是清晰的,那确定没问题的。