Javascript :this关键字 详解

箭头函数相信大家在日常开发中用到的地方非常之多,因为它很简洁,可读性强,但是它最大的好处,其实是解决了匿名函数的this指向问题,有利于封装回调函数。

var name = 'window'; // 其实是window.name = 'window'
var A = {
   name: 'A',
   sayHello: function(){
      console.log(this.name)
   }
}
A.sayHello();// 输出A
var B = {
  name: 'B'
}
A.sayHello.call(B);//输出B
A.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window

 从上面可以看到,sayHello这个方法是定义在A对象中的,当当我们使用call方法,把其指向B对象,最后输出了B;可以得出,sayHello的this只跟使用时的对象有关。

var name = 'window'; 
var A = {
   name: 'A',
   sayHello: () => {
      console.log(this.name)
   }
}
A.sayHello();// 还是以为输出A ? 错啦,其实输出的是window

一开始,我重点标注了“该函数所在的作用域指向的对象”,作用域是指函数内部,这里的箭头函数,也就是sayHello,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。

var name = 'window'; 
var A = {
   name: 'A',
   sayHello: function(){
      var s = () => console.log(this.name)
      return s//返回箭头函数s
   }
}
var sayHello = A.sayHello();
sayHello();// 输出A 

var B = {
   name: 'B';
}
sayHello.call(B); //还是A
sayHello.call(); //还是A

全局环境

无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象。

// 在浏览器中, window 对象同时也是全局对象:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b)  // "MDN"
console.log(b)         // "MDN"

函数(运行内)环境

在函数内部,this的值取决于函数被调用的方式。

 

function f1(){
  return this;
}
//在浏览器中:
f1() === window;   //在浏览器中,全局对象是window

然而,在严格模式下,this将保持他进入执行环境时的值,所以下面的this将会默认为undefined

function f2(){
  "use strict"; // 这里是严格模式
  return this;
}
f2() === undefined; // true

如果要想把 this 的值从一个环境传到另一个,就要用 call 或者apply 方法。

// 将一个对象作为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'

 当一个函数在其主体中使用 this 关键字时,可以通过使用函数继承自Function.prototype 的 callapply 方法将 this 值绑定到调用中的特定对象。

