JavaScript逐点突破系列之this是什么

了解this

也许你在其他面向对象的编程语言曾经看过this,也知道它会指向某个构造器(constructor)所建立的对象。但事实上在JavaScript里面,this所代表的不仅仅是那个被建立的对象。

先来看看ECMAScript 标准规范对this 的定义:

「The this keyword evaluates to the value of the ThisBinding of the current execution context.」
「this 这个关键字代表的值为当前执行上下文的ThisBinding。」

然后再来看看MDN 对this 的定义:

「In most cases, the value of this is determined by how a function is called.」
「在大多数的情况下,this 其值取决于函数的调用方式。」

好,如果上面两行就看得懂的话那么就不用再往下看了,Congratulations!

… 我想应该不会,至少我光看这两行还是不懂。

先来看个例子吧:

var getGender = function() {
    return people1.gender;
};

var people1 = {
    gender: 'female',
    getGender: getGender
};

var people2 = {
    gender: 'male',
    getGender: getGender
};

console.log(people1.getGender());    // female
console.log(people2.getGender());    // female

what?怎么people2变性了呢,这不是我想要的结果啊,为什么呢?

因为getGender()返回(return)写死了people1.gender的关系,结果自然是'female'。

那么,如果我们把getGender稍改一下:

var getGender = function() {
    return this.gender;
};

这个时候,你应该会分别得到femalemale两种结果。

所以回到前面讲的重点,从这个例子可以看出,即便people1people2getGender方法参照的都是同一个getGender function,但由于调用的对象不同,所以执行的结果也会不同

现在我们知道了第一个重点,**this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数的调用方式。**如何的区分this呢?

this到底是谁

看完上面的例子,还是有点似懂非懂吧?那接下来我们来看看不同的调用方式对 this 值的影响。

情况一:全局对象&调用普通函数

在全局环境中,this 指向全局对象,在浏览器中,它就是 window 对象。下面的示例中,无论是否是在严格模式下,this 都是指向全局对象。

var x = 1

console.log(this.x)               // 1
console.log(this.x === x)         // true
console.log(this === window)      // true

如果普通函数是在全局环境中被调用,在非严格模式下,普通函数中 this 也指向全局对象;如果是在严格模式下,this 将会是 undefined。ES5 为了使 JavaScript 运行在更有限制性的环境而添加了严格模式,严格模式为了消除安全隐患,禁止了 this 关键字指向全局对象。

var x = 1

function fn() {
    console.log(this);   // Window 全局对象
    console.log(this.x);  // 1
}

fn();     

使用严格模式后:

"use strict"     // 使用严格模式
var x = 1

function fn() {
    console.log(this);   // undefined
    console.log(this.x);  // 报错 "Cannot read property 'x' of undefined",因为此时 this 是 undefined
}

fn();  

情况二:作为对象方法的调用

我们知道,在对象里的值如果是原生值(primitive type;例如,字符串、数值、布尔值),我们会把这个新建立的东西称为「属性(property)」;如果对象里面的值是函数(function)的话,我们则会把这个新建立的东西称为「方法(method)」。

如果函数作为对象的一个方法时,并且作为对象的一个方法被调用时,函数中的this指向这个上一级对象

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

obj.fn()     

// obj.fn()结果打印出;
// Object {x: 2, fn: function}
// 2

var a = obj.fn
a()   

// a()结果打印出:
// Window 全局对象
// 1

在上面的例子中,直接运行 obj.fn() ,调用该函数的上一级对象是 obj,所以 this 指向 obj,得到 this.x 的值是 2;之后我们将 fn 方法首先赋值给变量 a,a 运行在全局环境中,所以此时 this 指向全局对象Window,得到 this.x 为 1。

我们再来看一个例子,如果函数被多个对象嵌套调用,this 会指向什么。

var x = 1
var obj = {
  x: 2,
  y: {
    x: 3,
    fn: function() {
      console.log(this);   // Object {x: 3, fn: function}
      console.log(this.x);   // 3
    }
  }
}

obj.y.fn();      

为什么结果不是 2 呢,因为在这种情况下记住一句话:this 始终会指向直接调用函数的上一级对象,即 y,上面例子实际执行的是下面的代码。

var y = {
  x: 3,
  fn: function() {
    console.log(this);   // Object {x: 3, fn: function}
    console.log(this.x);   // 3
  }
}

var x = 1
var obj = {
  x: 2,
  y: y
}

obj.y.fn();    

对象可以嵌套,函数也可以,如果函数嵌套,this 会有变化吗?我们通过下面代码来探讨一下。

var obj = {
    y: function() {
        console.log(this === obj);   // true
        console.log(this);   // Object {y: function}
        fn();

        function fn() {
            console.log(this === obj);   // false
            console.log(this);   // Window 全局对象
        }
    }
}

