JavaScript之深入理解this

定义

this是函数运行时自动生成的内部对象,即调用函数的那个对象。(不一定很准确的定义,但还算通俗易懂) 在大多数情况下,this的值由函数调用方式决定,它不能在执行期间赋值来设置,它在每次执行下可能都有不同的值。

全局执行环境(outside function)

在全局执行环境中,this一直指向全局对象(global object),不管是在严格模式还是在非严格模式中。

代码1

1console.log(this.document === document); //true 2 3// 在浏览器中,window对象也就是全局对象(global object) 4console.log(this === window); //true 5 6this.a = 37; 7console.log(window.a); //37

函数执行环境(inside function)

在函数执行环境中,this的值取决于函数的调用方式。

而函数的调用方式主要有4种:

  • 函数直接调用
  • 对象方法调用
  • 构造函数调用
  • call / apply / bind
  • 箭头函数(ES6)

函数直接调用

下面的代码在非严格模式执行时,this的值会指向全局对象;而在严格模式中,this的值将会默认为undefined。

代码2

1/* 非严格模式 */ 2function f1 () { 3 return this; 4} 5console.log(f1() === window); //true 6 7// in node; 8console.log(f1() === global); //true 9 10/* 严格模式 */ 11function f2 () { 12 'use strict' 13 return this; 14} 15console.log(f1() === undefined); //true

call / apply / bind 改变this的指向

call / apply

call和apply的用法很像,只是后面参数的传入形式不同。

代码3

1function add(c, d) { 2 return this.a + this.b + c + d; 3} 4 5var o = {a: 1, b: 3}; 6 7// call的第一个参数 是对象,也就是this的指向对象。后面的参数就是函数arguments对象的成员 8add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 9 10// call的第一个参数 是对象,也就是this的指向对象。后面的参数是数组,数组里的成员也就是函数arguments对象成员 11add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

使用call和apply时需要注意的是,当传入的第一个参数的值不是对象时,JavaScript会尝试使用ToObject 操作将其转化为对象。

代码4

1function bar() { 2 console.log(Object.prototype.toString.call(this)); 3} 4 5bar.call(7); // [object Number]

bind 方法

ECMAScript 5 引入了 Function.prototype.bind。调用f.bind(someObject)会创建一个与f具有相同函数体和作用域的函数,但是在这个新函数中,this将永久地被绑定到了bind的第一个参数,无论这个函数是如何被调用的。

代码5

1function f() { 2 return this.a; 3} 4 5var g = f.bind({a: 'azerty'}); //生成一个绑定函数g 6console.log(g()); // azerty 7 8var o = {a: 10, f: f, g: g}; 9console.log(o.f(), o.g()); //10, azerty 10 11//需要注意的是,绑定函数不可以再bind 12var h = g.bind({a: 'foo'}); 13console.log(h()); //azerty,不会变成foo

对象方法调用

当以对象里的方法的方式调用函数时,它们的 this 是调用该函数的对象.

下面的例子中,当 o.f() 被调用时,函数内的this将绑定到o对象。

1var prop = 36; 2var o = { 3 prop: 37, 4 bar: function() { 5 return this.prop; 6 } 7}; 8console.log(o.bar()); //37

构造函数调用

代码6

1function Person(name, age) { 2 this.name = name; 3 this.age = age; 4 this.introduce = function () { 5 console.log('My name is ' + this.name + ', I\'m ' + this.age); 6 }; 7} 8var Joseph = new Person('Joseph', 19); 9Joseph.introduce(); // "My name is Joseph, I'm 19"

由上述代码可以清晰的看到this与被新创建的对象绑定了。

注意:当构造器返回的默认值是一个this引用的对象时,可以手动设置返回其他的对象,如果返回值不是一个对象,返回this。(这句话看起来比较难理解,我们看下一个例子)。

代码7

1function Fn2() { 2 this.a = 9; // dead code 3 return {a: 10}; 4} 5var o = new Fn2(); 6console.log(o.a); // 10

这个例子说明了当构造函数返回的是一个对象的话,此时this的值会变成此时返回的对象。‘this.a = 9’成了僵尸代码。

箭头函数

在箭头函数( Arrow functions)中,this的值是封闭执行环境决定的。在全局环境中,那么被赋值为全局对象。

1var globalObject = this; 2var foo = (() => this); 3console.log(foo() === globalObject); // true

更重要的是它与其他情况不同的是,不管函数如何调用,上面this的值一直都是全局对象。call / bind 也不能改变它的值。

代码8

1// 作为对象方法被调用 2var obj = {foo: foo}; 3console.log(obj.foo() === globalObject); // true 4 5// 尝试用 call 改变this的值 6console.log(foo.call(obj) === globalObject); // true //this的值并未变成obj 7 8// 尝试用 bind 改变this的值 9foo = foo.bind(obj); 10console.log(foo() === globalObject); // true

案例

本文知识点都看完了。让我们看几个案例,检查自己的掌握情况。

例1

1var prop = 36; 2var o = { 3 prop: 37, 4 bar1: function() { 5 function foo1() { 6 return this.prop; 7 } 8 return foo1; 9 }, 10 bar2: function() { 11 var foo2 = (() => this.prop); //ES6箭头函数 12 return foo2; 13 } 14}; 15console.log('result1:'+o.bar1()()); // result1 ? 16console.log('result2:'+o.bar2()()); // result2 ? 17var fn2 = o.bar2; 18console.log('result3:'+fn2()()); // result3 ?

先揭晓答案:例1 result1 = 36,result2 = 37,result3 = 36。我的理解是,在result1中,o.bar1()执行导致foo函数return到了全局环境中,然后执行就变成了在全局中执行,所以得到的是全局中36的值。result2呢?因为this在箭头函数中。它的值不会改变。所以this仍指向o。那为什么result3又重新变了呢?因为此时‘var fn2 = o.bar2’相当于重新定义了一个函数,而this的值当然也就变为了全局对象。

1// 相当于这样 2var fn2 = function() { 3 function foo1() { 4 return this.prop; 5 } 6 return foo1; 7 } 8fn2()();

例2

1function sum(a,b) { 2 return a+b; 3}; 4var o = { 5 num: 1, 6 fn: function() { 7 function handle() { 8 return this.num = sum(this.num, this.num); 9 } 10 handle(); 11 } 12}; 13console.log('result:'+o.fn()); // result ?

同样先揭晓答案:result = undefined,用控制台可以看到此时this指向window,而不是o。这是个比较容易掉进去的坑(一般认为是当初的语言设计错误,被人诟病不少)。看似函数是由对象方法调用的,其实细心的话,我们可以看到。handle函数的执行,前面的没有对象的。这种情况下,this指向全局对象。解决办法也很简单。

1// 1、取消 handle函数的定义,直接在对象的方法中使用this 2fn2: function() { 3 this.value = sum(this.value, this.value); //2 4}, 5///2、使用变量保存外部函数的this。 6fn3: function() { 7 var that = this; // that == o 8 function handle() { 9 that.value = add(that.value, that.value); 10 } 11 handle(); 12}

另外,我在工作之余对前端面试题做了一系列的整理,有需要的朋友可以点击下方蓝字阅读:

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

mysql设置时区

mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0

JS 对象数组Array 根据对象object key的值排序sort,很风骚哦

有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak