Javascript中神奇的this

Javascript 当中的 this 与其他语言是完全不同的机制,很有可能会让一些编写其他语言的工程师迷惑。

1. 误以为 this 指向函数自身

根据 this 的英语语法,很容易将函数中出现的 this 理解为函数自身。在 javascript 当中函数作为一等公民,确实可以在调用的时候将属性值存储起来。但是如果使用方法不对,就会发生与实际预期不一致的情况。具体情况,请看下面代码

  function fn(num){
    this.count++;
  }

  fn.count = 0;

  for(var i=0;i<3;i++){
    fn(i);
  }
  console.log(fn.count); // 0

如果 fn 函数里面的 this 指向自身函数,那么 count 属性的属性值就应该产生变化,但实际上却是纹丝不动。对于这个问题,有些人会利用作用域来解决,比如这么写

  var data = {
    count:0
  };

  function fn(num){
    data.count++;
  }

  for(var i=0;i<3;i++){
    fn(i);
  }

  console.log(data.count);  //3

又或者更直接的这么写

  function fn(num){
    fn.count++;
  }

  fn.count = 0;

  for(var i=0;i<3;i++){
    fn(i);
  }

  console.log(fn.count);//3

虽然这两种方式都输出了正确的结果,但是却避开了 this 到底绑定在哪里的问题。如果对一个事物的工作原理不清晰,就往往会产生头痛治头,脚痛治脚的问题,从而导致代码变得的丑陋,而且维护性也会变得很差。

2. this神奇的绑定规则

2.1 默认绑定规则

第一种是最常见的 this 的绑定,看一下下面的代码

  function fn(){
    console.log(window === this); //浏览器环境
  }
  fn(); //true

函数fn 是直接在全局作用域下调用的,没有带其他任何修饰,这种情况下,函数调用的时候使用了 this 的默认绑定,指向了全局对象。

这样就清楚了第一个例子中的 this 指向, fn 函数中的 this 指向了全局变量,所以 this.count++ 相当于 window.count++(浏览器环境下),当然不会对 fn 函数的count属性产生影响。

有一点要说明的是,上面种情况只能在非严格模式(strict mode)下才能发生,在严格模式下,会将 this 默认绑定为 undefined。以避免全局变量的污染。

2.2 隐式绑定规则

如果函数在以对象为上下文进行调用,那么 this 的绑定就会产生变化。this 会绑定到调用这个函数的对象,查看下面代码:

  var obj = {
    a:1,
    fn:function(){
      console.log(this.a);
    }
  }

  obj.fn(); //1

即使函数声明不在对象当中,this 指向仍会产生变化

  function fn(){
    console.log(this.a);
  }
  var obj = {
    a:1,
    fn:fn
  }
  obj.fn(); //1

由此可见,this 的绑定,不与函数定义的位置有关,而是与调用者和调用方式有关。

在隐式的绑定规则下,有一些特殊的地方,需要注意。

2.2.1 多层对象调用 this 的指向

  function fn(){
    console.log(this.a);
  }
  var obj = {
    a:1,
    obj2:obj2
  }
  var obj2 = {
    a:2,
    obj3:obj3
  }
  var obj3 = {
    a:3,
    fn:fn
  }

  obj.obj2.obj3.fn(); //3

在多层对象引用下,this 指向的是调用的函数的那个对象。

2.2.2 隐式赋值可能存在丢失现象

查看下面代码

  function fn(){
    console.log(this);
  }
  var  obj = {
    fn:fn
  }

  var fun = obj.fn;
  fun(); //window

虽然 fn 引用了 obj.fun ,但是函数的调用方式,仍是不带任何修饰的,所以 this 还是绑定在了 window 上。
还有一种情况,容易让大家忽略,那就是传参的时候,其实会进行隐式赋值。

 function fn(){
    console.log(this);
  }

  function doFn(fn){
    fn();
  }

  var obj = {
    fn:fn
  }

  doFn(obj.fn); //window

隐式绑定 this 不是一种很推荐的方式,因为很有可能就发生丢失的情况,如果业务当中对 this 的绑定有要求,建议还是使用显示绑定的方式。

2.3 显式绑定规则

显示绑定就是利用函数原型上的 apply 与 call 方法来对 this 进行绑定。用法就是把想要绑定的对象作为第一个参数传进去。

  function fn(){
    console.log(this);
  }

  var obj = {};

  fn.call(obj); //{}

