JavaScript进阶之’this‘

在这里插入图片描述

只有掌握了JavaScript中的this操做符你才算正式迈入JavaScript这门语言的门槛!咱们一直都在用这个框架,那个框架,但每每忽视掉了js最基础的东西,笔者认为这些基础每每才是走下去,走深,最不可或缺的东西.那咱们就一块儿来学习一下js中神奇的this吧--------笔者查看了大量有关this的文章,有些是按照本身思路写的,有些是直接引用其余做者成熟的思路的文章javascript

1.什么是this?

学习一个知识首先要理解他的字面含义,经过翻译咱们知道,this的含义是这,这个(指较近的人或事物)的意思。那么咱们结合现实,咱们说的“这”在不一样的环境所指的事物是不同的。那么在JavaScript中this在不一样的环境调用所表达的含义也是很是丰富的。若是你以为JavaScript中的this和其余面向对象语言Java同样,是指存储在实例属性中的值,那你就大错特错了。JavaScript中的this有着在这门语言中不可或缺魔力。java

2.运行的宿主(环境)不一样this含义也是不同

宿主(环境)解释

JS的运行环境通常由宿主环境和执行期环境共同构成,宿主环境是由外壳程序(如web浏览器就是一个外壳程序)生成,执行期环境是由嵌入到外壳程序中的JS引擎(/JS解释器)生成的,在执行期环境JS能够生成内置静态对象、初始化执行环境等。node

对于JavaScript,宿主环境最多见的是web浏览器,浏览器提供了一个JavaScript运行的环境,这个环境里面,须要提供一些接口,好让JavaScript引擎可以和宿主环境对接。JavaScript引擎才是真正执行JavaScript代码的地方,常见的引擎有V8(目前最快JavaScript引擎、Google生产)、JavaScript coregit

可是环境不是惟一的,也就是JavaScript不只仅可以在浏览器里面跑,也能在其余提供了宿主环境的程序里面跑,最多见的就是nodejs。一样做为一个宿主环境,nodejs也有本身的JavaScript引擎--V8。根据官方的定义: Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applicationsgithub

3.全局中的this

1.浏览器环境中
  • 在浏览器里,在全局范围内,this等价于window对象。用var声明一个变量和给this或者window添加属性是等价的
<script>
   console.log(this === window) //true
   
   var a = 3;
   console.log(this.a, window.a)//3  3
</script>
复制代码

说明:在浏览器中,window对象同时也是全局对象web

1.node环境中
  • 在node环境里,若是使用REPL(Read-Eval-Print Loop,简称REPL:读取-求值-输出,是一个简单的,交互式的编程环境)来执行程序,this并非最高级的命名空间,最高级的是global.
> this === global
true
复制代码
  • 但在node环境里,若是执行一个js脚本,在全局范围内,this以一个空对象开始做为最高级的命名空间,这个时候,它和global不是等价的。
index.js 文件在node环境中执行

console.log(this) //Object {}
console.log(this === global); //false
复制代码
  • 在node环境里,在全局范围内,若是你用REPL执行一个脚本文件,用var声明一个变量并不会和在浏览器里面同样将这个变量添加给this。
index.js 文件在node环境中执行

var foo = "bar";
console.log(this.foo);//undefined
复制代码
  • 可是若是你不是用REPL执行脚本文件,而是直接执行代码,结果和在浏览器里面是同样的(神坑)
> var foo = "bar";
 > this.foo
 bar
 > global.foo
 bar
复制代码
  • 在node环境里,用REPL运行脚本文件的时候,若是在声明变量的时候没有使用var或者let,这个变量会自动添加到global对象,可是不会自动添加给this对象。若是是直接执行代码,则会同时添加给global和this
index.js 文件在node环境中执行

 foo = "bar";
 console.log(this.foo);//undefined
 console.log(global.foo);//bar
复制代码

上面的几种种状况可能你们已经绕晕了,总结起来就是:在浏览器里面this是老大,它等价于window对象,若是你声明一些全局变量(无论在任何地方),这些变量都会做为this的属性。在node里面,有两种执行JavaScript代码的方式,一种是直接执行写好的JavaScript文件,另一种是直接在里面执行一行行代码。对于直接运行一行行JavaScript代码的方式,global才是老大,this和它是等价的。在这种状况下,和浏览器比较类似,也就是声明一些全局变量会自动添加给老大global,顺带也会添加给this。可是在node里面直接脚本文件就不同了,你声明的全局变量不会自动添加到this,可是会添加到global对象。因此相同点是,在全局范围内,全局变量终究是属于老大的。编程