obj.y();  

在函数 y 中,this 指向了调用它的上一级对象 obj,这是没有问题的。但是在嵌套函数 fn 中,this 并不指向 obj。嵌套的函数不会从调用它的函数中继承 this,当嵌套函数作为函数调用时,其 this 值在非严格模式下指向全局对象,在严格模式是 undefined,所以上面例子实际执行的是下面的代码。

function fn() {
    console.log(this === obj);   // false
    console.log(this);   // Window 全局对象
}

var obj = {
    y: function() {
        console.log(this === obj);   // true
        console.log(this);   // Object {y: function}
        fn();
    }
}

obj.y();  

情况三:作为构造函数调用

我们可以使用 new 关键字,通过构造函数生成一个实例对象。此时,this 便指向这个新对象

var x = 1;

function Fn() {
   this.x = 2;
    console.log(this);  // Fn {x: 2}
}

var obj = new Fn();   // obj和Fn(..)调用中的this进行绑定
console.log(obj.x)   // 2

使用new来调用Fn(..)时,会构造一个新对象并把它(obj)绑定到Fn(..)调用中的this。还有值得一提的是,如果构造函数返回了非引用类型(string,number,boolean,null,undefined),this 仍然指向实例化的新对象。

var x = 1

function Fn() {
  this.x = 2

  return {
    x: 3
  }
}

var a = new Fn()

console.log(a.x)      // 3

因为Fn()返回(return)的是一个对象(引用类型),this 会指向这个return的对象。如果return的是一个非引用类型的值呢?

var x = 1

function Fn() {
  this.x = 2

  return 3
}

var a = new Fn()

console.log(a.x)      // 2

情况四:call 和 apply 方法调用

如果你想改变 this 的指向,可以使用 call 或 apply 方法。它们的第一个参数都是指定函数运行时其中的this指向。如果第一个参数不传(参数为空)或者传 null 、undefined,默认 this 指向全局对象(非严格模式)或 undefined(严格模式)。

var x = 1;

var obj = {
  x: 2
}

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

fn.call(obj)
// Object {x: 2}
// 2

fn.apply(obj)
// Object {x: 2}
// 2

fn.call()
// Window 全局对象
// 1

fn.apply(null)
// Window 全局对象
// 1

fn.call(undefined)
// Window 全局对象
// 1

使用 call 和 apply 时,如果给 this 传的不是对象,JavaScript 会使用相关构造函数将其转化为对象,比如传 number 类型,会进行new Number()操作,如传 string 类型,会进行new String()操作,如传 boolean 类型,会进行new Boolean()操作。

function fn() {
  console.log(Object.prototype.toString.call(this))
}

fn.call('love')      // [object String]
fn.apply(1)          // [object Number]
fn.call(true)          // [object Boolean]

call 和 apply 的区别在于,call 的第二个及后续参数是一个参数列表,apply 的第二个参数是数组。参数列表和参数数组都将作为函数的参数进行执行。

var x = 1

var obj = {
  x: 2
}

function Sum(y, z) {
  console.log(this.x + y + z)
}

Sum.call(obj, 3, 4)       // 9
Sum.apply(obj, [3, 4])    // 9

情况五:bind 方法调用

调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,新函数的 this 会永久的指向 bind 传入的第一个参数,无论这个函数是如何被调用的。

var x = 1

var obj1 = {
    x: 2
};
var obj2 = {
    x: 3
};

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

var a = fn.bind(obj1);
var b = a.bind(obj2);

fn();
// Window 全局对象
// 1

a();
// Object {x: 2}
// 2

b();
// Object {x: 2}
// 2

a.call(obj2);
// Object {x: 2}
// 2

在上面的例子中,虽然我们尝试给函数 a 重新指定 this 的指向,但是它依旧指向第一次 bind 传入的对象,即使是使用 call 或 apply 方法也不能改变这一事实,即永久的指向 bind 传入的第一次参数。

情况六:箭头函数中this指向

值得一提的是,从ES6 开始新增了箭头函数,先来看看MDN 上对箭头函数的说明

An arrow function expression has a shorter syntax than a function expression and does notbind its ownthis,arguments,super, ornew.target. Arrow functions are always anonymous. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

这里已经清楚了说明了,箭头函数没有自己的this绑定。箭头函数中使用的this,其实是直接包含它的那个函数或函数表达式中的this。在前面情况二中函数嵌套函数的例子中,被嵌套的函数不会继承上层函数的 this,如果使用箭头函数,会发生什么变化呢?

var obj = {
  y: function() {
        console.log(this === obj);   // true
        console.log(this);           // Object {y: function}

      var fn = () => {
          console.log(this === obj);   // true
          console.log(this);           // Object {y: function}
      }
      fn();
  }
}