有些时候会想将函数的 this 绑定在某个对象上,但是不需要立即调用,这样的话,直接利用 call 或者 apply 是无法做的。

  function fn(){
    console.log(this);
  }

  function bind(fn){
    fn();
  }

  var obj = {
    fn:fn
  }

  bind.call(obj,fn); //window

上面这个例子,看似好像可以,但实际上是 bind 函数的 this 绑定到了 obj 这个对象,但是 fn 仍然是没有任何修饰的调用,所以 fn 仍然是默认的绑定方式。

  function fn(){
    console.log(this);
  }

  function bind(fn,obj){
    return function(){
      fn.apply(obj,arguments);
    }
  }

  var obj = {
    fn:fn
  }

  var fun = bind(fn,obj);
  fun(); //obj

这样调用,就可以将灵活多变的 this ,牢牢的控制住了,因为 fn 的调用方式为 apply 调用。所以,this 就被绑定在传入的 obj 对象上,在 ES5 当中,函数的原型方法上多了一个 bind。效果与上面的函数基本一致,具体用法限于篇幅就不多说了。

2.4 new 绑定

new 是一个被很多人误解的一个关键字,但实际上 javascript 的 new 与传统面向对象的语言完全不同。
个人把 new 理解为一种特殊的函数调用,当使用 new 关键字来调用函数的时候,会执行下面操作,

  • 创建一个全新的对象
  • 将空对象的 __proto__ 指向构造函数的 prototype
  • 将新对象的 this 绑定到调用的函数上
  • 如果函数返回值为基本类型或者为 this又或者不返回任何值,那么将会返回这个创建的新对象,如果返回了一个对象,那么则会返回这个对象,而不会返回创建的新对象。
  function fn(a){
    this.a = a;
  }
  fn.prototype.hi = function(){
    console.log('hi')
  }

  var obj = new fn(2);

  console.log(obj);

  function fn(a){
    this.a = a;
    return {};
  }

  var obj = new fn(2);

  console.log(obj); //{}

2.5 特殊的传参

null 和 undefined 也是可以作为 this 的绑定对象的,但是实际上应用的是默认的绑定。
但是这种传参的实际效用是什么呢?
常见的用法是将一个数组展开,作为参数传入参数。比如

  function fn(a,b){
    console.log('a:',a,'b:',b);
  }

  fn.apply(null,[1,2]); // a: 1 b: 2

但是这种用法会有一个坑,那就是如果函数存在了 this ,那么就会应用默认的绑定规则,将 this 绑定在全局对象上,发生于预期不一致的情况。为了代码更加稳健,可以使创建一个比空对象更空的对象。

var obj = Object.create(null);
console.log(obj.__proto__); //undefined

var obj2 = {}
console.log(obj2.__proto__); //Object {}

Object原型上有一个 create 方法,这个方法会创建一个对象,然后将对象的原型指向传入的参数,所以传入 null 的话,产生一个没有 prototype 的对象,所以会比空对象更加"空"。

所以传入这个对象,会比传入 null 更加安全。

var obj = Object.create(null);

fn.apply(obj,[1,2]);

2.6 根据作用域来决定 this 的绑定

在 ES6 当中,出现了一个新的函数类型,箭头函数。

如果使用箭头函数,那么就不会使用上面提到的四种 this 绑定方式,而是根据作用域来决定

比较常见的是用于事件函数和定时器的情况。

下面是比较常见的传统 this 写法

  function fn(){
    var _this = this;
    setTimeout(function(){
      console.log(_this.a);
    },100)
  }

  var obj = {
    a:2
  }

  fn.call(obj); //2

如果使用箭头函数则可以这么写

  function fn(){
    setTimeout(()=>{
      //this 来源于 fn 函数的作用域
      console.log(this.a);
    },100)
  }

  var obj = {
    a:2
  }

  fn.call(obj); //2

2.7 事件函数当中 this 的绑定机制

如果是在事件函数当中,this 的绑定是指向触发事件的 DOM 元素的,

$('body')[0].addEventListener('click',function(){
  console.log(this);
},false);

点击 body 元素之后,控制台则会显示 body 元素

3. 小结

如果想判断一个函数的 this 绑定在哪里,首先是找到函数的调用位置,之后是按照规则来判断。

  • 如果函数调用时没有任何修饰条件,那么在严格模式下则会绑定到 undefined ,非严格模式下会绑定到全局。
  • 如果是用对象做上下文,来对函数进行调用,那么则会绑定到调用的这个对象上。
  • 如果是用 call 或者 apply 方法来进行调用的,则会绑定到第一个传入参数上。
  • 如果是使用 new 关键字来调用函数的,则会绑定到新创建的那个对象上.
  • 如果是在事件函数内,则会绑定到触发事件的那个DOM元素上。

