this - 想说爱你不容易

wmwy8455 8年前
   <h2>前言</h2>    <p>javascript中的this是啥东西?为啥我们经常被他搞得晕头转向不知所以?他是恶魔?是天使 ?是怪胎?让我们一起来揭开它那神秘的面纱。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/bf5d9f6a2f93aba0301473a2a10e243e.jpg"></p>    <h2>他是个啥</h2>    <p>首先 this 是Javascript语言的关键字之一,指函数 运行 时的当前对象。那既然和函数运行有关,js中函数有哪些调用模式呢?</p>    <ol>     <li> <p>纯粹的函数调用</p> </li>     <li> <p>对象的方法调用</p> </li>     <li> <p>构造函数调用</p> </li>     <li> <p>apply、call调用</p> </li>    </ol>    <p>我擦,有木有一千只草泥马在心里蹦腾不息,人家是要弄懂this,你这又是整的哪一出</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/435d494e90911bb265b812f48365f86d.jpg"></p>    <p>我们慢慢来,一步步从这些调用模式中探究this这个神奇的远古神兽</p>    <h2>纯粹的函数调用</h2>    <p>函数调用 即 functionName () 模式,这也是我们使用的最多的一种方式,其属于全局调用,浏览中默认情况下函数内部的this指向 window ,当然是在非严格模式下。</p>    <pre>  <code class="language-javascript">this.name = 'qianlong';    function showName () {    console.log(this.name);    console.log(this === window);  }    showName()      // qianlong  // true</code></pre>    <h2>对象的方法调用</h2>    <p>当一个函数作为对象的某个属性方法被调用的时候</p>    <pre>  <code class="language-javascript">var obj = {    name: 'qianlong',    showName: function () {      console.log(this.name);    }  };    obj.showName();  // qianlong</code></pre>    <p>可以看出 this 指向是obj这个对象,其实本质上讲函数调用形式内部 this 就是指向调用它的那个对象</p>    <p>上面的例子相当于</p>    <pre>  <code class="language-javascript">window.showName()</code></pre>    <p>这也是为什么可以读取到全局定义的name属性的原因。</p>    <p>再来</p>    <pre>  <code class="language-javascript">var showName = function () {      console.log(this.name);    },    obj = {      name: 'qianlong',      showName: showName    };      obj.showName();</code></pre>    <p>这个时候输出的是什么呢</p>    <p>结果是不变的,在js中,一切都是对象,而这里也只是将,obj的showName属性指向,showNmae函数的引用地址。</p>    <p>继续</p>    <p>当我们把 showName 方法赋值给了一个变量,又会有什么事情发生呢?</p>    <pre>  <code class="language-javascript">var obj = {    name: 'qianlong',    showName: function () {      console.log(this.name);    }  };    var tempShowName = obj.showName;    tempShowName()    // undefined</code></pre>    <p>为什么不是期望的那样输出 qianlong呢。obj的showName方法是一个对象,当把它赋值给了tempShowName变量,此时便和obj没有什么关系了,而这个时候的调用和下面是等价的。</p>    <pre>  <code class="language-javascript">window.tempShowName()</code></pre>    <p>window上此事并没有 name 属性,自然输出是 undefined 。</p>    <h2>构造函数调用</h2>    <p>当使用 new 去调用一个构造函数的时候,内部的this,指向的是实例化出来的对象。</p>    <pre>  <code class="language-javascript">var Person = function (name, sex) {    this.name = name;    this.sex = sex;    console.log(this);  };    var p1 = new Person('qianlong', 'boy');    // Person {name: 'qianlong', sex: 'boy'};</code></pre>    <p>构造函数也是函数,所以当你用普通调用方式调用时</p>    <pre>  <code class="language-javascript">var Person = function (name, sex) {    this.name = name;    this.sex = sex;    console.log(this);  };    Person('qianlong', 'boy');    // 这个时候相当于给window对象添加了name和sex两个属性。    window.name // 'qianlong'  window.sex // 'boy'</code></pre>    <h2>apply、call调用</h2>    <p>使用call和apply方式去调用一个函数的时候,内部的this指向的是传进来的第一个参数,当第一个参数是 undefined 或者 null 的时候,依旧指向 window</p>    <pre>  <code class="language-javascript">var showName = function () {         console.log(this);   };   showName() // window   showName.call(undefined) // window   showName.call(null) // window   showName.call({name: 'qianlong'}) // {name: 'qianlong'}</code></pre>    <h2>箭头函数</h2>    <p>在 ES6 的新规范中,加入了箭头函数,它和普通函数最不一样的一点就是 this 的指向,普通函数中的this,是运行时候决定的,而箭头函数却是定义时候就决定了。</p>    <pre>  <code class="language-javascript">var obj = {    name: 'qianlong',    showName: function () {      console.log(this.name);    },    showNameLater: function () {      setTimeout(() => {        console.log(this.name);      }, 1000)    }          };    obj.showNameLater();    // qianlong</code></pre>    <pre>  <code class="language-javascript">var obj = {    name: 'qianlong',    showName: () => {      console.log(this.name);    }  };    obj.showName();  // undefined</code></pre>    <h3>一些坑</h3>    <p>1. setTimeout</p>    <pre>  <code class="language-javascript">var obj = {    name: 'qianlong',    showName: function () {      console.log(this.name);    },    showNameLater: function () {       setTimeout(this.showName, 1000);    }  };    obj.showNameLater();    // undefined</code></pre>    <p>这里在执行setTimeout这个函数的时候传了obj的showName函数作为第一个参数,其效果与</p>    <pre>  <code class="language-javascript">var showName = obj.showName</code></pre>    <p>是相同的。而setTimeout内部其实也是执行了传进去这个函数而已,即。</p>    <pre>  <code class="language-javascript">showName();</code></pre>    <p>还记得这种调用方式和 window.showName() 是类似的效果吗?这个时候输入为undefined也就好理解了。</p>    <p>那么怎么解决这个问题呢,毕竟我们期望的效果是输出 qianlong 。</p>    <pre>  <code class="language-javascript">var obj = {    name: 'qianlong',    showName: function () {      console.log(this.name);    },    showNameLater: function () {       var self = this;      setTimeout(function () {        self.showName();      }, 1000);    }  };    obj.showNameLater();</code></pre>    <p>或者</p>    <pre>  <code class="language-javascript">var obj = {    name: 'qianlong',    showName: function () {      console.log(this.name);    },    showNameLater: function () {       setTimeout(this.showName.bind(this), 1000);    }  };    obj.showNameLater();</code></pre>    <p>2. setTimeout</p>    <p>尼玛坑爹啊,居然还是因为你。</p>    <pre>  <code class="language-javascript">'use strict';    function show() {    console.log(this);  }    show(); // undefined     setTimeout(show, 1); // window</code></pre>    <p>在严格模式下面,函数调用的时候没有指定this的情况下,内部this的表现为 undefined ,但是setTimeout却不同,其内部默认还是指向window。</p>    <p>3. 为构造函数指定this</p>    <pre>  <code class="language-javascript">var Person = function (name, sex) {    this.name = name;    this.sex = sex;  };    var p1 = new Person.call({});    // Uncaught TypeError: Person.call is not a constructor</code></pre>    <p>这里报错了,原因是我们去 new 了 Person.call 函数 ,这里的函数不是一个构造函数;</p>    <p>当然解决方式也是有的。</p>    <pre>  <code class="language-javascript">var Person = function (name, sex) {    this.name = name;    this.sex = sex;  };    var p1 = new (Person.bind({}))('qianlong', 'sex');    // Person {name: "qianlong", sex: "sex"}</code></pre>    <p>4. 为箭头函数指定this</p>    <pre>  <code class="language-javascript">var show = (str) => {    console.log(str);    console.log(this);  };    show('qianlong');  // qianlong  // window    show.call({name: 'qianlong'}, 'qianlong');  // qianlong  // window</code></pre>    <p>可以看到使用call来手动改变箭头函数中的this的时候,无法成功。 箭头函数中的 this 在定义它的时候已经决定了(执行定义它的作用域中的 this),与如何调用以及在哪里调用它无关,包括 (call, apply, bind) 等操作都无法改变它的 this。</p>    <h2>结语</h2>    <p>文章可能有些疏漏与错误之处,欢迎各位指正。</p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007983078</p>    <p> </p>