function add(c, d) {
  return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 第一个参数是作为‘this’使用的对象
// 后续参数作为参数传递给函数调用
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// 第一个参数也是作为‘this’使用的对象
// 第二个参数是一个数组,数组里的元素用作函数调用中的参数
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

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

记住最核心的一句话: 

哪个对象调用函数,函数里面的this指向哪个对象。

分几种情况谈论下:

一、普通函数调用

二、对象函数调用

三、构造函数调用

四、apply和call调用

五、箭头函数调用

一、普通函数调用

这个情况没特殊意外,就是指向全局对象-window。

1、使用let

/*普通函数调用*/
let username = "程新松";
function fn(){
    console.log(this.username);   //undefined
}
fn();

2、使用var

var name = "程新松";
function fn(){
    console.log(this.name);//程新松
}
fn();

 

4、let和var区别:

(1)let 允许把变量的作用域限制在块级域中;var 申明变量要么是全局的,要么是函数级的,而无法是块级的。

let varClass = function(){
    var name='程新松';
    if(true){
        var name='saucxs';
        console.log(name);// saucxs
    }
    console.log(name);// saucxs
}
varClass();

let letClass = function(){
    let name='程新松';
    if(true){
        let name='saucxs';
        console.log(name);// saucxs
    }
    console.log(name);// 程新松
}
letClass(); 

 (2)先let后var

let subClass = function(){
    let name='程新松';
    if(true){
        var name='saucxs';
        console.log(name);
    }
    console.log(name);
}
subClass();

 

var 是函数级作用域,相当于一个作用域中有两个n的变量了

var 作用于整个 subClass ,和let冲突了,let不能重复声明

(3)先var后let

let subClass = function(){
    var name='程新松';
    if(true){
        let name='saucxs';
        console.log(name);//saucxs
    }
    console.log(name);//程新松
}
subClass();

先申明var,再申明let,这个没有问题。

二、对象函数调用

 这个相信不难理解,就是哪个函数调用,this指向哪里

/*对象函数调用*/
//window.name='程新松';
//var name='程新松';
let name='程新松';
let obj={
    id:201102304,
    fn:function(){
        console.log(this.name);  //undefined
        console.log(this.id);   //201102304
    }
}
obj.fn();
/*需要注意的情况*/
let obj1={
    a:111
}
let obj2={
    a:222,
    fn:function(){
        console.log(this.a);
    }
}
obj1.fn=obj2.fn;
obj1.fn();  //111

 这个也不难理解,虽然 obj1.fn 是从 obj2.fn 赋值而来,但是调用函数的是 obj1 ,所以 this 指向 obj1 。

三、构造函数调用

/*构造函数调用*/
let structureClass=function(){
    this.name='程新松';
}
let subClass1=new structureClass();
console.log(subClass1.name);//程新松

let subClass=new structureClass();
subClass.name='成才';
console.log(subClass.name);//成才

但是有一个坑,虽然一般不会出现,但是有必要提一下。

在构造函数里面返回一个对象,会直接返回这个对象,而不是执行构造函数后创建的对象

let structureClass=function(){
    this.name='程新松';
    return {
        username:'saucxs'
    }
}
let subClass1=new structureClass();
console.log(subClass1); 
console.log(subClass1.name);

 四、apply和call调用

 1、apply和call简单来说就是会改变传入函数的this。

/*apply和call调用*/
let obj1={
    name:'程新松'
};
let obj2={
    name:'saucxs',
    fn:function(){
        console.log(this.name);
    }
}
obj2.fn.call(obj1);//程新松

 

此时虽然是 obj2 调用方法,但是使用 了 call ,动态的把 this 指向到 obj1 。相当于这个 obj2.fn 这个执行环境是 obj1 。

call 和 apply 两个主要用途:

1.改变 this 的指向(把 this 从 obj2 指向到 obj1 )

2.方法借用( obj1 没有 fn ,只是借用 obj2 方法)

2、call与apply区别

 call 和 apply 的作用,完全一样,唯一的区别就是在参数上面。

call 接收的参数不固定,第一个参数是函数体内 this 的指向,第二个参数以下是依次传入的参数。

apply接收两个参数,第一个参数也是函数体内 this 的指向。第二个参数是一个集合对象(数组或者类数组)

/*apply和call区别*/
let fn=function(a,b,c){
    console.log(a,b,c);
}
let arrArray=[1,2,3];
fn.call(window,arrArray);
fn.apply(window,arrArray);

 五、箭头函数调用

 首先不得不说,ES6 提供了箭头函数,增加了我们的开发效率,但是在箭头函数里面,没有 this ,箭头函数里面的 this 是继承外面的环境

/*箭头函数调用*/
let obj={
    name:'程新松',
    fn:function(){
        setTimeout(function(){console.log(this.name)})
    }
}
obj.fn();//undefined

不难发现,虽然 fn() 里面的 this 是指向 obj ,但是,传给 setTimeout 的是普通函数, this 指向是 window , window 下面没有 name ,所以这里输出 underfind 。

setTimeout的常见用法是让某个方法延迟执行。setTimeout方法是挂在window对象下的。

《javascript高级程序设计》中有:“超时调用的代码都是在全局作用域中执行的,因此函数中的this的值在非严格模式下指向window对象,在严格模式下是undefined”。这里讨论的是非严格模式。

setTimeout的延迟执行函数中的this,永远指向window,而setTimeout调用环境中的this,是根据上下文来确定的,默认为window。看下面的例子:

上面的例子可以看出。延迟执行函数中的this的确是指向了window,但是执行函数的其他变量需要根据上下文来确认。

/换成箭头函数
let obj={
    name:"程新松",
    fn:function(){
        setTimeout(()=>{console.log(this.name)});
    }
}
obj.fn();//程新松

 这次输出 程新松 是因为,传给 setTimeout 的是箭头函数,然后箭头函数里面没有 this ,所以要向上层作用域查找,在这个例子上, setTimeout 的上层作用域是 fn 。而 fn 里面的 this 指向 obj ,所以 setTimeout 里面的箭头函数的 this ,指向 obj 。所以输出 程新松

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值