最近正在看《你不知道的js》这本书,this部分已经看完,书中一些新的知识点刷新了我对this的理解,现回过头来做以总结,加深印象和理解。
首先抛出2个问题:
(1)this是什么?
(2)this怎么用?
先来看看第一个问题,this的概念。在最开始接触this的时候,我学到this有两层含义。
第一,this与事件连用时,代表事件源,也就是当前的html元素本身;
<body> <button id="btn">点击</button> </body> </html> <script> document.getElementById("btn").onclick=function(){ console.log("我是"+this) //我是[object HTMLButtonElement] } </script>
第二,与事件无关,当this出现在普通方法中时,this代表调用当前方法的对象本身;
function foo(){
console.log(this) //代表window对象 } foo();
然而,书中对this有不一样的解释。
误解一、this指向函数本身
我们首先通过个例子来看。
<script> function foo(num){ console.log("foo:"+num) //记录foo调用的次数 this.count++; } foo.count=0; for(var i=0;i<10;i++){ if(i>5){ foo(i); } }
//foo:6
//foo:7
//foo:8
//foo:9
console.log(foo.count)//0 </script>
上面代码中foo()方法虽然被调用了4次,但foo.count是0,说明this不是指向函数本身。而是指向被调用的对象。fool方法中的this指向window对象,而foo.count的对象指的是foo函数对象。所以由于对象不同,所以结果不同;
解决办法:
第一是通过词法作用域来解决,创建一个带用count属性的对象
(词法作用域:也就是在词法阶段定义的作用域。换句话说,词法作用域就是你在写代码的时候就已经决定了变量的作用域。)
<script> function foo(num){ console.log("foo:"+num) //记录foo调用的次数 data.count++; } var data={ count:0 } foo.count=0; for(var i=0;i<10;i++){ if(i>5){ foo(i); } } console.log(data.count)//4 </script>
<script> function foo(num){ console.log("foo:"+num) //记录foo调用的次数 foo.count++; } foo.count=0; for(var i=0;i<10;i++){ if(i>5){ foo(i); } } console.log(foo.count)//4 </script>
上面两种方法通过词法作用域都可以正确解决此问题,但是回避了this的问题。
<script> function foo(num){ console.log("foo:"+num) //记录foo调用的次数 this.count++; } foo.count=0; for(var i=0;i<10;i++){ if(i>5){ //使用call()改变this指向,将this指向函数对象foo本身 foo.call(foo,i); } } console.log(foo.count) </script>
上面通过call方法强制改变了this指向,解决了此问题。
误解二、this指向函数的作用域
需要知道的是this在任何情况下都不指向函数的词法作用域。在js内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域”对象”无法通过js代码访问,而只存在于js内部引擎。
所以,this到底是什么,书中对this的定义:this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。如下代码
当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。
function baz(){ console.log("baz");//当前调用栈是baz-全局作用域,因此当前调用位置是全局作用域 bar();//《--bar的调用位置}function bar(){ console.log("bar");//当前调用栈是bar-baz-全局作用域,因此当前调用位置是baz foo();//《--foo的调用位置}function foo(){ console.log("foo");//当前调用栈是foo-bar-baz-全局作用域,因此当前调用位置是bar} baz();//《--baz的调用位置是全局作用域
可以看到当前调用位置是当前调用栈的前一项。接下来分析调用位置如何决定this的绑定对象,也就是怎么用this。书中分了四条绑定规则。
第一、默认绑定
独立函数调用。
function foo(){ console.log(this.a) } var a=2; foo();
上面this指向全局window对象,但是使用严格模式的话,this将绑定到underfined
第二、隐式绑定
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到了这个上下文对象。
function foo(){ console.log(this.a) } var obj={ a:2, foo:foo }; obj.foo();//2
上面代码中因为调用位置会使用obj上下文来引用函数,所以this指向了obj对象。并且对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。
第三、显示绑定
通过call和apply方法改变this指向。它们的第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this。因为可以直接指定this的绑定对象,因此叫做显示绑定。
function foo(){ console.log(this.a) } var obj={ a:3, }; foo.call(obj);//3
另外还有bind绑定方法。
var obj = { init: 1, add: function(a, b) { return a + b + this.init; }}obj.add(1, 2); // 4var plus = obj.add;console.log(plus(3, 4)); // NaN,因为 this.init 不存在,这里的 this 指向 window/globalconsole.log(plus.bind(obj, 3, 4)())//8
第四、new绑定
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1、创建或者说构造一个新的对象;
2、这个新对象会被执行Prototype对象;
3、这个新对象会绑定到函数调用的this;
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象;
function foo(a){ console.log(this)//foo{} this.a=a; } var bar=new foo(2); console.log(bar.a)//2
第五、this与箭头函数