以上就是关于Javascript中神奇的this的相关介绍,希望对大家的学习有所帮助。

(0)

相关推荐

  • js中的this关键字详解

    this是Javascript语言的一个关键字. 它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.比如, 复制代码 代码如下: function test(){ this.x = 1; } 随着函数使用场合的不同,this的值会发生变化.但是有一个总的原则,那就是this指的是,调用函数的那个对象. 下面分四种情况,详细讨论this的用法. 情况一:纯粹的函数调用 这是函数的最通常用法,属于全局性调用,因此this就代表全局对象Global. 请看下面这段代码,它的运行结果是1.

  • Javascript this关键字使用分析

    关于js中的this关键字的文章已经不少了,我看过几篇,我写这篇文章的目的是从实例中分析出this的工作原理,希望对大家有所帮助. 一.基本的: 复制代码 代码如下: function doSomething(){ alert(this.id); } alert(window.doSomething);//证明了doSomething是属于window的 doSomething();//undefined window.onload = function(){ document.getEleme

  • 深入理解Javascript中this的作用域

    大家在使用Javascript的时候经常被this这个家伙搞得晕头转向的.对大多数有OOP开发经验的开发人员来说this是当前作用域中引用普通元素的标识符,但是在Javascript中它却显得古灵精怪的,因为它不是固定不变的,而是随着它的执行环境的改变而改变.在Javascript中this总是指向调用它所在方法的对象. 举一个简单的例子: 复制代码 代码如下: function test(){ alert(this); } var obj=function(){ var name='testO

  • javascript中onclick(this)用法介绍

    this指触发事件的对象 复制代码 代码如下: <input id="myinput" type="text" value="javascript中onclick中的this" onclick="javascript:test(this);"/> 复制代码 代码如下: function test(obj){ alert(obj); //[object HTMLInputElement] alert(obj.id);

  • javascript this用法小结

    this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在javascript中,由于 javascript的动态性(解释执行,当然也有简单的预编译过程),this的指向在运行时才确定.这个特性在给我们带来迷惑的同时也带来了编程上的 自由和灵活,结合apply(call)方法,可以使JS变得异常强大.2.变化的this 在JavaScript中,this通常指向的是我们正在执行的函数本身,或者是指向该函数所属的对象(运行时).当我们在页面中定义

  • JS中的this变量的使用介绍

    JavaScript中this的使用 在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程序时能够游刃有余. 对于this变量最要的是能够理清this所引用的对象到底是哪一个,也许很多资料上都有自己的解释,但有些概念讲的偏繁杂.而我的理解是:首先分析this所在的函数是当做哪个对象的方法调用的,则该对象就是this所引用的对象. 示例一. 复制代码 代码如下: var obj = {};

  • js报错 Object doesn't support this property or method的原因分析

    js报错: Object doesn't support this property or method 问题原因: 1:js代码用了javascript关键字 2:方法名和表单或者div form名冲突 3.button中有name="submit" 4.上传图片时提示:Object doesn't support this property or method 原因:服务器上安装的AspJpeg的版本低于1.4版. 解决方法:安装1.4或以上版本的AspJpeg组件. 我也遇到这个

  • Javascript this指针

    前言 Javascript是一门基于对象的动态语言,也就是说,所有东西都是对象,一个很典型的例子就是函数也被视为普通的对象.Javascript 可以通过一定的设计模式来实现面向对象的编程,其中this "指针"就是实现面向对象的一个很重要的特性.但是this也是Javascript中一个非常容易理解错,进而用错的特性.特别是对于接触静态语言比较久了的同志来说更是如此. 示例说明 我们先来看一个最简单的示例: <script type="text/javascript&

  • Javascript中神奇的this

    Javascript 当中的 this 与其他语言是完全不同的机制,很有可能会让一些编写其他语言的工程师迷惑. 1. 误以为 this 指向函数自身 根据 this 的英语语法,很容易将函数中出现的 this 理解为函数自身.在 javascript 当中函数作为一等公民,确实可以在调用的时候将属性值存储起来.但是如果使用方法不对,就会发生与实际预期不一致的情况.具体情况,请看下面代码 function fn(num){ this.count++; } fn.count = 0; for(var

  • javascript中神奇的 Date对象小结

    Date 对象算是较常用的对象之一,但很多人完全不会操作,就算一些简单的操作也用 moment 而不自己尝试一下. 本次分享下 Date 中的 date 使用技巧,希望能给大家启发. MDN官网介绍 setDate() 方法根据本地时间来指定一个日期对象的天数. 如果 dayValue 超出了月份的合理范围,setDate 将会相应地更新 Date 对象. 例如,如果为 dayValue 指定0,那么日期就会被设置为上个月的最后一天. 获取月份天数 // 获取月份天数 function getM

  • JavaScript中神奇的call()方法

    先看看关于call()的官方解释,"调用一个对象的一个方法,以另一个对象替换当前对象.",看了这样的解释,或许让你更摸不着头脑了.看例子: 复制代码 代码如下: var x = "我是全局变量"; //定义全局变量x function a(){ //定义函数类结构a     this.x = "我是在函数类结构a中声明的哦";    } //定义普通函数,弹出当前指针所包含的变量x的值 function f(){           alert

  • javascript中加号(+)操作符的一些神奇作用

    javascript是一门神奇的语言,这没神奇的语言中有一个神奇的加操作符. 常用的加操作符我们可以用来做: 1.加法运算,例如:alert(1+2); ==>32.字符串连接,例如:alert("a"+"b");==>"ab" 高级一点的还有"+=",也是做以上两种操作的. 昨天在javascript丛林群里问了问题:怎么把"2000-09-11 19:22"这个日期格式字符串转换成毫秒数?

  • 深入理解javascript中的 “this”

    一.前言: 我们知道 "this" 是javascript语言的一个关键字,在编写javascript代码的时候,经常会见到或者用到它. 但是,有一部分开发朋友,对 "this" 一知半解,下面我们就一起来探讨学习下javascript中 "this" 的具体含义吧! 二.This总结: This指针作用域: 1).在全局执行环境中使用this,表示Global对象,在浏览器中就是window对象. 2).当在函数执行环境中使用this时,情况就

  • JavaScript中使用数组方法汇总

    定义数组 复制代码 代码如下: Var arryMap = {riskId:"<%=riskid%>",riskType:"<%=risktype%>"}; 或 复制代码 代码如下: Var arry =[]; 使用: var risk = arryMap.riskId; Arry.push({id:"1",name:"1"}); Arry.push({id:"2",name:&qu

  • Javascript中的数据类型之旅

    虽然Javascript是弱类型语言,但是,它也有自己的几种数据类型,分别是:Number.String.Boolean.Object.Udefined.Null.其中,Object属于复杂数据类型,Object   由无序的键值对组成.其余几种都属于简单数据类型.注意:变量类型首字母大写,而变量值首字母是小写的. JavaScript不支持自定义类型,所以JavaScript中的所有值都属于这六种类型之一. 根据ECMAScript 5.1的规范,javascript中共有六种数据类型,分别为

  • 深入学习JavaScript中的原型prototype

    javascript 是一种 prototype based programming 的语言, 而与我们通常的 class based programming 有很大 的区别,我列举重要的几点如下: 1.函数是first class object, 也就是说函数与对象具有相同的语言地位 2.没有类,只有对象 3.函数也是一种对象,所谓的函数对象 4.对象是按 引用 来传递的 那么这种 prototype based programming 的语言如何实现继承呢(OO的一大基本要素), 这也便是

  • JavaScript 中 avalon绑定属性总结

    avalon是前端MVVM框架,将所有前端代码彻底分成两部分,视图的处理通过绑定实现(angular有个更炫酷的名词叫指令),业务逻辑则集中在一个个叫VM的对象中处理.我们只要操作VM的数据,它就自然而然地神奇地同步到视图. $model(所有非$属性),$event(事件对象) 1.作用域圈定 ms-controller:按着就近原则自下而上扫描DOM树 ms-important:仅扫描本节点及之下作为扫描区 ms-skip:使绑定失效 2.ms-duplex双向绑定属性:除了绑定(VM同步数

  • 深入理解关于javascript中apply()和call()方法的区别

    如果没接触过动态语言,以编译型语言的思维方式去理解javaScript将会有种神奇而怪异的感觉,因为意识上往往不可能的事偏偏就发生了,甚至觉得不可理喻.如果在学JavaScript这自由而变幻无穷的语言过程中遇到这种感觉,那么就从现在形始,请放下的您的"偏见",因为这对您来说绝对是一片新大陆,让JavaScrip慢慢融化以前一套凝固的编程意识,注入新的生机! 好,言归正传,先理解JavaScrtipt动态变换运行时上下文特性,这种特性主要就体现在apply, call两个方法的运用上.

随机推荐