全面解析 Javascript - this

ogdx0378 7年前
   <p style="text-align:center"><img src="https://simg.open-open.com/show/ef06b35b002dc6ca27ba6442cca6562f.jpg"></p>    <h2>为什么要写这篇文章?</h2>    <p>我之前在阿里面试,以及其它公司面试的时候曾经被问到过这个问题,所以想要分享给大家,如果你能清楚地知道下面 this 七种情况的区别,能跟面试官解释清楚,无疑是一个大大的加分项,理解完这篇文章,面试官再怎么问你 this 都不怕了。</p>    <p>谈及 Javascript 中的 this,竟然让人觉得头疼,它不像 Java,C++ 中的 this 指向调用 this 的该对象。</p>    <p>在函数中 this 到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了。</p>    <p>因为 this 的取值是执行上下文环境的一部分,每次调用函数,都会产生一个新的执行上下文环境。当你在代码中使用了 this,这个 this 的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻。</p>    <p>关于 this 的取值,大体上可以分为以下七种情况:</p>    <h2>情况一:全局 & 调用普通函数</h2>    <p>在全局环境中,this 永远指向 window。</p>    <pre>  <code class="language-javascript">console.log(this === window);     //true</code></pre>    <p>普通函数在调用时候(注意不是构造函数,前面不加 new),其中的 this 也是指向 window。</p>    <pre>  <code class="language-javascript">var x = 10;  function foo(){      console.log(this);     //Window      console.log(this.x);   //10  }  foo();</code></pre>    <h2>情况二:构造函数</h2>    <p>所谓的构造函数就是由一个函数 new 出来的对象,一般构造函数的函数名首字母大写,例如像 Object,Function,Array 这些都属于构造函数。</p>    <pre>  <code class="language-javascript">function Foo(){      this.x = 10;      console.log(this);    //Foo {x:10}  }  var foo = new Foo();  console.log(foo.x);      //10</code></pre>    <p>上述代码,如果函数作为构造函数使用,那么其中的 this 就代表它即将 new 出来的对象。</p>    <p>但是如果直接调用 Foo 函数,而不是 new Foo(),那就变成情况1,这时候 Foo() 就变成普通函数。</p>    <pre>  <code class="language-javascript">function Foo(){      this.x = 10;      console.log(this);    //Window  }  var foo = Foo();  console.log(foo.x);      //undefined</code></pre>    <h2>情况三:对象方法</h2>    <p>如果函数作为对象的方法时,方法中的 this 指向该对象。</p>    <pre>  <code class="language-javascript">var obj = {      x: 10,      foo: function () {          console.log(this);        //Object          console.log(this.x);      //10      }  };  obj.foo();</code></pre>    <p>注意:若是在对象方法中定义函数,那么情况就不同了。</p>    <pre>  <code class="language-javascript">var obj = {      x: 10,      foo: function () {          function f(){              console.log(this);      //Window              console.log(this.x);    //undefined          }          f();      }  }  obj.foo();</code></pre>    <p>可以这么理解:函数 f 虽然是在 obj.foo 内部定义的,但它仍然属于一个普通函数,this 仍指向 window。(这是个坑,要记牢)</p>    <p>在这里,如果想要调用上层作用域中的变量 obj.x,可以使用 self 缓存外部 this 变量。</p>    <pre>  <code class="language-javascript">var obj = {      x: 10,      foo: function () {          var self = this;          function f(){              console.log(self);      //{x: 10}              console.log(self.x);    //10          }          f();      }  }  obj.foo();</code></pre>    <p>如果 foo 函数不作为对象方法被调用:</p>    <pre>  <code class="language-javascript">var obj = {      x: 10,      foo: function () {          console.log(this);       //Window          console.log(this.x);     //undefined      }  };  var fn = obj.foo;  fn();</code></pre>    <p>obj.foo 被赋值给一个全局变量,并没有作为 obj 的一个属性被调用,那么此时 this 的值是 window。</p>    <h2>情况四:构造函数 prototype 属性</h2>    <pre>  <code class="language-javascript">function Foo(){      this.x = 10;  }  Foo.prototype.getX = function () {      console.log(this);        //Foo {x: 10, getX: function}      console.log(this.x);      //10  }  var foo = new Foo();  foo.getX();</code></pre>    <p>在 Foo.prototype.getX 函数中,this 指向的 foo 对象。不仅仅如此,即便是在整个原型链中,this 代表的也是当前对象的值。</p>    <h2>情况五:函数用 call、apply或者 bind 调用。</h2>    <pre>  <code class="language-javascript">var obj = {      x: 10  }  function foo(){      console.log(this);     //{x: 10}      console.log(this.x);   //10  }  foo.call(obj);  foo.apply(obj);  foo.bind(obj)();</code></pre>    <p>当一个函数被 call、apply 或者 bind 调用时,this 的值就取传入的对象的值。</p>    <h2>情况六:DOM event this</h2>    <p>在一个 HTML DOM 事件处理程序里,this 始终指向这个处理程序所绑定的 HTML DOM 节点:</p>    <pre>  <code class="language-javascript">function Listener(){         document.getElementById('foo').addEventListener('click', this.handleClick);     //这里的 this 指向 Listener 这个对象。不是强调的是这里的 this  }  Listener.prototype.handleClick = function (event) {      console.log(this);    //<div id="foo"></div>  }  var listener = new Listener();  document.getElementById('foo').click();</code></pre>    <p>这个很好理解,就相当于是给函数传参,使 handleClick 运行时上下文改变了,相当于下面这样的代码:</p>    <pre>  <code class="language-javascript">var obj = {      x: 10,      fn: function() {          console.log(this);         //Window          console.log(this.x);       //undefined      }  };  function foo(fn) {      fn();  }   foo(obj.fn);</code></pre>    <p>你也可以用通过 bind 切换上下文:</p>    <pre>  <code class="language-javascript">function  Listener(){      document.getElementById('foo').addEventListener('click',this.handleClick.bind(this));        }  Listener.prototype.handleClick = function (event) {      console.log(this);    //Listener {}  }  var listener = new Listener();  document.getElementById('foo').click();</code></pre>    <p>前六种情况总结一句话为: this 指向调用该方法的对象。</p>    <h2>情况七:箭头函数中的 this</h2>    <p>当使用箭头函数的时候,情况就有所不同了:箭头函数内部的 this 是词法作用域,由上下文确定。</p>    <pre>  <code class="language-javascript">var obj = {      x: 10,      foo: function() {          var fn = () => {              return () => {                  return () => {                      console.log(this);      //Object {x: 10}                      console.log(this.x);    //10                  }              }          }          fn()()();      }  }  obj.foo();</code></pre>    <p>现在,箭头函数完全修复了 this 的指向,this 总是指向词法作用域,也就是外层调用者 obj。</p>    <p>如果使用箭头函数,以前的这种 hack 写法:</p>    <pre>  <code class="language-javascript">var self = this;</code></pre>    <p>就不再需要了。</p>    <pre>  <code class="language-javascript">var obj = {      x: 10,      foo: function() {          var fn = () => {              return () => {                  return () => {                      console.log(this);    // Object {x: 10}                      console.log(this.x);  //10                  }              }          }          fn.bind({x: 14})()()();          fn.call({x: 14})()();      }  }  obj.foo();</code></pre>    <p>由于 this 在箭头函数中已经按照词法作用域绑定了,所以,用 call()或者 apply()调用箭头函数时,无法对 this 进行绑定,即传入的第一个参数被忽略。</p>    <h2>补充说明:</h2>    <ul>     <li>this 为保留字,你不能重写 this。</li>    </ul>    <pre>  <code class="language-javascript">function test(){      var this = {};     //Uncaught SyntaxError: Unexpected token this  }</code></pre>    <ul>     <li>宿主对象:</li>    </ul>    <ol>     <li>一门语言在运行的时候,需要一个环境,叫做宿主环境。</li>     <li>对于JavaScript,宿主环境最常见的是 web 浏览器,浏览器提供了一个 JavaScript 运行的环境,这个环境里面,需要提供一些接口,好让 JavaScript 引擎能够和宿主环境对接。</li>     <li>JavaScript 引擎才是真正执行 JavaScript 代码的地方,常见的引擎有 V8(目前最快 JavaScript 引擎、Google 生产)、JavaScript core。</li>     <li>在浏览器或者服务端( nodejs )都有自己的 JS 引擎,在浏览器中,全局对象为 window,而在 nodejs 中,全局对象为 global。</li>    </ol>    <p> </p>    <p>来自:https://zhuanlan.zhihu.com/p/25294187</p>    <p> </p>