4.函数(function)中的this

说明:在函数内部,this的值取决于函数被调用的方式浏览器

  • 不管是在浏览器环境仍是node环境,除了在DOM事件处理程序里或者给出了thisArg(接下来会讲到)外,若是不是用new调用,在函数里面使用this都是指代全局范围的this。
<script>
testa()
function testa(){
     testb()
    function testb(){
        console.log(this === window)//true
      }
 }
</script>

复制代码
index.js 文件在node环境中执行

foo = "bar";

function testThis () {
  this.foo = "foo";
}

console.log(global.foo);//bar
testThis();
console.log(global.foo);//foo

复制代码

说明:由于上面代码不在严格模式下,且this的值不是由该调用设置的,因此this的值默认指向全局对象。闭包

  • 除非你使用严格模式,这时候this就会变成undefined。
<script type="text/javascript">
      foo = "bar";
  
     function testThis() {
        "use strict";
        this.foo = "foo";
     }
  
      console.log(this.foo); //logs "bar"
     testThis();  //Uncaught TypeError: Cannot set property 'foo' of undefined 
 </script>
复制代码

说明:然而,在严格模式下,this将保持他进入执行环境时的值,因此下面的this将会默认为undefined,因此,在严格模式下,若是 this 没有被执行环境(execution context)定义,那它将保持为 undefined。app

  • 若是你在调用函数的时候在前面使用了new,this就会变成一个新的值,和global的this脱离干系。
<script type="text/javascript">
      foo = "bar";
  
      function testThis() {
        this.foo = "foo";
      }
  
      console.log(this.foo); //logs "bar"
      new testThis();
     console.log(this.foo); //logs "bar"
 
     console.log(new testThis().foo); //logs "foo"
</script>
复制代码

函数里面的this其实相对比较好理解,若是咱们在一个函数里面使用this,须要注意的就是咱们调用函数的方式,若是是正常的方式调用函数,this指代全局的this,若是咱们加一个new,这个函数就变成了一个构造函数,咱们就建立了一个实例,this指代这个实例,这个和其余面向对象的语言很像。另外,写JavaScript很常作的一件事就是绑定事件处理程序,也就是诸如button.addEventListener(‘click’, fn, false)之类的,若是在fn里面须要使用this,this指代事件处理程序对应的对象,也就是button。

  • 若是想把this的值从一个环境传到另外一个环境,就要用到call或者apply的方法。
<script type="text/javascript">
// 将一个对象做为call和apply的第一个参数,this会被绑定到这个对象。
var obj = {a: 'Custom'};

// 这个属性是在global对象定义的。
var a = 'Global';

function whatsThis(arg) {
  return this.a;  // this的值取决于函数的调用方式
}

whatsThis();          // 'Global'
whatsThis.call(obj);  // 'Custom'
whatsThis.apply(obj); // 'Custom'
</script>
复制代码

说明:使用 call 和 apply 函数的时候要注意,若是传递给 this 的值不是一个对象,JavaScript 会尝试使用内部 ToObject 操做将其转换为对象。所以,若是传递的值是一个原始值好比 7 或 'foo',那么就会使用相关构造函数将它转换为对象,因此原始值 7 会被转换为对象,像 new Number(7) 这样,而字符串 'foo' 转化成 new String('foo') 这样,例如:下面代码

<script type="text/javascript">
function bar() {
  console.log(Object.prototype.toString.call(this));
}

//原始值 7 被隐式转换为对象
bar.call(7); // [object Number]
</script>
复制代码
  • ECMAScript 5 引入了 Function.prototype.bind。调用f.bind(someObject)会建立一个与f具备相同函数体和做用域的函数,可是在这个新函数中,this将永久地被绑定到了bind的第一个参数,不管这个函数是如何被调用的。
<script type="text/javascript">
function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // azerty

var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
</script>
复制代码
  • ECMAScript 5 引入了 Function.prototype.bind。调用f.bind(someObject)会建立一个与f具备相同函数体和做用域的函数,可是在这个新函数中,this将永久地被绑定到了bind的第一个参数,不管这个函数是如何被调用的。
<script type="text/javascript">
function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // azerty