obj.y() 

和普通函数不一样,箭头函数中的 this 指向了 obj,这是因为它从上一层的函数中继承了 this,你可以理解为箭头函数修正了 this 的指向。所以箭头函数的this不是调用的时候决定的,而是在定义的时候处在的对象就是它的this

换句话说,箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window

var obj = {
  y: () => {
        console.log(this === obj);   // false
        console.log(this);           // Window 全局对象 

      var fn = () => {
          console.log(this === obj);   // false
          console.log(this);           // Window 全局对象
      }
      fn();
  }
}

obj.y() 

上例中,虽然存在两个箭头函数,其实this取决于最外层的箭头函数,由于obj是个对象而非函数,所以this指向为Window全局对象。

同 bind 一样,箭头函数也很“顽固”,我们无法通过 call 和 apply 来改变 this 的指向,即传入的第一个参数被忽略

var x = 1
var obj = {
    x: 2
}

var a = () => {
    console.log(this.x)
    console.log(this)
}

a.call(obj)
// 1
// Window 全局对象

a.apply(obj)
// 1
// Window 全局对象

上面的文字描述过多可能有点干涩,那么就看以下的这张流程图吧,我觉得这个图总结的很好,图中的流程只针对于单个规则。

小结

本篇文章介绍了 this 指向的几种情况,不同的运行环境和调用方式都会对 this 产生影响。总的来说,函数 this 的指向取决于当前调用该函数的对象,也就是执行时的对象。在这一节中,你需要掌握:

  • this 指向全局对象的情况;
  • 严格模式和非严格模式下 this 的区别;
  • 函数作为对象的方法调用时 this 指向的几种情况;
  • 作为构造函数时 this 的指向,以及是否 return 的区别;
  • 使用 call 和 apply 改变调用函数的对象;
  • bind 创建的函数中 this 的指向;
  • 箭头函数中的 this 指向。

