关于 JavaScript中 this ,看这一篇就够了

本文深入解析JavaScript中的this关键字,探讨其在不同环境下的行为,包括全局执行环境、普通函数、构造函数、箭头函数以及在严格模式下的表现。同时,文章讲解了如何使用call和apply方法改变函数执行时的this值,以及箭头函数与传统函数在this绑定上的区别。

首先「this」指当前执行代码的环境对象

一、在全局执行环境中,this 指向全局对象;在浏览器中全局对象是「window」,在「node」环境中全局对象是「global」:

var first_name = 'this_ex';

console.log(this.first_name);
console.log(first_name);
console.log(this === window);
console.log(window.first_name);

this.last_name = 'this_last';

console.log(last_name);
console.log(window.last_name);

// 输出
"this_ex"
"this_ex"
true
"this_ex"
"this_last"
"this_last"

但是在浏览器严格模式下(即浏览器按照 w3c 标准解析),this 将保持它进入指向环境时的值,所以下面的 this 将会默认为 undefined

function strict() {
    'use strict'
    return this;
}

console.log(strict() === undefined)

// 输出
true

在普通函数中,我们定义的属性如果与全局属性重名,那么在函数中更改属性值会覆盖全局属性:

var name = 'huang' 

function Person() {
  this.name = 'yao'
  console.log(this.name)
  console.log(name)
}

Person()

console.log(name)

// 输出
"yao"
"yao"
"yao"

所以我们为了避免这种情况,可以将「Person」作为构造函数,我们 new 一个「Person」的实例,这样就能避免破坏全局属性

var name = 'huang' 

function Person() {
  this.name = 'yao'
  console.log(this.name)
  console.log(name)
}

var p = new Person()

console.log(name)

// 输出

"yao"
"huang"
"huang"

OK,说到这里我们就要提一下这个「new」了,new 具体做了如下操作

  • 创建一个空的对象(即{})
  • 链接该对象(即设置该对象的构造函数)到另一个对象
  • 将步骤1新建的对象作为 this 的上下文
  • 如果该函数没有返回对象,则返回 this

我们先来看下列代码,再来分析步骤

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

var car1 = new Car('Eagle', 'Talon TSi', 1993);

console.log(car1.make);

console.log(car1.__proto__.constructor === Car)

// 输出
"Eagle"

true

「new」运算符先新建一个 car1 = {} 的对象,然后设置 car1 的构造函数等于「Car」,即设置 car1.__proto__.constructor = Car

然后将 car1 作为 Car 中 this 的上下文,然后执行 Car 函数,并且返回 this,所以最后我们才能通过 car1.make 输出内容

 

如果我们要把 this 值从一个环境传到另一个,那就要用到 call 或者 apply 方法

var obj = {
  first_name: 'huang'
}
first_name = 'first_name'
function Person(data) {
  console.log(this.first_name)
  console.log(data)
}
Person();
Person.call(obj,'is call');
Person.apply(obj, ['is apply']);

// 输出


"first_name"
undefined
"huang"
"is call"
"huang"
"is apply"

call 和 apply 做的事情就是将 obj 的 this 传到 Person 中,并执行 Person 函数。

call 和 apply 第一个参数默认传对象,如果传的不是对象,JavaScript 会尝试使用内部 ToObject 操作将其转化为对象,例如将数字「7」转化为对象,通过 Object.prototype.toString.call(7)  返回 [Object,Number],所以这也是判断对象是「函数」「数组」还是「null」的一种方法。

call 和 apply 后面的参数就是作为 Person 函数的入参,apply 后面必须是数组格式。

 

二、箭头函数即函数的另一个表现语法,在箭头函数出现之前,每个新定义的函数都有它自己的 this 值(在构造函数的情况下是一个新对象,在严格模式的函数调用中为 undefined,如果该函数被作为“对象方法”调用则为基础对象等),例如

function Person() {
    
    this.num = 0;
    
    setTimeout(function grow(){

       console.log(this.num + 1)

    },1000)
}

Person() // 1

var p = new Person() // NaN

将 this 分配给封闭的变量可以解决这个问题

function Person() {
  this.age = 0;
  let _that = this;
  setTimeout(() => {
    _that.age++
    console.log(this.age)
  },1000)
}
var p = new Person()
// 输出
1

也可以用箭头函数替代,箭头函数会继承 Person 的 this,也就可以拿到 age 的值了

function Person() {
  this.age = 0
  setTimeout(() => {
    this.age++
    console.log(this.age)
  },1000)
}
var p = new Person()
// 输出
1

在严格模式下也并不会影响箭头函数取得上一级作用链的 this

function Person() {
  this.age = 0;
  var closure = "123"
  setTimeout(function growUp() {
    this.age++;
    console.log(this.age)
    console.log(closure)
  }, 1000);
}

var p = new Person();

function PersonX() {
  'use strict'
  this.age = 0;
  var closure = "123"
  setTimeout(()=>{
    this.age++;
    console.log(this.age)
    console.log(closure)
  }, 1000);
}

var px = new PersonX();
// 输出
NaN
"123"
1
"123"

由于箭头函数没有自己的 this ,所以调用 apply 或者 call 方法时,只能传递参数,第一个参数会被忽略,如下:

var adder = {
  base: 1,
  
  add: function(a){
    var f = v => v + this.base;
    return f(a);
  },
  
  addThruCall: function(a){
    var f = v => v + this.base;
    var b = {
      base: 2
    }
    return f.call(b,a)
  }
}

console.log(adder.add(1));
console.log(adder.addThruCall(1))
// 输出
2
2

 在 f.call(b,a) 中,b 会被忽略,所以 addThruCall 中的 this.base 取的还是 adder 中的 this.base

箭头函数中不绑定 arguments 对象,arguments 对象即调用函数时传递给函数的参数数组,它是一个类数组对象,除了 length 和 index 属性外,没有别的属性,所以想要调用数组的 pop 或者别的方法,要先将 arguments 转化为数组

var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);
// ES2015
const args = Array.from(arguments);
const args = [...arguments];

然而箭头函数并不会绑定 arguments 对象

var arguments = [2,3,4];
var args = () => arguments[0];
console.log(args());

function foo(n) {
    var f = () => arguments[0] + n;
    return f()
}
console.log(foo(1))
// 输出
2
2

因为箭头函数不会绑定 arguments,所以 args 中取的是全局属性 arguments 的值,所以输出 2,在 foo 函数中 f 函数中的 arguments 取的是 foo 函数的 arguments 对象,所以 arguments[0] 即是 n,所以输出 2

综上,箭头函数表达式对非方法函数是最好的,让我们看看把它们作为方法时会怎样

'use strict';
var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log( this.i, this)
  }
}
obj.b(); 
// undefined
obj.c(); 
// 10, Object {...}

 箭头函数中不会产生 this,所以 b 中的this 指向 window 对象,所以 this.i 为 undefined,再看一个 Object.defineProperty 例子

'use strict';
var obj = {
  a: 10
};

Object.defineProperty(obj, "b", {
  
  get: () => {
      console.log(this.a, typeof this.a, this)
      return this.a + 10
  }
  
})

obj.b
// 输出
undefined
"undefined"
[Object,Window]

箭头函数不能用作构造器,和 new 一起用会抛出错误

var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor

箭头函数没有 prototype 属性

var Foo = () => {};
console.log(Foo.prototype); // undefined

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值