var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
</script>
复制代码
  • 在箭头函数中,this与封闭词法环境的this保持一致。在全局代码中,它将被设置为全局对象:
<script type="text/javascript">
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
</script>
复制代码

5.原型(prototype)中的this

  • 你建立的每个函数都是函数对象,他们会自动获取一个特殊的属性prototype,你能够给这个属性赋值。当你用new的方式调用一个函数的时候,你就能经过this访问你给prototype赋的值了。
<script type="text/javascript">
function Thing() {
       console.log(this.foo);
 }
 
 Thing.prototype.foo = "bar";
 
 var thing = new Thing(); //logs "bar"
 console.log(thing.foo);  //logs "bar"
</script>
复制代码
  • 当你使用new为你的函数建立多个实例的时候,这些实例会共享你给prototype设定的值。对于下面的例子,当你调用this.foo的时候,都会返回相同的值,除非你在某个实例里面重写了本身的this.foo
<script type="text/javascript">
 function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
  Thing.prototype.setFoo = function (newFoo) {
      this.foo = newFoo;
  }
 
 var thing1 = new Thing();
 var thing2 = new Thing();
 
 thing1.logFoo(); //logs "bar"
 thing2.logFoo(); //logs "bar"
 
 thing1.setFoo("foo");
 thing1.logFoo(); //logs "foo";
 thing2.logFoo(); //logs "bar";
 
 thing2.foo = "foobar";
 thing1.logFoo(); //logs "foo";
 thing2.logFoo(); //logs "foobar";
</script>
复制代码
  • 实例里面的this是一个特殊的对象。你能够把this想成一种获取prototype的值的一种方式。当你在一个实例里面直接给this添加属性的时候,会隐藏prototype中与之同名的属性。若是你想访问prototype中的这个属性值而不是你本身设定的属性值,你能够经过在实例里面删除你本身添加的属性的方式来实现。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
  Thing.prototype.setFoo = function (newFoo) {
      this.foo = newFoo;
  }
 Thing.prototype.deleteFoo = function () {
     delete this.foo;
 }
 var thing = new Thing();
 thing.setFoo("foo");
 thing.logFoo(); //logs "foo";
 thing.deleteFoo();
 thing.logFoo(); //logs "bar";
 thing.foo = "foobar";
 thing.logFoo(); //logs "foobar";
 delete thing.foo;
 thing.logFoo(); //logs "bar";
</script>
复制代码
  • 或者你也能直接经过引用函数对象的prototype 来得到你须要的值。
<script type="text/javascript">
function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      console.log(this.foo, Thing.prototype.foo);
  }
  
  var thing = new Thing();
  thing.foo = "foo";
  thing.logFoo(); //logs "foo bar";
</script>
复制代码

此时的this是指,构造函数的原型上的方法至于为何看下面

  • 经过一个函数建立的实例会共享这个函数的prototype属性的值,若是你给这个函数的prototype赋值一个Array,那么全部的实例都会共享这个Array,除非你在实例里面重写了这个Array,这种状况下,函数的prototype的Array就会被隐藏掉。
<script type="text/javascript">
 function Thing() {
 }
 Thing.prototype.things = [];
 
 
 var thing1 = new Thing();
 var thing2 = new Thing();
 thing1.things.push("foo");
 console.log(thing2.things); //logs ["foo"]
</script>
复制代码
  • 实际上你能够经过把多个函数的prototype连接起来的从而造成一个原型链,所以this就会魔法般地沿着这条原型链往上查找直到找你你须要引用的值。
<script type="text/javascript">
  function Thing1() {
  }
  Thing1.prototype.foo = "bar";
  
  function Thing2() {
  }
  Thing2.prototype = new Thing1();
  
  
 var thing = new Thing2();
 console.log(thing.foo); //logs "bar"
</script>
复制代码
  • 一些人利用原型链的特性来在JavaScript模仿经典的面向对象的继承方式。任何给用于构建原型链的函数的this的赋值的语句都会隐藏原型链上游的相同的属性。
<script type="text/javascript">
  function Thing1() {
  }
  Thing1.prototype.foo = "bar";
  
  function Thing2() {
      this.foo = "foo";
  }
  Thing2.prototype = new Thing1();
  
 function Thing3() {
 }
 Thing3.prototype = new Thing2();
 
 var thing = new Thing3();
 console.log(thing.foo); //logs "foo"