到此这篇关于JavaScript逐点突破系列之this是什么的文章就介绍到这了,更多相关JavaScript this内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 7道关于JS this的面试题,你能答对几个

    前言 在 JavaScript 中,this 是函数调用上下文.正是由于 this 的行为很复杂,所以在 JavaScript 面试中,总是会问到有关  this 的问题. 做好的准备面试的方法是练习,所以本文针对 this 关键字整理了 7 个有趣的面试. 注意:下面的 JavaScript 代码以非严格模式运行. 1:变量与属性 以下代码输出什么: const object = { message: 'Hello, World!', getMessage() { const message

  • JavaScript函数中this指向问题详解

    this关键字 哪个对象调用函数,函数里面的this指向哪个对象. **严格模式下:**在全局环境中,this指向的是undefined **非严格模式下:**在全局环境中,this指向的是window 全局定义的函数直接调用,this => window function fn(){ console.log(this); // 此时 this 指向 window } fn(); // 相当于 window.fn() 对象内部的函数调用,this => 调用者 var obj = { fn:f

  • JavaScript 中this指向问题案例详解

    总结 全局环境 ➡️ window 普通函数 ➡️ window 或 undefined 构造函数 ➡️ 构造出来的实例 箭头函数 ➡️ 定义时外层作用域中的 this 对象的方法 ➡️ 该对象 call().apply().bind() ➡️ 第一个参数 全局环境 无论是否在严格模式下,this 均指向 window 对象. console.log(this === window) // true // 严格模式 'use strict' console.log(this === window

  • JavaScript中的this指向问题详解

    前言 相信我,只要记住本文的 7️⃣ 步口诀,就能彻底掌握 JS 中的 this 指向. 先念口诀:箭头函数.new.bind.apply 和 call.欧比届点(obj.).直接调用.不在函数里. 按照口诀的顺序,只要满足前面某个场景,就可以确定 this 指向了. 接下来按照口诀顺序对它们进行详解,文中示例代码都运行在 Chrome 的 Console 控制台中. 文末有精心准备的练习题,用于检验学习成果,别忘了试试~ 1. 箭头函数 箭头函数排在第一个是因为它的 this 不会被改变,所以

  • Javascript中函数分类&this指向的实例详解

    JS中定义函数的三种方式 通过实例来说明吧 <script> //method1 function fn() { console.log('fn created '); } //method2 var fn2 = function () { console.log('fn2 created'); } //method3 var fn3 = new Function('test', 'console.log(test);'); fn3('fn3 test'); console.dir(fn3);

  • JavaScript逐点突破系列之this是什么

    了解this 也许你在其他面向对象的编程语言曾经看过this,也知道它会指向某个构造器(constructor)所建立的对象.但事实上在JavaScript里面,this所代表的不仅仅是那个被建立的对象. 先来看看ECMAScript 标准规范对this 的定义: 「The this keyword evaluates to the value of the ThisBinding of the current execution context.」 「this 这个关键字代表的值为当前执行上下

  • Javascript拖拽&拖放系列文章3之细说事件对象第1/4页

    在阅读本文前,可以先阅读<Javascript拖拽&拖放系列文章2之offsetLeft.offsetTop.offsetWidth.offsetHeight属性>这篇文章,以理清上下文关系.好了,让我们开始进入正题. 模型相同的属性/方法 1 Button属性 Integer类型,可读可写.对于特定的鼠标事件,表示按下的鼠标按钮,它可以在拖拽的时候,判断是否是鼠标左键引发mousedown事件.它的所有取值及其意义(参考自<Javascript高级程序设计>)好了,让我们

  • JavaScript lodash常见用法系列小结

    lodash一开始是Underscore.js库的一个fork,因为和其他(Underscore.js的)贡献者意见相左.John-David Dalton的最初目标,是提供更多"一致的跨浏览器行为--,并改善性能".之后,该项目在现有成功的基础之上取得了更大的成果,并于一月份发布了3.0版本. 下面给大家说下javascript lodash知识,具体详情如下所示: 1 _.compact用法 _.compact([0, 1, false, 2, '', 3,'mm']); var

  • 谈谈我对JavaScript原型和闭包系列理解(随手笔记6)

    相关阅读:谈谈我对JavaScript原型和闭包系列理解(随手笔记8)   谈谈我对JavaScript原型和闭包系列理解(随手笔记9) 什么是闭包 闭包是什么?闭包是Closure,这是静态语言所不具有的一个新特性.但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是: • 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在. • 闭包就是就是函数的"堆栈"在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配 • 当在一个函数内定义另外

  • 谈谈我对JavaScript原型和闭包系列理解(随手笔记8)

    在上篇文章给大家介绍了<谈谈我对JavaScript原型和闭包系列理解(随手笔记6)>, 谈谈我对JavaScript原型和闭包系列理解(随手笔记9)    可以点击了解详情. 执行上下文栈 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境.当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境.处于活动状态的执行上下文环境只有一个. 压栈出栈过程----执行上下文栈: var a = 10, //1. 进入全局上下文环境 fn, ba

  • 谈谈我对JavaScript原型和闭包系列理解(随手笔记9)

    相关阅读:谈谈我对JavaScript原型和闭包系列理解(随手笔记6)   谈谈我对JavaScript原型和闭包系列理解(随手笔记8) 作用域 引用<JavaScript语言精粹和编程实践>上对作用域的定义: 变量作用域又叫变量的可见性.变量作用域完成对信息的隐蔽,也就是处理"割据"问题. js中是没有块级作用域的(ES6中有一个let,可以在{},if,for里面声明,同时作用域限定在块级.let声明的变量不存在变量提升!这里不谈论这个,因为我也是偶然看到.). 我们在

  • Javascript公共脚本库系列(一): 弹出层脚本

    一.摘要 本系列文章是为了抽象通用的,跨浏览器的脚本方法. 本篇文章讲解弹出浮动层的javascript函数, 以及函数的原理和使用注意事项. 二.实现效果 用脚本弹出浮动层是我们最常用的脚本方法之一.下面是效果图:  点击图中的"航空公司"后,会在"航空公司"下面弹出浮动层. 在网上弹出框的脚本相当多, 而且还有各种第三方JS框架可供我们使用.但是其中有的脚本过于简单,仅仅粗略的实现弹出效果而忽略了灵活性,通用性和跨浏览器特性. 使用JS框架又有些杀鸡用牛刀.所以

  • JavaScript中的isXX系列是否继续使用的分析

    复制代码 代码如下: isNull: function(a){ return a === null; }, isUndefined: function(a){ return a === undefined; }, isNumber: function(a){ return typeof a === 'number'; }, isString: function(a){ return typeof a === 'string'; }, isBoolean: function(a){ return

  • 别了 JavaScript中的isXX系列

    复制代码 代码如下: isNull: function(a){ return a === null; }, isUndefined: function(a){ return a === undefined; }, isNumber: function(a){ return typeof a === 'number'; }, isString: function(a){ return typeof a === 'string'; }, isBoolean: function(a){ return

  • 每天一篇javascript学习小结(Function对象)

    小编两天都没有更新文章了,小伙伴们是不是等着急了,今天开始再继续我们的<每天一篇javascript学习小结>系列文章,希望大家继续关注. 1.Function  函数调用(类似call方法) function callSomeFunction(someFunction, someArgument){ return someFunction(someArgument); } function add10(num){ return num + 10; } var result1 = callSo

随机推荐