</script>
复制代码
  • 我喜欢把被赋值给prototype的函数叫作方法。在上面的例子中,我已经使用过方法了,如logFoo。这些方法有着相同的prototype,即建立这些实力的原始函数。我一般把这些原始函数叫作构造函数。在prototype里面定义的方法里面使用this会影响到当前实例的原型链的上游的this。这意味着你直接给this赋值的时候,隐藏了原型链上游的相同的属性值。这个实例的任何方法都会使用这个最新的值而不是原型里面定义的这个相同的值。
<script type="text/javascript">
  function Thing1() {
  }
  Thing1.prototype.foo = "bar";
  Thing1.prototype.logFoo = function () {
      console.log(this.foo);
  }
  
  function Thing2() {
      this.foo = "foo";
 }
 Thing2.prototype = new Thing1();
 
 
 var thing = new Thing2();
 thing.logFoo(); //logs "foo";
</script>
复制代码
  • 在JavaScript里面你能够嵌套函数,也就是你能够在函数里面定义函数。嵌套函数能够经过闭包捕获父函数的变量,可是这个函数没有继承this
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      var info = "attempting to log this.foo:";
      function doIt() {
          console.log(info, this.foo);
      }
      doIt();
 }
  
 var thing = new Thing();
 thing.logFoo();  //logs "attempting to log this.foo: undefined"
</script>
复制代码

在doIt里面的this是global对象或者在严格模式下面是undefined。这是形成不少不熟悉JavaScript的人深陷 this陷阱的根源。在这种状况下事情变得很是糟糕,就像你把一个实例的方法看成一个值,把这个值看成函数参数传递给另一个函数可是却不把这个实例传递给这个函数同样。在这种状况下,一个方法里面的环境变成了全局范围,或者在严格模式下面的undefined。

<script type="text/javascript">
function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {  
      console.log(this.foo);   
  }
  
  function doIt(method) {
      method();
 }
 
 
 var thing = new Thing();
 thing.logFoo(); //logs "bar"
 doIt(thing.logFoo); //logs undefined
</script>
复制代码
  • 一些人喜欢先把this捕获到一个变量里面,一般这个变量叫作self,来避免上面这种状况的发生
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      var self = this;
      var info = "attempting to log this.foo:";
      function doIt() {
          console.log(info, self.foo);
      }
     doIt();
 }
 
 
 var thing = new Thing();
 thing.logFoo();  //logs "attempting to log this.foo: bar"
</script>
复制代码
  • 可是当你须要把一个方法做为一个值传递给一个函数的时候并无论用。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () { 
      var self = this;
      function doIt() {
          console.log(self.foo);
      }
      doIt();
 }
 
 function doItIndirectly(method) {
     method();
 }
  
 var thing = new Thing();
 thing.logFoo(); //logs "bar"
 doItIndirectly(thing.logFoo); //logs undefined
</script>
复制代码
  • 你能够经过bind将实例和方法一切传递给函数来解决这个问题,bind是一个函数定义在全部函数和方法的函数对象上面
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () { 
      console.log(this.foo);
  }
  
  function doIt(method) {
      method();
 }
  
 var thing = new Thing();
 doIt(thing.logFoo.bind(thing)); //logs bar
</script>
复制代码
  • 你一样可使用apply和call来在新的上下文中调用方法或函数。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () { 
      function doIt() {
          console.log(this.foo);
      }
     doIt.apply(this);
  }
 
 function doItIndirectly(method) {
     method();
 }
 
 
 var thing = new Thing();
 doItIndirectly(thing.logFoo.bind(thing)); //logs bar
</script>
复制代码
  • 你能够用bind来代替任何一个函数或者方法的this,即使它没有赋值给实例的初始prototype。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
   
  function logFoo(aStr) {
      console.log(aStr, this.foo);
  }
   
 var thing = new Thing();
 logFoo.bind(thing)("using bind"); //logs "using bind bar"
 logFoo.apply(thing, ["using apply"]); //logs "using apply bar"
 logFoo.call(thing, "using call"); //logs "using call bar"
 logFoo("using nothing"); //logs "using nothing undefined"
</script>
复制代码
  • 你应该避免在构造函数里面返回任何东西,由于这可能代替原本应该返回的实例。
<script type="text/javascript">
  function Thing() {
      return {};
  }
  Thing.prototype.foo = "bar";
  
  
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
  
 var thing = new Thing();
 thing.logFoo(); //Uncaught TypeError: undefined is not a function
</script>
复制代码

怪的是,若是你在构造函数里面返回了一个原始值,上面所述的状况并不会发生而且返回语句被忽略了。最好不要在你将经过new调用的构造函数里面返回任何类型的数据,即使你知道本身正在作什么。若是你想建立一个工厂模式,经过一个函数来建立一个实例,这个时候不要使用new来调用函数。固然这个建议是可选的。

  • 你能够经过使用Object.create来避免使用new,这样一样可以建立一个实例。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
   
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
   
 var thing =  Object.create(Thing.prototype);
 thing.logFoo(); //logs "bar"
</script>
复制代码
  • 在这种状况下并不会调用构造函数
<script type="text/javascript">
  function Thing() {
      this.foo = "foo";
  }
  Thing.prototype.foo = "bar";
  
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
  
 var thing =  Object.create(Thing.prototype);
 thing.logFoo(); //logs "bar"
</script>
复制代码
  • 由于Object.create不会调用构造函数的特性在你继承模式下你想经过原型链重写构造函数的时候很是有用。
<script type="text/javascript">
  function Thing1() {
      this.foo = "foo";
  }
  Thing1.prototype.foo = "bar";
  
  function Thing2() {
      this.logFoo(); //logs "bar"
      Thing1.apply(this);
      this.logFoo(); //logs "foo"
 }
 Thing2.prototype = Object.create(Thing1.prototype);
 Thing2.prototype.logFoo = function () {
     console.log(this.foo);
 }
 
 var thing = new Thing2();
</script>
复制代码

6.对象(object )中的this

  • 在一个对象的一个函数里,你能够经过this来引用这个对象的其余属性。这个用new来新建一个实例是不同的。
<script type="text/javascript">
 var obj = {
     foo: "bar",
     logFoo: function () {
         console.log(this.foo);
     }
 }; 
 obj.logFoo(); //logs "bar"
</script>
复制代码
  • 注意,没有使用new,没有使用Object.create,也没有使用函数调用建立一个对象。你也能够将对象看成一个实例将函数绑定到上面。
<script type="text/javascript">
 var obj = {
     foo: "bar"
 };
 
 function logFoo() {
     console.log(this.foo);
 }
 
 logFoo.apply(obj); //logs "bar"
</script>
复制代码
  • 当你用这种方式使用this的时候,并不会越出当前的对象。只有有相同直接父元素的属性才能经过this共享变量
<script type="text/javascript">
  var obj = {
      foo: "bar",
      deeper: {
          logFoo: function () {
              console.log(this.foo);
          }
      }
  };
  
 obj.deeper.logFoo(); //logs undefined
</script>
复制代码
  • 你能够直接经过对象引用你须要的属性
<script type="text/javascript">
	var obj = {
	    foo: "bar",
	    deeper: {
	        logFoo: function () {
	            console.log(obj.foo);
	        }
	    }
	};
	
	obj.deeper.logFoo(); //logs "bar"
</script>
复制代码

7.DOM(event)中的this

  • 在一个HTML DOM事件处理程序里面,this始终指向这个处理程序被所绑定到的HTML DOM节点
<script type="text/javascript">
  function Listener() {
      document.getElementById("foo").addEventListener("click",
         this.handleClick);
  }
  Listener.prototype.handleClick = function (event) {
      console.log(this); //logs "<div id="foo"></div>"
  }
  
  var listener = new Listener();
 document.getElementById("foo").click();
</script>
复制代码
  • 除非你本身经过bind切换了上下文
<script type="text/javascript">
  function Listener() {
      document.getElementById("foo").addEventListener("click", 
          this.handleClick.bind(this));
  }
  Listener.prototype.handleClick = function (event) {
      console.log(this); //logs Listener {handleClick: function}
  }
  
  var listener = new Listener();
 document.getElementById("foo").click();
</script>
复制代码

8.HTML 中的this

  • 在HTML节点的属性里面,你能够放置JavaScript代码,this指向了这个元素
<div id="foo" onclick="console.log(this);"></div>
 <script type="text/javascript">
 document.getElementById("foo").click(); //logs <div id="foo"...
 </script>
复制代码

到此结束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

做者

做者: weshmily科技站长

官网: 百度搜索(weshmily科技)

CSDN博客:blog.csdn.net/qq_27118895

GitHub: github.com/weshmily

公众号:搜索"weshmilyqd"

相关文章
相关标签/搜索